Compare commits

...

4 commits

Author SHA1 Message Date
Francis Cao
f98e683979 add zod schema to realtime endpoint
Some checks are pending
Node.js CI / build (postgresql, 18.18, 10) (push) Waiting to run
2025-10-21 17:20:35 -07:00
Francis Cao
b7747b33e4 clean-up hooks for passing uneccessary date params
Some checks are pending
Create docker images (cloud) / Build, push, and deploy (push) Waiting to run
Node.js CI / build (postgresql, 18.18, 10) (push) Waiting to run
2025-10-21 16:52:04 -07:00
Francis Cao
06230ad2e9 clean up teams api messaging and permissions 2025-10-21 15:35:17 -07:00
Mike Cao
d8fdba77db Updated packages. 2025-10-21 13:39:43 -07:00
10 changed files with 399 additions and 385 deletions

View file

@ -72,8 +72,8 @@
"@dicebear/core": "^9.2.3",
"@fontsource/inter": "^5.2.8",
"@hello-pangea/dnd": "^17.0.0",
"@prisma/adapter-pg": "^6.16.3",
"@prisma/client": "^6.16.3",
"@prisma/adapter-pg": "^6.17.1",
"@prisma/client": "^6.17.1",
"@prisma/extension-read-replicas": "^0.4.1",
"@react-spring/web": "^10.0.3",
"@svgr/cli": "^8.1.0",
@ -113,7 +113,7 @@
"npm-run-all": "^4.1.5",
"papaparse": "^5.5.3",
"pg": "^8.16.3",
"prisma": "6.16.3",
"prisma": "6.17.1",
"pure-rand": "^7.0.1",
"react": "^19.2.0",
"react-dom": "^19.2.0",
@ -133,7 +133,7 @@
},
"devDependencies": {
"@formatjs/cli": "^4.2.29",
"@netlify/plugin-nextjs": "^5.14.0",
"@netlify/plugin-nextjs": "^5.14.2",
"@rollup/plugin-alias": "^5.0.0",
"@rollup/plugin-commonjs": "^25.0.4",
"@rollup/plugin-json": "^6.0.0",
@ -142,12 +142,12 @@
"@rollup/plugin-terser": "^0.4.4",
"@rollup/plugin-typescript": "^12.1.4",
"@types/jest": "^30.0.0",
"@types/node": "^24.8.1",
"@types/node": "^24.9.1",
"@types/react": "^19.2.2",
"@types/react-dom": "^19.2.2",
"@types/react-window": "^1.8.8",
"@typescript-eslint/eslint-plugin": "^8.46.1",
"@typescript-eslint/parser": "^8.46.1",
"@typescript-eslint/eslint-plugin": "^8.46.2",
"@typescript-eslint/parser": "^8.46.2",
"babel-plugin-react-compiler": "19.1.0-rc.2",
"cross-env": "^10.1.0",
"cypress": "^13.6.6",
@ -163,14 +163,14 @@
"extract-react-intl-messages": "^4.1.1",
"husky": "^9.1.7",
"jest": "^29.7.0",
"lint-staged": "^16.2.4",
"lint-staged": "^16.2.5",
"postcss": "^8.5.6",
"postcss-flexbugs-fixes": "^5.0.2",
"postcss-import": "^15.1.0",
"postcss-preset-env": "7.8.3",
"prettier": "^3.6.2",
"prompts": "2.4.2",
"rollup": "^4.52.4",
"rollup": "^4.52.5",
"rollup-plugin-copy": "^3.4.0",
"rollup-plugin-delete": "^3.0.1",
"rollup-plugin-dts": "^6.2.3",
@ -182,7 +182,7 @@
"stylelint-config-prettier": "^9.0.3",
"stylelint-config-recommended": "^14.0.0",
"tar": "^6.1.2",
"ts-jest": "^29.4.0",
"ts-jest": "^29.4.5",
"ts-node": "^10.9.1",
"tsup": "^8.5.0",
"typescript": "^5.9.3"

690
pnpm-lock.yaml generated

File diff suppressed because it is too large Load diff

View file

@ -1,15 +1,21 @@
import { json, unauthorized } from '@/lib/response';
import { getRealtimeData } from '@/queries/sql';
import { canViewWebsite } from '@/permissions';
import { startOfMinute, subMinutes } from 'date-fns';
import { REALTIME_RANGE } from '@/lib/constants';
import { parseRequest, getQueryFilters } from '@/lib/request';
import { getQueryFilters, parseRequest } from '@/lib/request';
import { json, unauthorized } from '@/lib/response';
import { timezoneParam } from '@/lib/schema';
import { canViewWebsite } from '@/permissions';
import { getRealtimeData } from '@/queries/sql';
import { startOfMinute, subMinutes } from 'date-fns';
import z from 'zod';
export async function GET(
request: Request,
{ params }: { params: Promise<{ websiteId: string }> },
) {
const { auth, query, error } = await parseRequest(request);
const schema = z.object({
timezone: timezoneParam,
});
const { auth, query, error } = await parseRequest(request, schema);
if (error) {
return error();

View file

@ -41,7 +41,7 @@ export async function POST(request: Request, { params }: { params: Promise<{ tea
const { teamId } = await params;
if (!(await canUpdateTeam(auth, teamId))) {
return unauthorized({ message: 'You must be the owner of this team.' });
return unauthorized({ message: 'You must be the owner/manager of this team.' });
}
const team = await updateTeam(teamId, body);
@ -62,7 +62,7 @@ export async function DELETE(
const { teamId } = await params;
if (!(await canDeleteTeam(auth, teamId))) {
return unauthorized({ message: 'You must be the owner of this team.' });
return unauthorized({ message: 'You must be the owner/manager of this team.' });
}
await deleteTeam(teamId);

View file

@ -3,6 +3,7 @@ import { parseRequest } from '@/lib/request';
import { badRequest, json, ok, unauthorized } from '@/lib/response';
import { deleteTeamUser, getTeamUser, updateTeamUser } from '@/queries/prisma';
import { z } from 'zod';
import { teamRoleParam } from '@/lib/schema';
export async function GET(
request: Request,
@ -17,7 +18,7 @@ export async function GET(
const { teamId, userId } = await params;
if (!(await canUpdateTeam(auth, teamId))) {
return unauthorized({ message: 'You must be the owner of this team.' });
return unauthorized({ message: 'You must be the owner/manager of this team.' });
}
const teamUser = await getTeamUser(teamId, userId);
@ -30,7 +31,7 @@ export async function POST(
{ params }: { params: Promise<{ teamId: string; userId: string }> },
) {
const schema = z.object({
role: z.string().regex(/team-member|team-view-only|team-manager/),
role: teamRoleParam,
});
const { auth, body, error } = await parseRequest(request, schema);
@ -42,7 +43,7 @@ export async function POST(
const { teamId, userId } = await params;
if (!(await canUpdateTeam(auth, teamId))) {
return unauthorized({ message: 'You must be the owner of this team.' });
return unauthorized({ message: 'You must be the owner/manager of this team.' });
}
const teamUser = await getTeamUser(teamId, userId);
@ -69,7 +70,7 @@ export async function DELETE(
const { teamId, userId } = await params;
if (!(await canDeleteTeamUser(auth, teamId, userId))) {
return unauthorized({ message: 'You must be the owner of this team.' });
return unauthorized({ message: 'You must be the owner/manager of this team.' });
}
const teamUser = await getTeamUser(teamId, userId);

View file

@ -1,9 +1,9 @@
import { z } from 'zod';
import { unauthorized, json, badRequest } from '@/lib/response';
import { canAddUserToTeam, canViewTeam } from '@/permissions';
import { getQueryFilters, parseRequest } from '@/lib/request';
import { pagingParams, teamRoleParam, searchParams } from '@/lib/schema';
import { badRequest, json, unauthorized } from '@/lib/response';
import { pagingParams, searchParams, teamRoleParam } from '@/lib/schema';
import { canUpdateTeam, canViewTeam } from '@/permissions';
import { createTeamUser, getTeamUser, getTeamUsers } from '@/queries/prisma';
import { z } from 'zod';
export async function GET(request: Request, { params }: { params: Promise<{ teamId: string }> }) {
const schema = z.object({
@ -20,7 +20,7 @@ export async function GET(request: Request, { params }: { params: Promise<{ team
const { teamId } = await params;
if (!(await canViewTeam(auth, teamId))) {
return unauthorized({ message: 'You must be the owner of this team.' });
return unauthorized({ message: 'You must be a member of this team.' });
}
const filters = await getQueryFilters(query);
@ -65,8 +65,8 @@ export async function POST(request: Request, { params }: { params: Promise<{ tea
const { teamId } = await params;
if (!(await canAddUserToTeam(auth))) {
return unauthorized();
if (!(await canUpdateTeam(auth, teamId))) {
return unauthorized({ message: 'You must be the owner/manager of this team.' });
}
const { userId, role } = body;

View file

@ -11,7 +11,6 @@ export async function GET(
{ params }: { params: Promise<{ websiteId: string }> },
) {
const schema = z.object({
compare: z.string().optional(),
...dateRangeParams,
...filterParams,
});

View file

@ -19,7 +19,7 @@ export function useWebsiteExpandedMetricsQuery(
options?: ReactQueryOptions<WebsiteExpandedMetricsData>,
) {
const { get, useQuery } = useApi();
const date = useDateParameters();
const { startAt, endAt, unit, timezone } = useDateParameters();
const filters = useFilterParameters();
return useQuery<WebsiteExpandedMetricsData>({
@ -27,14 +27,20 @@ export function useWebsiteExpandedMetricsQuery(
'websites:metrics:expanded',
{
websiteId,
...date,
startAt,
endAt,
unit,
timezone,
...filters,
...params,
},
],
queryFn: async () =>
get(`/websites/${websiteId}/metrics/expanded`, {
...date,
startAt,
endAt,
unit,
timezone,
...filters,
...params,
}),

View file

@ -6,15 +6,21 @@ import { useFilterParameters } from '@/components/hooks/useFilterParameters';
export function useWeeklyTrafficQuery(websiteId: string, params?: Record<string, string | number>) {
const { get, useQuery } = useApi();
const { modified } = useModified(`sessions`);
const date = useDateParameters();
const { startAt, endAt, unit, timezone } = useDateParameters();
const filters = useFilterParameters();
return useQuery({
queryKey: ['sessions', { websiteId, modified, ...params, ...date, ...filters }],
queryKey: [
'sessions',
{ websiteId, modified, startAt, endAt, unit, timezone, ...params, ...filters },
],
queryFn: () => {
return get(`/websites/${websiteId}/sessions/weekly`, {
startAt,
endAt,
unit,
timezone,
...params,
...date,
...filters,
});
},

View file

@ -39,10 +39,6 @@ export async function canDeleteTeam({ user }: Auth, teamId: string) {
return teamUser && hasPermission(teamUser.role, PERMISSIONS.teamDelete);
}
export async function canAddUserToTeam({ user }: Auth) {
return user.isAdmin;
}
export async function canDeleteTeamUser({ user }: Auth, teamId: string, removeUserId: string) {
if (user.isAdmin) {
return true;