- 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.
105 lines
2.5 KiB
TypeScript
105 lines
2.5 KiB
TypeScript
import { FastifyPluginAsync } from 'fastify';
|
|
import { z } from 'zod';
|
|
import { db } from '../config/database.js';
|
|
import { contentSections } from '../db/schema.js';
|
|
import { eq } from 'drizzle-orm';
|
|
|
|
// Fastify JSON schema for content section body
|
|
const contentBodyJsonSchema = {
|
|
type: 'object',
|
|
required: ['contentJson'],
|
|
properties: {
|
|
contentJson: {}, // allow any JSON
|
|
},
|
|
} as const;
|
|
|
|
const contentRoute: FastifyPluginAsync = async (fastify) => {
|
|
|
|
// Get content section
|
|
fastify.get('/content/:section', {
|
|
preHandler: [fastify.authenticate],
|
|
}, async (request, reply) => {
|
|
const { section } = request.params as { section: string };
|
|
|
|
const [content] = await db
|
|
.select()
|
|
.from(contentSections)
|
|
.where(eq(contentSections.sectionName, section))
|
|
.limit(1);
|
|
|
|
if (!content) {
|
|
return reply.code(404).send({ error: 'Content section not found' });
|
|
}
|
|
|
|
return {
|
|
section: content.sectionName,
|
|
content: content.contentJson,
|
|
updatedAt: content.updatedAt,
|
|
};
|
|
});
|
|
|
|
// Update content section
|
|
fastify.put('/content/:section', {
|
|
schema: {
|
|
body: contentBodyJsonSchema,
|
|
},
|
|
preHandler: [fastify.authenticate],
|
|
}, async (request, reply) => {
|
|
const { section } = request.params as { section: string };
|
|
const { contentJson } = request.body as any;
|
|
|
|
// Check if section exists
|
|
const [existing] = await db
|
|
.select()
|
|
.from(contentSections)
|
|
.where(eq(contentSections.sectionName, section))
|
|
.limit(1);
|
|
|
|
let result;
|
|
|
|
if (existing) {
|
|
// Update existing
|
|
[result] = await db
|
|
.update(contentSections)
|
|
.set({
|
|
contentJson,
|
|
updatedAt: new Date(),
|
|
})
|
|
.where(eq(contentSections.sectionName, section))
|
|
.returning();
|
|
} else {
|
|
// Create new
|
|
[result] = await db
|
|
.insert(contentSections)
|
|
.values({
|
|
sectionName: section,
|
|
contentJson,
|
|
})
|
|
.returning();
|
|
}
|
|
|
|
return {
|
|
section: result.sectionName,
|
|
content: result.contentJson,
|
|
updatedAt: result.updatedAt,
|
|
};
|
|
});
|
|
|
|
// List all content sections
|
|
fastify.get('/content', {
|
|
preHandler: [fastify.authenticate],
|
|
}, async (request, reply) => {
|
|
const sections = await db.select().from(contentSections);
|
|
|
|
return {
|
|
sections: (sections as any[]).map((s: any) => ({
|
|
section: s.sectionName,
|
|
content: s.contentJson,
|
|
updatedAt: s.updatedAt,
|
|
})),
|
|
};
|
|
});
|
|
};
|
|
|
|
export default contentRoute;
|