From 7d5556a6371b4fd3e298043098781a0c60f2c020 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Tue, 21 Jan 2025 20:57:47 -0800 Subject: [PATCH] Convert event-data, events, session-data, sessions routes. --- src/app/api/users/[userId]/route.ts | 2 + src/app/api/users/[userId]/teams/route.ts | 10 ++-- src/app/api/users/[userId]/usage/route.ts | 10 ++-- src/app/api/users/[userId]/websites/route.ts | 9 ++-- src/app/api/users/route.ts | 14 +++--- .../api/websites/[websiteId]/active/route.ts | 10 ++-- .../websites/[websiteId]/daterange/route.ts | 3 +- .../[websiteId]/event-data/events/route.ts | 42 ++++++++++++++++ .../[websiteId]/event-data/fields/route.ts | 40 +++++++++++++++ .../event-data/properties/route.ts | 38 ++++++++++++++ .../[websiteId]/event-data/stats/route.ts | 38 ++++++++++++++ .../[websiteId]/event-data/values/route.ts | 44 ++++++++++++++++ .../api/websites/[websiteId]/events/route.ts | 39 +++++++++++++++ .../[websiteId]/events/series/route.ts | 47 +++++++++++++++++ .../api/websites/[websiteId]/metrics/route.ts | 43 ++++++---------- .../websites/[websiteId]/pageviews/route.ts | 33 +++++------- .../api/websites/[websiteId]/reports/route.ts | 11 ++-- .../api/websites/[websiteId]/reset/route.ts | 3 +- src/app/api/websites/[websiteId]/route.ts | 23 +++------ .../session-data/properties/route.ts | 38 ++++++++++++++ .../[websiteId]/session-data/values/route.ts | 42 ++++++++++++++++ .../sessions/[sessionId]/activity/route.ts | 37 ++++++++++++++ .../sessions/[sessionId]/properties/route.ts | 20 ++++++++ .../[websiteId]/sessions/[sessionId]/route.ts | 20 ++++++++ .../websites/[websiteId]/sessions/route.ts | 39 +++++++++++++++ .../[websiteId]/sessions/stats/route.ts | 50 +++++++++++++++++++ .../[websiteId]/sessions/weekly/route.ts | 40 +++++++++++++++ .../api/websites/[websiteId]/stats/route.ts | 31 ++++-------- .../websites/[websiteId]/transfer/route.ts | 13 ++--- .../api/websites/[websiteId]/values/route.ts | 17 ++++--- src/lib/schema.ts | 21 +++++++- .../event-data/{events.ts => _events.ts} | 0 .../event-data/{fields.ts => _fields.ts} | 0 .../{properties.ts => _properties.ts} | 0 .../event-data/{stats.ts => _stats.ts} | 0 .../event-data/{values.ts => _values.ts} | 0 .../events/{index.ts => _index.ts} | 0 .../events/{series.ts => _series.ts} | 0 .../{properties.ts => _properties.ts} | 0 .../session-data/{values.ts => _values.ts} | 0 .../[sessionId]/{activity.ts => _activity.ts} | 0 .../[sessionId]/{index.ts => _index.ts} | 0 .../{properties.ts => _properties.ts} | 0 .../sessions/{index.ts => _index.ts} | 0 .../sessions/{stats.ts => _stats.ts} | 0 .../sessions/{weekly.ts => _weekly.ts} | 0 src/queries/index.ts | 1 + 47 files changed, 692 insertions(+), 136 deletions(-) create mode 100644 src/app/api/websites/[websiteId]/event-data/events/route.ts create mode 100644 src/app/api/websites/[websiteId]/event-data/fields/route.ts create mode 100644 src/app/api/websites/[websiteId]/event-data/properties/route.ts create mode 100644 src/app/api/websites/[websiteId]/event-data/stats/route.ts create mode 100644 src/app/api/websites/[websiteId]/event-data/values/route.ts create mode 100644 src/app/api/websites/[websiteId]/events/route.ts create mode 100644 src/app/api/websites/[websiteId]/events/series/route.ts create mode 100644 src/app/api/websites/[websiteId]/session-data/properties/route.ts create mode 100644 src/app/api/websites/[websiteId]/session-data/values/route.ts create mode 100644 src/app/api/websites/[websiteId]/sessions/[sessionId]/activity/route.ts create mode 100644 src/app/api/websites/[websiteId]/sessions/[sessionId]/properties/route.ts create mode 100644 src/app/api/websites/[websiteId]/sessions/[sessionId]/route.ts create mode 100644 src/app/api/websites/[websiteId]/sessions/route.ts create mode 100644 src/app/api/websites/[websiteId]/sessions/stats/route.ts create mode 100644 src/app/api/websites/[websiteId]/sessions/weekly/route.ts rename src/pages/api/websites/[websiteId]/event-data/{events.ts => _events.ts} (100%) rename src/pages/api/websites/[websiteId]/event-data/{fields.ts => _fields.ts} (100%) rename src/pages/api/websites/[websiteId]/event-data/{properties.ts => _properties.ts} (100%) rename src/pages/api/websites/[websiteId]/event-data/{stats.ts => _stats.ts} (100%) rename src/pages/api/websites/[websiteId]/event-data/{values.ts => _values.ts} (100%) rename src/pages/api/websites/[websiteId]/events/{index.ts => _index.ts} (100%) rename src/pages/api/websites/[websiteId]/events/{series.ts => _series.ts} (100%) rename src/pages/api/websites/[websiteId]/session-data/{properties.ts => _properties.ts} (100%) rename src/pages/api/websites/[websiteId]/session-data/{values.ts => _values.ts} (100%) rename src/pages/api/websites/[websiteId]/sessions/[sessionId]/{activity.ts => _activity.ts} (100%) rename src/pages/api/websites/[websiteId]/sessions/[sessionId]/{index.ts => _index.ts} (100%) rename src/pages/api/websites/[websiteId]/sessions/[sessionId]/{properties.ts => _properties.ts} (100%) rename src/pages/api/websites/[websiteId]/sessions/{index.ts => _index.ts} (100%) rename src/pages/api/websites/[websiteId]/sessions/{stats.ts => _stats.ts} (100%) rename src/pages/api/websites/[websiteId]/sessions/{weekly.ts => _weekly.ts} (100%) diff --git a/src/app/api/users/[userId]/route.ts b/src/app/api/users/[userId]/route.ts index 41cd6bcf..30c166f1 100644 --- a/src/app/api/users/[userId]/route.ts +++ b/src/app/api/users/[userId]/route.ts @@ -7,6 +7,7 @@ import { checkRequest } from 'lib/request'; export async function GET(request: Request, { params }: { params: Promise<{ userId: string }> }) { const { userId } = await params; + const auth = await checkAuth(request); if (!auth || !(await canViewUser(auth, userId))) { @@ -32,6 +33,7 @@ export async function POST(request: Request, { params }: { params: Promise<{ use } const { userId } = await params; + const auth = await checkAuth(request); if (!auth || !(await canUpdateUser(auth, userId))) { diff --git a/src/app/api/users/[userId]/teams/route.ts b/src/app/api/users/[userId]/teams/route.ts index 0cdccdaf..83238799 100644 --- a/src/app/api/users/[userId]/teams/route.ts +++ b/src/app/api/users/[userId]/teams/route.ts @@ -5,12 +5,10 @@ import { checkAuth } from 'lib/auth'; import { unauthorized, badRequest, json } from 'lib/response'; import { checkRequest } from 'lib/request'; -const schema = z.object({ - ...pagingParams, -}); - export async function GET(request: Request, { params }: { params: Promise<{ userId: string }> }) { - const { userId } = await params; + const schema = z.object({ + ...pagingParams, + }); const { query, error } = await checkRequest(request, schema); @@ -18,6 +16,8 @@ export async function GET(request: Request, { params }: { params: Promise<{ user return badRequest(error); } + const { userId } = await params; + const auth = await checkAuth(request); if (!auth || (!auth.user.isAdmin && (!userId || auth.user.id !== userId))) { diff --git a/src/app/api/users/[userId]/usage/route.ts b/src/app/api/users/[userId]/usage/route.ts index 177d3c35..275f665f 100644 --- a/src/app/api/users/[userId]/usage/route.ts +++ b/src/app/api/users/[userId]/usage/route.ts @@ -6,12 +6,12 @@ import { getEventDataUsage } from 'queries/analytics/events/getEventDataUsage'; import { checkAuth } from 'lib/auth'; import { checkRequest } from 'lib/request'; -const schema = z.object({ - startAt: z.coerce.number(), - endAt: z.coerce.number(), -}); - export async function GET(request: Request, { params }: { params: Promise<{ userId: string }> }) { + const schema = z.object({ + startAt: z.coerce.number().int(), + endAt: z.coerce.number().int(), + }); + const { query, error } = await checkRequest(request, schema); if (error) { diff --git a/src/app/api/users/[userId]/websites/route.ts b/src/app/api/users/[userId]/websites/route.ts index 61783cd6..189bf8fa 100644 --- a/src/app/api/users/[userId]/websites/route.ts +++ b/src/app/api/users/[userId]/websites/route.ts @@ -5,11 +5,11 @@ import { pagingParams } from 'lib/schema'; import { checkRequest } from 'lib/request'; import { checkAuth } from 'lib/auth'; -const schema = z.object({ - ...pagingParams, -}); - export async function GET(request: Request, { params }: { params: Promise<{ userId: string }> }) { + const schema = z.object({ + ...pagingParams, + }); + const { query, error } = await checkRequest(request, schema); if (error) { @@ -17,6 +17,7 @@ export async function GET(request: Request, { params }: { params: Promise<{ user } const { userId } = await params; + const auth = await checkAuth(request); if (!auth || (!auth.user.isAdmin && auth.user.id !== userId)) { diff --git a/src/app/api/users/route.ts b/src/app/api/users/route.ts index 870e6181..87959a0c 100644 --- a/src/app/api/users/route.ts +++ b/src/app/api/users/route.ts @@ -7,14 +7,14 @@ import { checkRequest } from 'lib/request'; import { unauthorized, json, badRequest } from 'lib/response'; import { createUser, getUserByUsername } from 'queries'; -const schema = z.object({ - username: z.string().max(255), - password: z.string(), - id: z.string().uuid(), - role: z.string().regex(/admin|user|view-only/i), -}); - export async function POST(request: Request) { + const schema = z.object({ + username: z.string().max(255), + password: z.string(), + id: z.string().uuid(), + role: z.string().regex(/admin|user|view-only/i), + }); + const { body, error } = await checkRequest(request, schema); if (error) { diff --git a/src/app/api/websites/[websiteId]/active/route.ts b/src/app/api/websites/[websiteId]/active/route.ts index 22bd1999..569bdb7b 100644 --- a/src/app/api/websites/[websiteId]/active/route.ts +++ b/src/app/api/websites/[websiteId]/active/route.ts @@ -6,15 +6,11 @@ export async function GET( request: Request, { params }: { params: Promise<{ websiteId: string }> }, ) { - const auth = await checkAuth(request); - - if (!auth) { - return unauthorized(); - } - const { websiteId } = await params; - if (!(await canViewWebsite(auth, websiteId))) { + const auth = await checkAuth(request); + + if (!auth || !(await canViewWebsite(auth, websiteId))) { return unauthorized(); } diff --git a/src/app/api/websites/[websiteId]/daterange/route.ts b/src/app/api/websites/[websiteId]/daterange/route.ts index 70460bd6..d4a562de 100644 --- a/src/app/api/websites/[websiteId]/daterange/route.ts +++ b/src/app/api/websites/[websiteId]/daterange/route.ts @@ -6,9 +6,10 @@ export async function GET( request: Request, { params }: { params: Promise<{ websiteId: string }> }, ) { - const auth = await checkAuth(request); const { websiteId } = await params; + const auth = await checkAuth(request); + if (!auth || !(await canViewWebsite(auth, websiteId))) { return unauthorized(); } diff --git a/src/app/api/websites/[websiteId]/event-data/events/route.ts b/src/app/api/websites/[websiteId]/event-data/events/route.ts new file mode 100644 index 00000000..143fae18 --- /dev/null +++ b/src/app/api/websites/[websiteId]/event-data/events/route.ts @@ -0,0 +1,42 @@ +import { z } from 'zod'; +import { checkRequest } from 'lib/request'; +import { badRequest, unauthorized, json } from 'lib/response'; +import { canViewWebsite, checkAuth } from 'lib/auth'; +import { getEventDataEvents } from 'queries/analytics/events/getEventDataEvents'; + +export async function GET( + request: Request, + { params }: { params: Promise<{ websiteId: string }> }, +) { + const schema = z.object({ + startAt: z.coerce.number().int(), + endAt: z.coerce.number().int(), + event: z.string().optional(), + }); + + const { query, error } = await checkRequest(request, schema); + + if (error) { + return badRequest(error); + } + + const { websiteId } = await params; + const { startAt, endAt, event } = query; + + const auth = await checkAuth(request); + + if (!auth || !(await canViewWebsite(auth, websiteId))) { + return unauthorized(); + } + + const startDate = new Date(+startAt); + const endDate = new Date(+endAt); + + const data = await getEventDataEvents(websiteId, { + startDate, + endDate, + event, + }); + + return json(data); +} diff --git a/src/app/api/websites/[websiteId]/event-data/fields/route.ts b/src/app/api/websites/[websiteId]/event-data/fields/route.ts new file mode 100644 index 00000000..3ef2f3b1 --- /dev/null +++ b/src/app/api/websites/[websiteId]/event-data/fields/route.ts @@ -0,0 +1,40 @@ +import { z } from 'zod'; +import { checkRequest } from 'lib/request'; +import { badRequest, unauthorized, json } from 'lib/response'; +import { canViewWebsite, checkAuth } from 'lib/auth'; +import { getEventDataFields } from 'queries'; + +export async function GET( + request: Request, + { params }: { params: Promise<{ websiteId: string }> }, +) { + const schema = z.object({ + startAt: z.coerce.number().int(), + endAt: z.coerce.number().int(), + }); + + const { query, error } = await checkRequest(request, schema); + + if (error) { + return badRequest(error); + } + + const { websiteId } = await params; + const { startAt, endAt } = query; + + const auth = await checkAuth(request); + + if (!(await canViewWebsite(auth, websiteId))) { + return unauthorized(); + } + + const startDate = new Date(+startAt); + const endDate = new Date(+endAt); + + const data = await getEventDataFields(websiteId, { + startDate, + endDate, + }); + + return json(data); +} diff --git a/src/app/api/websites/[websiteId]/event-data/properties/route.ts b/src/app/api/websites/[websiteId]/event-data/properties/route.ts new file mode 100644 index 00000000..68fdf4e3 --- /dev/null +++ b/src/app/api/websites/[websiteId]/event-data/properties/route.ts @@ -0,0 +1,38 @@ +import { z } from 'zod'; +import { checkRequest } from 'lib/request'; +import { badRequest, unauthorized, json } from 'lib/response'; +import { canViewWebsite, checkAuth } from 'lib/auth'; +import { getEventDataProperties } from 'queries'; + +export async function GET( + request: Request, + { params }: { params: Promise<{ websiteId: string }> }, +) { + const schema = z.object({ + startAt: z.coerce.number().int(), + endAt: z.coerce.number().int(), + propertyName: z.string().optional(), + }); + + const { query, error } = await checkRequest(request, schema); + + if (error) { + return badRequest(error); + } + + const { websiteId } = await params; + const { startAt, endAt, propertyName } = query; + + const auth = await checkAuth(request); + + if (!auth || !(await canViewWebsite(auth, websiteId))) { + return unauthorized(); + } + + const startDate = new Date(+startAt); + const endDate = new Date(+endAt); + + const data = await getEventDataProperties(websiteId, { startDate, endDate, propertyName }); + + return json(data); +} diff --git a/src/app/api/websites/[websiteId]/event-data/stats/route.ts b/src/app/api/websites/[websiteId]/event-data/stats/route.ts new file mode 100644 index 00000000..d958bbdc --- /dev/null +++ b/src/app/api/websites/[websiteId]/event-data/stats/route.ts @@ -0,0 +1,38 @@ +import { z } from 'zod'; +import { checkRequest } from 'lib/request'; +import { badRequest, unauthorized, json } from 'lib/response'; +import { canViewWebsite, checkAuth } from 'lib/auth'; +import { getEventDataStats } from 'queries'; + +export async function GET( + request: Request, + { params }: { params: Promise<{ websiteId: string }> }, +) { + const schema = z.object({ + startAt: z.coerce.number().int(), + endAt: z.coerce.number().int(), + propertyName: z.string().optional(), + }); + + const { query, error } = await checkRequest(request, schema); + + if (error) { + return badRequest(error); + } + + const { websiteId } = await params; + const { startAt, endAt } = query; + + const auth = await checkAuth(request); + + if (!auth || !(await canViewWebsite(auth, websiteId))) { + return unauthorized(); + } + + const startDate = new Date(+startAt); + const endDate = new Date(+endAt); + + const data = await getEventDataStats(websiteId, { startDate, endDate }); + + return json(data); +} diff --git a/src/app/api/websites/[websiteId]/event-data/values/route.ts b/src/app/api/websites/[websiteId]/event-data/values/route.ts new file mode 100644 index 00000000..0ecf20d3 --- /dev/null +++ b/src/app/api/websites/[websiteId]/event-data/values/route.ts @@ -0,0 +1,44 @@ +import { z } from 'zod'; +import { checkRequest } from 'lib/request'; +import { badRequest, unauthorized, json } from 'lib/response'; +import { canViewWebsite, checkAuth } from 'lib/auth'; +import { getEventDataValues } from 'queries'; + +export async function GET( + request: Request, + { params }: { params: Promise<{ websiteId: string }> }, +) { + const schema = z.object({ + startAt: z.coerce.number().int(), + endAt: z.coerce.number().int(), + eventName: z.string().optional(), + propertyName: z.string().optional(), + }); + + const { query, error } = await checkRequest(request, schema); + + if (error) { + return badRequest(error); + } + + const { websiteId } = await params; + const { startAt, endAt, eventName, propertyName } = query; + + const auth = await checkAuth(request); + + if (!auth || !(await canViewWebsite(auth, websiteId))) { + return unauthorized(); + } + + const startDate = new Date(+startAt); + const endDate = new Date(+endAt); + + const data = await getEventDataValues(websiteId, { + startDate, + endDate, + eventName, + propertyName, + }); + + return json(data); +} diff --git a/src/app/api/websites/[websiteId]/events/route.ts b/src/app/api/websites/[websiteId]/events/route.ts new file mode 100644 index 00000000..ef929312 --- /dev/null +++ b/src/app/api/websites/[websiteId]/events/route.ts @@ -0,0 +1,39 @@ +import { z } from 'zod'; +import { checkRequest } from 'lib/request'; +import { badRequest, unauthorized, json } from 'lib/response'; +import { canViewWebsite, checkAuth } from 'lib/auth'; +import { pagingParams } from 'lib/schema'; +import { getWebsiteEvents } from 'queries'; + +export async function GET( + request: Request, + { params }: { params: Promise<{ websiteId: string }> }, +) { + const schema = z.object({ + startAt: z.coerce.number().int(), + endAt: z.coerce.number().int(), + ...pagingParams, + }); + + const { query, error } = await checkRequest(request, schema); + + if (error) { + return badRequest(error); + } + + const { websiteId } = await params; + const { startAt, endAt } = query; + + const auth = await checkAuth(request); + + if (!auth || !(await canViewWebsite(auth, websiteId))) { + return unauthorized(); + } + + const startDate = new Date(+startAt); + const endDate = new Date(+endAt); + + const data = await getWebsiteEvents(websiteId, { startDate, endDate }, query); + + return json(data); +} diff --git a/src/app/api/websites/[websiteId]/events/series/route.ts b/src/app/api/websites/[websiteId]/events/series/route.ts new file mode 100644 index 00000000..4551e3bf --- /dev/null +++ b/src/app/api/websites/[websiteId]/events/series/route.ts @@ -0,0 +1,47 @@ +import { z } from 'zod'; +import { checkRequest, getRequestDateRange, getRequestFilters } from 'lib/request'; +import { badRequest, unauthorized, json } from 'lib/response'; +import { canViewWebsite, checkAuth } from 'lib/auth'; +import { filterParams, timezoneParam, unitParam } from 'lib/schema'; +import { getEventMetrics } from 'queries'; + +export async function GET( + request: Request, + { params }: { params: Promise<{ websiteId: string }> }, +) { + const schema = z.object({ + startAt: z.coerce.number().int(), + endAt: z.coerce.number().int(), + unit: unitParam, + timezone: timezoneParam, + ...filterParams, + }); + + const { query, error } = await checkRequest(request, schema); + + if (error) { + return badRequest(error); + } + + const { websiteId } = await params; + const { timezone } = query; + const { startDate, endDate, unit } = await getRequestDateRange(request); + + const auth = await checkAuth(request); + + if (!auth || !(await canViewWebsite(auth, websiteId))) { + return unauthorized(); + } + + const filters = { + ...getRequestFilters(request), + startDate, + endDate, + timezone, + unit, + }; + + const data = await getEventMetrics(websiteId, filters); + + return json(data); +} diff --git a/src/app/api/websites/[websiteId]/metrics/route.ts b/src/app/api/websites/[websiteId]/metrics/route.ts index 3edc0d88..3842c683 100644 --- a/src/app/api/websites/[websiteId]/metrics/route.ts +++ b/src/app/api/websites/[websiteId]/metrics/route.ts @@ -1,49 +1,36 @@ +import { z } from 'zod'; import { canViewWebsite, checkAuth } from 'lib/auth'; import { SESSION_COLUMNS, EVENT_COLUMNS, FILTER_COLUMNS, OPERATORS } from 'lib/constants'; import { getRequestFilters, getRequestDateRange, checkRequest } from 'lib/request'; -import { getPageviewMetrics, getSessionMetrics } from 'queries'; - -import { z } from 'zod'; import { json, unauthorized, badRequest } from 'lib/response'; - -const schema = z.object({ - type: z.string(), - startAt: z.coerce.number(), - endAt: z.coerce.number(), - // optional - url: z.string().optional(), - referrer: z.string().optional(), - title: z.string().optional(), - query: z.string().optional(), - host: z.string().optional(), - os: z.string().optional(), - browser: z.string().optional(), - device: z.string().optional(), - country: z.string().optional(), - region: z.string().optional(), - city: z.string().optional(), - language: z.string().optional(), - event: z.string().optional(), - limit: z.coerce.number().optional(), - offset: z.coerce.number().optional(), - search: z.string().optional(), - tag: z.string().optional(), -}); +import { getPageviewMetrics, getSessionMetrics } from 'queries'; +import { filterParams } from 'lib/schema'; export async function GET( request: Request, { params }: { params: Promise<{ websiteId: string }> }, ) { + const schema = z.object({ + type: z.string(), + startAt: z.coerce.number().int(), + endAt: z.coerce.number().int(), + limit: z.coerce.number().optional(), + offset: z.coerce.number().optional(), + search: z.string().optional(), + ...filterParams, + }); + const { query, error } = await checkRequest(request, schema); if (error) { return badRequest(error); } - const auth = await checkAuth(request); const { websiteId } = await params; const { type, limit, offset, search } = query; + const auth = await checkAuth(request); + if (!auth || !(await canViewWebsite(auth, websiteId))) { return unauthorized(); } diff --git a/src/app/api/websites/[websiteId]/pageviews/route.ts b/src/app/api/websites/[websiteId]/pageviews/route.ts index 7ba5b100..e9e6f32b 100644 --- a/src/app/api/websites/[websiteId]/pageviews/route.ts +++ b/src/app/api/websites/[websiteId]/pageviews/route.ts @@ -1,44 +1,35 @@ import { z } from 'zod'; import { canViewWebsite, checkAuth } from 'lib/auth'; import { getRequestFilters, getRequestDateRange, checkRequest } from 'lib/request'; -import { unit, timezone } from 'lib/schema'; +import { unitParam, timezoneParam, filterParams } from 'lib/schema'; import { getCompareDate } from 'lib/date'; import { badRequest, unauthorized, json } from 'lib/response'; import { getPageviewStats, getSessionStats } from 'queries'; -const schema = z.object({ - startAt: z.coerce.number(), - endAt: z.coerce.number(), - unit, - timezone, - url: z.string().optional(), - referrer: z.string().optional(), - title: z.string().optional(), - host: z.string().optional(), - os: z.string().optional(), - browser: z.string().optional(), - device: z.string().optional(), - country: z.string().optional(), - region: z.string().optional(), - city: z.string().optional(), - tag: z.string().optional(), - compare: z.string().optional(), -}); - export async function GET( request: Request, { params }: { params: Promise<{ websiteId: string }> }, ) { + const schema = z.object({ + startAt: z.coerce.number().int(), + endAt: z.coerce.number().int(), + unit: unitParam, + timezone: timezoneParam, + compare: z.string().optional(), + ...filterParams, + }); + const { query, error } = await checkRequest(request, schema); if (error) { return badRequest(error); } - const auth = await checkAuth(request); const { websiteId } = await params; const { timezone, compare } = query; + const auth = await checkAuth(request); + if (!auth || !(await canViewWebsite(auth, websiteId))) { return unauthorized(); } diff --git a/src/app/api/websites/[websiteId]/reports/route.ts b/src/app/api/websites/[websiteId]/reports/route.ts index f4fc641f..0098fa15 100644 --- a/src/app/api/websites/[websiteId]/reports/route.ts +++ b/src/app/api/websites/[websiteId]/reports/route.ts @@ -5,24 +5,25 @@ import { pagingParams } from 'lib/schema'; import { checkRequest } from 'lib/request'; import { badRequest, unauthorized, json } from 'lib/response'; -const schema = z.object({ - ...pagingParams, -}); - export async function GET( request: Request, { params }: { params: Promise<{ websiteId: string }> }, ) { + const schema = z.object({ + ...pagingParams, + }); + const { query, error } = await checkRequest(request, schema); if (error) { return badRequest(error); } - const auth = await checkAuth(request); const { websiteId } = await params; const { page, pageSize, search } = query; + const auth = await checkAuth(request); + if (!auth || !(await canViewWebsite(auth, websiteId))) { return unauthorized(); } diff --git a/src/app/api/websites/[websiteId]/reset/route.ts b/src/app/api/websites/[websiteId]/reset/route.ts index ae2131e8..bfbd11a8 100644 --- a/src/app/api/websites/[websiteId]/reset/route.ts +++ b/src/app/api/websites/[websiteId]/reset/route.ts @@ -6,9 +6,10 @@ export async function POST( request: Request, { params }: { params: Promise<{ websiteId: string }> }, ) { - const auth = await checkAuth(request); const { websiteId } = await params; + const auth = await checkAuth(request); + if (!auth || !(await canUpdateWebsite(auth, websiteId))) { return unauthorized(); } diff --git a/src/app/api/websites/[websiteId]/route.ts b/src/app/api/websites/[websiteId]/route.ts index 02bb00f8..e8ad8a0b 100644 --- a/src/app/api/websites/[websiteId]/route.ts +++ b/src/app/api/websites/[websiteId]/route.ts @@ -9,15 +9,11 @@ export async function GET( request: Request, { params }: { params: Promise<{ websiteId: string }> }, ) { - const auth = await checkAuth(request); - - if (!auth) { - return unauthorized(); - } - const { websiteId } = await params; - if (!(await canViewWebsite(auth, websiteId))) { + const auth = await checkAuth(request); + + if (!auth || !(await canViewWebsite(auth, websiteId))) { return unauthorized(); } @@ -42,10 +38,11 @@ export async function POST( return badRequest(error); } - const auth = await checkAuth(request); const { websiteId } = await params; const { name, domain, shareId } = body; + const auth = await checkAuth(request); + if (!auth || !(await canUpdateWebsite(auth, websiteId))) { return unauthorized(); } @@ -67,15 +64,11 @@ export async function DELETE( request: Request, { params }: { params: Promise<{ websiteId: string }> }, ) { - const auth = await checkAuth(request); - - if (!auth) { - return unauthorized(); - } - const { websiteId } = await params; - if (!(await canDeleteWebsite(auth, websiteId))) { + const auth = await checkAuth(request); + + if (!auth || !(await canDeleteWebsite(auth, websiteId))) { return unauthorized(); } diff --git a/src/app/api/websites/[websiteId]/session-data/properties/route.ts b/src/app/api/websites/[websiteId]/session-data/properties/route.ts new file mode 100644 index 00000000..af168f84 --- /dev/null +++ b/src/app/api/websites/[websiteId]/session-data/properties/route.ts @@ -0,0 +1,38 @@ +import { z } from 'zod'; +import { checkRequest } from 'lib/request'; +import { badRequest, unauthorized, json } from 'lib/response'; +import { canViewWebsite, checkAuth } from 'lib/auth'; +import { getSessionDataProperties } from 'queries'; + +export async function GET( + request: Request, + { params }: { params: Promise<{ websiteId: string }> }, +) { + const schema = z.object({ + startAt: z.coerce.number().int(), + endAt: z.coerce.number().int(), + propertyName: z.string().optional(), + }); + + const { query, error } = await checkRequest(request, schema); + + if (error) { + return badRequest(error); + } + + const { startAt, endAt, propertyName } = query; + const { websiteId } = await params; + + const auth = await checkAuth(request); + + if (!auth || !(await canViewWebsite(auth, websiteId))) { + return unauthorized(); + } + + const startDate = new Date(+startAt); + const endDate = new Date(+endAt); + + const data = await getSessionDataProperties(websiteId, { startDate, endDate, propertyName }); + + return json(data); +} diff --git a/src/app/api/websites/[websiteId]/session-data/values/route.ts b/src/app/api/websites/[websiteId]/session-data/values/route.ts new file mode 100644 index 00000000..627298af --- /dev/null +++ b/src/app/api/websites/[websiteId]/session-data/values/route.ts @@ -0,0 +1,42 @@ +import { z } from 'zod'; +import { checkRequest } from 'lib/request'; +import { badRequest, unauthorized, json } from 'lib/response'; +import { canViewWebsite, checkAuth } from 'lib/auth'; +import { getEventDataEvents } from 'queries/analytics/events/getEventDataEvents'; + +export async function GET( + request: Request, + { params }: { params: Promise<{ websiteId: string }> }, +) { + const schema = z.object({ + startAt: z.coerce.number().int(), + endAt: z.coerce.number().int(), + propertyName: z.string().optional(), + }); + + const { query, error } = await checkRequest(request, schema); + + if (error) { + return badRequest(error); + } + + const { startAt, endAt, event } = query; + const { websiteId } = await params; + + const auth = await checkAuth(request); + + if (!auth || !(await canViewWebsite(auth, websiteId))) { + return unauthorized(); + } + + const startDate = new Date(+startAt); + const endDate = new Date(+endAt); + + const data = await getEventDataEvents(websiteId, { + startDate, + endDate, + event, + }); + + return json(data); +} diff --git a/src/app/api/websites/[websiteId]/sessions/[sessionId]/activity/route.ts b/src/app/api/websites/[websiteId]/sessions/[sessionId]/activity/route.ts new file mode 100644 index 00000000..48123ffe --- /dev/null +++ b/src/app/api/websites/[websiteId]/sessions/[sessionId]/activity/route.ts @@ -0,0 +1,37 @@ +import { z } from 'zod'; +import { checkRequest } from 'lib/request'; +import { badRequest, unauthorized, json } from 'lib/response'; +import { canViewWebsite, checkAuth } from 'lib/auth'; +import { getSessionActivity } from 'queries'; + +export async function GET( + request: Request, + { params }: { params: Promise<{ websiteId: string; sessionId: string }> }, +) { + const schema = z.object({ + startAt: z.coerce.number().int(), + endAt: z.coerce.number().int(), + }); + + const { query, error } = await checkRequest(request, schema); + + if (error) { + return badRequest(error); + } + + const { websiteId, sessionId } = await params; + const { startAt, endAt } = query; + + const auth = await checkAuth(request); + + if (!auth || !(await canViewWebsite(auth, websiteId))) { + return unauthorized(); + } + + const startDate = new Date(+startAt); + const endDate = new Date(+endAt); + + const data = await getSessionActivity(websiteId, sessionId, startDate, endDate); + + return json(data); +} diff --git a/src/app/api/websites/[websiteId]/sessions/[sessionId]/properties/route.ts b/src/app/api/websites/[websiteId]/sessions/[sessionId]/properties/route.ts new file mode 100644 index 00000000..7c5863e8 --- /dev/null +++ b/src/app/api/websites/[websiteId]/sessions/[sessionId]/properties/route.ts @@ -0,0 +1,20 @@ +import { unauthorized, json } from 'lib/response'; +import { canViewWebsite, checkAuth } from 'lib/auth'; +import { getSessionData } from 'queries'; + +export async function GET( + request: Request, + { params }: { params: Promise<{ websiteId: string; sessionId: string }> }, +) { + const { websiteId, sessionId } = await params; + + const auth = await checkAuth(request); + + if (!auth || !(await canViewWebsite(auth, websiteId))) { + return unauthorized(); + } + + const data = await getSessionData(websiteId, sessionId); + + return json(data); +} diff --git a/src/app/api/websites/[websiteId]/sessions/[sessionId]/route.ts b/src/app/api/websites/[websiteId]/sessions/[sessionId]/route.ts new file mode 100644 index 00000000..6822aaa0 --- /dev/null +++ b/src/app/api/websites/[websiteId]/sessions/[sessionId]/route.ts @@ -0,0 +1,20 @@ +import { unauthorized, json } from 'lib/response'; +import { canViewWebsite, checkAuth } from 'lib/auth'; +import { getWebsiteSession } from 'queries'; + +export async function GET( + request: Request, + { params }: { params: Promise<{ websiteId: string; sessionId: string }> }, +) { + const { websiteId, sessionId } = await params; + + const auth = await checkAuth(request); + + if (!auth || !(await canViewWebsite(auth, websiteId))) { + return unauthorized(); + } + + const data = await getWebsiteSession(websiteId, sessionId); + + return json(data); +} diff --git a/src/app/api/websites/[websiteId]/sessions/route.ts b/src/app/api/websites/[websiteId]/sessions/route.ts new file mode 100644 index 00000000..c96a8ddb --- /dev/null +++ b/src/app/api/websites/[websiteId]/sessions/route.ts @@ -0,0 +1,39 @@ +import { z } from 'zod'; +import { checkRequest } from 'lib/request'; +import { badRequest, unauthorized, json } from 'lib/response'; +import { canViewWebsite, checkAuth } from 'lib/auth'; +import { pagingParams } from 'lib/schema'; +import { getWebsiteSessions } from 'queries'; + +export async function GET( + request: Request, + { params }: { params: Promise<{ websiteId: string }> }, +) { + const schema = z.object({ + startAt: z.coerce.number().int(), + endAt: z.coerce.number().int(), + ...pagingParams, + }); + + const { query, error } = await checkRequest(request, schema); + + if (error) { + return badRequest(error); + } + + const { websiteId } = await params; + const { startAt, endAt } = query; + + const auth = await checkAuth(request); + + if (!auth || !(await canViewWebsite(auth, websiteId))) { + return unauthorized(); + } + + const startDate = new Date(+startAt); + const endDate = new Date(+endAt); + + const data = await getWebsiteSessions(websiteId, { startDate, endDate }, query); + + return json(data); +} diff --git a/src/app/api/websites/[websiteId]/sessions/stats/route.ts b/src/app/api/websites/[websiteId]/sessions/stats/route.ts new file mode 100644 index 00000000..35c17021 --- /dev/null +++ b/src/app/api/websites/[websiteId]/sessions/stats/route.ts @@ -0,0 +1,50 @@ +import { z } from 'zod'; +import { checkRequest, getRequestDateRange, getRequestFilters } from 'lib/request'; +import { badRequest, unauthorized, json } from 'lib/response'; +import { canViewWebsite, checkAuth } from 'lib/auth'; +import { filterParams } from 'lib/schema'; +import { getWebsiteSessionStats } from 'queries'; + +export async function GET( + request: Request, + { params }: { params: Promise<{ websiteId: string }> }, +) { + const schema = z.object({ + startAt: z.coerce.number().int(), + endAt: z.coerce.number().int(), + ...filterParams, + }); + + const { error } = await checkRequest(request, schema); + + if (error) { + return badRequest(error); + } + + const { websiteId } = await params; + + const auth = await checkAuth(request); + + if (!auth || !(await canViewWebsite(auth, websiteId))) { + return unauthorized(); + } + + const { startDate, endDate } = await getRequestDateRange(request); + + const filters = getRequestFilters(request); + + const metrics = await getWebsiteSessionStats(websiteId, { + ...filters, + startDate, + endDate, + }); + + const data = Object.keys(metrics[0]).reduce((obj, key) => { + obj[key] = { + value: Number(metrics[0][key]) || 0, + }; + return obj; + }, {}); + + return json(data); +} diff --git a/src/app/api/websites/[websiteId]/sessions/weekly/route.ts b/src/app/api/websites/[websiteId]/sessions/weekly/route.ts new file mode 100644 index 00000000..05cc9cad --- /dev/null +++ b/src/app/api/websites/[websiteId]/sessions/weekly/route.ts @@ -0,0 +1,40 @@ +import { z } from 'zod'; +import { checkRequest } from 'lib/request'; +import { badRequest, unauthorized, json } from 'lib/response'; +import { canViewWebsite, checkAuth } from 'lib/auth'; +import { pagingParams, timezoneParam } from 'lib/schema'; +import { getWebsiteSessionsWeekly } from 'queries'; + +export async function GET( + request: Request, + { params }: { params: Promise<{ websiteId: string }> }, +) { + const schema = z.object({ + startAt: z.coerce.number().int(), + endAt: z.coerce.number().int(), + timezone: timezoneParam, + ...pagingParams, + }); + + const { query, error } = await checkRequest(request, schema); + + if (error) { + return badRequest(error); + } + + const { websiteId } = await params; + const { startAt, endAt, timezone } = query; + + const auth = await checkAuth(request); + + if (!auth || !(await canViewWebsite(auth, websiteId))) { + return unauthorized(); + } + + const startDate = new Date(+startAt); + const endDate = new Date(+endAt); + + const data = await getWebsiteSessionsWeekly(websiteId, { startDate, endDate, timezone }); + + return json(data); +} diff --git a/src/app/api/websites/[websiteId]/stats/route.ts b/src/app/api/websites/[websiteId]/stats/route.ts index 1c96a74f..76d9f9a6 100644 --- a/src/app/api/websites/[websiteId]/stats/route.ts +++ b/src/app/api/websites/[websiteId]/stats/route.ts @@ -3,42 +3,31 @@ import { checkRequest, getRequestDateRange, getRequestFilters } from 'lib/reques import { badRequest, unauthorized, json } from 'lib/response'; import { checkAuth, canViewWebsite } from 'lib/auth'; import { getCompareDate } from 'lib/date'; +import { filterParams } from 'lib/schema'; import { getWebsiteStats } from 'queries'; -const schema = z.object({ - startAt: z.coerce.number(), - endAt: z.coerce.number(), - // optional - url: z.string().optional(), - referrer: z.string().optional(), - title: z.string().optional(), - query: z.string().optional(), - event: z.string().optional(), - host: z.string().optional(), - os: z.string().optional(), - browser: z.string().optional(), - device: z.string().optional(), - country: z.string().optional(), - region: z.string().optional(), - city: z.string().optional(), - tag: z.string().optional(), - compare: z.string().optional(), -}); - export async function GET( request: Request, { params }: { params: Promise<{ websiteId: string }> }, ) { + const schema = z.object({ + startAt: z.coerce.number().int(), + endAt: z.coerce.number().int(), + compare: z.string().optional(), + ...filterParams, + }); + const { query, error } = await checkRequest(request, schema); if (error) { return badRequest(error); } - const auth = await checkAuth(request); const { websiteId } = await params; const { compare } = query; + const auth = await checkAuth(request); + if (!auth || !(await canViewWebsite(auth, websiteId))) { return unauthorized(); } diff --git a/src/app/api/websites/[websiteId]/transfer/route.ts b/src/app/api/websites/[websiteId]/transfer/route.ts index d97fe0f8..8771ecc2 100644 --- a/src/app/api/websites/[websiteId]/transfer/route.ts +++ b/src/app/api/websites/[websiteId]/transfer/route.ts @@ -4,25 +4,26 @@ import { updateWebsite } from 'queries'; import { checkRequest } from 'lib/request'; import { badRequest, unauthorized, json } from 'lib/response'; -const schema = z.object({ - userId: z.string().uuid().optional(), - teamId: z.string().uuid().optional(), -}); - export async function POST( request: Request, { params }: { params: Promise<{ websiteId: string }> }, ) { + const schema = z.object({ + userId: z.string().uuid().optional(), + teamId: z.string().uuid().optional(), + }); + const { body, error } = await checkRequest(request, schema); if (error) { return badRequest(error); } - const auth = await checkAuth(request); const { websiteId } = await params; const { userId, teamId } = body; + const auth = await checkAuth(request); + if (!auth) { return unauthorized(); } else if (userId) { diff --git a/src/app/api/websites/[websiteId]/values/route.ts b/src/app/api/websites/[websiteId]/values/route.ts index 1a4967b8..fe4edfbb 100644 --- a/src/app/api/websites/[websiteId]/values/route.ts +++ b/src/app/api/websites/[websiteId]/values/route.ts @@ -5,28 +5,29 @@ import { getValues } from 'queries'; import { checkRequest, getRequestDateRange } from 'lib/request'; import { badRequest, json, unauthorized } from 'lib/response'; -const schema = z.object({ - type: z.string(), - startAt: z.coerce.number(), - endAt: z.coerce.number(), - search: z.string().optional(), -}); - export async function GET( request: Request, { params }: { params: Promise<{ websiteId: string }> }, ) { + const schema = z.object({ + type: z.string(), + startAt: z.coerce.number().int(), + endAt: z.coerce.number().int(), + search: z.string().optional(), + }); + const { query, error } = await checkRequest(request, schema); if (error) { return badRequest(error); } - const auth = await checkAuth(request); const { websiteId } = await params; const { type, search } = query; const { startDate, endDate } = await getRequestDateRange(request); + const auth = await checkAuth(request); + if (!auth || !(await canViewWebsite(auth, websiteId))) { return unauthorized(); } diff --git a/src/lib/schema.ts b/src/lib/schema.ts index 9153d0f9..c39e47fd 100644 --- a/src/lib/schema.ts +++ b/src/lib/schema.ts @@ -22,10 +22,27 @@ export const pagingParams = { query: z.string().optional(), }; -export const timezone = z.string().refine(value => isValidTimezone(value), { +export const timezoneParam = z.string().refine(value => isValidTimezone(value), { message: 'Invalid timezone', }); -export const unit = z.string().refine(value => UNIT_TYPES.includes(value), { +export const unitParam = z.string().refine(value => UNIT_TYPES.includes(value), { message: 'Invalid unit', }); + +export const filterParams = { + url: z.string().optional(), + referrer: z.string().optional(), + title: z.string().optional(), + query: z.string().optional(), + os: z.string().optional(), + browser: z.string().optional(), + device: z.string().optional(), + country: z.string().optional(), + region: z.string().optional(), + city: z.string().optional(), + tag: z.string().optional(), + host: z.string().optional(), + language: z.string().optional(), + event: z.string().optional(), +}; diff --git a/src/pages/api/websites/[websiteId]/event-data/events.ts b/src/pages/api/websites/[websiteId]/event-data/_events.ts similarity index 100% rename from src/pages/api/websites/[websiteId]/event-data/events.ts rename to src/pages/api/websites/[websiteId]/event-data/_events.ts diff --git a/src/pages/api/websites/[websiteId]/event-data/fields.ts b/src/pages/api/websites/[websiteId]/event-data/_fields.ts similarity index 100% rename from src/pages/api/websites/[websiteId]/event-data/fields.ts rename to src/pages/api/websites/[websiteId]/event-data/_fields.ts diff --git a/src/pages/api/websites/[websiteId]/event-data/properties.ts b/src/pages/api/websites/[websiteId]/event-data/_properties.ts similarity index 100% rename from src/pages/api/websites/[websiteId]/event-data/properties.ts rename to src/pages/api/websites/[websiteId]/event-data/_properties.ts diff --git a/src/pages/api/websites/[websiteId]/event-data/stats.ts b/src/pages/api/websites/[websiteId]/event-data/_stats.ts similarity index 100% rename from src/pages/api/websites/[websiteId]/event-data/stats.ts rename to src/pages/api/websites/[websiteId]/event-data/_stats.ts diff --git a/src/pages/api/websites/[websiteId]/event-data/values.ts b/src/pages/api/websites/[websiteId]/event-data/_values.ts similarity index 100% rename from src/pages/api/websites/[websiteId]/event-data/values.ts rename to src/pages/api/websites/[websiteId]/event-data/_values.ts diff --git a/src/pages/api/websites/[websiteId]/events/index.ts b/src/pages/api/websites/[websiteId]/events/_index.ts similarity index 100% rename from src/pages/api/websites/[websiteId]/events/index.ts rename to src/pages/api/websites/[websiteId]/events/_index.ts diff --git a/src/pages/api/websites/[websiteId]/events/series.ts b/src/pages/api/websites/[websiteId]/events/_series.ts similarity index 100% rename from src/pages/api/websites/[websiteId]/events/series.ts rename to src/pages/api/websites/[websiteId]/events/_series.ts diff --git a/src/pages/api/websites/[websiteId]/session-data/properties.ts b/src/pages/api/websites/[websiteId]/session-data/_properties.ts similarity index 100% rename from src/pages/api/websites/[websiteId]/session-data/properties.ts rename to src/pages/api/websites/[websiteId]/session-data/_properties.ts diff --git a/src/pages/api/websites/[websiteId]/session-data/values.ts b/src/pages/api/websites/[websiteId]/session-data/_values.ts similarity index 100% rename from src/pages/api/websites/[websiteId]/session-data/values.ts rename to src/pages/api/websites/[websiteId]/session-data/_values.ts diff --git a/src/pages/api/websites/[websiteId]/sessions/[sessionId]/activity.ts b/src/pages/api/websites/[websiteId]/sessions/[sessionId]/_activity.ts similarity index 100% rename from src/pages/api/websites/[websiteId]/sessions/[sessionId]/activity.ts rename to src/pages/api/websites/[websiteId]/sessions/[sessionId]/_activity.ts diff --git a/src/pages/api/websites/[websiteId]/sessions/[sessionId]/index.ts b/src/pages/api/websites/[websiteId]/sessions/[sessionId]/_index.ts similarity index 100% rename from src/pages/api/websites/[websiteId]/sessions/[sessionId]/index.ts rename to src/pages/api/websites/[websiteId]/sessions/[sessionId]/_index.ts diff --git a/src/pages/api/websites/[websiteId]/sessions/[sessionId]/properties.ts b/src/pages/api/websites/[websiteId]/sessions/[sessionId]/_properties.ts similarity index 100% rename from src/pages/api/websites/[websiteId]/sessions/[sessionId]/properties.ts rename to src/pages/api/websites/[websiteId]/sessions/[sessionId]/_properties.ts diff --git a/src/pages/api/websites/[websiteId]/sessions/index.ts b/src/pages/api/websites/[websiteId]/sessions/_index.ts similarity index 100% rename from src/pages/api/websites/[websiteId]/sessions/index.ts rename to src/pages/api/websites/[websiteId]/sessions/_index.ts diff --git a/src/pages/api/websites/[websiteId]/sessions/stats.ts b/src/pages/api/websites/[websiteId]/sessions/_stats.ts similarity index 100% rename from src/pages/api/websites/[websiteId]/sessions/stats.ts rename to src/pages/api/websites/[websiteId]/sessions/_stats.ts diff --git a/src/pages/api/websites/[websiteId]/sessions/weekly.ts b/src/pages/api/websites/[websiteId]/sessions/_weekly.ts similarity index 100% rename from src/pages/api/websites/[websiteId]/sessions/weekly.ts rename to src/pages/api/websites/[websiteId]/sessions/_weekly.ts diff --git a/src/queries/index.ts b/src/queries/index.ts index 8c7e564a..63cebb45 100644 --- a/src/queries/index.ts +++ b/src/queries/index.ts @@ -27,6 +27,7 @@ export * from './analytics/sessions/getSessionDataProperties'; export * from './analytics/sessions/getSessionDataValues'; export * from './analytics/sessions/getSessionMetrics'; export * from './analytics/sessions/getWebsiteSessions'; +export * from './analytics/sessions/getWebsiteSessionStats'; export * from './analytics/sessions/getWebsiteSessionsWeekly'; export * from './analytics/sessions/getSessionActivity'; export * from './analytics/sessions/getSessionStats';