Publish menus from the browser instead of scp
The admin signs in with the shared password, creates a Menu Set, drops photos into it (shrunk in the browser first), reorders Pages by dragging or with up/down buttons, previews the Draft exactly as customers would see it, and publishes it over the previous set. Restore and delete close the loop. Mutations need a session, a same-origin request and a CSRF token derived from the session; a Draft's media is served only to a session holder.
This commit is contained in:
1 parent
9cb4021229
commit
58ecfa0dc3
14 files changed
+1824
-15
No files matched your search
+22
-1
@@ -8,14 +8,17 @@ import { readFile } from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
import Fastify, { type FastifyError, type FastifyInstance } from 'fastify';
|
||||
import cookie from '@fastify/cookie';
|
||||
import formbody from '@fastify/formbody';
|
||||
import multipart from '@fastify/multipart';
|
||||
import fastifyStatic from '@fastify/static';
|
||||
import { parseSiteConfig, type SiteConfig } from './config.ts';
|
||||
import { SESSION_COOKIE, sessionTtl } from './auth.ts';
|
||||
import { SESSION_COOKIE, sessionTtl, type LoginThrottle } from './auth.ts';
|
||||
import { createImagePipeline } from './images/pipeline.ts';
|
||||
import { SetStore, type ImagePipeline } from './storage/sets.ts';
|
||||
import { hasFatal, validateSite } from './storage/validate.ts';
|
||||
import { renderNotFound } from './render/pages.ts';
|
||||
import { registerPublicRoutes } from './routes/public.ts';
|
||||
import { registerAdminRoutes } from './routes/admin.ts';
|
||||
import { readEnvFile } from './env.ts';
|
||||
|
||||
export const SESSION_TTL_MS = 8 * 60 * 60 * 1000;
|
||||
@@ -48,6 +51,8 @@ export interface AppOptions {
|
||||
pipeline?: ImagePipeline;
|
||||
/** Test seam: defaults to the real clock. */
|
||||
now?: () => Date;
|
||||
/** Test seam: a shared throttle so rate-limit tests are deterministic. */
|
||||
throttle?: LoginThrottle;
|
||||
}
|
||||
|
||||
export async function buildApp(options: AppOptions): Promise<FastifyInstance> {
|
||||
@@ -58,6 +63,13 @@ export async function buildApp(options: AppOptions): Promise<FastifyInstance> {
|
||||
});
|
||||
|
||||
await app.register(cookie);
|
||||
await app.register(formbody);
|
||||
await app.register(multipart, {
|
||||
limits: {
|
||||
fileSize: options.config.limits.maxUploadBytes,
|
||||
files: options.config.limits.maxPagesPerSet,
|
||||
},
|
||||
});
|
||||
await app.register(fastifyStatic, {
|
||||
root: options.assetsDir,
|
||||
prefix: '/assets/',
|
||||
@@ -80,6 +92,15 @@ export async function buildApp(options: AppOptions): Promise<FastifyInstance> {
|
||||
sessionSecret: options.sessionSecret,
|
||||
});
|
||||
|
||||
await registerAdminRoutes(app, {
|
||||
config: options.config,
|
||||
store,
|
||||
sessionSecret: options.sessionSecret,
|
||||
adminPasswordHash: options.adminPasswordHash,
|
||||
secureCookies: options.secureCookies,
|
||||
throttle: options.throttle,
|
||||
});
|
||||
|
||||
app.setNotFoundHandler(async (_request, reply) => {
|
||||
const current = await store.currentBySection();
|
||||
const live = options.config.sections.filter((section) => current.has(section.slug));
|
||||
|
||||
Reference in new issue
Block a user