- Introduced Caddy server for serving frontend and API backend. - Implemented admin dashboard for creating, editing, and managing events. - Replaced session-based authentication with token-based OAuth using Gitea. - Added support for drag-and-drop event reordering in the admin interface. - Standardized Fastify route validation with JSON schemas. - Enhanced authentication flow with cookie-based state and secure token storage. - Reworked backend routes to handle publishing, event management, and content updates. - Updated `Dockerfile.caddy` and `fly.toml` for deployment configuration.
90 lines
3.4 KiB
TypeScript
90 lines
3.4 KiB
TypeScript
import { FastifyPluginAsync } from 'fastify';
|
|
import { db } from '../config/database.js';
|
|
import { events } from '../db/schema.js';
|
|
import { eq } from 'drizzle-orm';
|
|
|
|
// Fastify JSON schema for event body
|
|
const eventBodyJsonSchema = {
|
|
type: 'object',
|
|
required: ['title', 'date', 'description', 'imageUrl', 'displayOrder'],
|
|
properties: {
|
|
title: { type: 'string', minLength: 1, maxLength: 200 },
|
|
date: { type: 'string', minLength: 1, maxLength: 100 },
|
|
description: { type: 'string', minLength: 1 },
|
|
imageUrl: { type: 'string', minLength: 1 },
|
|
displayOrder: { type: 'integer', minimum: 0 },
|
|
isPublished: { type: 'boolean' },
|
|
},
|
|
} as const;
|
|
|
|
const reorderBodyJsonSchema = {
|
|
type: 'object',
|
|
required: ['orders'],
|
|
properties: {
|
|
orders: {
|
|
type: 'array',
|
|
items: {
|
|
type: 'object',
|
|
required: ['id', 'displayOrder'],
|
|
properties: {
|
|
id: { type: 'string' },
|
|
displayOrder: { type: 'integer', minimum: 0 },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
} as const;
|
|
|
|
const eventsRoute: FastifyPluginAsync = async (fastify) => {
|
|
// List all events (by displayOrder)
|
|
fastify.get('/events', { preHandler: [fastify.authenticate] }, async () => {
|
|
const all = await db.select().from(events).orderBy(events.displayOrder);
|
|
return { events: all };
|
|
});
|
|
|
|
// Get single event
|
|
fastify.get('/events/:id', { preHandler: [fastify.authenticate] }, async (request, reply) => {
|
|
const { id } = request.params as { id: string };
|
|
const rows = await db.select().from(events).where(eq(events.id, id)).limit(1);
|
|
if (rows.length === 0) return reply.code(404).send({ error: 'Event not found' });
|
|
return { event: rows[0] };
|
|
});
|
|
|
|
// Create event
|
|
fastify.post('/events', { schema: { body: eventBodyJsonSchema }, preHandler: [fastify.authenticate] }, async (request, reply) => {
|
|
const data = request.body as any;
|
|
const [row] = await db.insert(events).values(data).returning();
|
|
return reply.code(201).send({ event: row });
|
|
});
|
|
|
|
// Update event
|
|
fastify.put('/events/:id', { schema: { body: eventBodyJsonSchema }, preHandler: [fastify.authenticate] }, async (request, reply) => {
|
|
const { id } = request.params as { id: string };
|
|
const data = request.body as any;
|
|
const [row] = await db.update(events).set({ ...data, updatedAt: new Date() }).where(eq(events.id, id)).returning();
|
|
if (!row) return reply.code(404).send({ error: 'Event not found' });
|
|
return { event: row };
|
|
});
|
|
|
|
// Delete event
|
|
fastify.delete('/events/:id', { preHandler: [fastify.authenticate] }, async (request, reply) => {
|
|
const { id } = request.params as { id: string };
|
|
const [row] = await db.delete(events).where(eq(events.id, id)).returning();
|
|
if (!row) return reply.code(404).send({ error: 'Event not found' });
|
|
return { message: 'Event deleted successfully' };
|
|
});
|
|
|
|
// Reorder events (synchronous transaction for better-sqlite3)
|
|
fastify.put('/events/reorder', { schema: { body: reorderBodyJsonSchema }, preHandler: [fastify.authenticate] }, async (request) => {
|
|
const { orders } = request.body as { orders: Array<{ id: string; displayOrder: number }> };
|
|
db.transaction((tx: any) => {
|
|
for (const { id, displayOrder } of orders) {
|
|
tx.update(events).set({ displayOrder }).where(eq(events.id, id)).run?.();
|
|
}
|
|
});
|
|
return { message: 'Events reordered successfully' };
|
|
});
|
|
};
|
|
|
|
export default eventsRoute;
|