diff options
Diffstat (limited to 'src/lib/request.ts')
| -rw-r--r-- | src/lib/request.ts | 145 |
1 files changed, 145 insertions, 0 deletions
diff --git a/src/lib/request.ts b/src/lib/request.ts new file mode 100644 index 0000000..42c4490 --- /dev/null +++ b/src/lib/request.ts @@ -0,0 +1,145 @@ +import { z } from 'zod'; +import { checkAuth } from '@/lib/auth'; +import { DEFAULT_PAGE_SIZE, FILTER_COLUMNS } from '@/lib/constants'; +import { getAllowedUnits, getMinimumUnit, maxDate, parseDateRange } from '@/lib/date'; +import { fetchWebsite } from '@/lib/load'; +import { filtersArrayToObject } from '@/lib/params'; +import { badRequest, unauthorized } from '@/lib/response'; +import type { QueryFilters } from '@/lib/types'; +import { getWebsiteSegment } from '@/queries/prisma'; + +export async function parseRequest( + request: Request, + schema?: any, + options?: { skipAuth: boolean }, +): Promise<any> { + const url = new URL(request.url); + let query = Object.fromEntries(url.searchParams); + let body = await getJsonBody(request); + let error: () => undefined | undefined; + let auth = null; + + if (schema) { + const isGet = request.method === 'GET'; + const result = schema.safeParse(isGet ? query : body); + + if (!result.success) { + error = () => badRequest(z.treeifyError(result.error)); + } else if (isGet) { + query = result.data; + } else { + body = result.data; + } + } + + if (!options?.skipAuth && !error) { + auth = await checkAuth(request); + + if (!auth) { + error = () => unauthorized(); + } + } + + return { url, query, body, auth, error }; +} + +export async function getJsonBody(request: Request) { + try { + return await request.clone().json(); + } catch { + return undefined; + } +} + +export function getRequestDateRange(query: Record<string, string>) { + const { startAt, endAt, unit, timezone } = query; + + const startDate = new Date(+startAt); + const endDate = new Date(+endAt); + + return { + startDate, + endDate, + timezone, + unit: getAllowedUnits(startDate, endDate).includes(unit) + ? unit + : getMinimumUnit(startDate, endDate), + }; +} + +export function getRequestFilters(query: Record<string, any>) { + const result: Record<string, any> = {}; + + for (const key of Object.keys(FILTER_COLUMNS)) { + const value = query[key]; + if (value !== undefined) { + result[key] = value; + } + } + + return result; +} + +export async function setWebsiteDate(websiteId: string, data: Record<string, any>) { + const website = await fetchWebsite(websiteId); + + if (website?.resetAt) { + data.startDate = maxDate(data.startDate, new Date(website?.resetAt)); + } + + return data; +} + +export async function getQueryFilters( + params: Record<string, any>, + websiteId?: string, +): Promise<QueryFilters> { + const dateRange = getRequestDateRange(params); + const filters = getRequestFilters(params); + + if (websiteId) { + await setWebsiteDate(websiteId, dateRange); + + if (params.segment) { + const segmentParams = (await getWebsiteSegment(websiteId, params.segment)) + ?.parameters as Record<string, any>; + + Object.assign(filters, filtersArrayToObject(segmentParams.filters)); + } + + if (params.cohort) { + const cohortParams = (await getWebsiteSegment(websiteId, params.cohort)) + ?.parameters as Record<string, any>; + + const { startDate, endDate } = parseDateRange(cohortParams.dateRange); + + const cohortFilters = cohortParams.filters.map(({ name, ...props }) => ({ + ...props, + name: `cohort_${name}`, + })); + + cohortFilters.push({ + name: `cohort_${cohortParams.action.type}`, + operator: 'eq', + value: cohortParams.action.value, + }); + + Object.assign(filters, { + ...filtersArrayToObject(cohortFilters), + cohort_startDate: startDate, + cohort_endDate: endDate, + }); + } + } + + return { + ...dateRange, + ...filters, + page: params?.page, + pageSize: params?.pageSize ? params?.pageSize || DEFAULT_PAGE_SIZE : undefined, + orderBy: params?.orderBy, + sortDescending: params?.sortDescending, + search: params?.search, + compare: params?.compare, + }; +} |