Merge branch 'dev' into jajaja

This commit is contained in:
Mike Cao 2025-03-01 17:31:30 -08:00
commit fdc73268b7
16 changed files with 142 additions and 59 deletions

View file

@ -0,0 +1,39 @@
import { z } from 'zod';
import * as send from '@/app/api/send/route';
import { parseRequest } from '@/lib/request';
import { json, serverError } from '@/lib/response';
const schema = z.array(z.object({}).passthrough());
export async function POST(request: Request) {
try {
const { body, error } = await parseRequest(request, schema, { skipAuth: true });
if (error) {
return error();
}
const errors = [];
let index = 0;
for (const data of body) {
const newRequest = new Request(request, { body: JSON.stringify(data) });
const response = await send.POST(newRequest);
if (!response.ok) {
errors.push({ index, response: await response.json() });
}
index++;
}
return json({
size: body.length,
processed: body.length - errors.length,
errors: errors.length,
details: errors,
});
} catch (e) {
return serverError(e);
}
}

View file

@ -1,31 +1,33 @@
import { z } from 'zod';
import { isbot } from 'isbot';
import { createToken, parseToken } from '@/lib/jwt';
import { startOfHour, startOfMonth } from 'date-fns';
import clickhouse from '@/lib/clickhouse';
import { parseRequest } from '@/lib/request';
import { badRequest, json, forbidden, serverError } from '@/lib/response';
import { fetchSession, fetchWebsite } from '@/lib/load';
import { getClientInfo, hasBlockedIp } from '@/lib/detect';
import { secret, uuid, visitSalt } from '@/lib/crypto';
import { COLLECTION_TYPE, DOMAIN_REGEX } from '@/lib/constants';
import { createToken, parseToken } from '@/lib/jwt';
import { secret, uuid, hash } from '@/lib/crypto';
import { COLLECTION_TYPE } from '@/lib/constants';
import { anyObjectParam, urlOrPathParam } from '@/lib/schema';
import { createSession, saveEvent, saveSessionData } from '@/queries';
import { urlOrPathParam } from '@/lib/schema';
const schema = z.object({
type: z.enum(['event', 'identify']),
payload: z.object({
website: z.string().uuid(),
data: z.object({}).passthrough().optional(),
hostname: z.string().regex(DOMAIN_REGEX).max(100).optional(),
data: anyObjectParam.optional(),
hostname: z.string().max(100).optional(),
language: z.string().max(35).optional(),
referrer: urlOrPathParam.optional(),
screen: z.string().max(11).optional(),
title: z.string().optional(),
url: urlOrPathParam,
url: urlOrPathParam.optional(),
name: z.string().max(50).optional(),
tag: z.string().max(50).optional(),
ip: z.string().ip().optional(),
userAgent: z.string().optional(),
timestamp: z.coerce.number().int().optional(),
}),
});
@ -55,6 +57,7 @@ export async function POST(request: Request) {
data,
title,
tag,
timestamp,
} = payload;
// Cache check
@ -87,7 +90,13 @@ export async function POST(request: Request) {
return forbidden();
}
const sessionId = uuid(websiteId, ip, userAgent);
const createdAt = timestamp ? new Date(timestamp * 1000) : new Date();
const now = Math.floor(new Date().getTime() / 1000);
const sessionSalt = hash(startOfMonth(createdAt).toUTCString());
const visitSalt = hash(startOfHour(createdAt).toUTCString());
const sessionId = uuid(websiteId, ip, userAgent, sessionSalt);
// Find session
if (!clickhouse.enabled && !cache?.sessionId) {
@ -119,13 +128,12 @@ export async function POST(request: Request) {
}
// Visit info
const now = Math.floor(new Date().getTime() / 1000);
let visitId = cache?.visitId || uuid(sessionId, visitSalt());
let visitId = cache?.visitId || uuid(sessionId, visitSalt);
let iat = cache?.iat || now;
// Expire visit after 30 minutes
if (now - iat > 1800) {
visitId = uuid(sessionId, visitSalt());
if (!timestamp && now - iat > 1800) {
visitId = uuid(sessionId, visitSalt);
iat = now;
}
@ -179,6 +187,7 @@ export async function POST(request: Request) {
subdivision2,
city,
tag,
createdAt,
});
}
@ -191,12 +200,13 @@ export async function POST(request: Request) {
websiteId,
sessionId,
sessionData: data,
createdAt,
});
}
const token = createToken({ websiteId, sessionId, visitId, iat }, secret());
return json({ cache: token });
return json({ cache: token, sessionId, visitId });
} catch (e) {
return serverError(e);
}

View file

@ -8,6 +8,7 @@ import { createUser, getUserByUsername } from '@/queries';
export async function POST(request: Request) {
const schema = z.object({
id: z.string().uuid().optional(),
username: z.string().max(255),
password: z.string(),
role: z.string().regex(/admin|user|view-only/i),
@ -23,7 +24,7 @@ export async function POST(request: Request) {
return unauthorized();
}
const { username, password, role } = body;
const { id, username, password, role } = body;
const existingUser = await getUserByUsername(username, { showDeleted: true });
@ -32,7 +33,7 @@ export async function POST(request: Request) {
}
const user = await createUser({
id: uuid(),
id: id || uuid(),
username,
password: hashPassword(password),
role: role ?? ROLES.user,