import { Elysia, t } from 'elysia'; import { db } from '../db'; import { events, clients } from '../db/schema'; import { eq, and, gte, lte, sql } from 'drizzle-orm'; import type { User } from '../lib/auth'; export const eventRoutes = new Elysia({ prefix: '/events' }) // List events with optional filters .get('/', async ({ query, user }: { query: { clientId?: string; type?: string; upcoming?: string; // days ahead }; user: User; }) => { let conditions = [eq(events.userId, user.id)]; if (query.clientId) { conditions.push(eq(events.clientId, query.clientId)); } if (query.type) { conditions.push(eq(events.type, query.type)); } let results = await db.select({ event: events, client: { id: clients.id, firstName: clients.firstName, lastName: clients.lastName, }, }) .from(events) .innerJoin(clients, eq(events.clientId, clients.id)) .where(and(...conditions)) .orderBy(events.date); // Filter upcoming events if requested if (query.upcoming) { const daysAhead = parseInt(query.upcoming) || 7; const now = new Date(); const future = new Date(); future.setDate(future.getDate() + daysAhead); results = results.filter(r => { const eventDate = new Date(r.event.date); // For recurring events, check if the month/day falls within range if (r.event.recurring) { const thisYear = new Date( now.getFullYear(), eventDate.getMonth(), eventDate.getDate() ); const nextYear = new Date( now.getFullYear() + 1, eventDate.getMonth(), eventDate.getDate() ); return (thisYear >= now && thisYear <= future) || (nextYear >= now && nextYear <= future); } return eventDate >= now && eventDate <= future; }); } return results; }, { query: t.Object({ clientId: t.Optional(t.String({ format: 'uuid' })), type: t.Optional(t.String()), upcoming: t.Optional(t.String()), }), }) // Get single event .get('/:id', async ({ params, user }: { params: { id: string }; user: User }) => { const [event] = await db.select({ event: events, client: { id: clients.id, firstName: clients.firstName, lastName: clients.lastName, }, }) .from(events) .innerJoin(clients, eq(events.clientId, clients.id)) .where(and(eq(events.id, params.id), eq(events.userId, user.id))) .limit(1); if (!event) { throw new Error('Event not found'); } return event; }, { params: t.Object({ id: t.String({ format: 'uuid' }), }), }) // Create event .post('/', async ({ body, user }: { body: { clientId: string; type: string; title: string; date: string; recurring?: boolean; reminderDays?: number; }; user: User; }) => { // Verify client belongs to user const [client] = await db.select() .from(clients) .where(and(eq(clients.id, body.clientId), eq(clients.userId, user.id))) .limit(1); if (!client) { throw new Error('Client not found'); } const [event] = await db.insert(events) .values({ userId: user.id, clientId: body.clientId, type: body.type, title: body.title, date: new Date(body.date), recurring: body.recurring ?? false, reminderDays: body.reminderDays ?? 7, }) .returning(); return event; }, { body: t.Object({ clientId: t.String({ format: 'uuid' }), type: t.String({ minLength: 1 }), title: t.String({ minLength: 1 }), date: t.String(), // ISO date recurring: t.Optional(t.Boolean()), reminderDays: t.Optional(t.Number({ minimum: 0 })), }), }) // Update event .put('/:id', async ({ params, body, user }: { params: { id: string }; body: { type?: string; title?: string; date?: string; recurring?: boolean; reminderDays?: number; }; user: User; }) => { const updateData: Record = {}; if (body.type !== undefined) updateData.type = body.type; if (body.title !== undefined) updateData.title = body.title; if (body.date !== undefined) updateData.date = new Date(body.date); if (body.recurring !== undefined) updateData.recurring = body.recurring; if (body.reminderDays !== undefined) updateData.reminderDays = body.reminderDays; const [event] = await db.update(events) .set(updateData) .where(and(eq(events.id, params.id), eq(events.userId, user.id))) .returning(); if (!event) { throw new Error('Event not found'); } return event; }, { params: t.Object({ id: t.String({ format: 'uuid' }), }), body: t.Object({ type: t.Optional(t.String()), title: t.Optional(t.String()), date: t.Optional(t.String()), recurring: t.Optional(t.Boolean()), reminderDays: t.Optional(t.Number()), }), }) // Delete event .delete('/:id', async ({ params, user }: { params: { id: string }; user: User }) => { const [deleted] = await db.delete(events) .where(and(eq(events.id, params.id), eq(events.userId, user.id))) .returning({ id: events.id }); if (!deleted) { throw new Error('Event not found'); } return { success: true, id: deleted.id }; }, { params: t.Object({ id: t.String({ format: 'uuid' }), }), }) // Sync ALL events from all clients' birthdays/anniversaries .post('/sync-all', async ({ user }: { user: User }) => { // Get all clients for this user const userClients = await db.select() .from(clients) .where(eq(clients.userId, user.id)); let created = 0; let updated = 0; for (const client of userClients) { // Sync birthday if (client.birthday) { const [existing] = await db.select() .from(events) .where(and(eq(events.clientId, client.id), eq(events.type, 'birthday'))) .limit(1); if (!existing) { await db.insert(events).values({ userId: user.id, clientId: client.id, type: 'birthday', title: `${client.firstName}'s Birthday`, date: client.birthday, recurring: true, reminderDays: 7, }); created++; } else { await db.update(events) .set({ date: client.birthday, title: `${client.firstName}'s Birthday` }) .where(eq(events.id, existing.id)); updated++; } } // Sync anniversary if (client.anniversary) { const [existing] = await db.select() .from(events) .where(and(eq(events.clientId, client.id), eq(events.type, 'anniversary'))) .limit(1); if (!existing) { await db.insert(events).values({ userId: user.id, clientId: client.id, type: 'anniversary', title: `${client.firstName}'s Anniversary`, date: client.anniversary, recurring: true, reminderDays: 7, }); created++; } else { await db.update(events) .set({ date: client.anniversary, title: `${client.firstName}'s Anniversary` }) .where(eq(events.id, existing.id)); updated++; } } } return { success: true, created, updated, clientsProcessed: userClients.length }; }) // Sync events from client birthdays/anniversaries .post('/sync/:clientId', async ({ params, user }: { params: { clientId: string }; user: User }) => { // Get client const [client] = await db.select() .from(clients) .where(and(eq(clients.id, params.clientId), eq(clients.userId, user.id))) .limit(1); if (!client) { throw new Error('Client not found'); } const created = []; // Create birthday event if client has birthday if (client.birthday) { // Check if birthday event already exists const [existing] = await db.select() .from(events) .where(and( eq(events.clientId, client.id), eq(events.type, 'birthday') )) .limit(1); if (!existing) { const [event] = await db.insert(events) .values({ userId: user.id, clientId: client.id, type: 'birthday', title: `${client.firstName}'s Birthday`, date: client.birthday, recurring: true, reminderDays: 7, }) .returning(); created.push(event); } } // Create anniversary event if client has anniversary if (client.anniversary) { const [existing] = await db.select() .from(events) .where(and( eq(events.clientId, client.id), eq(events.type, 'anniversary') )) .limit(1); if (!existing) { const [event] = await db.insert(events) .values({ userId: user.id, clientId: client.id, type: 'anniversary', title: `${client.firstName}'s Anniversary`, date: client.anniversary, recurring: true, reminderDays: 7, }) .returning(); created.push(event); } } return { created }; }, { params: t.Object({ clientId: t.String({ format: 'uuid' }), }), });