Add CMS features with admin interface and OAuth authentication integration

- 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.
This commit is contained in:
2025-12-08 16:00:40 +01:00
parent 3b6cb0a3fb
commit daccc43677
16 changed files with 603 additions and 186 deletions

View File

@ -6,19 +6,24 @@ import { db } from '../config/database.js';
import { events, galleryImages, contentSections, publishHistory } from '../db/schema.js';
import { eq } from 'drizzle-orm';
const publishSchema = z.object({
commitMessage: z.string().min(1).max(200),
});
// Fastify JSON schema for publish body
const publishBodyJsonSchema = {
type: 'object',
required: ['commitMessage'],
properties: {
commitMessage: { type: 'string', minLength: 1, maxLength: 200 },
},
} as const;
const publishRoute: FastifyPluginAsync = async (fastify) => {
fastify.post('/publish', {
schema: {
body: publishSchema,
body: publishBodyJsonSchema,
},
preHandler: [fastify.authenticate],
}, async (request, reply) => {
try {
const { commitMessage } = request.body as z.infer<typeof publishSchema>;
const { commitMessage } = request.body as any;
const userId = request.user.id;
fastify.log.info('Starting publish process...');
@ -43,8 +48,8 @@ const publishRoute: FastifyPluginAsync = async (fastify) => {
.orderBy(galleryImages.displayOrder);
const sectionsData = await db.select().from(contentSections);
const sectionsMap = new Map(
sectionsData.map(s => [s.sectionName, s.contentJson as any])
const sectionsMap = new Map<string, any>(
(sectionsData as any[]).map((s: any) => [s.sectionName as string, s.contentJson as any])
);
fastify.log.info(`Fetched ${eventsData.length} events, ${galleryData.length} images, ${sectionsData.length} sections`);
@ -53,13 +58,13 @@ const publishRoute: FastifyPluginAsync = async (fastify) => {
const fileGenerator = new FileGeneratorService();
await fileGenerator.writeFiles(
gitService.getWorkspacePath(''),
eventsData.map(e => ({
(eventsData as any[]).map((e: any) => ({
title: e.title,
date: e.date,
description: e.description,
imageUrl: e.imageUrl,
})),
galleryData.map(g => ({
(galleryData as any[]).map((g: any) => ({
imageUrl: g.imageUrl,
altText: g.altText,
})),
@ -87,14 +92,14 @@ const publishRoute: FastifyPluginAsync = async (fastify) => {
};
} catch (error) {
fastify.log.error('Publish error:', error);
fastify.log.error({ err: error }, 'Publish error');
// Attempt to reset git state on error
try {
const gitService = new GitService();
await gitService.reset();
} catch (resetError) {
fastify.log.error('Failed to reset git state:', resetError);
fastify.log.error({ err: resetError }, 'Failed to reset git state');
}
return reply.code(500).send({