Public Access
1
0

feat: split research workflows and results into dedicated workspaces

This commit is contained in:
pguerrerox
2026-04-19 22:31:33 +00:00
parent dc7686f507
commit d1363d6677
23 changed files with 1646 additions and 713 deletions
+11 -1
View File
@@ -53,9 +53,15 @@ export function setSessionCookie(reply: FastifyReply, token: string, expiresAt:
}
export function clearSessionCookie(reply: FastifyReply) {
reply.clearCookie(SESSION_COOKIE_NAME, {
const env = getEnv();
reply.setCookie(SESSION_COOKIE_NAME, '', {
path: '/',
httpOnly: true,
sameSite: 'lax',
secure: env.NODE_ENV === 'production',
expires: new Date(0),
maxAge: 0,
});
}
@@ -89,6 +95,10 @@ export async function deleteSessionByToken(db: DbClient, token: string) {
await db.query('delete from public.sessions where token_hash = $1', [hashSessionToken(token)]);
}
export async function deleteSessionById(db: DbClient, sessionId: string) {
await db.query('delete from public.sessions where id = $1', [sessionId]);
}
export async function getSessionUserByToken(db: DbClient, token: string) {
const tokenHash = hashSessionToken(token);
const result = await db.query<SessionRow>(
+13 -2
View File
@@ -1,7 +1,7 @@
import type { FastifyPluginAsync, FastifyRequest } from 'fastify';
import { ZodError, z } from 'zod';
import { hashPassword, verifyPassword } from '../auth/passwords.js';
import { clearSessionCookie, createSession, deleteSessionByToken, getSessionTokenFromRequest, getSessionUserByToken, setSessionCookie, } from '../auth/sessions.js';
import { clearSessionCookie, createSession, deleteSessionById, deleteSessionByToken, getSessionTokenFromRequest, getSessionUserByToken, setSessionCookie, } from '../auth/sessions.js';
import { createUser, getUserByEmail, toAppUser } from '../auth/users.js';
import { getDbPool } from '../db/pool.js';
@@ -16,6 +16,10 @@ const loginSchema = z.object({
password: z.string().min(1),
});
const logoutSchema = z.object({
sessionId: z.string().uuid().optional(),
});
function getRequestMetadata(request: FastifyRequest) {
return {
userAgent: request.headers['user-agent'],
@@ -101,9 +105,16 @@ export const authRoutes: FastifyPluginAsync = async (app) => {
app.post('/auth/logout', async (request, reply) => {
const token = getSessionTokenFromRequest(request);
const payload = logoutSchema.safeParse(request.body);
const sessionId = payload.success ? payload.data.sessionId : undefined;
const db = getDbPool();
if (token) {
await deleteSessionByToken(getDbPool(), token);
await deleteSessionByToken(db, token);
}
if (sessionId) {
await deleteSessionById(db, sessionId);
}
clearSessionCookie(reply);
+2
View File
@@ -11,6 +11,8 @@ const runSearchSchema = z.object({
radiusKm: z.coerce.number().positive().max(50),
businessType: z.string().trim().min(1),
keywords: z.string().trim().optional(),
lat: z.number().finite().min(-90).max(90).optional(),
lng: z.number().finite().min(-180).max(180).optional(),
});
const jobParamsSchema = z.object({
+22 -5
View File
@@ -66,7 +66,19 @@ async function executeSearchJobAtCoordinates(
}
export async function runSearchForUser(db: Pool, userId: string, payload: RunSearchInput): Promise<RunSearchResult> {
const job = await createSearchJob(db, userId, payload);
const hasProvidedCoordinates = typeof payload.lat === 'number' && typeof payload.lng === 'number';
const job = hasProvidedCoordinates
? await createSearchJobForCoordinates(db, userId, {
name: payload.name || `${payload.businessType} in ${payload.location}`,
city: payload.location,
address: payload.location,
radiusKm: payload.radiusKm,
businessType: payload.businessType,
keywords: payload.keywords,
lat: payload.lat!,
lng: payload.lng!,
})
: await createSearchJob(db, userId, payload);
const jobId = job.id;
try {
@@ -75,14 +87,19 @@ export async function runSearchForUser(db: Pool, userId: string, payload: RunSea
throw new Error('GOOGLE_MAPS_SERVER_KEY is required for running research.');
}
const geocoded = await geocodeLocation(payload.location, env.GOOGLE_MAPS_SERVER_KEY);
await updateSearchJobCenter(db, jobId, geocoded.lat, geocoded.lng);
const resolvedCenter = hasProvidedCoordinates
? { lat: payload.lat!, lng: payload.lng! }
: await geocodeLocation(payload.location, env.GOOGLE_MAPS_SERVER_KEY);
if (!hasProvidedCoordinates) {
await updateSearchJobCenter(db, jobId, resolvedCenter.lat, resolvedCenter.lng);
}
const completedJob = await executeSearchJobAtCoordinates(db, {
jobId,
userId,
lat: geocoded.lat,
lng: geocoded.lng,
lat: resolvedCenter.lat,
lng: resolvedCenter.lng,
radiusKm: payload.radiusKm,
businessType: payload.businessType,
keywords: payload.keywords,
+2
View File
@@ -56,6 +56,8 @@ export type RunSearchInput = {
radiusKm: number;
businessType: string;
keywords?: string;
lat?: number;
lng?: number;
};
export type RunSearchResult = {