/** * Apply a seed file to this site. * * POST /_emdash/api/settings/seed/apply (body: a seed file) * * Update-on-conflict, content included; anything the site added on its own is * left alone, and site identity settings (title, tagline, url) are skipped. * Used by the hosting platform to give a new project its theme's seed, and * to re-sync a project with its theme. */ import type { APIRoute } from "astro"; import { requirePerm } from "#api/authorize.js"; import { apiError, apiSuccess, handleError } from "#api/error.js"; import { applySeed } from "#seed/apply.js"; import type { SeedFile } from "#seed/types.js"; import { validateSeed } from "#seed/validate.js"; export const prerender = false; const IDENTITY_KEYS = ["title", "tagline", "url"] as const; const MAX_BYTES = 8 * 1024 * 1024; export const POST: APIRoute = async ({ locals, request }) => { const { emdash, user } = locals; if (!emdash?.db) return apiError("NOT_CONFIGURED", "EmDash is not initialized", 500); const denied = requirePerm(user, "settings:manage"); if (denied) return denied; const text = await request.text(); if (text.length > MAX_BYTES) return apiError("PAYLOAD_TOO_LARGE", "Seed is too large", 413); let seed: SeedFile; try { seed = JSON.parse(text) as SeedFile; } catch { return apiError("INVALID_JSON", "Body must be a seed file (JSON)", 400); } const validation = validateSeed(seed); if (!validation.valid) { return apiError("INVALID_SEED", `Invalid seed file: ${validation.errors.join(", ")}`, 400); } try { if (seed.settings) { const settings = { ...seed.settings } as Record; for (const k of IDENTITY_KEYS) delete settings[k]; seed.settings = settings as typeof seed.settings; } const result = await applySeed(emdash.db, seed, { includeContent: true, onConflict: "update", storage: emdash.storage ?? undefined, }); return apiSuccess(result); } catch (error) { return handleError(error, "Failed to apply seed", "SEED_ERROR"); } };