4687f46384
- Add app/ekonomi/anvandare/prenumerationer/butik/feedback blocks to computeOpsSummary. - New feedback table, POST /api/feedback, GDPR erasure wiring. - Canary: only count unverified/rejected published recipes; treat verified/editorial as safe. - Add 'rejected' recipe verification status enum value. - Fix food-safety lint baseline: remove egg/cured fish from raw-protein list, relax genomstekt regex, add safe-cooking phrases to seed recipes. - Seed recipes now get verificationStatus=editorial. - Tests: ops summary blocks, feedback route, me.residual cleanup.
108 lines
4.2 KiB
TypeScript
108 lines
4.2 KiB
TypeScript
import Fastify from "fastify";
|
||
import cors from "@fastify/cors";
|
||
import rateLimit from "@fastify/rate-limit";
|
||
import type { AppConfig } from "./config.js";
|
||
import { corePlugin } from "./plugins/core.js";
|
||
import { authPlugin } from "./plugins/auth.js";
|
||
import { storagePlugin } from "./plugins/storage.js";
|
||
import { healthRoutes } from "./routes/health.js";
|
||
import { authRoutes } from "./routes/auth.js";
|
||
import { meRoutes } from "./routes/me.js";
|
||
import { householdRoutes } from "./routes/households.js";
|
||
import { inventoryRoutes } from "./routes/inventory.js";
|
||
import { scanRoutes } from "./routes/scans.js";
|
||
import { scanDiffRoutes } from "./routes/scan-diff.js";
|
||
import { recipeRoutes } from "./routes/recipes.js";
|
||
import { cookingSessionRoutes } from "./routes/cooking-sessions.js";
|
||
import { mealRoutes } from "./routes/meals.js";
|
||
import { shoppingRoutes } from "./routes/shopping.js";
|
||
import { planningRoutes } from "./routes/planning.js";
|
||
import { recommendationRoutes } from "./routes/recommendations.js";
|
||
import { reconciliationRoutes } from "./routes/reconciliations.js";
|
||
import { memoryRoutes } from "./routes/memory.js";
|
||
import { budgetRoutes } from "./routes/budget.js";
|
||
import { subscriptionRoutes } from "./routes/subscriptions.js";
|
||
import { communityRoutes } from "./routes/community.js";
|
||
import { adminRoutes } from "./routes/admin.js";
|
||
import { adminAnalyticsRoutes } from "./routes/admin-analytics.js";
|
||
import { adminReleaseGateRoutes } from "./routes/admin-release-gates.js";
|
||
import { adminWorkersRoutes } from "./routes/admin-workers.js";
|
||
import { analyticsRoutes } from "./routes/analytics.js";
|
||
import { onboardingRoutes } from "./routes/onboarding.js";
|
||
import { activationRoutes } from "./routes/activation.js";
|
||
import { opsRoutes } from "./routes/ops.js";
|
||
import { feedbackRoutes } from "./routes/feedback.js";
|
||
|
||
declare module "fastify" {
|
||
interface FastifyInstance {
|
||
routeCatalog: Array<{ method: string; url: string }>;
|
||
}
|
||
}
|
||
|
||
export async function buildServer(config: AppConfig) {
|
||
const app = Fastify({
|
||
logger: {
|
||
level: config.LOG_LEVEL,
|
||
redact: ["req.headers.authorization", "req.headers.cookie"],
|
||
},
|
||
trustProxy: true,
|
||
bodyLimit: 1024 * 1024,
|
||
});
|
||
|
||
// Endpointkatalog för /docs – hooken måste ligga före route-registreringen.
|
||
const routeCatalog: Array<{ method: string; url: string }> = [];
|
||
app.decorate("routeCatalog", routeCatalog);
|
||
app.addHook("onRoute", (route) => {
|
||
const methods = Array.isArray(route.method) ? route.method : [route.method];
|
||
for (const method of methods) {
|
||
if (method === "HEAD" || method === "OPTIONS") continue;
|
||
routeCatalog.push({ method, url: route.url });
|
||
}
|
||
});
|
||
|
||
await app.register(corePlugin, { config });
|
||
await app.register(cors, {
|
||
origin: config.CORS_ORIGINS.split(",").map((o) => o.trim()),
|
||
credentials: true,
|
||
});
|
||
await app.register(rateLimit, {
|
||
global: true,
|
||
// Överstyrbar för lasttest (scripts/loadtest.mjs) – produktion använder default 300/min.
|
||
max: Number(process.env.RATE_LIMIT_MAX ?? 300),
|
||
timeWindow: "1 minute",
|
||
keyGenerator: (req) => `${req.ip}`,
|
||
});
|
||
await app.register(authPlugin);
|
||
await app.register(storagePlugin);
|
||
|
||
await app.register(healthRoutes);
|
||
await app.register(authRoutes);
|
||
await app.register(meRoutes);
|
||
await app.register(householdRoutes);
|
||
await app.register(inventoryRoutes);
|
||
await app.register(scanRoutes);
|
||
await app.register(scanDiffRoutes);
|
||
await app.register(recipeRoutes);
|
||
await app.register(cookingSessionRoutes);
|
||
await app.register(mealRoutes);
|
||
await app.register(shoppingRoutes);
|
||
await app.register(planningRoutes);
|
||
await app.register(recommendationRoutes);
|
||
await app.register(reconciliationRoutes);
|
||
await app.register(memoryRoutes);
|
||
await app.register(budgetRoutes);
|
||
await app.register(subscriptionRoutes);
|
||
await app.register(communityRoutes);
|
||
await app.register(adminRoutes);
|
||
await app.register(adminAnalyticsRoutes);
|
||
await app.register(adminReleaseGateRoutes);
|
||
await app.register(adminWorkersRoutes);
|
||
await app.register(analyticsRoutes);
|
||
await app.register(onboardingRoutes);
|
||
await app.register(activationRoutes);
|
||
await app.register(opsRoutes);
|
||
await app.register(feedbackRoutes);
|
||
|
||
return app;
|
||
}
|