Fas 3 steg 3a: cooking_sessions-tabell + lifecycle-endpoints + timeout-jobb, gamla /cook kvar som kortkommando
This commit is contained in:
@@ -0,0 +1,255 @@
|
||||
import type { FastifyInstance } from "fastify";
|
||||
import { and, eq, gt, isNull, sql } from "drizzle-orm";
|
||||
import { schema, markMilestone } from "@app/database";
|
||||
import { allocateFefo } from "@app/inventory-engine";
|
||||
import { scaleNutrition } from "@app/nutrition-engine";
|
||||
import type { Unit } from "@app/shared-types";
|
||||
import { todayIso, emitEvent } from "./helpers.js";
|
||||
import { errors } from "./errors.js";
|
||||
import { loadFullRecipe } from "../routes/recipes.js";
|
||||
import { MEAL_TYPES } from "@app/shared-types";
|
||||
|
||||
export interface CompleteCookingInput {
|
||||
portionsCooked?: number;
|
||||
mealBoxPortions?: number;
|
||||
mealBoxFrozen?: boolean;
|
||||
mealBoxStorageLocationId?: string;
|
||||
deductInventory?: boolean;
|
||||
eaters?: Array<{ userId: string; portionFraction: number }>;
|
||||
date?: string;
|
||||
mealType?: string;
|
||||
inventoryOverrides?: Array<{ canonicalIngredientId: string; quantityUsed: number; unit: string }>;
|
||||
}
|
||||
|
||||
export interface CompleteCookingResult {
|
||||
ok: boolean;
|
||||
mealIds: string[];
|
||||
mealBoxId: string | null;
|
||||
inventoryDeductions: Array<{ itemId: string; quantity: number; unit: string; name: string }>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gemensam kärna för att "jag har lagat". Används av både
|
||||
* POST /v1/recipes/:id/cook (legacy shortcut) och
|
||||
* POST /v1/cooking-sessions/:id/complete.
|
||||
*/
|
||||
export async function completeCookingSessionCore(
|
||||
app: FastifyInstance,
|
||||
session: typeof schema.cookingSessions.$inferSelect,
|
||||
userId: string,
|
||||
input: CompleteCookingInput,
|
||||
correlationId: string,
|
||||
): Promise<CompleteCookingResult> {
|
||||
const recipe = await loadFullRecipe(app, session.recipeId);
|
||||
const householdId = session.householdId;
|
||||
const portionsCooked = input.portionsCooked ?? session.plannedPortions;
|
||||
const date = input.date ?? todayIso();
|
||||
const mealType = (input.mealType ?? session.plannedMealType) as (typeof MEAL_TYPES)[number];
|
||||
|
||||
// 1. FEFO-avdrag
|
||||
const deductions: Array<{ itemId: string; quantity: number; unit: string; name: string }> = [];
|
||||
if (input.deductInventory !== false) {
|
||||
const factor = portionsCooked / recipe.portions;
|
||||
const overrides = new Map(input.inventoryOverrides?.map((o) => [o.canonicalIngredientId, o]) ?? []);
|
||||
|
||||
for (const ing of recipe.ingredients) {
|
||||
if (ing.optional) continue;
|
||||
const override = overrides.get(ing.canonicalIngredientId);
|
||||
const requiredQty = override ? override.quantityUsed : ing.quantity * factor;
|
||||
const requiredUnit = override ? override.unit : ing.unit;
|
||||
if (requiredQty <= 0) continue;
|
||||
|
||||
const stock = await app.db
|
||||
.select()
|
||||
.from(schema.inventoryItems)
|
||||
.where(
|
||||
and(
|
||||
eq(schema.inventoryItems.householdId, householdId),
|
||||
eq(schema.inventoryItems.canonicalIngredientId, ing.canonicalIngredientId),
|
||||
isNull(schema.inventoryItems.depletedAt),
|
||||
gt(schema.inventoryItems.quantity, 0),
|
||||
),
|
||||
);
|
||||
if (stock.length === 0) continue;
|
||||
|
||||
const [info] = await app.db
|
||||
.select()
|
||||
.from(schema.canonicalIngredients)
|
||||
.where(eq(schema.canonicalIngredients.id, ing.canonicalIngredientId))
|
||||
.limit(1);
|
||||
|
||||
const allocation = allocateFefo(
|
||||
requiredQty,
|
||||
requiredUnit as Unit,
|
||||
stock.map((s) => ({
|
||||
id: s.id,
|
||||
canonicalIngredientId: s.canonicalIngredientId,
|
||||
quantity: s.quantity,
|
||||
unit: s.unit,
|
||||
bestBeforeDate: s.bestBeforeDate,
|
||||
useByDate: s.useByDate,
|
||||
openedAt: s.openedAt,
|
||||
frozenAt: s.frozenAt,
|
||||
thawedAt: s.thawedAt,
|
||||
purchasedAt: s.purchasedAt,
|
||||
})),
|
||||
{ densityGPerMl: info?.densityGPerMl, gramsPerPiece: info?.gramsPerPiece },
|
||||
);
|
||||
|
||||
for (const alloc of allocation.allocations) {
|
||||
const item = stock.find((s) => s.id === alloc.itemId)!;
|
||||
const newQty = Math.max(0, Math.round((item.quantity - alloc.quantity) * 1000) / 1000);
|
||||
await app.db.insert(schema.inventoryTransactions).values({
|
||||
householdId,
|
||||
inventoryItemId: alloc.itemId,
|
||||
cookingSessionId: session.id,
|
||||
type: "cook_use",
|
||||
quantityDelta: -(item.quantity - newQty),
|
||||
unit: item.unit,
|
||||
refType: "recipe_cook",
|
||||
refId: session.recipeId,
|
||||
actorUserId: userId,
|
||||
});
|
||||
await app.db
|
||||
.update(schema.inventoryItems)
|
||||
.set({
|
||||
quantity: newQty,
|
||||
depletedAt: newQty <= 0 ? new Date() : null,
|
||||
updatedAt: new Date(),
|
||||
})
|
||||
.where(eq(schema.inventoryItems.id, alloc.itemId));
|
||||
deductions.push({
|
||||
itemId: alloc.itemId,
|
||||
quantity: alloc.quantity,
|
||||
unit: alloc.unit,
|
||||
name: item.displayName,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Måltider
|
||||
const eaters =
|
||||
input.eaters && input.eaters.length > 0
|
||||
? input.eaters
|
||||
: [{ userId, portionFraction: 1 }];
|
||||
const mealIds: string[] = [];
|
||||
for (const eater of eaters) {
|
||||
const nutrition = scaleNutrition(recipe.nutritionPerPortion, eater.portionFraction);
|
||||
const [meal] = await app.db
|
||||
.insert(schema.meals)
|
||||
.values({
|
||||
userId: eater.userId,
|
||||
householdId,
|
||||
cookingSessionId: session.id,
|
||||
date,
|
||||
mealType,
|
||||
source: "cooked_recipe",
|
||||
recipeId: session.recipeId,
|
||||
titleSv: recipe.titleSv,
|
||||
portionFraction: eater.portionFraction,
|
||||
nutrition,
|
||||
nutritionIsEstimate: false,
|
||||
})
|
||||
.returning();
|
||||
mealIds.push(meal!.id);
|
||||
await emitEvent(app.db, {
|
||||
type: "MEAL_LOGGED",
|
||||
payload: { mealId: meal!.id, mealType, kcal: nutrition.kcal, source: "cooked_recipe" },
|
||||
userId: eater.userId,
|
||||
householdId,
|
||||
correlationId,
|
||||
});
|
||||
}
|
||||
|
||||
// 3. Matlådor
|
||||
let mealBoxId: string | null = null;
|
||||
const mealBoxPortions = input.mealBoxPortions ?? 0;
|
||||
if (mealBoxPortions > 0) {
|
||||
const locationId =
|
||||
input.mealBoxStorageLocationId ??
|
||||
(
|
||||
await app.db
|
||||
.select({ id: schema.storageLocations.id })
|
||||
.from(schema.storageLocations)
|
||||
.where(
|
||||
and(
|
||||
eq(schema.storageLocations.householdId, householdId),
|
||||
eq(schema.storageLocations.type, input.mealBoxFrozen ? "freezer" : "fridge"),
|
||||
),
|
||||
)
|
||||
.limit(1)
|
||||
)[0]?.id;
|
||||
if (!locationId) throw errors.badRequest("Ingen förvaringsplats för matlådor hittades.");
|
||||
|
||||
const useByDays = input.mealBoxFrozen ? 90 : 3;
|
||||
const recommendedUseBy = new Date(Date.parse(date) + useByDays * 86_400_000)
|
||||
.toISOString()
|
||||
.slice(0, 10);
|
||||
const [box] = await app.db
|
||||
.insert(schema.mealBoxes)
|
||||
.values({
|
||||
householdId,
|
||||
recipeId: session.recipeId,
|
||||
cookingSessionId: session.id,
|
||||
titleSv: recipe.titleSv,
|
||||
portions: mealBoxPortions,
|
||||
portionsRemaining: mealBoxPortions,
|
||||
nutritionPerPortion: recipe.nutritionPerPortion,
|
||||
cookedAt: date,
|
||||
storageLocationId: locationId,
|
||||
frozen: input.mealBoxFrozen ?? false,
|
||||
recommendedUseBy,
|
||||
})
|
||||
.returning();
|
||||
mealBoxId = box!.id;
|
||||
await emitEvent(app.db, {
|
||||
type: "MEAL_BOX_CREATED",
|
||||
payload: { mealBoxId: box!.id, portions: mealBoxPortions },
|
||||
userId,
|
||||
householdId,
|
||||
correlationId,
|
||||
});
|
||||
}
|
||||
|
||||
// 4. recipe_cooks + statistik
|
||||
await app.db.insert(schema.recipeCooks).values({
|
||||
recipeId: session.recipeId,
|
||||
userId,
|
||||
householdId,
|
||||
cookingSessionId: session.id,
|
||||
portionsCooked,
|
||||
cookedAt: new Date(),
|
||||
});
|
||||
await app.db
|
||||
.update(schema.recipes)
|
||||
.set({ cookCount: sql`${schema.recipes.cookCount} + 1` })
|
||||
.where(eq(schema.recipes.id, session.recipeId));
|
||||
await emitEvent(app.db, {
|
||||
type: "RECIPE_COOKED",
|
||||
payload: { recipeId: session.recipeId, portions: portionsCooked, mealBoxPortions },
|
||||
userId,
|
||||
householdId,
|
||||
correlationId,
|
||||
});
|
||||
|
||||
// 5. Uppdatera session
|
||||
await app.db
|
||||
.update(schema.cookingSessions)
|
||||
.set({
|
||||
status: "completed",
|
||||
completedAt: new Date(),
|
||||
actualPortionsEaten: portionsCooked - mealBoxPortions,
|
||||
leftoverEstimatePortions: mealBoxPortions,
|
||||
plannedDeductions: deductions,
|
||||
updatedAt: new Date(),
|
||||
})
|
||||
.where(eq(schema.cookingSessions.id, session.id));
|
||||
|
||||
await markMilestone(app.db, householdId, "firstCookingSessionCompletedAt");
|
||||
if (deductions.length > 0) {
|
||||
await markMilestone(app.db, householdId, "inventoryUpdatedAfterCookingAt");
|
||||
}
|
||||
|
||||
return { ok: true, mealIds, mealBoxId, inventoryDeductions: deductions };
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
import type { FastifyInstance } from "fastify";
|
||||
import { and, eq, lt } from "drizzle-orm";
|
||||
import { schema, markMilestone } from "@app/database";
|
||||
import {
|
||||
cookingSessionStartInputSchema,
|
||||
cookingSessionCompleteInputSchema,
|
||||
idParamSchema,
|
||||
} from "@app/validation";
|
||||
import { errors, parse } from "../lib/errors.js";
|
||||
import { requireActiveHousehold, requireMembership } from "../lib/helpers.js";
|
||||
import {
|
||||
cookingSessionStarted,
|
||||
cookingSessionCompleted,
|
||||
cookingSessionCancelled,
|
||||
} from "@app/analytics";
|
||||
import { trackProductAnalytics } from "../lib/helpers.js";
|
||||
import { completeCookingSessionCore } from "../lib/cooking.js";
|
||||
import { z } from "zod";
|
||||
|
||||
/**
|
||||
* Cooking Sessions (Fas 3 §6).
|
||||
*
|
||||
* - PLANNED reserverar aldrig lager.
|
||||
* - STARTED har 24 h på sig att complete/cancel (§20 timeout).
|
||||
* - COMPLETED/CANCELLED är terminala.
|
||||
* - Gamla POST /v1/recipes/:id/cook finns kvar som kortkommando.
|
||||
*/
|
||||
export async function cookingSessionRoutes(app: FastifyInstance) {
|
||||
const auth = { preHandler: [app.authenticate] };
|
||||
|
||||
/** Skapa ny session. Status planned om startNow saknas, annars started. */
|
||||
app.post("/v1/recipes/:id/cook/start", auth, async (req, reply) => {
|
||||
const { id } = parse(idParamSchema, req.params);
|
||||
const input = parse(cookingSessionStartInputSchema, req.body);
|
||||
const householdId = await requireActiveHousehold(app.db, req.userId);
|
||||
await requireMembership(app.db, householdId, req.userId);
|
||||
|
||||
const [recipe] = await app.db
|
||||
.select({ id: schema.recipes.id, portions: schema.recipes.portions })
|
||||
.from(schema.recipes)
|
||||
.where(eq(schema.recipes.id, id))
|
||||
.limit(1);
|
||||
if (!recipe) throw errors.notFound("Receptet finns inte.");
|
||||
|
||||
const now = new Date();
|
||||
const status = input.startNow ? "started" : "planned";
|
||||
|
||||
const [session] = await app.db
|
||||
.insert(schema.cookingSessions)
|
||||
.values({
|
||||
recipeId: id,
|
||||
householdId,
|
||||
startedByUserId: req.userId,
|
||||
status,
|
||||
plannedPortions: input.portions ?? recipe.portions,
|
||||
plannedMealType: input.mealType ?? "dinner",
|
||||
startedAt: input.startNow ? now : null,
|
||||
})
|
||||
.returning();
|
||||
|
||||
await trackProductAnalytics(
|
||||
app.db,
|
||||
req.userId,
|
||||
cookingSessionStarted({
|
||||
householdId,
|
||||
properties: {
|
||||
cookingSessionId: session!.id,
|
||||
recipeId: id,
|
||||
status,
|
||||
plannedPortions: input.portions ?? recipe.portions,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
return reply.status(201).send({ session: session });
|
||||
});
|
||||
|
||||
/** Starta en planned session. */
|
||||
app.post("/v1/cooking-sessions/:id/start", auth, async (req) => {
|
||||
const { id } = parse(idParamSchema, req.params);
|
||||
const session = await getOwnedSession(app, id, req.userId);
|
||||
if (session.status !== "planned") throw errors.conflict("Sessionen är inte planerad.");
|
||||
|
||||
const [updated] = await app.db
|
||||
.update(schema.cookingSessions)
|
||||
.set({ status: "started", startedAt: new Date(), updatedAt: new Date() })
|
||||
.where(eq(schema.cookingSessions.id, id))
|
||||
.returning();
|
||||
|
||||
await trackProductAnalytics(
|
||||
app.db,
|
||||
req.userId,
|
||||
cookingSessionStarted({
|
||||
householdId: session.householdId,
|
||||
properties: { cookingSessionId: id, recipeId: session.recipeId, status: "started" },
|
||||
}),
|
||||
);
|
||||
|
||||
return { session: updated };
|
||||
});
|
||||
|
||||
/** Avbryt session utan att röra lagret. */
|
||||
app.post("/v1/cooking-sessions/:id/cancel", auth, async (req) => {
|
||||
const params = z.object({ id: z.uuid() }).parse(req.params);
|
||||
const body = z.object({ reason: z.string().max(200).optional() }).parse(req.body ?? {});
|
||||
const session = await getOwnedSession(app, params.id, req.userId);
|
||||
if (session.status === "completed" || session.status === "cancelled") {
|
||||
throw errors.conflict("Sessionen är redan avslutad.");
|
||||
}
|
||||
|
||||
const [updated] = await app.db
|
||||
.update(schema.cookingSessions)
|
||||
.set({
|
||||
status: "cancelled",
|
||||
cancelledAt: new Date(),
|
||||
cancelReason: body.reason ?? null,
|
||||
updatedAt: new Date(),
|
||||
})
|
||||
.where(eq(schema.cookingSessions.id, params.id))
|
||||
.returning();
|
||||
|
||||
await trackProductAnalytics(
|
||||
app.db,
|
||||
req.userId,
|
||||
cookingSessionCancelled({
|
||||
householdId: session.householdId,
|
||||
properties: { cookingSessionId: params.id, recipeId: session.recipeId },
|
||||
}),
|
||||
);
|
||||
|
||||
return { session: updated };
|
||||
});
|
||||
|
||||
/**
|
||||
* Complete en session.
|
||||
* I steg 3a: anropar samma logik som gamla /cook, men länkar allt till cookingSessionId.
|
||||
*/
|
||||
app.post("/v1/cooking-sessions/:id/complete", auth, async (req) => {
|
||||
const { id } = parse(idParamSchema, req.params);
|
||||
const input = parse(cookingSessionCompleteInputSchema, req.body);
|
||||
const session = await getOwnedSession(app, id, req.userId);
|
||||
if (session.status !== "started") {
|
||||
throw errors.conflict("Sessionen måste vara startad för att avslutas.");
|
||||
}
|
||||
|
||||
const result = await completeCookingSessionCore(app, session, req.userId, input, req.correlationId);
|
||||
|
||||
const [updated] = await app.db
|
||||
.select()
|
||||
.from(schema.cookingSessions)
|
||||
.where(eq(schema.cookingSessions.id, id))
|
||||
.limit(1);
|
||||
|
||||
await trackProductAnalytics(
|
||||
app.db,
|
||||
req.userId,
|
||||
cookingSessionCompleted({
|
||||
householdId: session.householdId,
|
||||
properties: {
|
||||
cookingSessionId: id,
|
||||
recipeId: session.recipeId,
|
||||
portionsCooked: session.plannedPortions,
|
||||
mealBoxPortions: input.mealBoxPortions ?? 0,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
return { ...result, session: updated };
|
||||
});
|
||||
|
||||
/** Lista hushållets aktiva sessioner. */
|
||||
app.get("/v1/cooking-sessions", auth, async (req) => {
|
||||
const householdId = await requireActiveHousehold(app.db, req.userId);
|
||||
await requireMembership(app.db, householdId, req.userId);
|
||||
const sessions = await app.db
|
||||
.select()
|
||||
.from(schema.cookingSessions)
|
||||
.where(
|
||||
and(
|
||||
eq(schema.cookingSessions.householdId, householdId),
|
||||
eq(schema.cookingSessions.status, "started"),
|
||||
),
|
||||
)
|
||||
.orderBy(schema.cookingSessions.startedAt);
|
||||
return { sessions };
|
||||
});
|
||||
}
|
||||
|
||||
async function getOwnedSession(app: FastifyInstance, sessionId: string, userId: string) {
|
||||
const [session] = await app.db
|
||||
.select()
|
||||
.from(schema.cookingSessions)
|
||||
.where(eq(schema.cookingSessions.id, sessionId))
|
||||
.limit(1);
|
||||
if (!session) throw errors.notFound("Sessionen finns inte.");
|
||||
await requireMembership(app.db, session.householdId, userId);
|
||||
return session;
|
||||
}
|
||||
+17
-194
@@ -32,6 +32,7 @@ import {
|
||||
todayIso,
|
||||
} from "../lib/helpers.js";
|
||||
import { requireFeature } from "../lib/entitlements.js";
|
||||
import { completeCookingSessionCore } from "../lib/cooking.js";
|
||||
|
||||
/** Recept: sök, detalj, betyg, favoriter, "jag har lagat", substitutioner, användarrecept. */
|
||||
export async function recipeRoutes(app: FastifyInstance) {
|
||||
@@ -415,204 +416,26 @@ export async function recipeRoutes(app: FastifyInstance) {
|
||||
app.post("/v1/recipes/:id/cook", auth, async (req) => {
|
||||
const { id } = parse(idParamSchema, req.params);
|
||||
const input = parse(cookRecipeInputSchema, req.body);
|
||||
const recipe = await loadFullRecipe(app, id);
|
||||
await loadFullRecipe(app, id);
|
||||
const householdId = await requireActiveHousehold(app.db, req.userId);
|
||||
const date = input.date ?? todayIso();
|
||||
|
||||
// 1. Dra lager enligt FEFO (spec §17: prioritera utgångsdatum)
|
||||
const deductions: Array<{ itemId: string; quantity: number; unit: string; name: string }> = [];
|
||||
if (input.deductInventory) {
|
||||
const factor = input.portionsCooked / recipe.portions;
|
||||
const overrides = new Map(input.inventoryOverrides.map((o) => [o.canonicalIngredientId, o]));
|
||||
|
||||
for (const ing of recipe.ingredients) {
|
||||
if (ing.optional) continue;
|
||||
const override = overrides.get(ing.canonicalIngredientId);
|
||||
const requiredQty = override ? override.quantityUsed : ing.quantity * factor;
|
||||
const requiredUnit = override ? override.unit : ing.unit;
|
||||
if (requiredQty <= 0) continue;
|
||||
|
||||
const stock = await app.db
|
||||
.select()
|
||||
.from(schema.inventoryItems)
|
||||
.where(
|
||||
and(
|
||||
eq(schema.inventoryItems.householdId, householdId),
|
||||
eq(schema.inventoryItems.canonicalIngredientId, ing.canonicalIngredientId),
|
||||
isNull(schema.inventoryItems.depletedAt),
|
||||
gt(schema.inventoryItems.quantity, 0),
|
||||
),
|
||||
);
|
||||
if (stock.length === 0) continue;
|
||||
|
||||
const [info] = await app.db
|
||||
.select()
|
||||
.from(schema.canonicalIngredients)
|
||||
.where(eq(schema.canonicalIngredients.id, ing.canonicalIngredientId))
|
||||
.limit(1);
|
||||
|
||||
const allocation = allocateFefo(
|
||||
requiredQty,
|
||||
requiredUnit,
|
||||
stock.map((s) => ({
|
||||
id: s.id,
|
||||
canonicalIngredientId: s.canonicalIngredientId,
|
||||
quantity: s.quantity,
|
||||
unit: s.unit,
|
||||
bestBeforeDate: s.bestBeforeDate,
|
||||
useByDate: s.useByDate,
|
||||
openedAt: s.openedAt,
|
||||
frozenAt: s.frozenAt,
|
||||
thawedAt: s.thawedAt,
|
||||
purchasedAt: s.purchasedAt,
|
||||
})),
|
||||
{ densityGPerMl: info?.densityGPerMl, gramsPerPiece: info?.gramsPerPiece },
|
||||
);
|
||||
|
||||
for (const alloc of allocation.allocations) {
|
||||
const item = stock.find((s) => s.id === alloc.itemId)!;
|
||||
const newQty = Math.max(0, Math.round((item.quantity - alloc.quantity) * 1000) / 1000);
|
||||
await app.db.insert(schema.inventoryTransactions).values({
|
||||
householdId,
|
||||
inventoryItemId: alloc.itemId,
|
||||
type: "cook_use",
|
||||
quantityDelta: -(item.quantity - newQty),
|
||||
unit: item.unit,
|
||||
refType: "recipe_cook",
|
||||
refId: id,
|
||||
actorUserId: req.userId,
|
||||
});
|
||||
await app.db
|
||||
.update(schema.inventoryItems)
|
||||
.set({
|
||||
quantity: newQty,
|
||||
depletedAt: newQty <= 0 ? new Date() : null,
|
||||
updatedAt: new Date(),
|
||||
})
|
||||
.where(eq(schema.inventoryItems.id, alloc.itemId));
|
||||
deductions.push({
|
||||
itemId: alloc.itemId,
|
||||
quantity: alloc.quantity,
|
||||
unit: alloc.unit,
|
||||
name: item.displayName,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Logga måltid per ätare med portionsandel (spec §7: individuellt)
|
||||
const eaters =
|
||||
input.eaters.length > 0 ? input.eaters : [{ userId: req.userId, portionFraction: 1 }];
|
||||
const mealIds: string[] = [];
|
||||
for (const eater of eaters) {
|
||||
const nutrition = scaleNutrition(recipe.nutritionPerPortion, eater.portionFraction);
|
||||
const [meal] = await app.db
|
||||
.insert(schema.meals)
|
||||
.values({
|
||||
userId: eater.userId,
|
||||
householdId,
|
||||
date,
|
||||
mealType: input.mealType,
|
||||
source: "cooked_recipe",
|
||||
recipeId: id,
|
||||
titleSv: recipe.titleSv,
|
||||
portionFraction: eater.portionFraction,
|
||||
nutrition,
|
||||
nutritionIsEstimate: false,
|
||||
})
|
||||
.returning();
|
||||
mealIds.push(meal!.id);
|
||||
await emitEvent(app.db, {
|
||||
type: "MEAL_LOGGED",
|
||||
payload: {
|
||||
mealId: meal!.id,
|
||||
mealType: input.mealType,
|
||||
kcal: nutrition.kcal,
|
||||
source: "cooked_recipe",
|
||||
},
|
||||
userId: eater.userId,
|
||||
householdId,
|
||||
correlationId: req.correlationId,
|
||||
});
|
||||
}
|
||||
|
||||
// 3. Matlådor (spec §24)
|
||||
let mealBoxId: string | null = null;
|
||||
if (input.mealBoxPortions > 0) {
|
||||
const locationId =
|
||||
input.mealBoxStorageLocationId ??
|
||||
(
|
||||
await app.db
|
||||
.select({ id: schema.storageLocations.id })
|
||||
.from(schema.storageLocations)
|
||||
.where(
|
||||
and(
|
||||
eq(schema.storageLocations.householdId, householdId),
|
||||
eq(schema.storageLocations.type, input.mealBoxFrozen ? "freezer" : "fridge"),
|
||||
),
|
||||
)
|
||||
.limit(1)
|
||||
)[0]?.id;
|
||||
if (!locationId) throw errors.badRequest("Ingen förvaringsplats för matlådor hittades.");
|
||||
|
||||
const useByDays = input.mealBoxFrozen ? 90 : 3;
|
||||
const recommendedUseBy = new Date(Date.now() + useByDays * 86_400_000)
|
||||
.toISOString()
|
||||
.slice(0, 10);
|
||||
const [box] = await app.db
|
||||
.insert(schema.mealBoxes)
|
||||
.values({
|
||||
householdId,
|
||||
recipeId: id,
|
||||
titleSv: recipe.titleSv,
|
||||
portions: input.mealBoxPortions,
|
||||
portionsRemaining: input.mealBoxPortions,
|
||||
nutritionPerPortion: recipe.nutritionPerPortion,
|
||||
cookedAt: date,
|
||||
storageLocationId: locationId,
|
||||
frozen: input.mealBoxFrozen,
|
||||
recommendedUseBy,
|
||||
})
|
||||
.returning();
|
||||
mealBoxId = box!.id;
|
||||
await emitEvent(app.db, {
|
||||
type: "MEAL_BOX_CREATED",
|
||||
payload: { mealBoxId: box!.id, portions: input.mealBoxPortions },
|
||||
userId: req.userId,
|
||||
householdId,
|
||||
correlationId: req.correlationId,
|
||||
});
|
||||
}
|
||||
|
||||
// 4. Statistik + event
|
||||
await app.db.insert(schema.recipeCooks).values({
|
||||
recipeId: id,
|
||||
userId: req.userId,
|
||||
householdId,
|
||||
portionsCooked: input.portionsCooked,
|
||||
});
|
||||
await app.db
|
||||
.update(schema.recipes)
|
||||
.set({ cookCount: sql`${schema.recipes.cookCount} + 1` })
|
||||
.where(eq(schema.recipes.id, id));
|
||||
await emitEvent(app.db, {
|
||||
type: "RECIPE_COOKED",
|
||||
payload: {
|
||||
// Bakåtkompatibel shortcut: skapa session + complete direkt.
|
||||
const [session] = await app.db
|
||||
.insert(schema.cookingSessions)
|
||||
.values({
|
||||
recipeId: id,
|
||||
portions: input.portionsCooked,
|
||||
mealBoxPortions: input.mealBoxPortions,
|
||||
},
|
||||
userId: req.userId,
|
||||
householdId,
|
||||
correlationId: req.correlationId,
|
||||
});
|
||||
householdId,
|
||||
startedByUserId: req.userId,
|
||||
status: "started",
|
||||
plannedPortions: input.portionsCooked,
|
||||
plannedMealType: input.mealType,
|
||||
startedAt: new Date(),
|
||||
})
|
||||
.returning();
|
||||
|
||||
await markMilestone(app.db, householdId, "firstCookingSessionCompletedAt");
|
||||
if (input.deductInventory && deductions.length > 0) {
|
||||
await markMilestone(app.db, householdId, "inventoryUpdatedAfterCookingAt");
|
||||
}
|
||||
const result = await completeCookingSessionCore(app, session!, req.userId, input, req.correlationId);
|
||||
|
||||
return { ok: true, mealIds, mealBoxId, inventoryDeductions: deductions };
|
||||
return { sessionId: session!.id, ...result };
|
||||
});
|
||||
|
||||
/** Substitutionsförslag för en ingrediens (spec §20). */
|
||||
@@ -997,7 +820,7 @@ export async function recipeRoutes(app: FastifyInstance) {
|
||||
});
|
||||
}
|
||||
|
||||
async function loadFullRecipe(app: FastifyInstance, id: string) {
|
||||
export async function loadFullRecipe(app: FastifyInstance, id: string) {
|
||||
const [recipe] = await app.db
|
||||
.select()
|
||||
.from(schema.recipes)
|
||||
|
||||
@@ -13,6 +13,7 @@ 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";
|
||||
@@ -80,6 +81,7 @@ export async function buildServer(config: AppConfig) {
|
||||
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);
|
||||
|
||||
@@ -0,0 +1,238 @@
|
||||
import "./setup-env.js";
|
||||
import { describe, expect, it, beforeAll, afterAll } from "vitest";
|
||||
import { eq, inArray, count } from "drizzle-orm";
|
||||
import { buildServer } from "../src/server.js";
|
||||
import { loadConfig } from "../src/config.js";
|
||||
import { createDatabase, closeDatabase, schema } from "@app/database";
|
||||
import { cancelTimedOutCookingSessions } from "@app/database";
|
||||
import { computeBalance } from "@app/inventory-engine";
|
||||
|
||||
describe("cooking sessions", () => {
|
||||
const testDb = createDatabase(process.env.TEST_DATABASE_URL!);
|
||||
const config = loadConfig();
|
||||
let app: Awaited<ReturnType<typeof buildServer>>;
|
||||
let token: string;
|
||||
let householdId: string;
|
||||
let recipeId: string;
|
||||
const email = "cooking-session-test@example.invalid";
|
||||
|
||||
async function cleanup() {
|
||||
const existing = await testDb.db
|
||||
.select({ id: schema.users.id })
|
||||
.from(schema.users)
|
||||
.where(inArray(schema.users.email, [email]));
|
||||
for (const u of existing) {
|
||||
const sessions = await testDb.db
|
||||
.select({ id: schema.cookingSessions.id })
|
||||
.from(schema.cookingSessions)
|
||||
.where(eq(schema.cookingSessions.startedByUserId, u.id));
|
||||
for (const s of sessions) {
|
||||
await testDb.db
|
||||
.delete(schema.inventoryTransactions)
|
||||
.where(eq(schema.inventoryTransactions.cookingSessionId, s.id));
|
||||
await testDb.db.delete(schema.meals).where(eq(schema.meals.cookingSessionId, s.id));
|
||||
await testDb.db.delete(schema.mealBoxes).where(eq(schema.mealBoxes.cookingSessionId, s.id));
|
||||
await testDb.db.delete(schema.recipeCooks).where(eq(schema.recipeCooks.cookingSessionId, s.id));
|
||||
}
|
||||
await testDb.db.delete(schema.cookingSessions).where(eq(schema.cookingSessions.startedByUserId, u.id));
|
||||
const memberships = await testDb.db
|
||||
.select({ householdId: schema.householdMembers.householdId })
|
||||
.from(schema.householdMembers)
|
||||
.where(eq(schema.householdMembers.userId, u.id));
|
||||
for (const m of memberships) {
|
||||
await testDb.db.delete(schema.inventoryItems).where(eq(schema.inventoryItems.householdId, m.householdId));
|
||||
await testDb.db.delete(schema.storageLocations).where(eq(schema.storageLocations.householdId, m.householdId));
|
||||
await testDb.db.delete(schema.householdMembers).where(eq(schema.householdMembers.householdId, m.householdId));
|
||||
await testDb.db.delete(schema.households).where(eq(schema.households.id, m.householdId));
|
||||
}
|
||||
await testDb.db.delete(schema.userPreferences).where(eq(schema.userPreferences.userId, u.id));
|
||||
await testDb.db.delete(schema.users).where(eq(schema.users.id, u.id));
|
||||
}
|
||||
}
|
||||
|
||||
beforeAll(async () => {
|
||||
await cleanup();
|
||||
app = await buildServer(config);
|
||||
await app.ready();
|
||||
|
||||
const res = await app.inject({
|
||||
method: "POST",
|
||||
url: "/v1/auth/register",
|
||||
payload: { email, password: "Password123!", displayName: "Cooking Test" },
|
||||
});
|
||||
token = (JSON.parse(res.body) as { accessToken: string }).accessToken;
|
||||
|
||||
const quick = await app.inject({
|
||||
method: "POST",
|
||||
url: "/v1/onboarding/quick-start",
|
||||
headers: { authorization: `Bearer ${token}` },
|
||||
payload: { goals: ["less_waste"], precisionMode: "simple" },
|
||||
});
|
||||
householdId = (JSON.parse(quick.body) as { householdId: string }).householdId;
|
||||
|
||||
// Hitta ett seedat recept
|
||||
const recipes = await app.inject({
|
||||
method: "GET",
|
||||
url: "/v1/recipes?limit=1",
|
||||
headers: { authorization: `Bearer ${token}` },
|
||||
});
|
||||
recipeId = (JSON.parse(recipes.body) as { recipes: Array<{ id: string }> }).recipes[0]!.id;
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await cleanup();
|
||||
await closeDatabase();
|
||||
await app.close();
|
||||
});
|
||||
|
||||
it("creates a planned session", async () => {
|
||||
const res = await app.inject({
|
||||
method: "POST",
|
||||
url: `/v1/recipes/${recipeId}/cook/start`,
|
||||
headers: { authorization: `Bearer ${token}` },
|
||||
payload: { portions: 4, mealType: "dinner" },
|
||||
});
|
||||
expect(res.statusCode).toBe(201);
|
||||
const body = JSON.parse(res.body) as { session: { status: string; plannedPortions: number } };
|
||||
expect(body.session.status).toBe("planned");
|
||||
expect(body.session.plannedPortions).toBe(4);
|
||||
});
|
||||
|
||||
it("starts a planned session", async () => {
|
||||
const start = await app.inject({
|
||||
method: "POST",
|
||||
url: `/v1/recipes/${recipeId}/cook/start`,
|
||||
headers: { authorization: `Bearer ${token}` },
|
||||
payload: { portions: 2 },
|
||||
});
|
||||
const sessionId = (JSON.parse(start.body) as { session: { id: string } }).session.id;
|
||||
|
||||
const res = await app.inject({
|
||||
method: "POST",
|
||||
url: `/v1/cooking-sessions/${sessionId}/start`,
|
||||
headers: { authorization: `Bearer ${token}` },
|
||||
payload: {},
|
||||
});
|
||||
expect(res.statusCode).toBe(200);
|
||||
const body = JSON.parse(res.body) as { session: { status: string } };
|
||||
expect(body.session.status).toBe("started");
|
||||
});
|
||||
|
||||
it("cancels a session without touching inventory", async () => {
|
||||
const start = await app.inject({
|
||||
method: "POST",
|
||||
url: `/v1/recipes/${recipeId}/cook/start`,
|
||||
headers: { authorization: `Bearer ${token}` },
|
||||
payload: { startNow: true },
|
||||
});
|
||||
const sessionId = (JSON.parse(start.body) as { session: { id: string } }).session.id;
|
||||
|
||||
const before = await testDb.db
|
||||
.select({ count: count(schema.inventoryTransactions.id) })
|
||||
.from(schema.inventoryTransactions)
|
||||
.innerJoin(schema.inventoryItems, eq(schema.inventoryTransactions.inventoryItemId, schema.inventoryItems.id))
|
||||
.where(eq(schema.inventoryItems.householdId, householdId));
|
||||
|
||||
const res = await app.inject({
|
||||
method: "POST",
|
||||
url: `/v1/cooking-sessions/${sessionId}/cancel`,
|
||||
headers: { authorization: `Bearer ${token}` },
|
||||
payload: { reason: "vi åt ute" },
|
||||
});
|
||||
expect(res.statusCode).toBe(200);
|
||||
const body = JSON.parse(res.body) as { session: { status: string } };
|
||||
expect(body.session.status).toBe("cancelled");
|
||||
|
||||
const after = await testDb.db
|
||||
.select({ count: count(schema.inventoryTransactions.id) })
|
||||
.from(schema.inventoryTransactions)
|
||||
.innerJoin(schema.inventoryItems, eq(schema.inventoryTransactions.inventoryItemId, schema.inventoryItems.id))
|
||||
.where(eq(schema.inventoryItems.householdId, householdId));
|
||||
expect(after[0]!.count).toBe(before[0]!.count);
|
||||
});
|
||||
|
||||
it("completes a started session and links inventory, meals and recipe_cooks", async () => {
|
||||
const start = await app.inject({
|
||||
method: "POST",
|
||||
url: `/v1/recipes/${recipeId}/cook/start`,
|
||||
headers: { authorization: `Bearer ${token}` },
|
||||
payload: { startNow: true, portions: 4 },
|
||||
});
|
||||
const sessionId = (JSON.parse(start.body) as { session: { id: string } }).session.id;
|
||||
|
||||
const res = await app.inject({
|
||||
method: "POST",
|
||||
url: `/v1/cooking-sessions/${sessionId}/complete`,
|
||||
headers: { authorization: `Bearer ${token}` },
|
||||
payload: { mealBoxPortions: 0, deductInventory: true },
|
||||
});
|
||||
expect(res.statusCode).toBe(200);
|
||||
const body = JSON.parse(res.body) as {
|
||||
session: { status: string };
|
||||
mealIds: string[];
|
||||
inventoryDeductions: Array<{ itemId: string; quantity: number; unit: string; name: string }>;
|
||||
};
|
||||
expect(body.session.status).toBe("completed");
|
||||
expect(body.mealIds.length).toBeGreaterThan(0);
|
||||
|
||||
const txCount = await testDb.db
|
||||
.select({ count: count(schema.inventoryTransactions.id) })
|
||||
.from(schema.inventoryTransactions)
|
||||
.where(eq(schema.inventoryTransactions.cookingSessionId, sessionId));
|
||||
expect(Number(txCount[0]!.count)).toBeGreaterThanOrEqual(body.inventoryDeductions.length);
|
||||
|
||||
const cookCount = await testDb.db
|
||||
.select({ count: count(schema.recipeCooks.id) })
|
||||
.from(schema.recipeCooks)
|
||||
.where(eq(schema.recipeCooks.cookingSessionId, sessionId));
|
||||
expect(Number(cookCount[0]!.count)).toBe(1);
|
||||
|
||||
// Transaktionsinvariant: computeBalance ska matcha item.quantity
|
||||
for (const d of body.inventoryDeductions) {
|
||||
const item = await testDb.db
|
||||
.select({ id: schema.inventoryItems.id, quantity: schema.inventoryItems.quantity })
|
||||
.from(schema.inventoryItems)
|
||||
.where(eq(schema.inventoryItems.id, d.itemId))
|
||||
.limit(1);
|
||||
const txs = await testDb.db
|
||||
.select({ type: schema.inventoryTransactions.type, quantityDelta: schema.inventoryTransactions.quantityDelta, unit: schema.inventoryTransactions.unit })
|
||||
.from(schema.inventoryTransactions)
|
||||
.where(eq(schema.inventoryTransactions.inventoryItemId, d.itemId));
|
||||
const balance = computeBalance(txs);
|
||||
expect(Math.abs(balance.balance - (item[0]?.quantity ?? 0))).toBeLessThan(1e-6);
|
||||
}
|
||||
});
|
||||
|
||||
it("legacy POST /v1/recipes/:id/cook still works as shortcut", async () => {
|
||||
const res = await app.inject({
|
||||
method: "POST",
|
||||
url: `/v1/recipes/${recipeId}/cook`,
|
||||
headers: { authorization: `Bearer ${token}` },
|
||||
payload: { portionsCooked: 4, mealBoxPortions: 2, deductInventory: true },
|
||||
});
|
||||
expect(res.statusCode).toBe(200);
|
||||
const body = JSON.parse(res.body) as { ok: boolean; sessionId: string; mealBoxId: string | null };
|
||||
expect(body.ok).toBe(true);
|
||||
expect(body.sessionId).toBeDefined();
|
||||
expect(body.mealBoxId).toBeDefined();
|
||||
});
|
||||
|
||||
it("times out started sessions older than 24 h", async () => {
|
||||
const start = await app.inject({
|
||||
method: "POST",
|
||||
url: `/v1/recipes/${recipeId}/cook/start`,
|
||||
headers: { authorization: `Bearer ${token}` },
|
||||
payload: { startNow: true },
|
||||
});
|
||||
const sessionId = (JSON.parse(start.body) as { session: { id: string } }).session.id;
|
||||
|
||||
// Simulera 25 h gammal session
|
||||
await testDb.db
|
||||
.update(schema.cookingSessions)
|
||||
.set({ startedAt: new Date(Date.now() - 25 * 60 * 60 * 1000) })
|
||||
.where(eq(schema.cookingSessions.id, sessionId));
|
||||
|
||||
const timedOut = await cancelTimedOutCookingSessions(testDb.db);
|
||||
expect(timedOut.some((s: { id: string }) => s.id === sessionId)).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -8,6 +8,7 @@ import { processModerateRecipe } from "./processors/moderation.js";
|
||||
import { processTranslateRecipe } from "./processors/translation.js";
|
||||
import { processGenerateWeekPlan } from "./processors/weekplan.js";
|
||||
import {
|
||||
processCookingSessionTimeout,
|
||||
processExpiryNotifications,
|
||||
processMealBoxReminders,
|
||||
processMemorySync,
|
||||
@@ -117,6 +118,12 @@ const worker = new Worker(
|
||||
return;
|
||||
}
|
||||
|
||||
case "COOKING_SESSION_TIMEOUT": {
|
||||
const cancelled = await processCookingSessionTimeout(ctx);
|
||||
if (cancelled > 0) log(`Cooking session timeout: ${cancelled} avbrutna`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Deterministiska/planerade jobb som inte kräver egen processor ännu
|
||||
case "NORMALIZE_PRODUCTS":
|
||||
case "DEDUPLICATE_INVENTORY":
|
||||
@@ -213,6 +220,11 @@ async function registerRepeatableJobs() {
|
||||
{ every: 6 * 60 * 60 * 1000 }, // var 6:e timme; decay lever på dygnsskala
|
||||
{ name: "UPDATE_TRUST_STATES", data: { jobType: "UPDATE_TRUST_STATES" }, opts: baseOpts },
|
||||
);
|
||||
await queue.upsertJobScheduler(
|
||||
"scheduler-cooking-timeout",
|
||||
{ every: 60 * 60 * 1000 }, // varje timme räcker för 24 h-timeout
|
||||
{ name: "COOKING_SESSION_TIMEOUT", data: { jobType: "COOKING_SESSION_TIMEOUT" }, opts: baseOpts },
|
||||
);
|
||||
await queue.upsertJobScheduler(
|
||||
"scheduler-expiry",
|
||||
{ pattern: "0 7 * * *", tz: "Europe/Stockholm" },
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { and, asc, eq, gt, inArray, isNull, lte, sql } from "drizzle-orm";
|
||||
import { and, asc, eq, gt, inArray, isNull, lt, lte, sql } from "drizzle-orm";
|
||||
import { schema } from "@app/database";
|
||||
import { classifyExpiry, computeTrust } from "@app/inventory-engine";
|
||||
import { deriveMemoryUpdates } from "@app/memory-client";
|
||||
import { getLocaleContext } from "../locale.js";
|
||||
import type { WorkerContext } from "../context.js";
|
||||
import { cancelTimedOutCookingSessions } from "@app/database";
|
||||
|
||||
/**
|
||||
* Återkommande underhållsjobb: outbox-publicering, bäst före-notiser,
|
||||
@@ -84,6 +85,12 @@ export async function processTrustDecay(ctx: WorkerContext): Promise<number> {
|
||||
return updated;
|
||||
}
|
||||
|
||||
/** COOKING_SESSION_TIMEOUT (Fas 3 §6 / §20): avbryt STARTED-sessioner äldre än 24 h. */
|
||||
export async function processCookingSessionTimeout(ctx: WorkerContext): Promise<number> {
|
||||
const sessions = await cancelTimedOutCookingSessions(ctx.db);
|
||||
return sessions.length;
|
||||
}
|
||||
|
||||
/** SEND_EXPIRY_NOTIFICATION (spec §54): skapa notiser för varor som snart går ut. */
|
||||
export async function processExpiryNotifications(ctx: WorkerContext): Promise<number> {
|
||||
const households = await ctx.db.select({ id: schema.households.id }).from(schema.households);
|
||||
|
||||
@@ -0,0 +1,230 @@
|
||||
# Fas 3 – Cooking Sessions: REUSE/GAP-matris och stegplan
|
||||
|
||||
> Levereras före kod. Styrande dokument: utbyggnadsprompten §6 + `docs/19-beslutslogg.md` + anti-dubblettkartan.
|
||||
|
||||
---
|
||||
|
||||
## 1. REUSE/GAP-matris mot befintligt matlagningsflöde
|
||||
|
||||
| §6-krav / område | Befintlig implementation | Status | Rekommenderad åtgärd |
|
||||
|------------------|-------------------------|--------|----------------------|
|
||||
| **Lifecycle PLANNED → STARTED → COMPLETED/CANCELLED** | Saknas. `recipe_cooks` är en enda rad skapad vid `/cook` ("COMPLETED" implicit). Ingen separat sessionstabell. | **Saknas** | Inför `cooking_sessions` (se nedan). Behåll `recipe_cooks` som aggregerad historik för rekommendationer/variation. |
|
||||
| **FEFO-avdrag från inventory (§6)** | `/v1/recipes/:id/cook` kör `allocateFefo()` och skriver `inventory_transactions.type = 'cook_use'` med `refType='recipe_cook'`. | **Finns** | Återanvänd oförändrad. Lägg till `cookingSessionId` i transaktionen för att kunna ångra en hel session. |
|
||||
| **portioner / portionsCooked** | `cookRecipeInputSchema.portionsCooked` (1–24). Skalning i receptdetalj. | **Finns** | Återanvänd. |
|
||||
| **Individuell förbrukning per ätare** | `eaters[]` med `portionFraction` loggas i `meals`. | **Finns** | Återanvänd. |
|
||||
| **Minimala efterfrågor efteråt (§6.2)** | Efterflödet i `cooking/[id].tsx` frågar idag bara antal matlådor. Inga profiler för "hur mycket blev det kvar?" | **Delvis** | Inför 2–3 snabba frågor som extraheras till `cooking_sessions.actualPortionsEaten`, `leftoverEstimate` och `cookingSessionId` på meals. |
|
||||
| **Partiell förbrukning (§6.3)** | Saknas. `/cook` drar allt på en gång. Användaren kan efteråt manuellt justera via `/v1/inventory/items/:id/transactions`. | **Saknas** | Låt `/cook` skapa `cook_use`-transaktioner med `undoUntil = cookingSessionId`; vid efterfrågan skriv reverseringstransaktioner för det som inte åts. |
|
||||
| **undo_until (ångra hel session)** | Transaktionsmodellen stöder bara enskilda nya transaktioner. Ingen koppling tillbaka till en session. | **Saknas** | Lägg `cookingSessionId` på `inventory_transactions`, `meals`, `meal_boxes` och tillåt `POST /v1/cooking-sessions/:id/undo`. |
|
||||
| **Rester till BEFINTLIGA meal_boxes (§6.4)** | `meal_boxes` finns. `/cook` kan skapa EN ny matlåda per tillfälle. | **Delvis** | Utöka så att rester kan läggas i en **befintlig** `meal_box` (matcha på recipeId + hushåll + `status=available`) istället för att alltid skapa ny. |
|
||||
| **Anti-dubblettkartan: ingen prepared_food_batches-tabell** | Finns ingen sådan tabell. | **OK** | Behåll. Använd `meal_boxes` som enda restbehållare. Motivering: samma koncept (portioner kvar, ät-senast-datum, näringssnapshot), sparar dubblettlogik. |
|
||||
| **Näringssnapshot på rester** | `meal_boxes.nutritionPerPortion` sparas vid skapande. | **Finns** | Återanvänd. |
|
||||
| **recommendations läser recipe_cooks** | `recommendations.ts` gör `SELECT recipeId, max(cookedAt) FROM recipe_cooks GROUP BY recipeId`. | **Finns** | **Risk:** om vi ändrar semantiken i `recipe_cooks` måste denna fråga vara bakåtkompatibel. Rekommendation ska fortsätta se sista tillfället ett recept lagades. |
|
||||
| **Testprotokoll steg 5** | "Laga nu → cooking mode → klart → logga → ingredienser dras från lagret; måltiden syns i Min dag". | **Finns** | Måste förbli sant. Nytt flöde får inte kräva fler steg än idag; nya frågor ska vara valbara/skipbara. |
|
||||
| **Testprotokoll steg 8** | "Matlådor: laga recept med portioner till lådor → lådan syns med ät-senast-datum". | **Finns** | Måste förbli sant. Nya rest-flödet ska INTE bryta skapandet av nya lådor. |
|
||||
| **Alla strängar ×12** | i18next + 12 locales. | **Finns** | Lägg till nya nycklar i alla 12 språk. |
|
||||
| **Hermetiska tester** | `apps/api/test/*` använder `TEST_DATABASE_URL` och setup-env. | **Finns** | Fortsätt samma mönster. |
|
||||
| **Mjölkprincipen i texter** | Alla UI-strängar undviker "kassera automatiskt" och skiljer bäst-före/sista-förbrukningsdag. | **Finns** | Fortsätt. Inga texter får garantera matsäkerhet. |
|
||||
|
||||
### Slutsats av matrisen
|
||||
Cooking Sessions är en **utbyggnad**, inte ett parallellsystem:
|
||||
- **Kärnan** (`recipe_cooks`, FEFO-avdrag, meals, meal_boxes) finns redan.
|
||||
- **Det som saknas** är en session-boog (`cooking_sessions`) som knyter samman påbörjat/klart/ångrat, plus möjligheten att (a) skjuta upp delar av förbrukningen till efterfrågan och (b) lägga rester i befintliga matlådor.
|
||||
|
||||
---
|
||||
|
||||
## 2. Anti-dubblettkartan – varför INGEN `prepared_food_batches`-tabell
|
||||
|
||||
Befintlig `meal_boxes` kan redan representera:
|
||||
- Portioner som återstår
|
||||
- Receptursprung (`recipeId`)
|
||||
- Näringsvärden per portion (`nutritionPerPortion`)
|
||||
- Förvaringsplats och frystillstånd
|
||||
- Rekommenderat ät-senast-datum
|
||||
- Status (`available`/`consumed`/`discarded`)
|
||||
- Reservering för användare
|
||||
|
||||
Att lägga till `prepared_food_batches` skulle skapa:
|
||||
- Dubbla konsumtions-endpoints
|
||||
- Dubbla "ät senast"-logiker
|
||||
- Dubbla måltidsloggningsvägar
|
||||
- Extra migrering och index
|
||||
- Förvirring i UI: "är detta en matlåda eller en batch?"
|
||||
|
||||
**Beslut:** Utöka `meal_boxes` med ett fält `leftoverSource` (`cook_session`) och en kolumn `cookingSessionId`. Då är resterna fortfarande matlådor ur användarens perspektiv, men spårbart till en cooking session.
|
||||
|
||||
---
|
||||
|
||||
## 3. STEGPLAN
|
||||
|
||||
### Steg 3a – Lifecycle + planned usage ovanpå befintlig `/cook`
|
||||
**Mål:** En cooking session kan startas, slutföras eller avbrytas utan att bryta dagens `/cook`-beteende.
|
||||
|
||||
**Ändringar:**
|
||||
1. Ny tabell `cooking_sessions`:
|
||||
- `id`, `recipeId`, `householdId`, `startedByUserId`
|
||||
- `status`: `planned`, `started`, `completed`, `cancelled`
|
||||
- `plannedPortions`, `plannedMealType`
|
||||
- `startedAt`, `completedAt`, `cancelledAt`
|
||||
- `cancelReason` (frivillig text, max 200)
|
||||
2. Lägg `cookingSessionId` nullable på:
|
||||
- `inventory_transactions`
|
||||
- `meals`
|
||||
- `meal_boxes`
|
||||
- `recipe_cooks` (för spårbarhet)
|
||||
3. Nytt API:
|
||||
- `POST /v1/recipes/:id/cook/start` → skapar `cooking_session` med status `started`.
|
||||
- `POST /v1/cooking-sessions/:id/complete` → kör dagens `/cook`-logik (FEFO, meals, mealBoxes) men länkar allt till `cookingSessionId`.
|
||||
- `POST /v1/cooking-sessions/:id/cancel` → sätter status `cancelled`, ingen lagerpåverkan.
|
||||
4. **Bakåtkompatibilitet:** Behåll `POST /v1/recipes/:id/cook` som ett kortkommando som skapar session + complete i samma anrop (så testprotokoll steg 5 fortsätter fungera).
|
||||
|
||||
**Berörda filer:**
|
||||
- `packages/database/src/schema/recipes.ts` (cooking_sessions, recipe_cooks-kolumn)
|
||||
- `packages/database/src/schema/meals.ts` (meal_boxes-kolumn)
|
||||
- `packages/database/src/schema/inventory.ts` (inventory_transactions-kolumn)
|
||||
- `packages/validation/src/recipes.ts` (nya schemas)
|
||||
- `apps/api/src/routes/recipes.ts` (nytt `/cook/start`, refactor `/cook`)
|
||||
- `apps/api/src/routes/cooking-sessions.ts` (ny)
|
||||
- `apps/api/src/server.ts`
|
||||
- `apps/mobile/src/app/cooking/[id].tsx` (starta session)
|
||||
- `apps/mobile/src/app/recipe/[id].tsx` ("Laga nu" kan gå via start)
|
||||
- `apps/mobile/src/locales/*/common.json` (+12)
|
||||
|
||||
**Migration:** 0011_cooking_sessions.sql
|
||||
|
||||
**Testplan:**
|
||||
- POST /cook/start → status started
|
||||
- POST /cooking-sessions/:id/complete → samma resultat som idag: inventory dras, meals skapas, mealBox skapas
|
||||
- POST /recipes/:id/cook (gammalt) fortfarande grönt
|
||||
- recipe_cooks.fas3_verifierat: rekommendationer ser fortfarande `max(cookedAt)` korrekt
|
||||
|
||||
---
|
||||
|
||||
### Steg 3b – Minimala efterfrågor §6.2 + antagandeprofiler
|
||||
**Mål:** Efter "klart" ställs max 2–3 snabba frågor. Appen kan fylla i svar automatiskt från profiler (t.ex. "vi brukar äta alla portioner" / "vi brukar ha 2 lådor över").
|
||||
|
||||
**Ändringar:**
|
||||
1. Lägg kolumner på `cooking_sessions`:
|
||||
- `actualPortionsEaten` (nullable int)
|
||||
- `leftoverEstimatePortions` (nullable int)
|
||||
- `leftoverNote` (text)
|
||||
- `postCookProfile` (jsonb med senaste svaren)
|
||||
2. Lägg `postCookQuestions` i `user_preferences` eller `households`:
|
||||
- Profil: alltid samma svar, t.ex. `"we_eat_all"` eller `"leftovers_n"`.
|
||||
3. App-skärm efter cooking mode:
|
||||
- "Hur många portioner åt ni?" (default = portionsCooked - mealBoxPortions)
|
||||
- "Vill du spara rester som matlådor?" (default från profil)
|
||||
- Frågorna ska kunna skippas med ett tryck (default antas).
|
||||
|
||||
**Berörda filer:**
|
||||
- `packages/database/src/schema/recipes.ts` (kolumner)
|
||||
- `packages/validation/src/recipes.ts`
|
||||
- `apps/api/src/routes/cooking-sessions.ts`
|
||||
- `apps/mobile/src/app/cooking/[id].tsx` (efterflöde)
|
||||
- `apps/mobile/src/locales/*/common.json`
|
||||
|
||||
**Migration:** 0012_cooking_questions.sql
|
||||
|
||||
**Testplan:**
|
||||
- Profil sparas och återanvänds nästa session
|
||||
- Skippa frågor → defaults används
|
||||
- Validering: `actualPortionsEaten + leftoverEstimatePortions ≤ portionsCooked`
|
||||
|
||||
---
|
||||
|
||||
### Steg 3c – Partiell förbrukning §6.3 + undo_until
|
||||
**Mål:** Om användaren säger att de bara åt 3 av 4 portioner, ska FEFO-avdraget justeras så att motsvarande råvaror återstår.
|
||||
|
||||
**Ändringar:**
|
||||
1. Vid complete: använd `actualPortionsEaten` för att räkna om råvaror.
|
||||
- Exempel: 4 planerade portioner → 3 ätna = använd 75 % av varje ingrediens.
|
||||
- Om `actualPortionsEaten` är null, använd `portionsCooked` (dagens beteende).
|
||||
2. Lagra ursprungligt FEFO-avdrag i `cooking_sessions.plannedDeductions` (jsonb).
|
||||
3. `POST /v1/cooking-sessions/:id/undo`:
|
||||
- Status måste vara `completed`.
|
||||
- För varje `inventory_transactions` med `cookingSessionId`: skapa en `correction` med motsatt delta.
|
||||
- För varje `meal` med `cookingSessionId`: radera (eller markera som cancelled).
|
||||
- För varje `meal_box` med `cookingSessionId`: sätt status `discarded`.
|
||||
- Sätt `cooking_sessions.status = 'cancelled'`.
|
||||
4. Lägg `cookingSessionId` i inventory item detail så användaren kan ångra enskilda transaktioner därifrån också.
|
||||
|
||||
**Berörda filer:**
|
||||
- `apps/api/src/routes/cooking-sessions.ts`
|
||||
- `apps/api/src/routes/inventory.ts` (visa cookingSessionId i transaktionslistan)
|
||||
- `packages/inventory-engine/src/fefo.ts` (ev. helper för omräkning)
|
||||
- `apps/mobile/src/app/cooking/[id].tsx`
|
||||
|
||||
**Migration:** ingår i 0011 (kolumn cookingSessionId)
|
||||
|
||||
**Testplan:**
|
||||
- Laga 4 portioner, ät 3 → lager innehåller 25 % kvar av varje ingrediens
|
||||
- Undo → allt återställs
|
||||
- Invariant: `computeBalance(transaktioner) === item.quantity` efter complete och efter undo
|
||||
|
||||
---
|
||||
|
||||
### Steg 3d – Rester §6.4 via befintliga meal_boxes
|
||||
**Mål:** Efterfrågade rester hamnar i antingen (1) befintlig available matlåda med samma `recipeId`, eller (2) ny matlåda.
|
||||
|
||||
**Ändringar:**
|
||||
1. Vid complete, om `leftoverEstimatePortions > 0`:
|
||||
- Sök `meal_boxes` där `recipeId = recipe.id`, `householdId`, `status = 'available'`, `frozen = false` (eller matcha valt frystillstånd), sorterat på `recommendedUseBy`.
|
||||
- Om träff: öka `portions` och `portionsRemaining` med `leftoverEstimatePortions`, uppdatera `recommendedUseBy` till det tidigare av de två datumen (mjölkprincipen: behåll kortaste hållbarhet).
|
||||
- Om ingen träff: skapa ny matlåda (dagens logik).
|
||||
2. Lägg `leftoverSource = 'cook_session'` och `cookingSessionId` på meal_box.
|
||||
3. Frysval: om användaren väljer frys, skapa/utöka fryst matlåda med `frozen=true`, useBy=90 dagar.
|
||||
|
||||
**Berörda filer:**
|
||||
- `packages/database/src/schema/meals.ts`
|
||||
- `apps/api/src/routes/recipes.ts` eller `cooking-sessions.ts`
|
||||
- `apps/mobile/src/app/cooking/[id].tsx`
|
||||
- `apps/mobile/src/app/meal-boxes.tsx` (visa source-ikon om det är rester)
|
||||
|
||||
**Migration:** 0013_meal_box_leftover_source.sql
|
||||
|
||||
**Testplan:**
|
||||
- Två cooking sessions med samma recept → en matlåda har ökade portioner
|
||||
- Frysta rester → separat fryst låda
|
||||
- Ingen dubblett i rekommendationer: matlådor-först-sektionen räknar fortfarande `portionsRemaining > 0`
|
||||
|
||||
---
|
||||
|
||||
## 4. RISKER
|
||||
|
||||
| Risk | Påverkan | Minskning |
|
||||
|------|----------|-----------|
|
||||
| `recommendations.ts` använder `recipe_cooks` för "senast lagat". Om `cookedAt` ändras semantik bryts variation. | Medel | Behåll `recipe_cooks.cookedAt` som tidpunkt för färdig session. Lägg separata tidsstämplar på `cooking_sessions`. |
|
||||
| Testprotokoll steg 5 kräver att "Laga nu → klart → logga" fortsätter fungera utan extra kranar. | Hög | Behåll `/recipes/:id/cook` som kortkommando. Nya frågor ska vara skipbara med tydliga defaults. |
|
||||
| Testprotokoll steg 8 kräver att nya matlådor skapas. | Hög | Nya rest-flödet ska inte blockera skapandet; befintlig-låda-utökning är opt-in. |
|
||||
| Partiell förbrukning kan leda till brutna invarianter om omräkning blir fel. | Hög | Invariantstest efter varje complete/undo; använd samma enhetskonvertering som FEFO. |
|
||||
| Undo kan radera måltider som andra flöden refererar till. | Medel | Använd soft-delete (`deletedAt` på meals) eller markera som `cancelled` istället för hård radering. |
|
||||
| UI-blödning: nya frågor kan kännas som ett formulär. | Medel | Designa som kort med +/- och "kom ihåg mitt val". |
|
||||
| Matsäkerhetstexter kan osynligt bli för skarpa. | Medel | Varje ny i18n-nyckel granskas: aldrig "säkert att äta", alltid "lukta och smaka" / "rekommenderas före". |
|
||||
| Hermetiska tester kan läcka `.env`. | Låg | Fortsätt `setup-env.ts` + `vitest.config.ts`. |
|
||||
|
||||
---
|
||||
|
||||
## 5. Översiktliga berörda tabeller
|
||||
|
||||
| Tabell | Förändring |
|
||||
|--------|------------|
|
||||
| `cooking_sessions` | **Ny** |
|
||||
| `recipe_cooks` | `cookingSessionId` nullable |
|
||||
| `inventory_transactions` | `cookingSessionId` nullable |
|
||||
| `meals` | `cookingSessionId` nullable, ev. `deletedAt` |
|
||||
| `meal_boxes` | `cookingSessionId` nullable, `leftoverSource` text |
|
||||
| `users` / `households` | post-cook-profil jsonb (steg 3b) |
|
||||
|
||||
---
|
||||
|
||||
## 6. Leveransordning
|
||||
|
||||
1. Steg 3a: lifecycle + bakåtkompatibel `/cook`
|
||||
2. Steg 3b: efterfrågor + profiler
|
||||
3. Steg 3c: partiell förbrukning + undo
|
||||
4. Steg 3d: rester till befintliga meal_boxes
|
||||
|
||||
Per steg: `brand-guard`, `typecheck`, `test`, `build`, `first-deploy staging`, patch + bundle.
|
||||
|
||||
---
|
||||
|
||||
*Rapport färdig. Inväntar godkännande innan kod påbörjas.*
|
||||
@@ -0,0 +1,34 @@
|
||||
CREATE TABLE "cooking_sessions" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"recipe_id" uuid NOT NULL,
|
||||
"household_id" uuid NOT NULL,
|
||||
"started_by_user_id" uuid NOT NULL,
|
||||
"status" text DEFAULT 'planned' NOT NULL,
|
||||
"planned_portions" integer NOT NULL,
|
||||
"planned_meal_type" "meal_type" DEFAULT 'dinner' NOT NULL,
|
||||
"actual_portions_eaten" integer,
|
||||
"leftover_estimate_portions" integer,
|
||||
"planned_deductions" jsonb,
|
||||
"started_at" timestamp with time zone,
|
||||
"completed_at" timestamp with time zone,
|
||||
"cancelled_at" timestamp with time zone,
|
||||
"cancel_reason" text,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "inventory_transactions" ADD COLUMN "cooking_session_id" uuid;--> statement-breakpoint
|
||||
ALTER TABLE "recipe_cooks" ADD COLUMN "cooking_session_id" uuid;--> statement-breakpoint
|
||||
ALTER TABLE "meal_boxes" ADD COLUMN "cooking_session_id" uuid;--> statement-breakpoint
|
||||
ALTER TABLE "meals" ADD COLUMN "cooking_session_id" uuid;--> statement-breakpoint
|
||||
ALTER TABLE "cooking_sessions" ADD CONSTRAINT "cooking_sessions_recipe_id_recipes_id_fk" FOREIGN KEY ("recipe_id") REFERENCES "public"."recipes"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "cooking_sessions" ADD CONSTRAINT "cooking_sessions_household_id_households_id_fk" FOREIGN KEY ("household_id") REFERENCES "public"."households"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "cooking_sessions" ADD CONSTRAINT "cooking_sessions_started_by_user_id_users_id_fk" FOREIGN KEY ("started_by_user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
CREATE INDEX "cooking_sessions_household_status_idx" ON "cooking_sessions" USING btree ("household_id","status");--> statement-breakpoint
|
||||
CREATE INDEX "cooking_sessions_recipe_idx" ON "cooking_sessions" USING btree ("recipe_id");--> statement-breakpoint
|
||||
CREATE INDEX "cooking_sessions_timeout_idx" ON "cooking_sessions" USING btree ("status","started_at");--> statement-breakpoint
|
||||
ALTER TABLE "inventory_transactions" ADD CONSTRAINT "inventory_transactions_cooking_session_id_cooking_sessions_id_fk" FOREIGN KEY ("cooking_session_id") REFERENCES "public"."cooking_sessions"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "recipe_cooks" ADD CONSTRAINT "recipe_cooks_cooking_session_id_cooking_sessions_id_fk" FOREIGN KEY ("cooking_session_id") REFERENCES "public"."cooking_sessions"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "meal_boxes" ADD CONSTRAINT "meal_boxes_cooking_session_id_cooking_sessions_id_fk" FOREIGN KEY ("cooking_session_id") REFERENCES "public"."cooking_sessions"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "meals" ADD CONSTRAINT "meals_cooking_session_id_cooking_sessions_id_fk" FOREIGN KEY ("cooking_session_id") REFERENCES "public"."cooking_sessions"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
|
||||
CREATE INDEX "recipe_cooks_session_idx" ON "recipe_cooks" USING btree ("cooking_session_id");
|
||||
@@ -0,0 +1,9514 @@
|
||||
{
|
||||
"id": "7ac1db62-1431-4f5e-94f6-d64200ec8419",
|
||||
"prevId": "a01343e7-7bd4-4b87-821e-446d7c9fc428",
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"tables": {
|
||||
"public.admin_totp": {
|
||||
"name": "admin_totp",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"secret_base32": {
|
||||
"name": "secret_base32",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"enabled_at": {
|
||||
"name": "enabled_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"last_used_step": {
|
||||
"name": "last_used_step",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"admin_totp_user_id_users_id_fk": {
|
||||
"name": "admin_totp_user_id_users_id_fk",
|
||||
"tableFrom": "admin_totp",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.email_verification_tokens": {
|
||||
"name": "email_verification_tokens",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"token_hash": {
|
||||
"name": "token_hash",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"expires_at": {
|
||||
"name": "expires_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"used_at": {
|
||||
"name": "used_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"email_verification_tokens_user_idx": {
|
||||
"name": "email_verification_tokens_user_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "user_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"email_verification_tokens_hash_unique": {
|
||||
"name": "email_verification_tokens_hash_unique",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "token_hash",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"email_verification_tokens_user_id_users_id_fk": {
|
||||
"name": "email_verification_tokens_user_id_users_id_fk",
|
||||
"tableFrom": "email_verification_tokens",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.password_reset_tokens": {
|
||||
"name": "password_reset_tokens",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"token_hash": {
|
||||
"name": "token_hash",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"expires_at": {
|
||||
"name": "expires_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"used_at": {
|
||||
"name": "used_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"password_reset_tokens_user_idx": {
|
||||
"name": "password_reset_tokens_user_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "user_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"password_reset_tokens_hash_unique": {
|
||||
"name": "password_reset_tokens_hash_unique",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "token_hash",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"password_reset_tokens_user_id_users_id_fk": {
|
||||
"name": "password_reset_tokens_user_id_users_id_fk",
|
||||
"tableFrom": "password_reset_tokens",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.refresh_tokens": {
|
||||
"name": "refresh_tokens",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"token_hash": {
|
||||
"name": "token_hash",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"family_id": {
|
||||
"name": "family_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"expires_at": {
|
||||
"name": "expires_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"revoked_at": {
|
||||
"name": "revoked_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"replaced_by_token_id": {
|
||||
"name": "replaced_by_token_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"user_agent": {
|
||||
"name": "user_agent",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"ip": {
|
||||
"name": "ip",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"refresh_tokens_user_idx": {
|
||||
"name": "refresh_tokens_user_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "user_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"refresh_tokens_hash_unique": {
|
||||
"name": "refresh_tokens_hash_unique",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "token_hash",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"refresh_tokens_user_id_users_id_fk": {
|
||||
"name": "refresh_tokens_user_id_users_id_fk",
|
||||
"tableFrom": "refresh_tokens",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.user_consents": {
|
||||
"name": "user_consents",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"kind": {
|
||||
"name": "kind",
|
||||
"type": "consent_kind",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"status": {
|
||||
"name": "status",
|
||||
"type": "consent_status",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"granted_at": {
|
||||
"name": "granted_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"revoked_at": {
|
||||
"name": "revoked_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"user_consents_user_id_users_id_fk": {
|
||||
"name": "user_consents_user_id_users_id_fk",
|
||||
"tableFrom": "user_consents",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {
|
||||
"user_consents_user_id_kind_pk": {
|
||||
"name": "user_consents_user_id_kind_pk",
|
||||
"columns": [
|
||||
"user_id",
|
||||
"kind"
|
||||
]
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.user_credentials": {
|
||||
"name": "user_credentials",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"password_hash": {
|
||||
"name": "password_hash",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"password_updated_at": {
|
||||
"name": "password_updated_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"user_credentials_user_id_users_id_fk": {
|
||||
"name": "user_credentials_user_id_users_id_fk",
|
||||
"tableFrom": "user_credentials",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.user_health_profiles": {
|
||||
"name": "user_health_profiles",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"birth_year": {
|
||||
"name": "birth_year",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"sex": {
|
||||
"name": "sex",
|
||||
"type": "sex",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"height_cm": {
|
||||
"name": "height_cm",
|
||||
"type": "double precision",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"weight_kg": {
|
||||
"name": "weight_kg",
|
||||
"type": "double precision",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"target_weight_kg": {
|
||||
"name": "target_weight_kg",
|
||||
"type": "double precision",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"activity_level": {
|
||||
"name": "activity_level",
|
||||
"type": "activity_level",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'moderate'"
|
||||
},
|
||||
"training_sessions_per_week": {
|
||||
"name": "training_sessions_per_week",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"training_types": {
|
||||
"name": "training_types",
|
||||
"type": "text[]",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'{}'"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"user_health_profiles_user_id_users_id_fk": {
|
||||
"name": "user_health_profiles_user_id_users_id_fk",
|
||||
"tableFrom": "user_health_profiles",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.user_preferences": {
|
||||
"name": "user_preferences",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"primary_goal": {
|
||||
"name": "primary_goal",
|
||||
"type": "goal_type",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"goals": {
|
||||
"name": "goals",
|
||||
"type": "goal_type[]",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'{}'"
|
||||
},
|
||||
"diet_pattern": {
|
||||
"name": "diet_pattern",
|
||||
"type": "diet_pattern",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'omnivore'"
|
||||
},
|
||||
"religious_rule": {
|
||||
"name": "religious_rule",
|
||||
"type": "religious_rule",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'none'"
|
||||
},
|
||||
"allergens": {
|
||||
"name": "allergens",
|
||||
"type": "allergen[]",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'{}'"
|
||||
},
|
||||
"intolerances": {
|
||||
"name": "intolerances",
|
||||
"type": "text[]",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'{}'"
|
||||
},
|
||||
"avoid_ingredient_ids": {
|
||||
"name": "avoid_ingredient_ids",
|
||||
"type": "text[]",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'{}'"
|
||||
},
|
||||
"favorite_cuisines": {
|
||||
"name": "favorite_cuisines",
|
||||
"type": "cuisine[]",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'{}'"
|
||||
},
|
||||
"disliked_dishes": {
|
||||
"name": "disliked_dishes",
|
||||
"type": "text[]",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'{}'"
|
||||
},
|
||||
"spice_level_max": {
|
||||
"name": "spice_level_max",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 3
|
||||
},
|
||||
"weekly_budget_minor": {
|
||||
"name": "weekly_budget_minor",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"max_cooking_minutes_weekday": {
|
||||
"name": "max_cooking_minutes_weekday",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"equipment": {
|
||||
"name": "equipment",
|
||||
"type": "text[]",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'{}'"
|
||||
},
|
||||
"default_portions": {
|
||||
"name": "default_portions",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 2
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"user_preferences_user_id_users_id_fk": {
|
||||
"name": "user_preferences_user_id_users_id_fk",
|
||||
"tableFrom": "user_preferences",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.users": {
|
||||
"name": "users",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"email_verified_at": {
|
||||
"name": "email_verified_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"display_name": {
|
||||
"name": "display_name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"role": {
|
||||
"name": "role",
|
||||
"type": "user_role",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'user'"
|
||||
},
|
||||
"locale": {
|
||||
"name": "locale",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'sv-SE'"
|
||||
},
|
||||
"precision_mode": {
|
||||
"name": "precision_mode",
|
||||
"type": "precision_mode",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'simple'"
|
||||
},
|
||||
"onboarding_completed": {
|
||||
"name": "onboarding_completed",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": false
|
||||
},
|
||||
"onboarding_step": {
|
||||
"name": "onboarding_step",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'a'"
|
||||
},
|
||||
"deleted_at": {
|
||||
"name": "deleted_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"users_email_unique": {
|
||||
"name": "users_email_unique",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "email",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.user_locale_preferences": {
|
||||
"name": "user_locale_preferences",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"language_tag": {
|
||||
"name": "language_tag",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'sv-SE'"
|
||||
},
|
||||
"region_code": {
|
||||
"name": "region_code",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'SE'"
|
||||
},
|
||||
"time_zone": {
|
||||
"name": "time_zone",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'Europe/Stockholm'"
|
||||
},
|
||||
"measurement_system": {
|
||||
"name": "measurement_system",
|
||||
"type": "measurement_system",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'METRIC'"
|
||||
},
|
||||
"temperature_unit": {
|
||||
"name": "temperature_unit",
|
||||
"type": "temperature_unit",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'CELSIUS'"
|
||||
},
|
||||
"currency_code": {
|
||||
"name": "currency_code",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'SEK'"
|
||||
},
|
||||
"first_day_of_week": {
|
||||
"name": "first_day_of_week",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 1
|
||||
},
|
||||
"use_24_hour_time": {
|
||||
"name": "use_24_hour_time",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": true
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"user_locale_preferences_user_id_users_id_fk": {
|
||||
"name": "user_locale_preferences_user_id_users_id_fk",
|
||||
"tableFrom": "user_locale_preferences",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.household_members": {
|
||||
"name": "household_members",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"household_id": {
|
||||
"name": "household_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"role": {
|
||||
"name": "role",
|
||||
"type": "household_role",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'member'"
|
||||
},
|
||||
"portion_factor": {
|
||||
"name": "portion_factor",
|
||||
"type": "double precision",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 1
|
||||
},
|
||||
"joined_at": {
|
||||
"name": "joined_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"household_members_user_idx": {
|
||||
"name": "household_members_user_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "user_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"household_members_household_id_households_id_fk": {
|
||||
"name": "household_members_household_id_households_id_fk",
|
||||
"tableFrom": "household_members",
|
||||
"tableTo": "households",
|
||||
"columnsFrom": [
|
||||
"household_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"household_members_user_id_users_id_fk": {
|
||||
"name": "household_members_user_id_users_id_fk",
|
||||
"tableFrom": "household_members",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {
|
||||
"household_members_household_id_user_id_pk": {
|
||||
"name": "household_members_household_id_user_id_pk",
|
||||
"columns": [
|
||||
"household_id",
|
||||
"user_id"
|
||||
]
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.households": {
|
||||
"name": "households",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"invite_code": {
|
||||
"name": "invite_code",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"weekly_budget_minor": {
|
||||
"name": "weekly_budget_minor",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"first_scan_completed_at": {
|
||||
"name": "first_scan_completed_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"fifth_item_confirmed_at": {
|
||||
"name": "fifth_item_confirmed_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"first_recipe_recommendation_viewed_at": {
|
||||
"name": "first_recipe_recommendation_viewed_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"first_recipe_saved_or_started_at": {
|
||||
"name": "first_recipe_saved_or_started_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"activated_at": {
|
||||
"name": "activated_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"first_cooking_session_completed_at": {
|
||||
"name": "first_cooking_session_completed_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"inventory_updated_after_cooking_at": {
|
||||
"name": "inventory_updated_after_cooking_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"value_confirmed_at": {
|
||||
"name": "value_confirmed_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"size": {
|
||||
"name": "size",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"currency_code": {
|
||||
"name": "currency_code",
|
||||
"type": "varchar(3)",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'SEK'"
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"households_invite_code_unique": {
|
||||
"name": "households_invite_code_unique",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "invite_code",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.storage_locations": {
|
||||
"name": "storage_locations",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"household_id": {
|
||||
"name": "household_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"type": {
|
||||
"name": "type",
|
||||
"type": "storage_location_type",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"sublocations": {
|
||||
"name": "sublocations",
|
||||
"type": "text[]",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'{}'"
|
||||
},
|
||||
"sort_order": {
|
||||
"name": "sort_order",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 0
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"storage_locations_household_idx": {
|
||||
"name": "storage_locations_household_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "household_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"storage_locations_household_id_households_id_fk": {
|
||||
"name": "storage_locations_household_id_households_id_fk",
|
||||
"tableFrom": "storage_locations",
|
||||
"tableTo": "households",
|
||||
"columnsFrom": [
|
||||
"household_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.canonical_ingredients": {
|
||||
"name": "canonical_ingredients",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"name_sv": {
|
||||
"name": "name_sv",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"name_en": {
|
||||
"name": "name_en",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"aliases": {
|
||||
"name": "aliases",
|
||||
"type": "text[]",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'{}'"
|
||||
},
|
||||
"category": {
|
||||
"name": "category",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"default_unit": {
|
||||
"name": "default_unit",
|
||||
"type": "unit",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"density_g_per_ml": {
|
||||
"name": "density_g_per_ml",
|
||||
"type": "double precision",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"grams_per_piece": {
|
||||
"name": "grams_per_piece",
|
||||
"type": "double precision",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"allergens": {
|
||||
"name": "allergens",
|
||||
"type": "allergen[]",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'{}'"
|
||||
},
|
||||
"is_vegan": {
|
||||
"name": "is_vegan",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": false
|
||||
},
|
||||
"is_vegetarian": {
|
||||
"name": "is_vegetarian",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": false
|
||||
},
|
||||
"contains_gluten": {
|
||||
"name": "contains_gluten",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": false
|
||||
},
|
||||
"contains_lactose": {
|
||||
"name": "contains_lactose",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": false
|
||||
},
|
||||
"is_pork": {
|
||||
"name": "is_pork",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": false
|
||||
},
|
||||
"is_beef": {
|
||||
"name": "is_beef",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": false
|
||||
},
|
||||
"is_alcohol": {
|
||||
"name": "is_alcohol",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": false
|
||||
},
|
||||
"nutrition_per_100": {
|
||||
"name": "nutrition_per_100",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"nutrition_provenance": {
|
||||
"name": "nutrition_provenance",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"peak_seasons": {
|
||||
"name": "peak_seasons",
|
||||
"type": "text[]",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'{}'"
|
||||
},
|
||||
"shelf_life_guidance": {
|
||||
"name": "shelf_life_guidance",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"default_price_minor_per_kg": {
|
||||
"name": "default_price_minor_per_kg",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"canonical_ingredients_category_idx": {
|
||||
"name": "canonical_ingredients_category_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "category",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.substitutions": {
|
||||
"name": "substitutions",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"from_ingredient_id": {
|
||||
"name": "from_ingredient_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"to_ingredient_id": {
|
||||
"name": "to_ingredient_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"ratio": {
|
||||
"name": "ratio",
|
||||
"type": "double precision",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 1
|
||||
},
|
||||
"instructions_sv": {
|
||||
"name": "instructions_sv",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"best_for": {
|
||||
"name": "best_for",
|
||||
"type": "text[]",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'{}'"
|
||||
},
|
||||
"not_recommended_for": {
|
||||
"name": "not_recommended_for",
|
||||
"type": "text[]",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'{}'"
|
||||
},
|
||||
"flavor_impact_sv": {
|
||||
"name": "flavor_impact_sv",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"texture_impact_sv": {
|
||||
"name": "texture_impact_sv",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"priority": {
|
||||
"name": "priority",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"substitutions_from_idx": {
|
||||
"name": "substitutions_from_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "from_ingredient_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"substitutions_pair_unique": {
|
||||
"name": "substitutions_pair_unique",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "from_ingredient_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "to_ingredient_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"substitutions_from_ingredient_id_canonical_ingredients_id_fk": {
|
||||
"name": "substitutions_from_ingredient_id_canonical_ingredients_id_fk",
|
||||
"tableFrom": "substitutions",
|
||||
"tableTo": "canonical_ingredients",
|
||||
"columnsFrom": [
|
||||
"from_ingredient_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"substitutions_to_ingredient_id_canonical_ingredients_id_fk": {
|
||||
"name": "substitutions_to_ingredient_id_canonical_ingredients_id_fk",
|
||||
"tableFrom": "substitutions",
|
||||
"tableTo": "canonical_ingredients",
|
||||
"columnsFrom": [
|
||||
"to_ingredient_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.products": {
|
||||
"name": "products",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"gtin": {
|
||||
"name": "gtin",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"brand": {
|
||||
"name": "brand",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"canonical_ingredient_id": {
|
||||
"name": "canonical_ingredient_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"package_size_value": {
|
||||
"name": "package_size_value",
|
||||
"type": "double precision",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"package_size_unit": {
|
||||
"name": "package_size_unit",
|
||||
"type": "unit",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"ingredients_text": {
|
||||
"name": "ingredients_text",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"allergens": {
|
||||
"name": "allergens",
|
||||
"type": "allergen[]",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'{}'"
|
||||
},
|
||||
"may_contain_allergens": {
|
||||
"name": "may_contain_allergens",
|
||||
"type": "allergen[]",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'{}'"
|
||||
},
|
||||
"nutrition": {
|
||||
"name": "nutrition",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"image_urls": {
|
||||
"name": "image_urls",
|
||||
"type": "text[]",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'{}'"
|
||||
},
|
||||
"language": {
|
||||
"name": "language",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'sv'"
|
||||
},
|
||||
"market": {
|
||||
"name": "market",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'SE'"
|
||||
},
|
||||
"data_source": {
|
||||
"name": "data_source",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"verification_status": {
|
||||
"name": "verification_status",
|
||||
"type": "verification_status",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'unverified'"
|
||||
},
|
||||
"version": {
|
||||
"name": "version",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 1
|
||||
},
|
||||
"valid_from": {
|
||||
"name": "valid_from",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"valid_to": {
|
||||
"name": "valid_to",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"created_by_user_id": {
|
||||
"name": "created_by_user_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"products_gtin_version_unique": {
|
||||
"name": "products_gtin_version_unique",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "gtin",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "version",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"where": "\"products\".\"gtin\" IS NOT NULL",
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"products_gtin_current_idx": {
|
||||
"name": "products_gtin_current_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "gtin",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"where": "\"products\".\"valid_to\" IS NULL",
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"products_canonical_idx": {
|
||||
"name": "products_canonical_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "canonical_ingredient_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"products_canonical_ingredient_id_canonical_ingredients_id_fk": {
|
||||
"name": "products_canonical_ingredient_id_canonical_ingredients_id_fk",
|
||||
"tableFrom": "products",
|
||||
"tableTo": "canonical_ingredients",
|
||||
"columnsFrom": [
|
||||
"canonical_ingredient_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.inventory_conflicts": {
|
||||
"name": "inventory_conflicts",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"household_id": {
|
||||
"name": "household_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"inventory_item_id": {
|
||||
"name": "inventory_item_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"scan_job_id": {
|
||||
"name": "scan_job_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"conflict_type": {
|
||||
"name": "conflict_type",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"source_a": {
|
||||
"name": "source_a",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"source_a_priority": {
|
||||
"name": "source_a_priority",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 0
|
||||
},
|
||||
"source_b": {
|
||||
"name": "source_b",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"source_b_priority": {
|
||||
"name": "source_b_priority",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 0
|
||||
},
|
||||
"payload_a": {
|
||||
"name": "payload_a",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"payload_b": {
|
||||
"name": "payload_b",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"proposed_resolution": {
|
||||
"name": "proposed_resolution",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"status": {
|
||||
"name": "status",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'open'"
|
||||
},
|
||||
"resolved_by_user_id": {
|
||||
"name": "resolved_by_user_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"resolution": {
|
||||
"name": "resolution",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"model_version": {
|
||||
"name": "model_version",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"prompt_version": {
|
||||
"name": "prompt_version",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"resolved_at": {
|
||||
"name": "resolved_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"inventory_conflicts_household_idx": {
|
||||
"name": "inventory_conflicts_household_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "household_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"inventory_conflicts_item_idx": {
|
||||
"name": "inventory_conflicts_item_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "inventory_item_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"inventory_conflicts_status_idx": {
|
||||
"name": "inventory_conflicts_status_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "status",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"inventory_conflicts_household_id_households_id_fk": {
|
||||
"name": "inventory_conflicts_household_id_households_id_fk",
|
||||
"tableFrom": "inventory_conflicts",
|
||||
"tableTo": "households",
|
||||
"columnsFrom": [
|
||||
"household_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"inventory_conflicts_inventory_item_id_inventory_items_id_fk": {
|
||||
"name": "inventory_conflicts_inventory_item_id_inventory_items_id_fk",
|
||||
"tableFrom": "inventory_conflicts",
|
||||
"tableTo": "inventory_items",
|
||||
"columnsFrom": [
|
||||
"inventory_item_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"inventory_conflicts_resolved_by_user_id_users_id_fk": {
|
||||
"name": "inventory_conflicts_resolved_by_user_id_users_id_fk",
|
||||
"tableFrom": "inventory_conflicts",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"resolved_by_user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "set null",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.inventory_items": {
|
||||
"name": "inventory_items",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"household_id": {
|
||||
"name": "household_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"canonical_ingredient_id": {
|
||||
"name": "canonical_ingredient_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"product_id": {
|
||||
"name": "product_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"display_name": {
|
||||
"name": "display_name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"brand": {
|
||||
"name": "brand",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"quantity": {
|
||||
"name": "quantity",
|
||||
"type": "double precision",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 0
|
||||
},
|
||||
"unit": {
|
||||
"name": "unit",
|
||||
"type": "unit",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"storage_location_id": {
|
||||
"name": "storage_location_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"sublocation": {
|
||||
"name": "sublocation",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"purchased_at": {
|
||||
"name": "purchased_at",
|
||||
"type": "date",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"opened_at": {
|
||||
"name": "opened_at",
|
||||
"type": "date",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"best_before_date": {
|
||||
"name": "best_before_date",
|
||||
"type": "date",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"use_by_date": {
|
||||
"name": "use_by_date",
|
||||
"type": "date",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"date_kind": {
|
||||
"name": "date_kind",
|
||||
"type": "date_kind",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"frozen_at": {
|
||||
"name": "frozen_at",
|
||||
"type": "date",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"thawed_at": {
|
||||
"name": "thawed_at",
|
||||
"type": "date",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"price_minor": {
|
||||
"name": "price_minor",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"nutrition_per_100": {
|
||||
"name": "nutrition_per_100",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"source": {
|
||||
"name": "source",
|
||||
"type": "inventory_source",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"confidence": {
|
||||
"name": "confidence",
|
||||
"type": "double precision",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 1
|
||||
},
|
||||
"verified_by_user": {
|
||||
"name": "verified_by_user",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": false
|
||||
},
|
||||
"last_verified_at": {
|
||||
"name": "last_verified_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"expiry_status": {
|
||||
"name": "expiry_status",
|
||||
"type": "expiry_status",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'unknown'"
|
||||
},
|
||||
"trust_state": {
|
||||
"name": "trust_state",
|
||||
"type": "varchar(16)",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'unverified'"
|
||||
},
|
||||
"depleted_at": {
|
||||
"name": "depleted_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"model_version": {
|
||||
"name": "model_version",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"prompt_version": {
|
||||
"name": "prompt_version",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"inventory_items_household_idx": {
|
||||
"name": "inventory_items_household_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "household_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"inventory_items_household_bb_idx": {
|
||||
"name": "inventory_items_household_bb_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "household_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "best_before_date",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"inventory_items_location_idx": {
|
||||
"name": "inventory_items_location_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "storage_location_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"inventory_items_canonical_idx": {
|
||||
"name": "inventory_items_canonical_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "canonical_ingredient_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"inventory_items_household_id_households_id_fk": {
|
||||
"name": "inventory_items_household_id_households_id_fk",
|
||||
"tableFrom": "inventory_items",
|
||||
"tableTo": "households",
|
||||
"columnsFrom": [
|
||||
"household_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"inventory_items_canonical_ingredient_id_canonical_ingredients_id_fk": {
|
||||
"name": "inventory_items_canonical_ingredient_id_canonical_ingredients_id_fk",
|
||||
"tableFrom": "inventory_items",
|
||||
"tableTo": "canonical_ingredients",
|
||||
"columnsFrom": [
|
||||
"canonical_ingredient_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"inventory_items_product_id_products_id_fk": {
|
||||
"name": "inventory_items_product_id_products_id_fk",
|
||||
"tableFrom": "inventory_items",
|
||||
"tableTo": "products",
|
||||
"columnsFrom": [
|
||||
"product_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"inventory_items_storage_location_id_storage_locations_id_fk": {
|
||||
"name": "inventory_items_storage_location_id_storage_locations_id_fk",
|
||||
"tableFrom": "inventory_items",
|
||||
"tableTo": "storage_locations",
|
||||
"columnsFrom": [
|
||||
"storage_location_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.inventory_transactions": {
|
||||
"name": "inventory_transactions",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"household_id": {
|
||||
"name": "household_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"inventory_item_id": {
|
||||
"name": "inventory_item_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"type": {
|
||||
"name": "type",
|
||||
"type": "inventory_transaction_type",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"quantity_delta": {
|
||||
"name": "quantity_delta",
|
||||
"type": "double precision",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"unit": {
|
||||
"name": "unit",
|
||||
"type": "unit",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"ref_type": {
|
||||
"name": "ref_type",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"ref_id": {
|
||||
"name": "ref_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"cooking_session_id": {
|
||||
"name": "cooking_session_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"actor_user_id": {
|
||||
"name": "actor_user_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"note": {
|
||||
"name": "note",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"value_minor": {
|
||||
"name": "value_minor",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"inventory_tx_item_idx": {
|
||||
"name": "inventory_tx_item_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "inventory_item_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"inventory_tx_household_time_idx": {
|
||||
"name": "inventory_tx_household_time_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "household_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "created_at",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"inventory_tx_type_idx": {
|
||||
"name": "inventory_tx_type_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "type",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"inventory_transactions_household_id_households_id_fk": {
|
||||
"name": "inventory_transactions_household_id_households_id_fk",
|
||||
"tableFrom": "inventory_transactions",
|
||||
"tableTo": "households",
|
||||
"columnsFrom": [
|
||||
"household_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"inventory_transactions_inventory_item_id_inventory_items_id_fk": {
|
||||
"name": "inventory_transactions_inventory_item_id_inventory_items_id_fk",
|
||||
"tableFrom": "inventory_transactions",
|
||||
"tableTo": "inventory_items",
|
||||
"columnsFrom": [
|
||||
"inventory_item_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"inventory_transactions_cooking_session_id_cooking_sessions_id_fk": {
|
||||
"name": "inventory_transactions_cooking_session_id_cooking_sessions_id_fk",
|
||||
"tableFrom": "inventory_transactions",
|
||||
"tableTo": "cooking_sessions",
|
||||
"columnsFrom": [
|
||||
"cooking_session_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "set null",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"inventory_transactions_actor_user_id_users_id_fk": {
|
||||
"name": "inventory_transactions_actor_user_id_users_id_fk",
|
||||
"tableFrom": "inventory_transactions",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"actor_user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "set null",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.inventory_decay_profiles": {
|
||||
"name": "inventory_decay_profiles",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "varchar(64)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"description": {
|
||||
"name": "description",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"half_life_days": {
|
||||
"name": "half_life_days",
|
||||
"type": "double precision",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 7
|
||||
},
|
||||
"stale_after_days": {
|
||||
"name": "stale_after_days",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 30
|
||||
},
|
||||
"active": {
|
||||
"name": "active",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": true
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"inventory_decay_profiles_name_unique": {
|
||||
"name": "inventory_decay_profiles_name_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"name"
|
||||
]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.inventory_item_decay_profile": {
|
||||
"name": "inventory_item_decay_profile",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"inventory_item_id": {
|
||||
"name": "inventory_item_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"decay_profile_id": {
|
||||
"name": "decay_profile_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"inventory_item_decay_profile_inventory_item_id_inventory_items_id_fk": {
|
||||
"name": "inventory_item_decay_profile_inventory_item_id_inventory_items_id_fk",
|
||||
"tableFrom": "inventory_item_decay_profile",
|
||||
"tableTo": "inventory_items",
|
||||
"columnsFrom": [
|
||||
"inventory_item_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"inventory_item_decay_profile_decay_profile_id_inventory_decay_profiles_id_fk": {
|
||||
"name": "inventory_item_decay_profile_decay_profile_id_inventory_decay_profiles_id_fk",
|
||||
"tableFrom": "inventory_item_decay_profile",
|
||||
"tableTo": "inventory_decay_profiles",
|
||||
"columnsFrom": [
|
||||
"decay_profile_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "restrict",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.cooking_sessions": {
|
||||
"name": "cooking_sessions",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"recipe_id": {
|
||||
"name": "recipe_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"household_id": {
|
||||
"name": "household_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"started_by_user_id": {
|
||||
"name": "started_by_user_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"status": {
|
||||
"name": "status",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'planned'"
|
||||
},
|
||||
"planned_portions": {
|
||||
"name": "planned_portions",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"planned_meal_type": {
|
||||
"name": "planned_meal_type",
|
||||
"type": "meal_type",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'dinner'"
|
||||
},
|
||||
"actual_portions_eaten": {
|
||||
"name": "actual_portions_eaten",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"leftover_estimate_portions": {
|
||||
"name": "leftover_estimate_portions",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"planned_deductions": {
|
||||
"name": "planned_deductions",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"started_at": {
|
||||
"name": "started_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"completed_at": {
|
||||
"name": "completed_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"cancelled_at": {
|
||||
"name": "cancelled_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"cancel_reason": {
|
||||
"name": "cancel_reason",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"cooking_sessions_household_status_idx": {
|
||||
"name": "cooking_sessions_household_status_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "household_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "status",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"cooking_sessions_recipe_idx": {
|
||||
"name": "cooking_sessions_recipe_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "recipe_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"cooking_sessions_timeout_idx": {
|
||||
"name": "cooking_sessions_timeout_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "status",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "started_at",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"cooking_sessions_recipe_id_recipes_id_fk": {
|
||||
"name": "cooking_sessions_recipe_id_recipes_id_fk",
|
||||
"tableFrom": "cooking_sessions",
|
||||
"tableTo": "recipes",
|
||||
"columnsFrom": [
|
||||
"recipe_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"cooking_sessions_household_id_households_id_fk": {
|
||||
"name": "cooking_sessions_household_id_households_id_fk",
|
||||
"tableFrom": "cooking_sessions",
|
||||
"tableTo": "households",
|
||||
"columnsFrom": [
|
||||
"household_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"cooking_sessions_started_by_user_id_users_id_fk": {
|
||||
"name": "cooking_sessions_started_by_user_id_users_id_fk",
|
||||
"tableFrom": "cooking_sessions",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"started_by_user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.recipe_cooks": {
|
||||
"name": "recipe_cooks",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"recipe_id": {
|
||||
"name": "recipe_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"household_id": {
|
||||
"name": "household_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"cooking_session_id": {
|
||||
"name": "cooking_session_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"portions_cooked": {
|
||||
"name": "portions_cooked",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"cooked_at": {
|
||||
"name": "cooked_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"recipe_cooks_recipe_idx": {
|
||||
"name": "recipe_cooks_recipe_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "recipe_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"recipe_cooks_user_idx": {
|
||||
"name": "recipe_cooks_user_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "user_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"recipe_cooks_session_idx": {
|
||||
"name": "recipe_cooks_session_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "cooking_session_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"recipe_cooks_recipe_id_recipes_id_fk": {
|
||||
"name": "recipe_cooks_recipe_id_recipes_id_fk",
|
||||
"tableFrom": "recipe_cooks",
|
||||
"tableTo": "recipes",
|
||||
"columnsFrom": [
|
||||
"recipe_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"recipe_cooks_user_id_users_id_fk": {
|
||||
"name": "recipe_cooks_user_id_users_id_fk",
|
||||
"tableFrom": "recipe_cooks",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"recipe_cooks_cooking_session_id_cooking_sessions_id_fk": {
|
||||
"name": "recipe_cooks_cooking_session_id_cooking_sessions_id_fk",
|
||||
"tableFrom": "recipe_cooks",
|
||||
"tableTo": "cooking_sessions",
|
||||
"columnsFrom": [
|
||||
"cooking_session_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "set null",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.recipe_favorites": {
|
||||
"name": "recipe_favorites",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"recipe_id": {
|
||||
"name": "recipe_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"recipe_favorites_user_idx": {
|
||||
"name": "recipe_favorites_user_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "user_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"recipe_favorites_recipe_id_recipes_id_fk": {
|
||||
"name": "recipe_favorites_recipe_id_recipes_id_fk",
|
||||
"tableFrom": "recipe_favorites",
|
||||
"tableTo": "recipes",
|
||||
"columnsFrom": [
|
||||
"recipe_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"recipe_favorites_user_id_users_id_fk": {
|
||||
"name": "recipe_favorites_user_id_users_id_fk",
|
||||
"tableFrom": "recipe_favorites",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {
|
||||
"recipe_favorites_recipe_id_user_id_pk": {
|
||||
"name": "recipe_favorites_recipe_id_user_id_pk",
|
||||
"columns": [
|
||||
"recipe_id",
|
||||
"user_id"
|
||||
]
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.recipe_ingredients": {
|
||||
"name": "recipe_ingredients",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"recipe_id": {
|
||||
"name": "recipe_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"canonical_ingredient_id": {
|
||||
"name": "canonical_ingredient_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"display_name_sv": {
|
||||
"name": "display_name_sv",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"quantity": {
|
||||
"name": "quantity",
|
||||
"type": "double precision",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"unit": {
|
||||
"name": "unit",
|
||||
"type": "unit",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"note": {
|
||||
"name": "note",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"optional": {
|
||||
"name": "optional",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": false
|
||||
},
|
||||
"group_name": {
|
||||
"name": "group_name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"sort_order": {
|
||||
"name": "sort_order",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"recipe_ingredients_recipe_idx": {
|
||||
"name": "recipe_ingredients_recipe_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "recipe_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"recipe_ingredients_canonical_idx": {
|
||||
"name": "recipe_ingredients_canonical_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "canonical_ingredient_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"recipe_ingredients_recipe_id_recipes_id_fk": {
|
||||
"name": "recipe_ingredients_recipe_id_recipes_id_fk",
|
||||
"tableFrom": "recipe_ingredients",
|
||||
"tableTo": "recipes",
|
||||
"columnsFrom": [
|
||||
"recipe_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"recipe_ingredients_canonical_ingredient_id_canonical_ingredients_id_fk": {
|
||||
"name": "recipe_ingredients_canonical_ingredient_id_canonical_ingredients_id_fk",
|
||||
"tableFrom": "recipe_ingredients",
|
||||
"tableTo": "canonical_ingredients",
|
||||
"columnsFrom": [
|
||||
"canonical_ingredient_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.recipe_ratings": {
|
||||
"name": "recipe_ratings",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"recipe_id": {
|
||||
"name": "recipe_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"stars": {
|
||||
"name": "stars",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"feedback_tags": {
|
||||
"name": "feedback_tags",
|
||||
"type": "text[]",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'{}'"
|
||||
},
|
||||
"comment": {
|
||||
"name": "comment",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"recipe_ratings_user_recipe_unique": {
|
||||
"name": "recipe_ratings_user_recipe_unique",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "recipe_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "user_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"recipe_ratings_recipe_idx": {
|
||||
"name": "recipe_ratings_recipe_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "recipe_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"recipe_ratings_recipe_id_recipes_id_fk": {
|
||||
"name": "recipe_ratings_recipe_id_recipes_id_fk",
|
||||
"tableFrom": "recipe_ratings",
|
||||
"tableTo": "recipes",
|
||||
"columnsFrom": [
|
||||
"recipe_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"recipe_ratings_user_id_users_id_fk": {
|
||||
"name": "recipe_ratings_user_id_users_id_fk",
|
||||
"tableFrom": "recipe_ratings",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.recipe_similarities": {
|
||||
"name": "recipe_similarities",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"recipe_a_id": {
|
||||
"name": "recipe_a_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"recipe_b_id": {
|
||||
"name": "recipe_b_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"similarity_score": {
|
||||
"name": "similarity_score",
|
||||
"type": "double precision",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"classification": {
|
||||
"name": "classification",
|
||||
"type": "recipe_similarity_class",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"details": {
|
||||
"name": "details",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"recipe_similarities_pair_unique": {
|
||||
"name": "recipe_similarities_pair_unique",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "recipe_a_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "recipe_b_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"recipe_similarities_recipe_a_id_recipes_id_fk": {
|
||||
"name": "recipe_similarities_recipe_a_id_recipes_id_fk",
|
||||
"tableFrom": "recipe_similarities",
|
||||
"tableTo": "recipes",
|
||||
"columnsFrom": [
|
||||
"recipe_a_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"recipe_similarities_recipe_b_id_recipes_id_fk": {
|
||||
"name": "recipe_similarities_recipe_b_id_recipes_id_fk",
|
||||
"tableFrom": "recipe_similarities",
|
||||
"tableTo": "recipes",
|
||||
"columnsFrom": [
|
||||
"recipe_b_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.recipe_source_registry": {
|
||||
"name": "recipe_source_registry",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"source_name": {
|
||||
"name": "source_name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"license": {
|
||||
"name": "license",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"right_to_store": {
|
||||
"name": "right_to_store",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"right_to_modify": {
|
||||
"name": "right_to_modify",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"right_to_display": {
|
||||
"name": "right_to_display",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"attribution_required": {
|
||||
"name": "attribution_required",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": false
|
||||
},
|
||||
"attribution_text": {
|
||||
"name": "attribution_text",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"commercial_use": {
|
||||
"name": "commercial_use",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"valid_from": {
|
||||
"name": "valid_from",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"valid_to": {
|
||||
"name": "valid_to",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"notes": {
|
||||
"name": "notes",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.recipe_steps": {
|
||||
"name": "recipe_steps",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"recipe_id": {
|
||||
"name": "recipe_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"step_number": {
|
||||
"name": "step_number",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"instruction_sv": {
|
||||
"name": "instruction_sv",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"timer_seconds": {
|
||||
"name": "timer_seconds",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"temperature_c": {
|
||||
"name": "temperature_c",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"tip": {
|
||||
"name": "tip",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"recipe_steps_recipe_idx": {
|
||||
"name": "recipe_steps_recipe_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "recipe_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"recipe_steps_recipe_step_unique": {
|
||||
"name": "recipe_steps_recipe_step_unique",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "recipe_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "step_number",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"recipe_steps_recipe_id_recipes_id_fk": {
|
||||
"name": "recipe_steps_recipe_id_recipes_id_fk",
|
||||
"tableFrom": "recipe_steps",
|
||||
"tableTo": "recipes",
|
||||
"columnsFrom": [
|
||||
"recipe_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.recipes": {
|
||||
"name": "recipes",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"slug": {
|
||||
"name": "slug",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"title_sv": {
|
||||
"name": "title_sv",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"description_sv": {
|
||||
"name": "description_sv",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "''"
|
||||
},
|
||||
"country": {
|
||||
"name": "country",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"region": {
|
||||
"name": "region",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"cuisine": {
|
||||
"name": "cuisine",
|
||||
"type": "cuisine",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"meal_types": {
|
||||
"name": "meal_types",
|
||||
"type": "meal_type[]",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'{}'"
|
||||
},
|
||||
"tags": {
|
||||
"name": "tags",
|
||||
"type": "text[]",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'{}'"
|
||||
},
|
||||
"methods": {
|
||||
"name": "methods",
|
||||
"type": "text[]",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'{}'"
|
||||
},
|
||||
"equipment": {
|
||||
"name": "equipment",
|
||||
"type": "text[]",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'{}'"
|
||||
},
|
||||
"difficulty": {
|
||||
"name": "difficulty",
|
||||
"type": "recipe_difficulty",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'easy'"
|
||||
},
|
||||
"prep_time_minutes": {
|
||||
"name": "prep_time_minutes",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 0
|
||||
},
|
||||
"cook_time_minutes": {
|
||||
"name": "cook_time_minutes",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 0
|
||||
},
|
||||
"total_time_minutes": {
|
||||
"name": "total_time_minutes",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 0
|
||||
},
|
||||
"portions": {
|
||||
"name": "portions",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 4
|
||||
},
|
||||
"nutrition_per_portion": {
|
||||
"name": "nutrition_per_portion",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"allergens": {
|
||||
"name": "allergens",
|
||||
"type": "allergen[]",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'{}'"
|
||||
},
|
||||
"spice_level": {
|
||||
"name": "spice_level",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 0
|
||||
},
|
||||
"estimated_cost_minor_per_portion": {
|
||||
"name": "estimated_cost_minor_per_portion",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"storage_guidance_sv": {
|
||||
"name": "storage_guidance_sv",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"meal_prep_friendly": {
|
||||
"name": "meal_prep_friendly",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": false
|
||||
},
|
||||
"freezer_friendly": {
|
||||
"name": "freezer_friendly",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": false
|
||||
},
|
||||
"peak_seasons": {
|
||||
"name": "peak_seasons",
|
||||
"type": "text[]",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'{}'"
|
||||
},
|
||||
"holiday_tags": {
|
||||
"name": "holiday_tags",
|
||||
"type": "text[]",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'{}'"
|
||||
},
|
||||
"dna": {
|
||||
"name": "dna",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"variant_type": {
|
||||
"name": "variant_type",
|
||||
"type": "recipe_variant_type",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'standard'"
|
||||
},
|
||||
"variant_of_recipe_id": {
|
||||
"name": "variant_of_recipe_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"forked_from_recipe_id": {
|
||||
"name": "forked_from_recipe_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"status": {
|
||||
"name": "status",
|
||||
"type": "recipe_status",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'draft'"
|
||||
},
|
||||
"verification_status": {
|
||||
"name": "verification_status",
|
||||
"type": "recipe_verification_status",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'unverified'"
|
||||
},
|
||||
"source_type": {
|
||||
"name": "source_type",
|
||||
"type": "recipe_source_type",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"source_registry_id": {
|
||||
"name": "source_registry_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"creator_user_id": {
|
||||
"name": "creator_user_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"creator_display_name": {
|
||||
"name": "creator_display_name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"image_urls": {
|
||||
"name": "image_urls",
|
||||
"type": "text[]",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'{}'"
|
||||
},
|
||||
"version": {
|
||||
"name": "version",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 1
|
||||
},
|
||||
"rating_average": {
|
||||
"name": "rating_average",
|
||||
"type": "double precision",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"rating_count": {
|
||||
"name": "rating_count",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 0
|
||||
},
|
||||
"cook_count": {
|
||||
"name": "cook_count",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 0
|
||||
},
|
||||
"favorite_count": {
|
||||
"name": "favorite_count",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 0
|
||||
},
|
||||
"moderation_note": {
|
||||
"name": "moderation_note",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"recipes_slug_unique": {
|
||||
"name": "recipes_slug_unique",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "slug",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"recipes_status_idx": {
|
||||
"name": "recipes_status_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "status",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"recipes_cuisine_idx": {
|
||||
"name": "recipes_cuisine_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "cuisine",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"recipes_variant_of_idx": {
|
||||
"name": "recipes_variant_of_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "variant_of_recipe_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"recipes_creator_idx": {
|
||||
"name": "recipes_creator_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "creator_user_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"recipes_total_time_idx": {
|
||||
"name": "recipes_total_time_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "total_time_minutes",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"recipes_source_registry_id_recipe_source_registry_id_fk": {
|
||||
"name": "recipes_source_registry_id_recipe_source_registry_id_fk",
|
||||
"tableFrom": "recipes",
|
||||
"tableTo": "recipe_source_registry",
|
||||
"columnsFrom": [
|
||||
"source_registry_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"recipes_creator_user_id_users_id_fk": {
|
||||
"name": "recipes_creator_user_id_users_id_fk",
|
||||
"tableFrom": "recipes",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"creator_user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "set null",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.ingredient_translations": {
|
||||
"name": "ingredient_translations",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"ingredient_id": {
|
||||
"name": "ingredient_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"language_tag": {
|
||||
"name": "language_tag",
|
||||
"type": "varchar(35)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"aliases": {
|
||||
"name": "aliases",
|
||||
"type": "text[]",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'{}'"
|
||||
},
|
||||
"source": {
|
||||
"name": "source",
|
||||
"type": "translation_source",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'seed'"
|
||||
},
|
||||
"status": {
|
||||
"name": "status",
|
||||
"type": "translation_status",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'published'"
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"ingredient_translations_unique": {
|
||||
"name": "ingredient_translations_unique",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "ingredient_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "language_tag",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"ingredient_translations_lang_idx": {
|
||||
"name": "ingredient_translations_lang_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "language_tag",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"ingredient_translations_ingredient_id_canonical_ingredients_id_fk": {
|
||||
"name": "ingredient_translations_ingredient_id_canonical_ingredients_id_fk",
|
||||
"tableFrom": "ingredient_translations",
|
||||
"tableTo": "canonical_ingredients",
|
||||
"columnsFrom": [
|
||||
"ingredient_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.recipe_step_translations": {
|
||||
"name": "recipe_step_translations",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"recipe_id": {
|
||||
"name": "recipe_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"language_tag": {
|
||||
"name": "language_tag",
|
||||
"type": "varchar(35)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"step_number": {
|
||||
"name": "step_number",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"instruction": {
|
||||
"name": "instruction",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"tip": {
|
||||
"name": "tip",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"recipe_step_translations_recipe_id_recipes_id_fk": {
|
||||
"name": "recipe_step_translations_recipe_id_recipes_id_fk",
|
||||
"tableFrom": "recipe_step_translations",
|
||||
"tableTo": "recipes",
|
||||
"columnsFrom": [
|
||||
"recipe_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {
|
||||
"recipe_step_translations_recipe_id_language_tag_step_number_pk": {
|
||||
"name": "recipe_step_translations_recipe_id_language_tag_step_number_pk",
|
||||
"columns": [
|
||||
"recipe_id",
|
||||
"language_tag",
|
||||
"step_number"
|
||||
]
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.recipe_translations": {
|
||||
"name": "recipe_translations",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"recipe_id": {
|
||||
"name": "recipe_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"language_tag": {
|
||||
"name": "language_tag",
|
||||
"type": "varchar(35)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"title": {
|
||||
"name": "title",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"description": {
|
||||
"name": "description",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"storage_guidance": {
|
||||
"name": "storage_guidance",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"status": {
|
||||
"name": "status",
|
||||
"type": "translation_status",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'draft_ai'"
|
||||
},
|
||||
"source": {
|
||||
"name": "source",
|
||||
"type": "translation_source",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'ai'"
|
||||
},
|
||||
"verification": {
|
||||
"name": "verification",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"recipe_translations_unique": {
|
||||
"name": "recipe_translations_unique",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "recipe_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "language_tag",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"recipe_translations_lang_idx": {
|
||||
"name": "recipe_translations_lang_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "language_tag",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "status",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"recipe_translations_recipe_id_recipes_id_fk": {
|
||||
"name": "recipe_translations_recipe_id_recipes_id_fk",
|
||||
"tableFrom": "recipe_translations",
|
||||
"tableTo": "recipes",
|
||||
"columnsFrom": [
|
||||
"recipe_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.unit_translations": {
|
||||
"name": "unit_translations",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"unit_code": {
|
||||
"name": "unit_code",
|
||||
"type": "unit",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"language_tag": {
|
||||
"name": "language_tag",
|
||||
"type": "varchar(35)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"abbreviation": {
|
||||
"name": "abbreviation",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {
|
||||
"unit_translations_unit_code_language_tag_pk": {
|
||||
"name": "unit_translations_unit_code_language_tag_pk",
|
||||
"columns": [
|
||||
"unit_code",
|
||||
"language_tag"
|
||||
]
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.allergen_market_rules": {
|
||||
"name": "allergen_market_rules",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"region_code": {
|
||||
"name": "region_code",
|
||||
"type": "varchar(2)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"allergen": {
|
||||
"name": "allergen",
|
||||
"type": "allergen",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"must_highlight": {
|
||||
"name": "must_highlight",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": true
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {
|
||||
"allergen_market_rules_region_code_allergen_pk": {
|
||||
"name": "allergen_market_rules_region_code_allergen_pk",
|
||||
"columns": [
|
||||
"region_code",
|
||||
"allergen"
|
||||
]
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.nutrition_display_profiles": {
|
||||
"name": "nutrition_display_profiles",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"region_code": {
|
||||
"name": "region_code",
|
||||
"type": "varchar(2)",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"energy_display": {
|
||||
"name": "energy_display",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"salt_display": {
|
||||
"name": "salt_display",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"energy_label_key": {
|
||||
"name": "energy_label_key",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.meal_boxes": {
|
||||
"name": "meal_boxes",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"household_id": {
|
||||
"name": "household_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"recipe_id": {
|
||||
"name": "recipe_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"cooking_session_id": {
|
||||
"name": "cooking_session_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"title_sv": {
|
||||
"name": "title_sv",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"portions": {
|
||||
"name": "portions",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"portions_remaining": {
|
||||
"name": "portions_remaining",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"nutrition_per_portion": {
|
||||
"name": "nutrition_per_portion",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"cooked_at": {
|
||||
"name": "cooked_at",
|
||||
"type": "date",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"storage_location_id": {
|
||||
"name": "storage_location_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"frozen": {
|
||||
"name": "frozen",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": false
|
||||
},
|
||||
"recommended_use_by": {
|
||||
"name": "recommended_use_by",
|
||||
"type": "date",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"reserved_for_user_id": {
|
||||
"name": "reserved_for_user_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"status": {
|
||||
"name": "status",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'available'"
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"meal_boxes_household_idx": {
|
||||
"name": "meal_boxes_household_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "household_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"meal_boxes_use_by_idx": {
|
||||
"name": "meal_boxes_use_by_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "household_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "recommended_use_by",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"meal_boxes_household_id_households_id_fk": {
|
||||
"name": "meal_boxes_household_id_households_id_fk",
|
||||
"tableFrom": "meal_boxes",
|
||||
"tableTo": "households",
|
||||
"columnsFrom": [
|
||||
"household_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"meal_boxes_recipe_id_recipes_id_fk": {
|
||||
"name": "meal_boxes_recipe_id_recipes_id_fk",
|
||||
"tableFrom": "meal_boxes",
|
||||
"tableTo": "recipes",
|
||||
"columnsFrom": [
|
||||
"recipe_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "set null",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"meal_boxes_cooking_session_id_cooking_sessions_id_fk": {
|
||||
"name": "meal_boxes_cooking_session_id_cooking_sessions_id_fk",
|
||||
"tableFrom": "meal_boxes",
|
||||
"tableTo": "cooking_sessions",
|
||||
"columnsFrom": [
|
||||
"cooking_session_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "set null",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"meal_boxes_storage_location_id_storage_locations_id_fk": {
|
||||
"name": "meal_boxes_storage_location_id_storage_locations_id_fk",
|
||||
"tableFrom": "meal_boxes",
|
||||
"tableTo": "storage_locations",
|
||||
"columnsFrom": [
|
||||
"storage_location_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"meal_boxes_reserved_for_user_id_users_id_fk": {
|
||||
"name": "meal_boxes_reserved_for_user_id_users_id_fk",
|
||||
"tableFrom": "meal_boxes",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"reserved_for_user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "set null",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.meals": {
|
||||
"name": "meals",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"household_id": {
|
||||
"name": "household_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"date": {
|
||||
"name": "date",
|
||||
"type": "date",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"meal_type": {
|
||||
"name": "meal_type",
|
||||
"type": "meal_type",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"source": {
|
||||
"name": "source",
|
||||
"type": "meal_log_source",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"recipe_id": {
|
||||
"name": "recipe_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"cooking_session_id": {
|
||||
"name": "cooking_session_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"title_sv": {
|
||||
"name": "title_sv",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"portion_fraction": {
|
||||
"name": "portion_fraction",
|
||||
"type": "double precision",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 1
|
||||
},
|
||||
"nutrition": {
|
||||
"name": "nutrition",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"nutrition_is_estimate": {
|
||||
"name": "nutrition_is_estimate",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": false
|
||||
},
|
||||
"estimate_min_kcal": {
|
||||
"name": "estimate_min_kcal",
|
||||
"type": "double precision",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"estimate_max_kcal": {
|
||||
"name": "estimate_max_kcal",
|
||||
"type": "double precision",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"items": {
|
||||
"name": "items",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"photo_url": {
|
||||
"name": "photo_url",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"scan_job_id": {
|
||||
"name": "scan_job_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"logged_at": {
|
||||
"name": "logged_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"meals_user_date_idx": {
|
||||
"name": "meals_user_date_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "user_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "date",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"meals_household_idx": {
|
||||
"name": "meals_household_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "household_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"meals_user_id_users_id_fk": {
|
||||
"name": "meals_user_id_users_id_fk",
|
||||
"tableFrom": "meals",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"meals_household_id_households_id_fk": {
|
||||
"name": "meals_household_id_households_id_fk",
|
||||
"tableFrom": "meals",
|
||||
"tableTo": "households",
|
||||
"columnsFrom": [
|
||||
"household_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "set null",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"meals_recipe_id_recipes_id_fk": {
|
||||
"name": "meals_recipe_id_recipes_id_fk",
|
||||
"tableFrom": "meals",
|
||||
"tableTo": "recipes",
|
||||
"columnsFrom": [
|
||||
"recipe_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "set null",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"meals_cooking_session_id_cooking_sessions_id_fk": {
|
||||
"name": "meals_cooking_session_id_cooking_sessions_id_fk",
|
||||
"tableFrom": "meals",
|
||||
"tableTo": "cooking_sessions",
|
||||
"columnsFrom": [
|
||||
"cooking_session_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "set null",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.shopping_list_items": {
|
||||
"name": "shopping_list_items",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"shopping_list_id": {
|
||||
"name": "shopping_list_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"canonical_ingredient_id": {
|
||||
"name": "canonical_ingredient_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"display_name": {
|
||||
"name": "display_name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"quantity": {
|
||||
"name": "quantity",
|
||||
"type": "double precision",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 1
|
||||
},
|
||||
"unit": {
|
||||
"name": "unit",
|
||||
"type": "unit",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'COUNT'"
|
||||
},
|
||||
"store_section": {
|
||||
"name": "store_section",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'hygien_ovrigt'"
|
||||
},
|
||||
"suggested_package_size": {
|
||||
"name": "suggested_package_size",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"estimated_price_minor": {
|
||||
"name": "estimated_price_minor",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"checked": {
|
||||
"name": "checked",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": false
|
||||
},
|
||||
"added_by_user_id": {
|
||||
"name": "added_by_user_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"origin": {
|
||||
"name": "origin",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'manual'"
|
||||
},
|
||||
"sort_order": {
|
||||
"name": "sort_order",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"shopping_list_items_list_idx": {
|
||||
"name": "shopping_list_items_list_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "shopping_list_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"shopping_list_items_shopping_list_id_shopping_lists_id_fk": {
|
||||
"name": "shopping_list_items_shopping_list_id_shopping_lists_id_fk",
|
||||
"tableFrom": "shopping_list_items",
|
||||
"tableTo": "shopping_lists",
|
||||
"columnsFrom": [
|
||||
"shopping_list_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"shopping_list_items_canonical_ingredient_id_canonical_ingredients_id_fk": {
|
||||
"name": "shopping_list_items_canonical_ingredient_id_canonical_ingredients_id_fk",
|
||||
"tableFrom": "shopping_list_items",
|
||||
"tableTo": "canonical_ingredients",
|
||||
"columnsFrom": [
|
||||
"canonical_ingredient_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"shopping_list_items_added_by_user_id_users_id_fk": {
|
||||
"name": "shopping_list_items_added_by_user_id_users_id_fk",
|
||||
"tableFrom": "shopping_list_items",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"added_by_user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "set null",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.shopping_lists": {
|
||||
"name": "shopping_lists",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"household_id": {
|
||||
"name": "household_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'Inköpslista'"
|
||||
},
|
||||
"status": {
|
||||
"name": "status",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'active'"
|
||||
},
|
||||
"week_plan_id": {
|
||||
"name": "week_plan_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"shopping_lists_household_idx": {
|
||||
"name": "shopping_lists_household_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "household_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"shopping_lists_household_id_households_id_fk": {
|
||||
"name": "shopping_lists_household_id_households_id_fk",
|
||||
"tableFrom": "shopping_lists",
|
||||
"tableTo": "households",
|
||||
"columnsFrom": [
|
||||
"household_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"shopping_lists_week_plan_id_week_plans_id_fk": {
|
||||
"name": "shopping_lists_week_plan_id_week_plans_id_fk",
|
||||
"tableFrom": "shopping_lists",
|
||||
"tableTo": "week_plans",
|
||||
"columnsFrom": [
|
||||
"week_plan_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "set null",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.week_plan_entries": {
|
||||
"name": "week_plan_entries",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"week_plan_id": {
|
||||
"name": "week_plan_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"date": {
|
||||
"name": "date",
|
||||
"type": "date",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"meal_type": {
|
||||
"name": "meal_type",
|
||||
"type": "meal_type",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"recipe_id": {
|
||||
"name": "recipe_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"meal_box_id": {
|
||||
"name": "meal_box_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"title_sv": {
|
||||
"name": "title_sv",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"portions": {
|
||||
"name": "portions",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 2
|
||||
},
|
||||
"status": {
|
||||
"name": "status",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'planned'"
|
||||
},
|
||||
"reschedule_reason_sv": {
|
||||
"name": "reschedule_reason_sv",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"sort_order": {
|
||||
"name": "sort_order",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"week_plan_entries_plan_idx": {
|
||||
"name": "week_plan_entries_plan_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "week_plan_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"week_plan_entries_week_plan_id_week_plans_id_fk": {
|
||||
"name": "week_plan_entries_week_plan_id_week_plans_id_fk",
|
||||
"tableFrom": "week_plan_entries",
|
||||
"tableTo": "week_plans",
|
||||
"columnsFrom": [
|
||||
"week_plan_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"week_plan_entries_recipe_id_recipes_id_fk": {
|
||||
"name": "week_plan_entries_recipe_id_recipes_id_fk",
|
||||
"tableFrom": "week_plan_entries",
|
||||
"tableTo": "recipes",
|
||||
"columnsFrom": [
|
||||
"recipe_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "set null",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"week_plan_entries_meal_box_id_meal_boxes_id_fk": {
|
||||
"name": "week_plan_entries_meal_box_id_meal_boxes_id_fk",
|
||||
"tableFrom": "week_plan_entries",
|
||||
"tableTo": "meal_boxes",
|
||||
"columnsFrom": [
|
||||
"meal_box_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "set null",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.week_plans": {
|
||||
"name": "week_plans",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"household_id": {
|
||||
"name": "household_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"week_start_date": {
|
||||
"name": "week_start_date",
|
||||
"type": "date",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"status": {
|
||||
"name": "status",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'draft'"
|
||||
},
|
||||
"generated_by": {
|
||||
"name": "generated_by",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'user'"
|
||||
},
|
||||
"notes": {
|
||||
"name": "notes",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"week_plans_household_week_idx": {
|
||||
"name": "week_plans_household_week_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "household_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "week_start_date",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"week_plans_household_id_households_id_fk": {
|
||||
"name": "week_plans_household_id_households_id_fk",
|
||||
"tableFrom": "week_plans",
|
||||
"tableTo": "households",
|
||||
"columnsFrom": [
|
||||
"household_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.price_observations": {
|
||||
"name": "price_observations",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"canonical_ingredient_id": {
|
||||
"name": "canonical_ingredient_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"product_id": {
|
||||
"name": "product_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"store_name": {
|
||||
"name": "store_name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"price_minor": {
|
||||
"name": "price_minor",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"quantity": {
|
||||
"name": "quantity",
|
||||
"type": "double precision",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"unit": {
|
||||
"name": "unit",
|
||||
"type": "unit",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"price_per_kg_minor": {
|
||||
"name": "price_per_kg_minor",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"observed_at": {
|
||||
"name": "observed_at",
|
||||
"type": "date",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"source": {
|
||||
"name": "source",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'receipt'"
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"price_observations_ingredient_idx": {
|
||||
"name": "price_observations_ingredient_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "canonical_ingredient_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "observed_at",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"price_observations_canonical_ingredient_id_canonical_ingredients_id_fk": {
|
||||
"name": "price_observations_canonical_ingredient_id_canonical_ingredients_id_fk",
|
||||
"tableFrom": "price_observations",
|
||||
"tableTo": "canonical_ingredients",
|
||||
"columnsFrom": [
|
||||
"canonical_ingredient_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"price_observations_product_id_products_id_fk": {
|
||||
"name": "price_observations_product_id_products_id_fk",
|
||||
"tableFrom": "price_observations",
|
||||
"tableTo": "products",
|
||||
"columnsFrom": [
|
||||
"product_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.receipt_lines": {
|
||||
"name": "receipt_lines",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"receipt_id": {
|
||||
"name": "receipt_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"raw_text": {
|
||||
"name": "raw_text",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"normalized_name": {
|
||||
"name": "normalized_name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"canonical_ingredient_id": {
|
||||
"name": "canonical_ingredient_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"product_id": {
|
||||
"name": "product_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"quantity": {
|
||||
"name": "quantity",
|
||||
"type": "double precision",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"unit": {
|
||||
"name": "unit",
|
||||
"type": "unit",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"unit_price_minor": {
|
||||
"name": "unit_price_minor",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"total_price_minor": {
|
||||
"name": "total_price_minor",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"confidence": {
|
||||
"name": "confidence",
|
||||
"type": "double precision",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 0
|
||||
},
|
||||
"verified_by_user": {
|
||||
"name": "verified_by_user",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": false
|
||||
},
|
||||
"added_to_inventory": {
|
||||
"name": "added_to_inventory",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": false
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"receipt_lines_receipt_idx": {
|
||||
"name": "receipt_lines_receipt_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "receipt_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"receipt_lines_receipt_id_receipts_id_fk": {
|
||||
"name": "receipt_lines_receipt_id_receipts_id_fk",
|
||||
"tableFrom": "receipt_lines",
|
||||
"tableTo": "receipts",
|
||||
"columnsFrom": [
|
||||
"receipt_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"receipt_lines_canonical_ingredient_id_canonical_ingredients_id_fk": {
|
||||
"name": "receipt_lines_canonical_ingredient_id_canonical_ingredients_id_fk",
|
||||
"tableFrom": "receipt_lines",
|
||||
"tableTo": "canonical_ingredients",
|
||||
"columnsFrom": [
|
||||
"canonical_ingredient_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"receipt_lines_product_id_products_id_fk": {
|
||||
"name": "receipt_lines_product_id_products_id_fk",
|
||||
"tableFrom": "receipt_lines",
|
||||
"tableTo": "products",
|
||||
"columnsFrom": [
|
||||
"product_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.receipts": {
|
||||
"name": "receipts",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"household_id": {
|
||||
"name": "household_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"store_name": {
|
||||
"name": "store_name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"purchase_date": {
|
||||
"name": "purchase_date",
|
||||
"type": "date",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"total_minor": {
|
||||
"name": "total_minor",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"discount_minor": {
|
||||
"name": "discount_minor",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"image_url": {
|
||||
"name": "image_url",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"scan_job_id": {
|
||||
"name": "scan_job_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"status": {
|
||||
"name": "status",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'pending'"
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"receipts_household_idx": {
|
||||
"name": "receipts_household_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "household_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "purchase_date",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"receipts_household_id_households_id_fk": {
|
||||
"name": "receipts_household_id_households_id_fk",
|
||||
"tableFrom": "receipts",
|
||||
"tableTo": "households",
|
||||
"columnsFrom": [
|
||||
"household_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.ai_corrections": {
|
||||
"name": "ai_corrections",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"scan_job_id": {
|
||||
"name": "scan_job_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"task_type": {
|
||||
"name": "task_type",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"ai_output": {
|
||||
"name": "ai_output",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"user_correction": {
|
||||
"name": "user_correction",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"model_version": {
|
||||
"name": "model_version",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"prompt_version": {
|
||||
"name": "prompt_version",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"consent_snapshot": {
|
||||
"name": "consent_snapshot",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"exported_to_training": {
|
||||
"name": "exported_to_training",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"training_batch_id": {
|
||||
"name": "training_batch_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"ai_corrections_task_idx": {
|
||||
"name": "ai_corrections_task_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "task_type",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "created_at",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"ai_corrections_scan_job_id_scan_jobs_id_fk": {
|
||||
"name": "ai_corrections_scan_job_id_scan_jobs_id_fk",
|
||||
"tableFrom": "ai_corrections",
|
||||
"tableTo": "scan_jobs",
|
||||
"columnsFrom": [
|
||||
"scan_job_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "set null",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"ai_corrections_user_id_users_id_fk": {
|
||||
"name": "ai_corrections_user_id_users_id_fk",
|
||||
"tableFrom": "ai_corrections",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.scan_jobs": {
|
||||
"name": "scan_jobs",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"household_id": {
|
||||
"name": "household_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"scan_type": {
|
||||
"name": "scan_type",
|
||||
"type": "scan_type",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"job_type": {
|
||||
"name": "job_type",
|
||||
"type": "job_type",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"status": {
|
||||
"name": "status",
|
||||
"type": "job_status",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'queued'"
|
||||
},
|
||||
"s3_keys": {
|
||||
"name": "s3_keys",
|
||||
"type": "text[]",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'{}'"
|
||||
},
|
||||
"context": {
|
||||
"name": "context",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"result": {
|
||||
"name": "result",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"error": {
|
||||
"name": "error",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"model_version": {
|
||||
"name": "model_version",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"prompt_version": {
|
||||
"name": "prompt_version",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"latency_ms": {
|
||||
"name": "latency_ms",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"cost_usd": {
|
||||
"name": "cost_usd",
|
||||
"type": "double precision",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"attempts": {
|
||||
"name": "attempts",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 0
|
||||
},
|
||||
"completed_at": {
|
||||
"name": "completed_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"scan_jobs_user_idx": {
|
||||
"name": "scan_jobs_user_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "user_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "created_at",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"scan_jobs_status_idx": {
|
||||
"name": "scan_jobs_status_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "status",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"scan_jobs_user_id_users_id_fk": {
|
||||
"name": "scan_jobs_user_id_users_id_fk",
|
||||
"tableFrom": "scan_jobs",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"scan_jobs_household_id_households_id_fk": {
|
||||
"name": "scan_jobs_household_id_households_id_fk",
|
||||
"tableFrom": "scan_jobs",
|
||||
"tableTo": "households",
|
||||
"columnsFrom": [
|
||||
"household_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "set null",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.food_memories": {
|
||||
"name": "food_memories",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"household_id": {
|
||||
"name": "household_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"recipe_id": {
|
||||
"name": "recipe_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"title_sv": {
|
||||
"name": "title_sv",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"summary_sv": {
|
||||
"name": "summary_sv",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"holiday_tag": {
|
||||
"name": "holiday_tag",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"photo_url": {
|
||||
"name": "photo_url",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"stars": {
|
||||
"name": "stars",
|
||||
"type": "double precision",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"occurred_at": {
|
||||
"name": "occurred_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"food_memories_household_idx": {
|
||||
"name": "food_memories_household_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "household_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "occurred_at",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"food_memories_holiday_idx": {
|
||||
"name": "food_memories_holiday_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "holiday_tag",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"food_memories_user_id_users_id_fk": {
|
||||
"name": "food_memories_user_id_users_id_fk",
|
||||
"tableFrom": "food_memories",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"food_memories_household_id_households_id_fk": {
|
||||
"name": "food_memories_household_id_households_id_fk",
|
||||
"tableFrom": "food_memories",
|
||||
"tableTo": "households",
|
||||
"columnsFrom": [
|
||||
"household_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"food_memories_recipe_id_recipes_id_fk": {
|
||||
"name": "food_memories_recipe_id_recipes_id_fk",
|
||||
"tableFrom": "food_memories",
|
||||
"tableTo": "recipes",
|
||||
"columnsFrom": [
|
||||
"recipe_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "set null",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.memory_items": {
|
||||
"name": "memory_items",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"household_id": {
|
||||
"name": "household_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"kind": {
|
||||
"name": "kind",
|
||||
"type": "memory_kind",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"key": {
|
||||
"name": "key",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"summary_sv": {
|
||||
"name": "summary_sv",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"value": {
|
||||
"name": "value",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"origin": {
|
||||
"name": "origin",
|
||||
"type": "signal_origin",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"confidence": {
|
||||
"name": "confidence",
|
||||
"type": "double precision",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 0.5
|
||||
},
|
||||
"verified_by_user": {
|
||||
"name": "verified_by_user",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": false
|
||||
},
|
||||
"paused": {
|
||||
"name": "paused",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": false
|
||||
},
|
||||
"last_used_at": {
|
||||
"name": "last_used_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"expires_at": {
|
||||
"name": "expires_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"memory_items_user_idx": {
|
||||
"name": "memory_items_user_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "user_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "kind",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"memory_items_household_idx": {
|
||||
"name": "memory_items_household_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "household_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "kind",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"memory_items_key_idx": {
|
||||
"name": "memory_items_key_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "key",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"memory_items_user_id_users_id_fk": {
|
||||
"name": "memory_items_user_id_users_id_fk",
|
||||
"tableFrom": "memory_items",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"memory_items_household_id_households_id_fk": {
|
||||
"name": "memory_items_household_id_households_id_fk",
|
||||
"tableFrom": "memory_items",
|
||||
"tableTo": "households",
|
||||
"columnsFrom": [
|
||||
"household_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.taste_signals": {
|
||||
"name": "taste_signals",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"axis": {
|
||||
"name": "axis",
|
||||
"type": "taste_axis",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"direction": {
|
||||
"name": "direction",
|
||||
"type": "double precision",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"strength": {
|
||||
"name": "strength",
|
||||
"type": "double precision",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 0.5
|
||||
},
|
||||
"origin": {
|
||||
"name": "origin",
|
||||
"type": "signal_origin",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"ref_recipe_id": {
|
||||
"name": "ref_recipe_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"taste_signals_user_idx": {
|
||||
"name": "taste_signals_user_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "user_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "axis",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"taste_signals_user_id_users_id_fk": {
|
||||
"name": "taste_signals_user_id_users_id_fk",
|
||||
"tableFrom": "taste_signals",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"taste_signals_ref_recipe_id_recipes_id_fk": {
|
||||
"name": "taste_signals_ref_recipe_id_recipes_id_fk",
|
||||
"tableFrom": "taste_signals",
|
||||
"tableTo": "recipes",
|
||||
"columnsFrom": [
|
||||
"ref_recipe_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "set null",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.season_events": {
|
||||
"name": "season_events",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"slug": {
|
||||
"name": "slug",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"name_sv": {
|
||||
"name": "name_sv",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"market": {
|
||||
"name": "market",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'SE'"
|
||||
},
|
||||
"date_rule": {
|
||||
"name": "date_rule",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"lead_days": {
|
||||
"name": "lead_days",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 7
|
||||
},
|
||||
"food_tags": {
|
||||
"name": "food_tags",
|
||||
"type": "text[]",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'{}'"
|
||||
},
|
||||
"recipe_slugs": {
|
||||
"name": "recipe_slugs",
|
||||
"type": "text[]",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'{}'"
|
||||
},
|
||||
"priority": {
|
||||
"name": "priority",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 0
|
||||
},
|
||||
"active": {
|
||||
"name": "active",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": true
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"season_events_slug_market_unique": {
|
||||
"name": "season_events_slug_market_unique",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "slug",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "market",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.ai_usage_counters": {
|
||||
"name": "ai_usage_counters",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"month": {
|
||||
"name": "month",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"ai_scans": {
|
||||
"name": "ai_scans",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 0
|
||||
},
|
||||
"ai_tokens_in": {
|
||||
"name": "ai_tokens_in",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 0
|
||||
},
|
||||
"ai_tokens_out": {
|
||||
"name": "ai_tokens_out",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 0
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"ai_usage_user_month_unique": {
|
||||
"name": "ai_usage_user_month_unique",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "user_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "month",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"ai_usage_counters_user_id_users_id_fk": {
|
||||
"name": "ai_usage_counters_user_id_users_id_fk",
|
||||
"tableFrom": "ai_usage_counters",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.store_notifications": {
|
||||
"name": "store_notifications",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"provider": {
|
||||
"name": "provider",
|
||||
"type": "subscription_provider",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"notification_type": {
|
||||
"name": "notification_type",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"raw_payload": {
|
||||
"name": "raw_payload",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"signature_verified": {
|
||||
"name": "signature_verified",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": false
|
||||
},
|
||||
"processed": {
|
||||
"name": "processed",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": false
|
||||
},
|
||||
"processed_at": {
|
||||
"name": "processed_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"error": {
|
||||
"name": "error",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"store_notifications_pending_idx": {
|
||||
"name": "store_notifications_pending_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "processed",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "created_at",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.store_transactions": {
|
||||
"name": "store_transactions",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"provider": {
|
||||
"name": "provider",
|
||||
"type": "subscription_provider",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"transaction_id": {
|
||||
"name": "transaction_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"original_transaction_id": {
|
||||
"name": "original_transaction_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"product_id": {
|
||||
"name": "product_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"raw_payload": {
|
||||
"name": "raw_payload",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"processed_at": {
|
||||
"name": "processed_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"store_transactions_unique": {
|
||||
"name": "store_transactions_unique",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "provider",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "transaction_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"store_transactions_user_id_users_id_fk": {
|
||||
"name": "store_transactions_user_id_users_id_fk",
|
||||
"tableFrom": "store_transactions",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "set null",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.subscription_events": {
|
||||
"name": "subscription_events",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"subscription_id": {
|
||||
"name": "subscription_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"event_type": {
|
||||
"name": "event_type",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"payload": {
|
||||
"name": "payload",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"subscription_events_sub_idx": {
|
||||
"name": "subscription_events_sub_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "subscription_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "created_at",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"subscription_events_subscription_id_subscriptions_id_fk": {
|
||||
"name": "subscription_events_subscription_id_subscriptions_id_fk",
|
||||
"tableFrom": "subscription_events",
|
||||
"tableTo": "subscriptions",
|
||||
"columnsFrom": [
|
||||
"subscription_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"subscription_events_user_id_users_id_fk": {
|
||||
"name": "subscription_events_user_id_users_id_fk",
|
||||
"tableFrom": "subscription_events",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "set null",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.subscriptions": {
|
||||
"name": "subscriptions",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"household_id": {
|
||||
"name": "household_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"provider": {
|
||||
"name": "provider",
|
||||
"type": "subscription_provider",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"product_id": {
|
||||
"name": "product_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"plan": {
|
||||
"name": "plan",
|
||||
"type": "subscription_plan",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"original_transaction_id": {
|
||||
"name": "original_transaction_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"status": {
|
||||
"name": "status",
|
||||
"type": "subscription_status",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"purchased_at": {
|
||||
"name": "purchased_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"expires_at": {
|
||||
"name": "expires_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"grace_period_expires_at": {
|
||||
"name": "grace_period_expires_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"canceled_at": {
|
||||
"name": "canceled_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"last_verified_at": {
|
||||
"name": "last_verified_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"subscriptions_user_idx": {
|
||||
"name": "subscriptions_user_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "user_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"subscriptions_original_tx_unique": {
|
||||
"name": "subscriptions_original_tx_unique",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "provider",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "original_transaction_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"where": "\"subscriptions\".\"original_transaction_id\" IS NOT NULL",
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"subscriptions_user_id_users_id_fk": {
|
||||
"name": "subscriptions_user_id_users_id_fk",
|
||||
"tableFrom": "subscriptions",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"subscriptions_household_id_households_id_fk": {
|
||||
"name": "subscriptions_household_id_households_id_fk",
|
||||
"tableFrom": "subscriptions",
|
||||
"tableTo": "households",
|
||||
"columnsFrom": [
|
||||
"household_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "set null",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.trials": {
|
||||
"name": "trials",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"started_at": {
|
||||
"name": "started_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"ends_at": {
|
||||
"name": "ends_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"trials_user_id_users_id_fk": {
|
||||
"name": "trials_user_id_users_id_fk",
|
||||
"tableFrom": "trials",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.analytics_daily_snapshots": {
|
||||
"name": "analytics_daily_snapshots",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"date": {
|
||||
"name": "date",
|
||||
"type": "varchar(10)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"household_id": {
|
||||
"name": "household_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"event_name": {
|
||||
"name": "event_name",
|
||||
"type": "varchar(64)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"count": {
|
||||
"name": "count",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 0
|
||||
},
|
||||
"unique_sessions": {
|
||||
"name": "unique_sessions",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 0
|
||||
},
|
||||
"properties_fingerprint": {
|
||||
"name": "properties_fingerprint",
|
||||
"type": "varchar(64)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"pa_daily_household_date_idx": {
|
||||
"name": "pa_daily_household_date_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "household_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "date",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"pa_daily_date_event_idx": {
|
||||
"name": "pa_daily_date_event_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "date",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "event_name",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"analytics_daily_snapshots_household_id_households_id_fk": {
|
||||
"name": "analytics_daily_snapshots_household_id_households_id_fk",
|
||||
"tableFrom": "analytics_daily_snapshots",
|
||||
"tableTo": "households",
|
||||
"columnsFrom": [
|
||||
"household_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.product_analytics_events": {
|
||||
"name": "product_analytics_events",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"occurred_at": {
|
||||
"name": "occurred_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"received_at": {
|
||||
"name": "received_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"event_name": {
|
||||
"name": "event_name",
|
||||
"type": "varchar(64)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"anonymous_id": {
|
||||
"name": "anonymous_id",
|
||||
"type": "varchar(64)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"session_id": {
|
||||
"name": "session_id",
|
||||
"type": "varchar(64)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"household_id": {
|
||||
"name": "household_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"app_version": {
|
||||
"name": "app_version",
|
||||
"type": "varchar(32)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"platform": {
|
||||
"name": "platform",
|
||||
"type": "varchar(16)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"locale": {
|
||||
"name": "locale",
|
||||
"type": "varchar(16)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"experiment_variant": {
|
||||
"name": "experiment_variant",
|
||||
"type": "varchar(128)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"properties": {
|
||||
"name": "properties",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'{}'::jsonb"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"pa_events_name_occurred_idx": {
|
||||
"name": "pa_events_name_occurred_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "event_name",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "occurred_at",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"pa_events_household_occurred_idx": {
|
||||
"name": "pa_events_household_occurred_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "household_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "occurred_at",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"pa_events_user_occurred_idx": {
|
||||
"name": "pa_events_user_occurred_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "user_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "occurred_at",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"pa_events_session_idx": {
|
||||
"name": "pa_events_session_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "session_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"pa_events_received_idx": {
|
||||
"name": "pa_events_received_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "received_at",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"product_analytics_events_user_id_users_id_fk": {
|
||||
"name": "product_analytics_events_user_id_users_id_fk",
|
||||
"tableFrom": "product_analytics_events",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "set null",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"product_analytics_events_household_id_households_id_fk": {
|
||||
"name": "product_analytics_events_household_id_households_id_fk",
|
||||
"tableFrom": "product_analytics_events",
|
||||
"tableTo": "households",
|
||||
"columnsFrom": [
|
||||
"household_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "set null",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.ai_eval_cases": {
|
||||
"name": "ai_eval_cases",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"task_type": {
|
||||
"name": "task_type",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"name_sv": {
|
||||
"name": "name_sv",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"input_ref": {
|
||||
"name": "input_ref",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"expected_output": {
|
||||
"name": "expected_output",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"category": {
|
||||
"name": "category",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'standard'"
|
||||
},
|
||||
"active": {
|
||||
"name": "active",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": true
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.ai_eval_runs": {
|
||||
"name": "ai_eval_runs",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"task_type": {
|
||||
"name": "task_type",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"model_version": {
|
||||
"name": "model_version",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"prompt_version": {
|
||||
"name": "prompt_version",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"metrics": {
|
||||
"name": "metrics",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"passed": {
|
||||
"name": "passed",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"notes": {
|
||||
"name": "notes",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"ai_eval_runs_task_idx": {
|
||||
"name": "ai_eval_runs_task_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "task_type",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "created_at",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.audit_logs": {
|
||||
"name": "audit_logs",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"actor_user_id": {
|
||||
"name": "actor_user_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"actor_type": {
|
||||
"name": "actor_type",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'user'"
|
||||
},
|
||||
"action": {
|
||||
"name": "action",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"target_type": {
|
||||
"name": "target_type",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"target_id": {
|
||||
"name": "target_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"metadata": {
|
||||
"name": "metadata",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"ip": {
|
||||
"name": "ip",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"correlation_id": {
|
||||
"name": "correlation_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"audit_logs_actor_idx": {
|
||||
"name": "audit_logs_actor_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "actor_user_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "created_at",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"audit_logs_action_idx": {
|
||||
"name": "audit_logs_action_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "action",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "created_at",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.creator_follows": {
|
||||
"name": "creator_follows",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"follower_user_id": {
|
||||
"name": "follower_user_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"creator_user_id": {
|
||||
"name": "creator_user_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"creator_follows_follower_user_id_users_id_fk": {
|
||||
"name": "creator_follows_follower_user_id_users_id_fk",
|
||||
"tableFrom": "creator_follows",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"follower_user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"creator_follows_creator_user_id_users_id_fk": {
|
||||
"name": "creator_follows_creator_user_id_users_id_fk",
|
||||
"tableFrom": "creator_follows",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"creator_user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {
|
||||
"creator_follows_follower_user_id_creator_user_id_pk": {
|
||||
"name": "creator_follows_follower_user_id_creator_user_id_pk",
|
||||
"columns": [
|
||||
"follower_user_id",
|
||||
"creator_user_id"
|
||||
]
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.creator_stats": {
|
||||
"name": "creator_stats",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"visibility": {
|
||||
"name": "visibility",
|
||||
"type": "profile_visibility",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'private'"
|
||||
},
|
||||
"level": {
|
||||
"name": "level",
|
||||
"type": "creator_level",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'beginner'"
|
||||
},
|
||||
"published_recipes": {
|
||||
"name": "published_recipes",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 0
|
||||
},
|
||||
"followers": {
|
||||
"name": "followers",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 0
|
||||
},
|
||||
"total_cooks": {
|
||||
"name": "total_cooks",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 0
|
||||
},
|
||||
"total_favorites": {
|
||||
"name": "total_favorites",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 0
|
||||
},
|
||||
"average_rating": {
|
||||
"name": "average_rating",
|
||||
"type": "double precision",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"verified_recipes": {
|
||||
"name": "verified_recipes",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 0
|
||||
},
|
||||
"badges": {
|
||||
"name": "badges",
|
||||
"type": "text[]",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'{}'"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"creator_stats_user_id_users_id_fk": {
|
||||
"name": "creator_stats_user_id_users_id_fk",
|
||||
"tableFrom": "creator_stats",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.domain_events": {
|
||||
"name": "domain_events",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"type": {
|
||||
"name": "type",
|
||||
"type": "event_type",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"household_id": {
|
||||
"name": "household_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"payload": {
|
||||
"name": "payload",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"correlation_id": {
|
||||
"name": "correlation_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"occurred_at": {
|
||||
"name": "occurred_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"published_at": {
|
||||
"name": "published_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"domain_events_unpublished_idx": {
|
||||
"name": "domain_events_unpublished_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "published_at",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "occurred_at",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"domain_events_type_idx": {
|
||||
"name": "domain_events_type_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "type",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "occurred_at",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"domain_events_household_idx": {
|
||||
"name": "domain_events_household_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "household_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "occurred_at",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.feature_flags": {
|
||||
"name": "feature_flags",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"key": {
|
||||
"name": "key",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"enabled": {
|
||||
"name": "enabled",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": false
|
||||
},
|
||||
"description_sv": {
|
||||
"name": "description_sv",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"rollout_percent": {
|
||||
"name": "rollout_percent",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 100
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.idempotency_keys": {
|
||||
"name": "idempotency_keys",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"key": {
|
||||
"name": "key",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"endpoint": {
|
||||
"name": "endpoint",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"response_status": {
|
||||
"name": "response_status",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"response_body": {
|
||||
"name": "response_body",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"idempotency_keys_user_id_users_id_fk": {
|
||||
"name": "idempotency_keys_user_id_users_id_fk",
|
||||
"tableFrom": "idempotency_keys",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {
|
||||
"idempotency_keys_user_id_key_endpoint_pk": {
|
||||
"name": "idempotency_keys_user_id_key_endpoint_pk",
|
||||
"columns": [
|
||||
"user_id",
|
||||
"key",
|
||||
"endpoint"
|
||||
]
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.notifications": {
|
||||
"name": "notifications",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"type": {
|
||||
"name": "type",
|
||||
"type": "notification_type",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"title_sv": {
|
||||
"name": "title_sv",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"body_sv": {
|
||||
"name": "body_sv",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"data": {
|
||||
"name": "data",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"template_key": {
|
||||
"name": "template_key",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"variables": {
|
||||
"name": "variables",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"locale": {
|
||||
"name": "locale",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"scheduled_for": {
|
||||
"name": "scheduled_for",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"sent_at": {
|
||||
"name": "sent_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"read_at": {
|
||||
"name": "read_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"notifications_user_idx": {
|
||||
"name": "notifications_user_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "user_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "created_at",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"notifications_user_id_users_id_fk": {
|
||||
"name": "notifications_user_id_users_id_fk",
|
||||
"tableFrom": "notifications",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.push_tokens": {
|
||||
"name": "push_tokens",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"token": {
|
||||
"name": "token",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"platform": {
|
||||
"name": "platform",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"push_tokens_user_id_users_id_fk": {
|
||||
"name": "push_tokens_user_id_users_id_fk",
|
||||
"tableFrom": "push_tokens",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {
|
||||
"push_tokens_user_id_token_pk": {
|
||||
"name": "push_tokens_user_id_token_pk",
|
||||
"columns": [
|
||||
"user_id",
|
||||
"token"
|
||||
]
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.waste_summaries": {
|
||||
"name": "waste_summaries",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"household_id": {
|
||||
"name": "household_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"week_start_date": {
|
||||
"name": "week_start_date",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"discarded_count": {
|
||||
"name": "discarded_count",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 0
|
||||
},
|
||||
"discarded_value_minor": {
|
||||
"name": "discarded_value_minor",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 0
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"waste_summaries_household_id_households_id_fk": {
|
||||
"name": "waste_summaries_household_id_households_id_fk",
|
||||
"tableFrom": "waste_summaries",
|
||||
"tableTo": "households",
|
||||
"columnsFrom": [
|
||||
"household_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {
|
||||
"waste_summaries_household_id_week_start_date_pk": {
|
||||
"name": "waste_summaries_household_id_week_start_date_pk",
|
||||
"columns": [
|
||||
"household_id",
|
||||
"week_start_date"
|
||||
]
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.release_gates": {
|
||||
"name": "release_gates",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"gate_key": {
|
||||
"name": "gate_key",
|
||||
"type": "varchar(64)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"category": {
|
||||
"name": "category",
|
||||
"type": "release_gate_category",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"name_sv": {
|
||||
"name": "name_sv",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"name_en": {
|
||||
"name": "name_en",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"description_sv": {
|
||||
"name": "description_sv",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"description_en": {
|
||||
"name": "description_en",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"target_value": {
|
||||
"name": "target_value",
|
||||
"type": "double precision",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"comparison": {
|
||||
"name": "comparison",
|
||||
"type": "release_gate_comparison",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"evaluation_window_days": {
|
||||
"name": "evaluation_window_days",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 7
|
||||
},
|
||||
"last_value": {
|
||||
"name": "last_value",
|
||||
"type": "double precision",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"last_evaluated_at": {
|
||||
"name": "last_evaluated_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"status": {
|
||||
"name": "status",
|
||||
"type": "release_gate_status",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'pending'"
|
||||
},
|
||||
"blocking": {
|
||||
"name": "blocking",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": false
|
||||
},
|
||||
"notes": {
|
||||
"name": "notes",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"release_gates_category_idx": {
|
||||
"name": "release_gates_category_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "category",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"release_gates_status_idx": {
|
||||
"name": "release_gates_status_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "status",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"release_gates_gate_key_unique": {
|
||||
"name": "release_gates_gate_key_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"gate_key"
|
||||
]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
}
|
||||
},
|
||||
"enums": {
|
||||
"public.activity_level": {
|
||||
"name": "activity_level",
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"sedentary",
|
||||
"light",
|
||||
"moderate",
|
||||
"active",
|
||||
"very_active"
|
||||
]
|
||||
},
|
||||
"public.allergen": {
|
||||
"name": "allergen",
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"gluten",
|
||||
"crustaceans",
|
||||
"eggs",
|
||||
"fish",
|
||||
"peanuts",
|
||||
"soy",
|
||||
"milk",
|
||||
"tree_nuts",
|
||||
"celery",
|
||||
"mustard",
|
||||
"sesame",
|
||||
"sulphites",
|
||||
"lupin",
|
||||
"molluscs"
|
||||
]
|
||||
},
|
||||
"public.consent_kind": {
|
||||
"name": "consent_kind",
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"personalization",
|
||||
"anonymized_improvement",
|
||||
"image_training",
|
||||
"health_integration",
|
||||
"location_weather",
|
||||
"push_notifications",
|
||||
"product_analytics"
|
||||
]
|
||||
},
|
||||
"public.consent_status": {
|
||||
"name": "consent_status",
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"granted",
|
||||
"denied",
|
||||
"revoked"
|
||||
]
|
||||
},
|
||||
"public.cooking_method": {
|
||||
"name": "cooking_method",
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"stovetop",
|
||||
"oven",
|
||||
"grill",
|
||||
"airfryer",
|
||||
"wok",
|
||||
"slow_cooker",
|
||||
"sous_vide",
|
||||
"microwave",
|
||||
"no_cook",
|
||||
"pressure_cooker",
|
||||
"deep_fry",
|
||||
"steam"
|
||||
]
|
||||
},
|
||||
"public.creator_level": {
|
||||
"name": "creator_level",
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"beginner",
|
||||
"sous_chef",
|
||||
"chef",
|
||||
"master_chef",
|
||||
"legend"
|
||||
]
|
||||
},
|
||||
"public.cuisine": {
|
||||
"name": "cuisine",
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"swedish",
|
||||
"nordic",
|
||||
"italian",
|
||||
"french",
|
||||
"spanish",
|
||||
"greek",
|
||||
"thai",
|
||||
"chinese",
|
||||
"japanese",
|
||||
"korean",
|
||||
"vietnamese",
|
||||
"indian",
|
||||
"mexican",
|
||||
"american",
|
||||
"turkish",
|
||||
"lebanese",
|
||||
"moroccan",
|
||||
"middle_eastern",
|
||||
"international"
|
||||
]
|
||||
},
|
||||
"public.date_kind": {
|
||||
"name": "date_kind",
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"best_before",
|
||||
"use_by"
|
||||
]
|
||||
},
|
||||
"public.diet_pattern": {
|
||||
"name": "diet_pattern",
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"omnivore",
|
||||
"flexitarian",
|
||||
"pescatarian",
|
||||
"vegetarian",
|
||||
"vegan",
|
||||
"low_carb",
|
||||
"keto",
|
||||
"high_protein",
|
||||
"mediterranean"
|
||||
]
|
||||
},
|
||||
"public.event_type": {
|
||||
"name": "event_type",
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"PRODUCT_ADDED",
|
||||
"PRODUCT_UPDATED",
|
||||
"PRODUCT_CONSUMED",
|
||||
"PRODUCT_DISCARDED",
|
||||
"RECIPE_COOKED",
|
||||
"RECIPE_RATED",
|
||||
"RECIPE_CREATED",
|
||||
"RECIPE_FORKED",
|
||||
"MEAL_LOGGED",
|
||||
"MEAL_PHOTO_ANALYZED",
|
||||
"HOUSEHOLD_MEMBER_ADDED",
|
||||
"SUBSCRIPTION_STARTED",
|
||||
"SUBSCRIPTION_CHANGED",
|
||||
"AI_CORRECTED",
|
||||
"MEMORY_UPDATED",
|
||||
"SHOPPING_COMPLETED",
|
||||
"WEEK_PLAN_UPDATED",
|
||||
"MEAL_BOX_CREATED",
|
||||
"MEAL_BOX_CONSUMED"
|
||||
]
|
||||
},
|
||||
"public.expiry_status": {
|
||||
"name": "expiry_status",
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"fresh",
|
||||
"use_soon",
|
||||
"expiring",
|
||||
"expired",
|
||||
"unknown"
|
||||
]
|
||||
},
|
||||
"public.goal_type": {
|
||||
"name": "goal_type",
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"lose_weight",
|
||||
"gain_weight",
|
||||
"build_muscle",
|
||||
"maintain_weight",
|
||||
"more_protein",
|
||||
"less_fat",
|
||||
"more_fiber",
|
||||
"more_variety",
|
||||
"less_waste",
|
||||
"lower_cost",
|
||||
"cook_more"
|
||||
]
|
||||
},
|
||||
"public.household_role": {
|
||||
"name": "household_role",
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"owner",
|
||||
"adult",
|
||||
"member",
|
||||
"child"
|
||||
]
|
||||
},
|
||||
"public.inventory_source": {
|
||||
"name": "inventory_source",
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"fridge_photo",
|
||||
"freezer_photo",
|
||||
"pantry_photo",
|
||||
"ingredient_photo",
|
||||
"barcode",
|
||||
"receipt",
|
||||
"digital_receipt",
|
||||
"label_photo",
|
||||
"manual_search",
|
||||
"free_text",
|
||||
"voice",
|
||||
"cooked_recipe",
|
||||
"connector",
|
||||
"seed"
|
||||
]
|
||||
},
|
||||
"public.inventory_transaction_type": {
|
||||
"name": "inventory_transaction_type",
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"purchase",
|
||||
"consume",
|
||||
"discard",
|
||||
"adjust",
|
||||
"cook_use",
|
||||
"leftover_created",
|
||||
"leftover_consumed",
|
||||
"correction",
|
||||
"freeze",
|
||||
"thaw",
|
||||
"move"
|
||||
]
|
||||
},
|
||||
"public.job_status": {
|
||||
"name": "job_status",
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"queued",
|
||||
"running",
|
||||
"awaiting_confirmation",
|
||||
"completed",
|
||||
"failed",
|
||||
"canceled"
|
||||
]
|
||||
},
|
||||
"public.job_type": {
|
||||
"name": "job_type",
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"ANALYZE_FRIDGE_IMAGE",
|
||||
"ANALYZE_PANTRY_IMAGE",
|
||||
"ANALYZE_MEAL_IMAGE",
|
||||
"READ_RECEIPT",
|
||||
"READ_NUTRITION_LABEL",
|
||||
"READ_EXPIRY_DATE",
|
||||
"NORMALIZE_PRODUCTS",
|
||||
"DEDUPLICATE_INVENTORY",
|
||||
"CALCULATE_NUTRITION",
|
||||
"GENERATE_RECIPE_OPTIONS",
|
||||
"RANK_RECIPES",
|
||||
"UPDATE_USER_MEMORY",
|
||||
"GENERATE_WEEK_PLAN",
|
||||
"SEND_EXPIRY_NOTIFICATION",
|
||||
"VERIFY_SUBSCRIPTION",
|
||||
"PROCESS_STORE_NOTIFICATION",
|
||||
"BUILD_TRAINING_SAMPLE",
|
||||
"RUN_AI_EVALUATION"
|
||||
]
|
||||
},
|
||||
"public.meal_log_source": {
|
||||
"name": "meal_log_source",
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"cooked_recipe",
|
||||
"plate_photo",
|
||||
"barcode",
|
||||
"product",
|
||||
"free_text",
|
||||
"voice",
|
||||
"previous_meal",
|
||||
"meal_box",
|
||||
"manual"
|
||||
]
|
||||
},
|
||||
"public.meal_type": {
|
||||
"name": "meal_type",
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"breakfast",
|
||||
"lunch",
|
||||
"dinner",
|
||||
"snack",
|
||||
"dessert",
|
||||
"starter",
|
||||
"buffet",
|
||||
"party"
|
||||
]
|
||||
},
|
||||
"public.memory_kind": {
|
||||
"name": "memory_kind",
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"structured_fact",
|
||||
"event",
|
||||
"semantic",
|
||||
"profile_summary",
|
||||
"recipe_memory"
|
||||
]
|
||||
},
|
||||
"public.notification_type": {
|
||||
"name": "notification_type",
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"expiry_warning",
|
||||
"meal_box_reminder",
|
||||
"quick_dinner_suggestion",
|
||||
"holiday_upcoming",
|
||||
"creator_new_recipe",
|
||||
"week_plan_change",
|
||||
"pantry_forecast",
|
||||
"subscription_status"
|
||||
]
|
||||
},
|
||||
"public.precision_mode": {
|
||||
"name": "precision_mode",
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"simple",
|
||||
"exact"
|
||||
]
|
||||
},
|
||||
"public.profile_visibility": {
|
||||
"name": "profile_visibility",
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"private",
|
||||
"friends",
|
||||
"national",
|
||||
"global"
|
||||
]
|
||||
},
|
||||
"public.recipe_difficulty": {
|
||||
"name": "recipe_difficulty",
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"beginner",
|
||||
"easy",
|
||||
"medium",
|
||||
"advanced",
|
||||
"expert"
|
||||
]
|
||||
},
|
||||
"public.recipe_similarity_class": {
|
||||
"name": "recipe_similarity_class",
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"duplicate",
|
||||
"variant",
|
||||
"inspired",
|
||||
"independent"
|
||||
]
|
||||
},
|
||||
"public.recipe_source_type": {
|
||||
"name": "recipe_source_type",
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"own_editorial",
|
||||
"ai_assisted_reviewed",
|
||||
"open_license",
|
||||
"public_domain",
|
||||
"licensed_database",
|
||||
"creator_agreement",
|
||||
"user_generated"
|
||||
]
|
||||
},
|
||||
"public.recipe_status": {
|
||||
"name": "recipe_status",
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"draft",
|
||||
"submitted",
|
||||
"ai_checked",
|
||||
"in_moderation",
|
||||
"published",
|
||||
"rejected",
|
||||
"archived"
|
||||
]
|
||||
},
|
||||
"public.recipe_variant_type": {
|
||||
"name": "recipe_variant_type",
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"standard",
|
||||
"high_protein",
|
||||
"low_calorie",
|
||||
"low_fat",
|
||||
"vegetarian",
|
||||
"vegan",
|
||||
"gluten_free",
|
||||
"lactose_free",
|
||||
"kid_friendly",
|
||||
"airfryer",
|
||||
"budget"
|
||||
]
|
||||
},
|
||||
"public.recipe_verification_status": {
|
||||
"name": "recipe_verification_status",
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"unverified",
|
||||
"community",
|
||||
"verified",
|
||||
"editorial"
|
||||
]
|
||||
},
|
||||
"public.religious_rule": {
|
||||
"name": "religious_rule",
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"none",
|
||||
"halal",
|
||||
"kosher",
|
||||
"hindu_no_beef",
|
||||
"buddhist_vegetarian"
|
||||
]
|
||||
},
|
||||
"public.scan_type": {
|
||||
"name": "scan_type",
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"fridge",
|
||||
"freezer",
|
||||
"pantry",
|
||||
"ingredients",
|
||||
"plate",
|
||||
"receipt",
|
||||
"barcode",
|
||||
"expiry_date",
|
||||
"nutrition_label",
|
||||
"product_package"
|
||||
]
|
||||
},
|
||||
"public.sex": {
|
||||
"name": "sex",
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"female",
|
||||
"male",
|
||||
"unspecified"
|
||||
]
|
||||
},
|
||||
"public.signal_origin": {
|
||||
"name": "signal_origin",
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"user_stated",
|
||||
"observed",
|
||||
"ai_inferred"
|
||||
]
|
||||
},
|
||||
"public.storage_location_type": {
|
||||
"name": "storage_location_type",
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"fridge",
|
||||
"freezer",
|
||||
"pantry",
|
||||
"garage_freezer",
|
||||
"wine_fridge",
|
||||
"cellar",
|
||||
"meal_box",
|
||||
"custom"
|
||||
]
|
||||
},
|
||||
"public.subscription_plan": {
|
||||
"name": "subscription_plan",
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"free",
|
||||
"household",
|
||||
"family",
|
||||
"large_household"
|
||||
]
|
||||
},
|
||||
"public.subscription_provider": {
|
||||
"name": "subscription_provider",
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"apple",
|
||||
"google",
|
||||
"promo",
|
||||
"none"
|
||||
]
|
||||
},
|
||||
"public.subscription_status": {
|
||||
"name": "subscription_status",
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"trial",
|
||||
"active",
|
||||
"in_grace",
|
||||
"on_hold",
|
||||
"paused",
|
||||
"canceled",
|
||||
"expired"
|
||||
]
|
||||
},
|
||||
"public.taste_axis": {
|
||||
"name": "taste_axis",
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"spice",
|
||||
"salt",
|
||||
"acid",
|
||||
"creaminess",
|
||||
"garlic",
|
||||
"sweetness",
|
||||
"herbs",
|
||||
"umami"
|
||||
]
|
||||
},
|
||||
"public.unit": {
|
||||
"name": "unit",
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"GRAM",
|
||||
"KILOGRAM",
|
||||
"MILLILITER",
|
||||
"DECILITER",
|
||||
"LITER",
|
||||
"TEASPOON",
|
||||
"TABLESPOON",
|
||||
"CUP_US",
|
||||
"FLUID_OUNCE_US",
|
||||
"OUNCE",
|
||||
"POUND",
|
||||
"COUNT",
|
||||
"PORTION",
|
||||
"PINCH",
|
||||
"SLICE",
|
||||
"CLOVE",
|
||||
"CAN",
|
||||
"PACKAGE"
|
||||
]
|
||||
},
|
||||
"public.user_role": {
|
||||
"name": "user_role",
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"user",
|
||||
"moderator",
|
||||
"admin"
|
||||
]
|
||||
},
|
||||
"public.verification_status": {
|
||||
"name": "verification_status",
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"unverified",
|
||||
"user_verified",
|
||||
"editorially_verified"
|
||||
]
|
||||
},
|
||||
"public.measurement_system": {
|
||||
"name": "measurement_system",
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"METRIC",
|
||||
"US_CUSTOMARY",
|
||||
"MIXED"
|
||||
]
|
||||
},
|
||||
"public.temperature_unit": {
|
||||
"name": "temperature_unit",
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"CELSIUS",
|
||||
"FAHRENHEIT"
|
||||
]
|
||||
},
|
||||
"public.translation_source": {
|
||||
"name": "translation_source",
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"seed",
|
||||
"ai",
|
||||
"human"
|
||||
]
|
||||
},
|
||||
"public.translation_status": {
|
||||
"name": "translation_status",
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"draft_ai",
|
||||
"in_review",
|
||||
"published"
|
||||
]
|
||||
},
|
||||
"public.release_gate_category": {
|
||||
"name": "release_gate_category",
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"product",
|
||||
"quality",
|
||||
"retention",
|
||||
"economy"
|
||||
]
|
||||
},
|
||||
"public.release_gate_comparison": {
|
||||
"name": "release_gate_comparison",
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"gte",
|
||||
"lte",
|
||||
"eq"
|
||||
]
|
||||
},
|
||||
"public.release_gate_status": {
|
||||
"name": "release_gate_status",
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"pending",
|
||||
"passed",
|
||||
"failed",
|
||||
"blocked",
|
||||
"not_measurable"
|
||||
]
|
||||
}
|
||||
},
|
||||
"schemas": {},
|
||||
"sequences": {},
|
||||
"roles": {},
|
||||
"policies": {},
|
||||
"views": {},
|
||||
"_meta": {
|
||||
"columns": {},
|
||||
"schemas": {},
|
||||
"tables": {}
|
||||
}
|
||||
}
|
||||
@@ -78,6 +78,13 @@
|
||||
"when": 1786040250124,
|
||||
"tag": "0010_melted_chimera",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 11,
|
||||
"version": "7",
|
||||
"when": 1786046260703,
|
||||
"tag": "0011_gray_corsair",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { and, eq, lt } from "drizzle-orm";
|
||||
import * as schema from "./schema/index.js";
|
||||
|
||||
const SESSION_TIMEOUT_MS = 24 * 60 * 60 * 1000;
|
||||
|
||||
/**
|
||||
* Timeout-jobb (Fas 3 §6 / §20): avbryt STARTED-sessioner äldre än 24 h utan complete.
|
||||
* PLANNED reserverar aldrig lager, så den behöver ingen timeout.
|
||||
*/
|
||||
export async function cancelTimedOutCookingSessions(db: { update: Function }) {
|
||||
const deadline = new Date(Date.now() - SESSION_TIMEOUT_MS);
|
||||
const sessions = await db
|
||||
.update(schema.cookingSessions)
|
||||
.set({ status: "cancelled", cancelledAt: new Date(), updatedAt: new Date() })
|
||||
.where(
|
||||
and(
|
||||
eq(schema.cookingSessions.status, "started"),
|
||||
lt(schema.cookingSessions.startedAt, deadline),
|
||||
),
|
||||
)
|
||||
.returning();
|
||||
return sessions;
|
||||
}
|
||||
@@ -4,3 +4,4 @@ export * from "./schema/index.js";
|
||||
export * from "./analytics-gdpr.js";
|
||||
export * from "./release-gates.js";
|
||||
export * from "./activation.js";
|
||||
export * from "./cooking.js";
|
||||
|
||||
@@ -25,6 +25,7 @@ import { households, storageLocations } from "./households.js";
|
||||
import { canonicalIngredients } from "./ingredients.js";
|
||||
import { products } from "./products.js";
|
||||
import { users } from "./users.js";
|
||||
import { cookingSessions } from "./recipes.js";
|
||||
|
||||
/**
|
||||
* Food Twin (spec §8): lagerposter med fullständig spårbarhet.
|
||||
@@ -96,6 +97,9 @@ export const inventoryTransactions = pgTable(
|
||||
unit: unitEnum("unit").notNull(),
|
||||
refType: text("ref_type"),
|
||||
refId: uuid("ref_id"),
|
||||
cookingSessionId: uuid("cooking_session_id").references(() => cookingSessions.id, {
|
||||
onDelete: "set null",
|
||||
}),
|
||||
actorUserId: uuid("actor_user_id").references(() => users.id, { onDelete: "set null" }),
|
||||
note: text("note"),
|
||||
/** Värde i SEK för matsvinnsberäkning vid discard (spec §12, §26). */
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
import type { NutritionValues } from "@app/shared-types";
|
||||
import { createdAt, mealLogSourceEnum, mealTypeEnum } from "./_shared.js";
|
||||
import { households, storageLocations } from "./households.js";
|
||||
import { recipes } from "./recipes.js";
|
||||
import { recipes, cookingSessions } from "./recipes.js";
|
||||
import { users } from "./users.js";
|
||||
|
||||
/**
|
||||
@@ -32,6 +32,9 @@ export const meals = pgTable(
|
||||
mealType: mealTypeEnum("meal_type").notNull(),
|
||||
source: mealLogSourceEnum("source").notNull(),
|
||||
recipeId: uuid("recipe_id").references(() => recipes.id, { onDelete: "set null" }),
|
||||
cookingSessionId: uuid("cooking_session_id").references(() => cookingSessions.id, {
|
||||
onDelete: "set null",
|
||||
}),
|
||||
titleSv: text("title_sv").notNull(),
|
||||
portionFraction: doublePrecision("portion_fraction").notNull().default(1),
|
||||
nutrition: jsonb("nutrition").$type<NutritionValues>().notNull(),
|
||||
@@ -58,6 +61,9 @@ export const mealBoxes = pgTable(
|
||||
.notNull()
|
||||
.references(() => households.id, { onDelete: "cascade" }),
|
||||
recipeId: uuid("recipe_id").references(() => recipes.id, { onDelete: "set null" }),
|
||||
cookingSessionId: uuid("cooking_session_id").references(() => cookingSessions.id, {
|
||||
onDelete: "set null",
|
||||
}),
|
||||
titleSv: text("title_sv").notNull(),
|
||||
portions: integer("portions").notNull(),
|
||||
portionsRemaining: integer("portions_remaining").notNull(),
|
||||
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
} from "./_shared.js";
|
||||
import { canonicalIngredients } from "./ingredients.js";
|
||||
import { users } from "./users.js";
|
||||
import { households } from "./households.js";
|
||||
|
||||
/** Juridiskt source registry (spec §15). */
|
||||
export const recipeSourceRegistry = pgTable("recipe_source_registry", {
|
||||
@@ -190,6 +191,46 @@ export const recipeFavorites = pgTable(
|
||||
],
|
||||
);
|
||||
|
||||
/** Logg över lagningar – grund för betyg, ranking, Food Memory (spec §37–39). */
|
||||
/** Cooking session lifecycle (Fas 3 §6). PLANNED reserverar aldrig lager; STARTED har 24 h på sig att complete/cancel. */
|
||||
export const cookingSessions = pgTable(
|
||||
"cooking_sessions",
|
||||
{
|
||||
id: uuid("id").primaryKey().defaultRandom(),
|
||||
recipeId: uuid("recipe_id")
|
||||
.notNull()
|
||||
.references(() => recipes.id, { onDelete: "cascade" }),
|
||||
householdId: uuid("household_id")
|
||||
.notNull()
|
||||
.references(() => households.id, { onDelete: "cascade" }),
|
||||
startedByUserId: uuid("started_by_user_id")
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
status: text("status").notNull().default("planned"),
|
||||
plannedPortions: integer("planned_portions").notNull(),
|
||||
plannedMealType: mealTypeEnum("planned_meal_type").notNull().default("dinner"),
|
||||
/** Antal portioner som faktiskt åts (steg 3c). */
|
||||
actualPortionsEaten: integer("actual_portions_eaten"),
|
||||
/** Förberedda rester efter sessionen (steg 3d). */
|
||||
leftoverEstimatePortions: integer("leftover_estimate_portions"),
|
||||
/** Ursprungligt FEFO-förslag, sparas för ev. undo/omräkning. */
|
||||
plannedDeductions: jsonb("planned_deductions").$type<
|
||||
Array<{ itemId: string; quantity: number; unit: string; name: string }>
|
||||
>(),
|
||||
startedAt: timestamp("started_at", { withTimezone: true }),
|
||||
completedAt: timestamp("completed_at", { withTimezone: true }),
|
||||
cancelledAt: timestamp("cancelled_at", { withTimezone: true }),
|
||||
cancelReason: text("cancel_reason"),
|
||||
createdAt: createdAt(),
|
||||
updatedAt: updatedAt(),
|
||||
},
|
||||
(t) => [
|
||||
index("cooking_sessions_household_status_idx").on(t.householdId, t.status),
|
||||
index("cooking_sessions_recipe_idx").on(t.recipeId),
|
||||
index("cooking_sessions_timeout_idx").on(t.status, t.startedAt),
|
||||
],
|
||||
);
|
||||
|
||||
/** Logg över lagningar – grund för betyg, ranking, Food Memory (spec §37–39). */
|
||||
export const recipeCooks = pgTable(
|
||||
"recipe_cooks",
|
||||
@@ -202,12 +243,16 @@ export const recipeCooks = pgTable(
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
householdId: uuid("household_id"),
|
||||
cookingSessionId: uuid("cooking_session_id").references(() => cookingSessions.id, {
|
||||
onDelete: "set null",
|
||||
}),
|
||||
portionsCooked: integer("portions_cooked").notNull(),
|
||||
cookedAt: timestamp("cooked_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
},
|
||||
(t) => [
|
||||
index("recipe_cooks_recipe_idx").on(t.recipeId),
|
||||
index("recipe_cooks_user_idx").on(t.userId),
|
||||
index("recipe_cooks_session_idx").on(t.cookingSessionId),
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
@@ -130,3 +130,27 @@ export const substitutionQuerySchema = z.object({
|
||||
context: z.string().max(60).optional(),
|
||||
});
|
||||
export type SubstitutionQuery = z.infer<typeof substitutionQuerySchema>;
|
||||
|
||||
export const cookingSessionStartInputSchema = z.object({
|
||||
portions: z.number().int().min(1).max(24).optional(),
|
||||
mealType: z.enum(MEAL_TYPES).optional(),
|
||||
startNow: z.boolean().default(false),
|
||||
});
|
||||
export type CookingSessionStartInput = z.infer<typeof cookingSessionStartInputSchema>;
|
||||
|
||||
export const cookingSessionCompleteInputSchema = cookRecipeInputSchema.partial().extend({
|
||||
portionsCooked: z.number().int().min(1).max(24).optional(),
|
||||
mealBoxPortions: z.number().int().min(0).max(24).default(0),
|
||||
mealBoxFrozen: z.boolean().default(false),
|
||||
deductInventory: z.boolean().default(true),
|
||||
eaters: z
|
||||
.array(
|
||||
z.object({
|
||||
userId: uuidSchema,
|
||||
portionFraction: z.number().min(0.1).max(3).default(1),
|
||||
}),
|
||||
)
|
||||
.default([]),
|
||||
mealType: z.enum(MEAL_TYPES).optional(),
|
||||
});
|
||||
export type CookingSessionCompleteInput = z.infer<typeof cookingSessionCompleteInputSchema>;
|
||||
|
||||
Reference in New Issue
Block a user