Phase 1 ops core: base blocks, feedback, canary fixes

- 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.
This commit is contained in:
Sven (AAMOS AI)
2026-08-10 06:35:51 +07:00
parent 8f20b2ef1b
commit 4687f46384
16 changed files with 606 additions and 44 deletions
+48
View File
@@ -0,0 +1,48 @@
import type { FastifyInstance } from "fastify";
import { z } from "zod";
import { schema, trackProductAnalytics } from "@app/database";
import { feedbackSubmitted } from "@app/analytics";
import { errors, parse } from "../lib/errors.js";
const createFeedbackSchema = z.object({
typ: z.enum(["bug", "onskemal"]),
rubrik: z.string().min(1).max(200),
text: z.string().min(1).max(5000),
plattform: z.enum(["ios", "android", "web"]).optional(),
appVersion: z.string().max(32).optional(),
});
/**
* POST /api/feedback användarfeedback (doc §2).
* Skapar en öppen ticket och spårar ett anonymiserat analytics-event.
* Själva text-kroppen visas aldrig i /ops/v1/summary.
*/
export async function feedbackRoutes(app: FastifyInstance) {
const auth = { preHandler: [app.authenticate] };
app.post("/api/feedback", auth, async (req) => {
const body = parse(createFeedbackSchema, req.body);
const [row] = await app.db
.insert(schema.feedback)
.values({
userId: req.userId,
typ: body.typ,
rubrik: body.rubrik,
text: body.text,
status: "oppen",
plattform: body.plattform ?? null,
appVersion: body.appVersion ?? null,
})
.returning();
if (!row) throw errors.internal("Kunde inte skapa feedback.");
await trackProductAnalytics(app.db, req.userId, {
...feedbackSubmitted(),
properties: { typ: body.typ },
});
return { id: row.id, status: row.status };
});
}
+2
View File
@@ -31,6 +31,7 @@ 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 {
@@ -100,6 +101,7 @@ export async function buildServer(config: AppConfig) {
await app.register(onboardingRoutes);
await app.register(activationRoutes);
await app.register(opsRoutes);
await app.register(feedbackRoutes);
return app;
}