import { getDefaultIntervalByDates } from '@openpanel/constants'; import { eventBuffer, getChartStartEndDate, getSettingsForProject, overviewService, } from '@openpanel/db'; import { zChartEventFilter, zRange } from '@openpanel/validation'; import type { FastifyReply, FastifyRequest } from 'fastify'; import { z } from 'zod'; import { parseQueryString } from '@/utils/parse-zod-query-string'; const zGetMetricsQuery = z.object({ startDate: z.string().nullish(), endDate: z.string().nullish(), range: zRange.default('7d'), filters: z.array(zChartEventFilter).default([]), }); // Website stats - main metrics overview export async function getMetrics( request: FastifyRequest<{ Params: { projectId: string }; Querystring: z.infer; }>, reply: FastifyReply ) { const { timezone } = await getSettingsForProject(request.params.projectId); const parsed = zGetMetricsQuery.safeParse(parseQueryString(request.query)); if (parsed.success === false) { return reply.status(400).send({ error: 'Bad Request', message: 'Invalid query parameters', details: parsed.error, }); } const { startDate, endDate } = getChartStartEndDate(parsed.data, timezone); reply.send( await overviewService.getMetrics({ projectId: request.params.projectId, filters: parsed.data.filters, startDate, endDate, interval: getDefaultIntervalByDates(startDate, endDate) ?? 'day', timezone, }) ); } // Live visitors (real-time) export async function getLiveVisitors( request: FastifyRequest<{ Params: { projectId: string }; }>, reply: FastifyReply ) { reply.send({ visitors: await eventBuffer.getActiveVisitorCount(request.params.projectId), }); } export const zGetTopPagesQuery = z.object({ filters: z.array(zChartEventFilter).default([]), startDate: z.string().nullish(), endDate: z.string().nullish(), range: zRange.default('7d'), cursor: z.number().optional(), limit: z.number().default(10), }); // Page views with top pages export async function getPages( request: FastifyRequest<{ Params: { projectId: string }; Querystring: z.infer; }>, reply: FastifyReply ) { const { timezone } = await getSettingsForProject(request.params.projectId); const { startDate, endDate } = getChartStartEndDate(request.query, timezone); const parsed = zGetTopPagesQuery.safeParse(parseQueryString(request.query)); if (parsed.success === false) { return reply.status(400).send({ error: 'Bad Request', message: 'Invalid query parameters', details: parsed.error, }); } return overviewService.getTopPages({ projectId: request.params.projectId, filters: parsed.data.filters, startDate, endDate, timezone, }); } const zGetOverviewGenericQuery = z.object({ filters: z.array(zChartEventFilter).default([]), startDate: z.string().nullish(), endDate: z.string().nullish(), range: zRange.default('7d'), column: z.enum([ // Referrers 'referrer', 'referrer_name', 'referrer_type', 'utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', // Geo 'region', 'country', 'city', // Device 'device', 'brand', 'model', 'browser', 'browser_version', 'os', 'os_version', ]), cursor: z.number().optional(), limit: z.number().default(10), }); export function getOverviewGeneric( column: z.infer['column'] ) { return async ( request: FastifyRequest<{ Params: { projectId: string; key: string }; Querystring: z.infer; }>, reply: FastifyReply ) => { const { timezone } = await getSettingsForProject(request.params.projectId); const { startDate, endDate } = getChartStartEndDate( request.query, timezone ); const parsed = zGetOverviewGenericQuery.safeParse({ ...parseQueryString(request.query), column, }); if (parsed.success === false) { return reply.status(400).send({ error: 'Bad Request', message: 'Invalid query parameters', details: parsed.error, }); } // TODO: Implement overview generic endpoint reply.send( await overviewService.getTopGeneric({ column, projectId: request.params.projectId, filters: parsed.data.filters, startDate, endDate, timezone, }) ); }; }