Files
Gallus_Pub/backend/src/routes/gallery.ts
Kenzo af930f345c 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.
2025-12-08 16:00:40 +01:00

135 lines
3.7 KiB
TypeScript

import { FastifyPluginAsync } from 'fastify';
import { z } from 'zod';
import { db } from '../config/database.js';
import { galleryImages } from '../db/schema.js';
import { eq } from 'drizzle-orm';
// Fastify JSON schema for gallery image body
const galleryBodyJsonSchema = {
type: 'object',
required: ['imageUrl', 'altText', 'displayOrder'],
properties: {
imageUrl: { type: 'string', minLength: 1 },
altText: { type: 'string', minLength: 1, maxLength: 200 },
displayOrder: { type: 'integer', minimum: 0 },
isPublished: { type: 'boolean' },
},
} as const;
const galleryRoute: FastifyPluginAsync = async (fastify) => {
// List all gallery images
fastify.get('/gallery', {
preHandler: [fastify.authenticate],
}, async (request, reply) => {
const images = await db.select().from(galleryImages).orderBy(galleryImages.displayOrder);
return { images };
});
// Get single gallery image
fastify.get('/gallery/:id', {
preHandler: [fastify.authenticate],
}, async (request, reply) => {
const { id } = request.params as { id: string };
const image = await db.select().from(galleryImages).where(eq(galleryImages.id, id)).limit(1);
if (image.length === 0) {
return reply.code(404).send({ error: 'Image not found' });
}
return { image: image[0] };
});
// Create gallery image
fastify.post('/gallery', {
schema: {
body: galleryBodyJsonSchema,
},
preHandler: [fastify.authenticate],
}, async (request, reply) => {
const data = request.body as any;
const [newImage] = await db.insert(galleryImages).values(data).returning();
return reply.code(201).send({ image: newImage });
});
// Update gallery image
fastify.put('/gallery/:id', {
schema: {
body: galleryBodyJsonSchema,
},
preHandler: [fastify.authenticate],
}, async (request, reply) => {
const { id } = request.params as { id: string };
const data = request.body as any;
const [updated] = await db
.update(galleryImages)
.set(data)
.where(eq(galleryImages.id, id))
.returning();
if (!updated) {
return reply.code(404).send({ error: 'Image not found' });
}
return { image: updated };
});
// Delete gallery image
fastify.delete('/gallery/:id', {
preHandler: [fastify.authenticate],
}, async (request, reply) => {
const { id } = request.params as { id: string };
const [deleted] = await db
.delete(galleryImages)
.where(eq(galleryImages.id, id))
.returning();
if (!deleted) {
return reply.code(404).send({ error: 'Image not found' });
}
return { message: 'Image deleted successfully' };
});
// Reorder gallery images
fastify.put('/gallery/reorder', {
schema: {
body: {
type: 'object',
required: ['orders'],
properties: {
orders: {
type: 'array',
items: {
type: 'object',
required: ['id', 'displayOrder'],
properties: {
id: { type: 'string' },
displayOrder: { type: 'integer', minimum: 0 },
},
},
},
},
},
},
preHandler: [fastify.authenticate],
}, async (request, reply) => {
const { orders } = request.body as { orders: Array<{ id: string; displayOrder: number }> };
// Update all in synchronous transaction (better-sqlite3 requirement)
db.transaction((tx: any) => {
for (const { id, displayOrder } of orders) {
tx.update(galleryImages).set({ displayOrder }).where(eq(galleryImages.id, id)).run?.();
}
});
return { message: 'Gallery images reordered successfully' };
});
};
export default galleryRoute;