Uploads werden nach AVIF gewandelt statt nach WebP und heissen jetzt nach dem Inhalt statt nach Zeitstempel und Zufall: - Events uebernehmen den Event-Titel, die Gallery den Alt-Text, Bilder in den Textbereichen ihren Zweck, PDFs den urspruenglichen Dateinamen. - Ohne solche Angabe wird der Dateiname vor dem Upload verwendet. - Umlaute werden ausgeschrieben (Getraenkekarte, nicht Getrnkekarte), Sonderzeichen und Pfadanteile fallen weg. - Gleiche Namen werden durchnummeriert, damit zwei Events namens "Karaoke" sich nicht gegenseitig ueberschreiben. Besitz statt Namensmuster: Die Loeschsicherheit haing bisher am Namensmuster <zeitstempel>-<zufall>. Mit sprechenden Namen traegt der Name diese Information nicht mehr - ein hochgeladenes karaoke-abend.avif ist von einem handgepflegten event_karaoke.jpg nicht zu unterscheiden. Deshalb fuehrt das CMS jetzt in managed_assets Buch darueber, welche Dateien es selbst angelegt hat, und loescht ausschliesslich diese. Uploads von vor der Umstellung werden weiterhin am alten Muster erkannt, damit sie aufraeumbar bleiben. initDatabase() legte Tabellen nur an, wenn users noch fehlte. Auf einer bestehenden Datenbank waere managed_assets damit nie entstanden. Der Block laeuft jetzt bei jedem Start; alle Anweisungen sind IF NOT EXISTS. Nebenbei repariert: die Formulare schickten den Alt-Text NACH der Datei. Zu dem Zeitpunkt hat der Server ihn noch nicht geparst, in der Gallery landete deshalb immer der Dateiname als Alt-Text. Textfelder gehen jetzt vor der Datei raus. Gemessen an einem 4032x3024-Bild: 0.90 MB rein, 36 KB AVIF bei 1600x1200 raus, 579 ms. Das Testbild ist synthetisch und komprimiert besser als ein echtes Foto - die Verkleinerung auf 1600px greift aber immer. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
143 lines
4.3 KiB
TypeScript
143 lines
4.3 KiB
TypeScript
import { FastifyPluginAsync } from 'fastify';
|
|
import { z } from 'zod';
|
|
import { GitService } from '../services/git.service.js';
|
|
import { FileGeneratorService } from '../services/file-generator.service.js';
|
|
import { sweepOrphanedImages } from '../services/image-refs.service.js';
|
|
import { db } from '../config/database.js';
|
|
import { events, galleryImages, contentSections, publishHistory } from '../db/schema.js';
|
|
import { eq } from 'drizzle-orm';
|
|
|
|
// 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: publishBodyJsonSchema,
|
|
},
|
|
preHandler: [fastify.authenticate],
|
|
}, async (request, reply) => {
|
|
try {
|
|
const { commitMessage } = request.body as any;
|
|
const userId = request.user.id;
|
|
|
|
fastify.log.info('Starting publish process...');
|
|
|
|
// Initialize git service
|
|
const gitService = new GitService();
|
|
await gitService.initialize();
|
|
|
|
fastify.log.info('Git repository initialized');
|
|
|
|
// Verwaiste Uploads entfernen, bevor committet wird
|
|
const removedImages = await sweepOrphanedImages();
|
|
|
|
if (removedImages.length > 0) {
|
|
fastify.log.info(`Removed ${removedImages.length} orphaned image(s): ${removedImages.join(', ')}`);
|
|
}
|
|
|
|
// Fetch all content from database
|
|
const eventsData = await db
|
|
.select()
|
|
.from(events)
|
|
.where(eq(events.isPublished, true))
|
|
.orderBy(events.displayOrder);
|
|
|
|
const galleryData = await db
|
|
.select()
|
|
.from(galleryImages)
|
|
.where(eq(galleryImages.isPublished, true))
|
|
.orderBy(galleryImages.displayOrder);
|
|
|
|
const sectionsData = await db.select().from(contentSections);
|
|
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`);
|
|
|
|
// Generate and write files
|
|
const fileGenerator = new FileGeneratorService();
|
|
await fileGenerator.writeFiles(
|
|
gitService.getWorkspacePath(''),
|
|
(eventsData as any[]).map((e: any) => ({
|
|
title: e.title,
|
|
date: e.date,
|
|
description: e.description,
|
|
imageUrl: e.imageUrl,
|
|
})),
|
|
(galleryData as any[]).map((g: any) => ({
|
|
imageUrl: g.imageUrl,
|
|
altText: g.altText,
|
|
})),
|
|
sectionsMap
|
|
);
|
|
|
|
fastify.log.info('Files generated successfully');
|
|
|
|
// Commit and push
|
|
const commitHash = await gitService.commitAndPush(commitMessage);
|
|
|
|
fastify.log.info(`Changes committed: ${commitHash}`);
|
|
|
|
// Record in history. Der Push ist an dieser Stelle bereits durch -
|
|
// ein Fehler im Protokoll darf die Veroeffentlichung nicht als
|
|
// gescheitert melden und den Workspace zuruecksetzen.
|
|
try {
|
|
await db.insert(publishHistory).values({
|
|
userId,
|
|
commitHash,
|
|
commitMessage,
|
|
});
|
|
} catch (historyError) {
|
|
fastify.log.warn({ err: historyError }, 'Could not record publish history');
|
|
}
|
|
|
|
return {
|
|
success: true,
|
|
commitHash,
|
|
removedImages: removedImages.length,
|
|
message: 'Changes published successfully',
|
|
};
|
|
|
|
} catch (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({ err: resetError }, 'Failed to reset git state');
|
|
}
|
|
|
|
return reply.code(500).send({
|
|
success: false,
|
|
error: 'Failed to publish changes',
|
|
details: error instanceof Error ? error.message : 'Unknown error',
|
|
});
|
|
}
|
|
});
|
|
|
|
// Get publish history
|
|
fastify.get('/publish/history', {
|
|
preHandler: [fastify.authenticate],
|
|
}, async (request, reply) => {
|
|
const history = await db
|
|
.select()
|
|
.from(publishHistory)
|
|
.orderBy(publishHistory.publishedAt)
|
|
.limit(20);
|
|
|
|
return { history };
|
|
});
|
|
};
|
|
|
|
export default publishRoute;
|