Files
network-app-api/src/routes/events.ts
Hammer 7634306832 fix: resolve TypeScript errors for CI pipeline
- Fix pg-boss Job type imports (PgBoss.Job -> Job from pg-boss)
- Replace deprecated teamConcurrency with localConcurrency
- Add null checks for possibly undefined values (clients, import rows)
- Fix tone type narrowing in profile.ts
- Fix test type assertions (non-null assertions, explicit Record types)
- Extract auth middleware into shared module
- Fix rate limiter Map generic type
2026-01-30 03:27:58 +00:00

351 lines
9.7 KiB
TypeScript

import { authMiddleware } from '../middleware/auth';
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' })
.use(authMiddleware)
// 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<string, unknown> = {};
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' }),
}),
});