Files
network-app-api/src/routes/templates.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

148 lines
5.4 KiB
TypeScript

import { authMiddleware } from '../middleware/auth';
import { Elysia, t } from 'elysia';
import { db } from '../db';
import { emailTemplates } from '../db/schema';
import { eq, and, desc, sql } from 'drizzle-orm';
import type { User } from '../lib/auth';
export const templateRoutes = new Elysia({ prefix: '/templates' })
.use(authMiddleware)
// List templates
.get('/', async ({ query, user }: { query: { category?: string }; user: User }) => {
let conditions = [eq(emailTemplates.userId, user.id)];
if (query.category) {
conditions.push(eq(emailTemplates.category, query.category));
}
return db.select()
.from(emailTemplates)
.where(and(...conditions))
.orderBy(desc(emailTemplates.usageCount));
}, {
query: t.Object({
category: t.Optional(t.String()),
}),
})
// Get single template
.get('/:id', async ({ params, user }: { params: { id: string }; user: User }) => {
const [template] = await db.select()
.from(emailTemplates)
.where(and(eq(emailTemplates.id, params.id), eq(emailTemplates.userId, user.id)))
.limit(1);
if (!template) throw new Error('Template not found');
return template;
}, {
params: t.Object({ id: t.String({ format: 'uuid' }) }),
})
// Create template
.post('/', async ({ body, user }: { body: { name: string; category: string; subject: string; content: string; isDefault?: boolean }; user: User }) => {
// If marking as default, unmark others in same category
if (body.isDefault) {
await db.update(emailTemplates)
.set({ isDefault: false })
.where(and(eq(emailTemplates.userId, user.id), eq(emailTemplates.category, body.category)));
}
const [template] = await db.insert(emailTemplates)
.values({
userId: user.id,
name: body.name,
category: body.category,
subject: body.subject,
content: body.content,
isDefault: body.isDefault || false,
})
.returning();
return template;
}, {
body: t.Object({
name: t.String({ minLength: 1 }),
category: t.String({ minLength: 1 }),
subject: t.String({ minLength: 1 }),
content: t.String({ minLength: 1 }),
isDefault: t.Optional(t.Boolean()),
}),
})
// Update template
.put('/:id', async (ctx: any) => {
const { params, body, user } = ctx as {
params: { id: string };
body: { name?: string; category?: string; subject?: string; content?: string; isDefault?: boolean };
user: User;
};
// If marking as default, unmark others
if (body.isDefault && body.category) {
await db.update(emailTemplates)
.set({ isDefault: false })
.where(and(eq(emailTemplates.userId, user.id), eq(emailTemplates.category, body.category)));
}
const updateData: Record<string, unknown> = { updatedAt: new Date() };
if (body.name !== undefined) updateData.name = body.name;
if (body.category !== undefined) updateData.category = body.category;
if (body.subject !== undefined) updateData.subject = body.subject;
if (body.content !== undefined) updateData.content = body.content;
if (body.isDefault !== undefined) updateData.isDefault = body.isDefault;
const [template] = await db.update(emailTemplates)
.set(updateData)
.where(and(eq(emailTemplates.id, params.id), eq(emailTemplates.userId, user.id)))
.returning();
if (!template) throw new Error('Template not found');
return template;
}, {
params: t.Object({ id: t.String({ format: 'uuid' }) }),
body: t.Object({
name: t.Optional(t.String({ minLength: 1 })),
category: t.Optional(t.String({ minLength: 1 })),
subject: t.Optional(t.String({ minLength: 1 })),
content: t.Optional(t.String({ minLength: 1 })),
isDefault: t.Optional(t.Boolean()),
}),
})
// Use template (increment usage count + return with placeholders filled)
.post('/:id/use', async (ctx: any) => {
const { params, body, user } = ctx;
const [template] = await db.select()
.from(emailTemplates)
.where(and(eq(emailTemplates.id, params.id), eq(emailTemplates.userId, user.id)))
.limit(1);
if (!template) throw new Error('Template not found');
// Increment usage count
await db.update(emailTemplates)
.set({ usageCount: sql`${emailTemplates.usageCount} + 1` })
.where(eq(emailTemplates.id, params.id));
// Fill placeholders
let subject = template.subject;
let content = template.content;
const vars = body.variables || {};
for (const [key, value] of Object.entries(vars)) {
const re = new RegExp(`\\{\\{${key}\\}\\}`, 'g');
subject = subject.replace(re, value as string);
content = content.replace(re, value as string);
}
return { subject, content, templateId: template.id, templateName: template.name };
}, {
params: t.Object({ id: t.String({ format: 'uuid' }) }),
body: t.Object({
clientId: t.Optional(t.String({ format: 'uuid' })),
variables: t.Optional(t.Record(t.String(), t.String())),
}),
})
// Delete template
.delete('/:id', async (ctx: any) => {
const { params, user } = ctx;
const [deleted] = await db.delete(emailTemplates)
.where(and(eq(emailTemplates.id, params.id), eq(emailTemplates.userId, user.id)))
.returning({ id: emailTemplates.id });
if (!deleted) throw new Error('Template not found');
return { success: true, id: deleted.id };
}, {
params: t.Object({ id: t.String({ format: 'uuid' }) }),
});