Fas 1b punkt 6: First Scan Coach + aktiveringsmätning
This commit is contained in:
@@ -27,6 +27,14 @@ interface EventCountsResult {
|
||||
unique_users: number;
|
||||
}>;
|
||||
}
|
||||
interface ActivationResult {
|
||||
totalHouseholds: number;
|
||||
scanned: number;
|
||||
activated: number;
|
||||
valueConfirmed: number;
|
||||
activationRate: number;
|
||||
valueConfirmationRate: number;
|
||||
}
|
||||
|
||||
function today() {
|
||||
return new Date().toISOString().slice(0, 10);
|
||||
@@ -51,6 +59,7 @@ export function AnalyticsPage() {
|
||||
const [countsStart, setCountsStart] = useState<string>(weekAgo());
|
||||
const [countsEnd, setCountsEnd] = useState<string>(today());
|
||||
const [counts, setCounts] = useState<EventCountsResult | null>(null);
|
||||
const [activation, setActivation] = useState<ActivationResult | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -60,6 +69,9 @@ export function AnalyticsPage() {
|
||||
if (res.funnels[0]) setFunnelName(res.funnels[0].name);
|
||||
})
|
||||
.catch((e) => setError(String(e.message)));
|
||||
api<ActivationResult>("/admin/v1/analytics/activation")
|
||||
.then(setActivation)
|
||||
.catch((e) => setError(String(e.message)));
|
||||
}, []);
|
||||
|
||||
const loadFunnel = () => {
|
||||
@@ -107,6 +119,30 @@ export function AnalyticsPage() {
|
||||
<h2>Analytics</h2>
|
||||
{error && <div className="error-text">{error}</div>}
|
||||
|
||||
{activation && (
|
||||
<div className="panel">
|
||||
<h3>Aktivering</h3>
|
||||
<div className="metrics">
|
||||
<div className="stat">
|
||||
<div className="value">{activation.totalHouseholds}</div>
|
||||
<div className="label">Hushåll</div>
|
||||
</div>
|
||||
<div className="stat">
|
||||
<div className="value">{activation.scanned}</div>
|
||||
<div className="label">Skannat</div>
|
||||
</div>
|
||||
<div className="stat">
|
||||
<div className="value">{(activation.activationRate * 100).toFixed(1)}%</div>
|
||||
<div className="label">Aktiverade</div>
|
||||
</div>
|
||||
<div className="stat">
|
||||
<div className="value">{(activation.valueConfirmationRate * 100).toFixed(1)}%</div>
|
||||
<div className="label">Value confirmed</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="panel">
|
||||
<h3>Funnels</h3>
|
||||
<div className="form-row">
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import type { FastifyInstance } from "fastify";
|
||||
import { getActivationState, schema } from "@app/database";
|
||||
import { errors } from "../lib/errors.js";
|
||||
import { getActiveHouseholdId } from "../lib/helpers.js";
|
||||
|
||||
/**
|
||||
* Hushållsaktiveringsstatus (spec §4.2):
|
||||
* - ACTIVATED_HOUSEHOLD
|
||||
* - VALUE_CONFIRMED_HOUSEHOLD
|
||||
*/
|
||||
export async function activationRoutes(app: FastifyInstance) {
|
||||
const auth = { preHandler: [app.authenticate] };
|
||||
|
||||
app.get("/v1/households/me/activation", auth, async (req) => {
|
||||
const householdId = await getActiveHouseholdId(app.db, req.userId);
|
||||
if (!householdId) throw errors.notFound("Aktivt hushåll saknas.");
|
||||
|
||||
const state = await getActivationState(app.db, householdId);
|
||||
return {
|
||||
...state,
|
||||
isActivated: state.activatedAt != null,
|
||||
isValueConfirmed: state.valueConfirmedAt != null,
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -200,6 +200,28 @@ export async function adminAnalyticsRoutes(app: FastifyInstance) {
|
||||
const rows = Array.isArray(result) ? result : result.rows;
|
||||
return { counts: rows };
|
||||
});
|
||||
|
||||
/** Household activation metrics (Fas 1b). */
|
||||
app.get("/admin/v1/analytics/activation", admin, async () => {
|
||||
const result = await app.db.execute(sql`
|
||||
SELECT
|
||||
COUNT(*) FILTER (WHERE first_scan_completed_at IS NOT NULL) AS scanned,
|
||||
COUNT(*) FILTER (WHERE activated_at IS NOT NULL) AS activated,
|
||||
COUNT(*) FILTER (WHERE value_confirmed_at IS NOT NULL) AS value_confirmed,
|
||||
COUNT(*) AS total
|
||||
FROM households
|
||||
`);
|
||||
const rows = Array.isArray(result) ? result : result.rows;
|
||||
const r = rows[0] as Record<string, number>;
|
||||
return {
|
||||
totalHouseholds: Number(r.total ?? 0),
|
||||
scanned: Number(r.scanned ?? 0),
|
||||
activated: Number(r.activated ?? 0),
|
||||
valueConfirmed: Number(r.value_confirmed ?? 0),
|
||||
activationRate: r.total ? Number(r.activated) / Number(r.total) : 0,
|
||||
valueConfirmationRate: r.total ? Number(r.value_confirmed) / Number(r.total) : 0,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/** Escape a string/date parameter for raw SQL by wrapping it in single quotes.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { FastifyInstance } from "fastify";
|
||||
import { and, desc, eq, gt, ilike, inArray, isNull, lte, or, sql } from "drizzle-orm";
|
||||
import { schema } from "@app/database";
|
||||
import { markMilestone, schema } from "@app/database";
|
||||
import {
|
||||
cookRecipeInputSchema,
|
||||
createUserRecipeInputSchema,
|
||||
@@ -226,6 +226,11 @@ export async function recipeRoutes(app: FastifyInstance) {
|
||||
|
||||
// Innehållsspråk (i18n-spec §13–14): publicerad översättning om användarens
|
||||
// språk inte är svenska; annars svensk källa. Struktur ändras aldrig.
|
||||
const householdId = await getActiveHouseholdId(app.db, req.userId);
|
||||
if (householdId) {
|
||||
void markMilestone(app.db, householdId, "firstRecipeRecommendationViewedAt");
|
||||
}
|
||||
|
||||
const languageTag = await userLanguageTag(app.db, req.userId);
|
||||
const translation = await resolveRecipeTranslation(app.db, id, languageTag);
|
||||
const ingredientNames = await resolveIngredientNames(
|
||||
@@ -366,6 +371,10 @@ export async function recipeRoutes(app: FastifyInstance) {
|
||||
.update(schema.recipes)
|
||||
.set({ favoriteCount: sql`${schema.recipes.favoriteCount} + 1` })
|
||||
.where(eq(schema.recipes.id, id));
|
||||
const householdId = await getActiveHouseholdId(app.db, req.userId);
|
||||
if (householdId) {
|
||||
void markMilestone(app.db, householdId, "firstRecipeSavedOrStartedAt");
|
||||
}
|
||||
return { ok: true };
|
||||
});
|
||||
|
||||
@@ -598,6 +607,11 @@ export async function recipeRoutes(app: FastifyInstance) {
|
||||
correlationId: req.correlationId,
|
||||
});
|
||||
|
||||
await markMilestone(app.db, householdId, "firstCookingSessionCompletedAt");
|
||||
if (input.deductInventory && deductions.length > 0) {
|
||||
await markMilestone(app.db, householdId, "inventoryUpdatedAfterCookingAt");
|
||||
}
|
||||
|
||||
return { ok: true, mealIds, mealBoxId, inventoryDeductions: deductions };
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { FastifyInstance } from "fastify";
|
||||
import { and, eq, gt, isNull } from "drizzle-orm";
|
||||
import { schema } from "@app/database";
|
||||
import { markMilestone, schema } from "@app/database";
|
||||
import type { JobType, ScanType } from "@app/shared-types";
|
||||
import { confirmScanInputSchema, createScanInputSchema, idParamSchema } from "@app/validation";
|
||||
import { errors, parse } from "../lib/errors.js";
|
||||
@@ -200,6 +200,12 @@ export async function scanRoutes(app: FastifyInstance) {
|
||||
.set({ status: "completed", updatedAt: new Date() })
|
||||
.where(eq(schema.scanJobs.id, id));
|
||||
|
||||
const acceptedCount = input.items.filter((i) => i.action !== "reject").length;
|
||||
await markMilestone(app.db, householdId, "firstScanCompletedAt");
|
||||
if (acceptedCount >= 5) {
|
||||
await markMilestone(app.db, householdId, "fifthItemConfirmedAt");
|
||||
}
|
||||
|
||||
return { ok: true, createdItemIds: created };
|
||||
});
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ import { adminReleaseGateRoutes } from "./routes/admin-release-gates.js";
|
||||
import { adminWorkersRoutes } from "./routes/admin-workers.js";
|
||||
import { analyticsRoutes } from "./routes/analytics.js";
|
||||
import { onboardingRoutes } from "./routes/onboarding.js";
|
||||
import { activationRoutes } from "./routes/activation.js";
|
||||
|
||||
declare module "fastify" {
|
||||
interface FastifyInstance {
|
||||
@@ -90,6 +91,7 @@ export async function buildServer(config: AppConfig) {
|
||||
await app.register(adminWorkersRoutes);
|
||||
await app.register(analyticsRoutes);
|
||||
await app.register(onboardingRoutes);
|
||||
await app.register(activationRoutes);
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Alert, View } from "react-native";
|
||||
import { router } from "expo-router";
|
||||
import * as ImagePicker from "expo-image-picker";
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
Title,
|
||||
} from "@/components/ui";
|
||||
import { spacing } from "@/lib/theme";
|
||||
import { FirstScanCoach, useFirstScanCoach } from "@/components/FirstScanCoach";
|
||||
|
||||
/** Skanna (spec §4.2): kyl, frys, skafferi, ingredienser, tallrik, kvitto, streckkod, datum, näringsdeklaration. */
|
||||
|
||||
@@ -43,11 +44,16 @@ interface Entitlements {
|
||||
|
||||
export default function ScanScreen() {
|
||||
const [busyType, setBusyType] = useState<string | null>(null);
|
||||
const coach = useFirstScanCoach();
|
||||
const entitlements = useQuery({
|
||||
queryKey: ["entitlements"],
|
||||
queryFn: () => api<Entitlements>("/v1/me/entitlements"),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
void coach.showIfNeeded();
|
||||
}, []);
|
||||
|
||||
const startScan = async (scanType: string) => {
|
||||
if (scanType === "barcode") {
|
||||
router.push("/barcode");
|
||||
@@ -93,6 +99,7 @@ export default function ScanScreen() {
|
||||
|
||||
return (
|
||||
<Screen>
|
||||
<FirstScanCoach visible={coach.visible} onDismiss={coach.dismiss} />
|
||||
<Title>{t("scan.title")}</Title>
|
||||
<Small>{t("scan.subtitle")}</Small>
|
||||
{quota != null && <Small>{t("scan.quotaLeft", { count: Math.max(0, quota) })}</Small>}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import { useState } from "react";
|
||||
import { Modal, View } from "react-native";
|
||||
import AsyncStorage from "@react-native-async-storage/async-storage";
|
||||
import { Body, Button, Card, Heading, Screen, Small, Spacer } from "@/components/ui";
|
||||
import { spacing } from "@/lib/theme";
|
||||
import { t } from "@/lib/i18n";
|
||||
|
||||
const COACH_DISMISSED_KEY = "firstScanCoachDismissed";
|
||||
|
||||
export function useFirstScanCoach() {
|
||||
const [visible, setVisible] = useState(false);
|
||||
|
||||
const showIfNeeded = async () => {
|
||||
const dismissed = await AsyncStorage.getItem(COACH_DISMISSED_KEY);
|
||||
if (dismissed !== "true") setVisible(true);
|
||||
};
|
||||
|
||||
const dismiss = async () => {
|
||||
await AsyncStorage.setItem(COACH_DISMISSED_KEY, "true");
|
||||
setVisible(false);
|
||||
};
|
||||
|
||||
return { visible, showIfNeeded, dismiss };
|
||||
}
|
||||
|
||||
export function FirstScanCoach({ visible, onDismiss }: { visible: boolean; onDismiss: () => void }) {
|
||||
return (
|
||||
<Modal visible={visible} transparent animationType="fade">
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
backgroundColor: "rgba(0,0,0,0.6)",
|
||||
justifyContent: "center",
|
||||
padding: spacing.lg,
|
||||
}}
|
||||
>
|
||||
<Card>
|
||||
<Heading>{t("scan.coach.title")}</Heading>
|
||||
<Small>{t("scan.coach.body")}</Small>
|
||||
<Spacer size={spacing.md} />
|
||||
<Small>• {t("scan.coach.tip1")}</Small>
|
||||
<Small>• {t("scan.coach.tip2")}</Small>
|
||||
<Small>• {t("scan.coach.tip3")}</Small>
|
||||
<Spacer size={spacing.lg} />
|
||||
<Button label={t("scan.coach.button")} onPress={onDismiss} />
|
||||
</Card>
|
||||
</View>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
@@ -191,9 +191,9 @@
|
||||
"onboarding.mode": "Hvor præcis vil du være?",
|
||||
"onboarding.modeExact": "Præcis tilstand",
|
||||
"onboarding.modeExactDesc": "Vej maden, indtast gram, fuld kontrol over tallene.",
|
||||
"onboarding.modesCombine": "Tilstandene kan kombineres – enkelt til hverdag, præcist når du vil.",
|
||||
"onboarding.modeSimple": "Enkel tilstand",
|
||||
"onboarding.modeSimpleDesc": "Tag billeder af maden, accepter estimater, minimalt besvær.",
|
||||
"onboarding.modesCombine": "Tilstandene kan kombineres – enkelt til hverdag, præcist når du vil.",
|
||||
"onboarding.notMedical": "{brand} giver vejledning – ikke lægelig rådgivning.",
|
||||
"onboarding.stepBSubtitle": "Tilpas din profil for bedre forslag.",
|
||||
"onboarding.stepBTitle": "Fortsæt opsætning",
|
||||
@@ -270,6 +270,12 @@
|
||||
"recipe.time": "{min} min",
|
||||
"scan.analyzing": "Analyserer …",
|
||||
"scan.barcode": "Stregkode",
|
||||
"scan.coach.body": "Tag et billede af hele hylden eller køleskabet. Hold telefonen rolig og sørg for godt lys.",
|
||||
"scan.coach.button": "Forstået",
|
||||
"scan.coach.tip1": "Sørg for at alle varer er synlige",
|
||||
"scan.coach.tip2": "Undgå blitz og skygger",
|
||||
"scan.coach.tip3": "Bekræft usikre rækker i næste trin",
|
||||
"scan.coach.title": "Første scanning",
|
||||
"scan.expiry": "Bedst før-dato",
|
||||
"scan.failed": "Analysen mislykkedes. Prøv igen eller registrér manuelt.",
|
||||
"scan.freezer": "Fryser",
|
||||
@@ -306,10 +312,10 @@
|
||||
"scan.title": "Scan",
|
||||
"shopping.addPlaceholder": "Tilføj vare …",
|
||||
"shopping.complete": "Afslut indkøbsturen",
|
||||
"shopping.completeNote": "Afkrydsede varer lægges på madlageret.",
|
||||
"shopping.completedBody_one": "1 vare blev lagt i madlageret.",
|
||||
"shopping.completedBody_other": "{count} varer blev lagt i madlageret.",
|
||||
"shopping.completedTitle": "Færdig!",
|
||||
"shopping.completeNote": "Afkrydsede varer lægges på madlageret.",
|
||||
"shopping.empty": "Listen er tom.",
|
||||
"shopping.estimated": "ca. {amount}",
|
||||
"shopping.estimatedTotal": "Anslået i alt: ca. {amount}",
|
||||
|
||||
@@ -191,9 +191,9 @@
|
||||
"onboarding.mode": "Wie genau möchtest du es?",
|
||||
"onboarding.modeExact": "Exakter Modus",
|
||||
"onboarding.modeExactDesc": "Essen wiegen, Gramm eingeben, volle Kontrolle über die Zahlen.",
|
||||
"onboarding.modesCombine": "Die Modi lassen sich kombinieren – im Alltag einfach, exakt wenn du willst.",
|
||||
"onboarding.modeSimple": "Einfacher Modus",
|
||||
"onboarding.modeSimpleDesc": "Essen fotografieren, Schätzungen akzeptieren, kein Aufwand.",
|
||||
"onboarding.modesCombine": "Die Modi lassen sich kombinieren – im Alltag einfach, exakt wenn du willst.",
|
||||
"onboarding.notMedical": "{brand} bietet Orientierung – keine medizinische Beratung.",
|
||||
"onboarding.stepBSubtitle": "Passe dein Profil für bessere Vorschläge an.",
|
||||
"onboarding.stepBTitle": "Einrichtung fortsetzen",
|
||||
@@ -270,6 +270,12 @@
|
||||
"recipe.time": "{min} Min.",
|
||||
"scan.analyzing": "Analyse läuft …",
|
||||
"scan.barcode": "Barcode",
|
||||
"scan.coach.body": "Take a photo of the whole shelf or fridge. Keep the phone steady and make sure the light is good.",
|
||||
"scan.coach.button": "Verstanden",
|
||||
"scan.coach.tip1": "Make sure all items are visible",
|
||||
"scan.coach.tip2": "Avoid flash and shadows",
|
||||
"scan.coach.tip3": "Confirm uncertain items in the next step",
|
||||
"scan.coach.title": "Erster Scan",
|
||||
"scan.expiry": "Mindesthaltbarkeitsdatum",
|
||||
"scan.failed": "Analyse fehlgeschlagen. Erneut versuchen oder manuell erfassen.",
|
||||
"scan.freezer": "Gefrierschrank",
|
||||
@@ -306,10 +312,10 @@
|
||||
"scan.title": "Scannen",
|
||||
"shopping.addPlaceholder": "Artikel hinzufügen …",
|
||||
"shopping.complete": "Einkauf abschließen",
|
||||
"shopping.completeNote": "Abgehakte Artikel wandern in den Vorrat.",
|
||||
"shopping.completedBody_one": "1 Artikel wurde in den Vorrat übernommen.",
|
||||
"shopping.completedBody_other": "{count} Artikel wurden in den Vorrat übernommen.",
|
||||
"shopping.completedTitle": "Fertig!",
|
||||
"shopping.completeNote": "Abgehakte Artikel wandern in den Vorrat.",
|
||||
"shopping.empty": "Die Liste ist leer.",
|
||||
"shopping.estimated": "ca. {amount}",
|
||||
"shopping.estimatedTotal": "Geschätzte Summe: ca. {amount}",
|
||||
|
||||
@@ -191,9 +191,9 @@
|
||||
"onboarding.mode": "How precise do you want to be?",
|
||||
"onboarding.modeExact": "Exact mode",
|
||||
"onboarding.modeExactDesc": "Weigh food, enter grams, full control of the numbers.",
|
||||
"onboarding.modesCombine": "You can mix modes – simple day to day, exact when you want.",
|
||||
"onboarding.modeSimple": "Simple mode",
|
||||
"onboarding.modeSimpleDesc": "Snap photos of your food, accept estimates, minimal fuss.",
|
||||
"onboarding.modesCombine": "You can mix modes – simple day to day, exact when you want.",
|
||||
"onboarding.notMedical": "{brand} provides guidance – not medical advice.",
|
||||
"onboarding.stepBSubtitle": "Customize your profile for better suggestions.",
|
||||
"onboarding.stepBTitle": "Continue setup",
|
||||
@@ -270,6 +270,12 @@
|
||||
"recipe.time": "{min} min",
|
||||
"scan.analyzing": "Analyzing …",
|
||||
"scan.barcode": "Barcode",
|
||||
"scan.coach.body": "Take a photo of the whole shelf or fridge. Keep the phone steady and make sure the light is good.",
|
||||
"scan.coach.button": "Got it",
|
||||
"scan.coach.tip1": "Make sure all items are visible",
|
||||
"scan.coach.tip2": "Avoid flash and shadows",
|
||||
"scan.coach.tip3": "Confirm uncertain items in the next step",
|
||||
"scan.coach.title": "First scan",
|
||||
"scan.expiry": "Best-before date",
|
||||
"scan.failed": "The analysis failed. Try again or add items manually.",
|
||||
"scan.freezer": "Freezer",
|
||||
@@ -306,10 +312,10 @@
|
||||
"scan.title": "Scan",
|
||||
"shopping.addPlaceholder": "Add item …",
|
||||
"shopping.complete": "Finish shopping trip",
|
||||
"shopping.completeNote": "Checked items are added to your food inventory.",
|
||||
"shopping.completedBody_one": "1 item was added to your inventory.",
|
||||
"shopping.completedBody_other": "{count} items were added to your inventory.",
|
||||
"shopping.completedTitle": "Done!",
|
||||
"shopping.completeNote": "Checked items are added to your food inventory.",
|
||||
"shopping.empty": "The list is empty.",
|
||||
"shopping.estimated": "approx. {amount}",
|
||||
"shopping.estimatedTotal": "Estimated total: approx. {amount}",
|
||||
|
||||
@@ -191,9 +191,9 @@
|
||||
"onboarding.mode": "¿Cuánta precisión quieres?",
|
||||
"onboarding.modeExact": "Modo exacto",
|
||||
"onboarding.modeExactDesc": "Pesa la comida, introduce gramos, control total de las cifras.",
|
||||
"onboarding.modesCombine": "Puedes combinar los modos: sencillo a diario, exacto cuando quieras.",
|
||||
"onboarding.modeSimple": "Modo sencillo",
|
||||
"onboarding.modeSimpleDesc": "Fotografía tu comida, acepta estimaciones, sin complicaciones.",
|
||||
"onboarding.modesCombine": "Puedes combinar los modos: sencillo a diario, exacto cuando quieras.",
|
||||
"onboarding.notMedical": "{brand} ofrece orientación, no consejo médico.",
|
||||
"onboarding.stepBSubtitle": "Personaliza tu perfil para mejores sugerencias.",
|
||||
"onboarding.stepBTitle": "Continuar configuración",
|
||||
@@ -270,6 +270,12 @@
|
||||
"recipe.time": "{min} min",
|
||||
"scan.analyzing": "Analizando …",
|
||||
"scan.barcode": "Código de barras",
|
||||
"scan.coach.body": "Take a photo of the whole shelf or fridge. Keep the phone steady and make sure the light is good.",
|
||||
"scan.coach.button": "Entendido",
|
||||
"scan.coach.tip1": "Make sure all items are visible",
|
||||
"scan.coach.tip2": "Avoid flash and shadows",
|
||||
"scan.coach.tip3": "Confirm uncertain items in the next step",
|
||||
"scan.coach.title": "Primer escaneo",
|
||||
"scan.expiry": "Fecha de consumo preferente",
|
||||
"scan.failed": "El análisis falló. Inténtalo de nuevo o añade manualmente.",
|
||||
"scan.freezer": "Congelador",
|
||||
@@ -306,10 +312,10 @@
|
||||
"scan.title": "Escanear",
|
||||
"shopping.addPlaceholder": "Añadir producto …",
|
||||
"shopping.complete": "Terminar la compra",
|
||||
"shopping.completeNote": "Los productos marcados se añaden al inventario.",
|
||||
"shopping.completedBody_one": "Se añadió 1 producto al inventario.",
|
||||
"shopping.completedBody_other": "Se añadieron {count} productos al inventario.",
|
||||
"shopping.completedTitle": "¡Listo!",
|
||||
"shopping.completeNote": "Los productos marcados se añaden al inventario.",
|
||||
"shopping.empty": "La lista está vacía.",
|
||||
"shopping.estimated": "aprox. {amount}",
|
||||
"shopping.estimatedTotal": "Total estimado: aprox. {amount}",
|
||||
|
||||
@@ -191,9 +191,9 @@
|
||||
"onboarding.mode": "Kuinka tarkka haluat olla?",
|
||||
"onboarding.modeExact": "Tarkka tila",
|
||||
"onboarding.modeExactDesc": "Punnitse ruoka, syötä grammat, täysi hallinta lukuihin.",
|
||||
"onboarding.modesCombine": "Tiloja voi yhdistellä – arjessa helposti, tarkasti kun haluat.",
|
||||
"onboarding.modeSimple": "Helppo tila",
|
||||
"onboarding.modeSimpleDesc": "Kuvaa ruokasi, hyväksy arviot, mahdollisimman vaivatonta.",
|
||||
"onboarding.modesCombine": "Tiloja voi yhdistellä – arjessa helposti, tarkasti kun haluat.",
|
||||
"onboarding.notMedical": "{brand} antaa ohjeita – ei lääketieteellisiä neuvoja.",
|
||||
"onboarding.stepBSubtitle": "Mukauta profiiliasi parempia ehdotuksia varten.",
|
||||
"onboarding.stepBTitle": "Jatka asetuksia",
|
||||
@@ -270,6 +270,12 @@
|
||||
"recipe.time": "{min} min",
|
||||
"scan.analyzing": "Analysoidaan …",
|
||||
"scan.barcode": "Viivakoodi",
|
||||
"scan.coach.body": "Take a photo of the whole shelf or fridge. Keep the phone steady and make sure the light is good.",
|
||||
"scan.coach.button": "Ymmärrän",
|
||||
"scan.coach.tip1": "Make sure all items are visible",
|
||||
"scan.coach.tip2": "Avoid flash and shadows",
|
||||
"scan.coach.tip3": "Confirm uncertain items in the next step",
|
||||
"scan.coach.title": "Ensimmäinen skannaus",
|
||||
"scan.expiry": "Parasta ennen -päiväys",
|
||||
"scan.failed": "Analyysi epäonnistui. Yritä uudelleen tai lisää käsin.",
|
||||
"scan.freezer": "Pakastin",
|
||||
@@ -306,10 +312,10 @@
|
||||
"scan.title": "Skannaa",
|
||||
"shopping.addPlaceholder": "Lisää tuote …",
|
||||
"shopping.complete": "Päätä ostosreissu",
|
||||
"shopping.completeNote": "Merkityt tuotteet lisätään ruokavarastoon.",
|
||||
"shopping.completedBody_one": "1 tuote lisättiin ruokavarastoon.",
|
||||
"shopping.completedBody_other": "{count} tuotetta lisättiin ruokavarastoon.",
|
||||
"shopping.completedTitle": "Valmis!",
|
||||
"shopping.completeNote": "Merkityt tuotteet lisätään ruokavarastoon.",
|
||||
"shopping.empty": "Lista on tyhjä.",
|
||||
"shopping.estimated": "n. {amount}",
|
||||
"shopping.estimatedTotal": "Arvioitu yhteensä: n. {amount}",
|
||||
|
||||
@@ -191,9 +191,9 @@
|
||||
"onboarding.mode": "Quel niveau de précision ?",
|
||||
"onboarding.modeExact": "Mode exact",
|
||||
"onboarding.modeExactDesc": "Pesez, saisissez les grammes, contrôle total des chiffres.",
|
||||
"onboarding.modesCombine": "Les modes se combinent : simple au quotidien, précis quand vous voulez.",
|
||||
"onboarding.modeSimple": "Mode simple",
|
||||
"onboarding.modeSimpleDesc": "Photographiez vos aliments, acceptez les estimations, zéro prise de tête.",
|
||||
"onboarding.modesCombine": "Les modes se combinent : simple au quotidien, précis quand vous voulez.",
|
||||
"onboarding.notMedical": "{brand} fournit des repères, pas un avis médical.",
|
||||
"onboarding.stepBSubtitle": "Personnalisez votre profil pour de meilleures suggestions.",
|
||||
"onboarding.stepBTitle": "Continuer la configuration",
|
||||
@@ -270,6 +270,12 @@
|
||||
"recipe.time": "{min} min",
|
||||
"scan.analyzing": "Analyse en cours …",
|
||||
"scan.barcode": "Code-barres",
|
||||
"scan.coach.body": "Take a photo of the whole shelf or fridge. Keep the phone steady and make sure the light is good.",
|
||||
"scan.coach.button": "Compris",
|
||||
"scan.coach.tip1": "Make sure all items are visible",
|
||||
"scan.coach.tip2": "Avoid flash and shadows",
|
||||
"scan.coach.tip3": "Confirm uncertain items in the next step",
|
||||
"scan.coach.title": "Premier scan",
|
||||
"scan.expiry": "Date limite",
|
||||
"scan.failed": "L'analyse a échoué. Réessayez ou saisissez manuellement.",
|
||||
"scan.freezer": "Congélateur",
|
||||
@@ -306,10 +312,10 @@
|
||||
"scan.title": "Scanner",
|
||||
"shopping.addPlaceholder": "Ajouter un article …",
|
||||
"shopping.complete": "Terminer les courses",
|
||||
"shopping.completeNote": "Les articles cochés rejoignent le stock.",
|
||||
"shopping.completedBody_one": "1 article a été ajouté au stock.",
|
||||
"shopping.completedBody_other": "{count} articles ont été ajoutés au stock.",
|
||||
"shopping.completedTitle": "Terminé !",
|
||||
"shopping.completeNote": "Les articles cochés rejoignent le stock.",
|
||||
"shopping.empty": "La liste est vide.",
|
||||
"shopping.estimated": "env. {amount}",
|
||||
"shopping.estimatedTotal": "Total estimé : env. {amount}",
|
||||
|
||||
@@ -191,9 +191,9 @@
|
||||
"onboarding.mode": "Quanta precisione vuoi?",
|
||||
"onboarding.modeExact": "Modalità esatta",
|
||||
"onboarding.modeExactDesc": "Pesa il cibo, inserisci i grammi, pieno controllo dei numeri.",
|
||||
"onboarding.modesCombine": "Le modalità si possono combinare: semplice ogni giorno, esatta quando vuoi.",
|
||||
"onboarding.modeSimple": "Modalità semplice",
|
||||
"onboarding.modeSimpleDesc": "Fotografa il cibo, accetta le stime, zero complicazioni.",
|
||||
"onboarding.modesCombine": "Le modalità si possono combinare: semplice ogni giorno, esatta quando vuoi.",
|
||||
"onboarding.notMedical": "{brand} offre indicazioni, non consigli medici.",
|
||||
"onboarding.stepBSubtitle": "Personalizza il profilo per suggerimenti migliori.",
|
||||
"onboarding.stepBTitle": "Continua configurazione",
|
||||
@@ -270,6 +270,12 @@
|
||||
"recipe.time": "{min} min",
|
||||
"scan.analyzing": "Analisi in corso …",
|
||||
"scan.barcode": "Codice a barre",
|
||||
"scan.coach.body": "Take a photo of the whole shelf or fridge. Keep the phone steady and make sure the light is good.",
|
||||
"scan.coach.button": "Capito",
|
||||
"scan.coach.tip1": "Make sure all items are visible",
|
||||
"scan.coach.tip2": "Avoid flash and shadows",
|
||||
"scan.coach.tip3": "Confirm uncertain items in the next step",
|
||||
"scan.coach.title": "Prima scansione",
|
||||
"scan.expiry": "Data di scadenza",
|
||||
"scan.failed": "Analisi non riuscita. Riprova o inserisci manualmente.",
|
||||
"scan.freezer": "Congelatore",
|
||||
@@ -306,10 +312,10 @@
|
||||
"scan.title": "Scansiona",
|
||||
"shopping.addPlaceholder": "Aggiungi prodotto …",
|
||||
"shopping.complete": "Concludi la spesa",
|
||||
"shopping.completeNote": "I prodotti spuntati vengono aggiunti alla dispensa.",
|
||||
"shopping.completedBody_one": "1 prodotto aggiunto alla dispensa.",
|
||||
"shopping.completedBody_other": "{count} prodotti aggiunti alla dispensa.",
|
||||
"shopping.completedTitle": "Fatto!",
|
||||
"shopping.completeNote": "I prodotti spuntati vengono aggiunti alla dispensa.",
|
||||
"shopping.empty": "La lista è vuota.",
|
||||
"shopping.estimated": "circa {amount}",
|
||||
"shopping.estimatedTotal": "Totale stimato: circa {amount}",
|
||||
|
||||
@@ -191,9 +191,9 @@
|
||||
"onboarding.mode": "Hvor nøyaktig vil du være?",
|
||||
"onboarding.modeExact": "Nøyaktig modus",
|
||||
"onboarding.modeExactDesc": "Vei maten, angi gram, full kontroll på tallene.",
|
||||
"onboarding.modesCombine": "Modusene kan kombineres – enkelt til hverdags, nøyaktig når du vil.",
|
||||
"onboarding.modeSimple": "Enkel modus",
|
||||
"onboarding.modeSimpleDesc": "Ta bilde av maten, godta estimater, minimalt styr.",
|
||||
"onboarding.modesCombine": "Modusene kan kombineres – enkelt til hverdags, nøyaktig når du vil.",
|
||||
"onboarding.notMedical": "{brand} gir veiledning – ikke medisinske råd.",
|
||||
"onboarding.stepBSubtitle": "Tilpass profilen din for bedre forslag.",
|
||||
"onboarding.stepBTitle": "Fortsett oppsett",
|
||||
@@ -270,6 +270,12 @@
|
||||
"recipe.time": "{min} min",
|
||||
"scan.analyzing": "Analyserer …",
|
||||
"scan.barcode": "Strekkode",
|
||||
"scan.coach.body": "Ta et bilde av hele hyllen eller kjøleskapet. Hold telefonen i ro og sørg for godt lys.",
|
||||
"scan.coach.button": "Skjønner",
|
||||
"scan.coach.tip1": "Sørg for at alle varer er synlige",
|
||||
"scan.coach.tip2": "Unngå blitz og skygger",
|
||||
"scan.coach.tip3": "Bekreft usikre rader i neste steg",
|
||||
"scan.coach.title": "Første skanning",
|
||||
"scan.expiry": "Best før-dato",
|
||||
"scan.failed": "Analysen mislyktes. Prøv igjen eller registrer manuelt.",
|
||||
"scan.freezer": "Fryser",
|
||||
@@ -306,10 +312,10 @@
|
||||
"scan.title": "Skann",
|
||||
"shopping.addPlaceholder": "Legg til vare …",
|
||||
"shopping.complete": "Avslutt handleturen",
|
||||
"shopping.completeNote": "Avkryssede varer legges i matlageret.",
|
||||
"shopping.completedBody_one": "1 vare ble lagt inn i matlageret.",
|
||||
"shopping.completedBody_other": "{count} varer ble lagt inn i matlageret.",
|
||||
"shopping.completedTitle": "Ferdig!",
|
||||
"shopping.completeNote": "Avkryssede varer legges i matlageret.",
|
||||
"shopping.empty": "Listen er tom.",
|
||||
"shopping.estimated": "ca. {amount}",
|
||||
"shopping.estimatedTotal": "Anslått totalt: ca. {amount}",
|
||||
|
||||
@@ -191,9 +191,9 @@
|
||||
"onboarding.mode": "Hoe precies wil je het?",
|
||||
"onboarding.modeExact": "Exacte modus",
|
||||
"onboarding.modeExactDesc": "Weeg je eten, voer grammen in, volledige controle over de cijfers.",
|
||||
"onboarding.modesCombine": "De standen zijn te combineren – simpel in het dagelijks leven, exact wanneer je wilt.",
|
||||
"onboarding.modeSimple": "Simpele modus",
|
||||
"onboarding.modeSimpleDesc": "Fotografeer je eten, accepteer schattingen, minimaal gedoe.",
|
||||
"onboarding.modesCombine": "De standen zijn te combineren – simpel in het dagelijks leven, exact wanneer je wilt.",
|
||||
"onboarding.notMedical": "{brand} geeft richtlijnen – geen medisch advies.",
|
||||
"onboarding.stepBSubtitle": "Pas je profiel aan voor betere suggesties.",
|
||||
"onboarding.stepBTitle": "Doorgaan met instellen",
|
||||
@@ -270,6 +270,12 @@
|
||||
"recipe.time": "{min} min",
|
||||
"scan.analyzing": "Analyseren …",
|
||||
"scan.barcode": "Barcode",
|
||||
"scan.coach.body": "Take a photo of the whole shelf or fridge. Keep the phone steady and make sure the light is good.",
|
||||
"scan.coach.button": "Begrepen",
|
||||
"scan.coach.tip1": "Make sure all items are visible",
|
||||
"scan.coach.tip2": "Avoid flash and shadows",
|
||||
"scan.coach.tip3": "Confirm uncertain items in the next step",
|
||||
"scan.coach.title": "Eerste scan",
|
||||
"scan.expiry": "THT-datum",
|
||||
"scan.failed": "De analyse is mislukt. Probeer opnieuw of voer handmatig in.",
|
||||
"scan.freezer": "Vriezer",
|
||||
@@ -306,10 +312,10 @@
|
||||
"scan.title": "Scannen",
|
||||
"shopping.addPlaceholder": "Product toevoegen …",
|
||||
"shopping.complete": "Boodschappen afronden",
|
||||
"shopping.completeNote": "Afgevinkte producten gaan naar de voorraad.",
|
||||
"shopping.completedBody_one": "1 product is aan de voorraad toegevoegd.",
|
||||
"shopping.completedBody_other": "{count} producten zijn aan de voorraad toegevoegd.",
|
||||
"shopping.completedTitle": "Klaar!",
|
||||
"shopping.completeNote": "Afgevinkte producten gaan naar de voorraad.",
|
||||
"shopping.empty": "De lijst is leeg.",
|
||||
"shopping.estimated": "ca. {amount}",
|
||||
"shopping.estimatedTotal": "Geschat totaal: ca. {amount}",
|
||||
|
||||
@@ -197,9 +197,9 @@
|
||||
"onboarding.mode": "Jak dokładnie chcesz to robić?",
|
||||
"onboarding.modeExact": "Tryb dokładny",
|
||||
"onboarding.modeExactDesc": "Waż jedzenie, wpisuj gramy, pełna kontrola nad liczbami.",
|
||||
"onboarding.modesCombine": "Tryby można łączyć – na co dzień prosto, dokładnie, gdy chcesz.",
|
||||
"onboarding.modeSimple": "Tryb prosty",
|
||||
"onboarding.modeSimpleDesc": "Fotografuj jedzenie, akceptuj szacunki, minimum zachodu.",
|
||||
"onboarding.modesCombine": "Tryby można łączyć – na co dzień prosto, dokładnie, gdy chcesz.",
|
||||
"onboarding.notMedical": "{brand} to wskazówki – nie porada medyczna.",
|
||||
"onboarding.stepBSubtitle": "Dostosuj swój profil, aby uzyskać lepsze sugestie.",
|
||||
"onboarding.stepBTitle": "Kontynuuj konfigurację",
|
||||
@@ -280,6 +280,12 @@
|
||||
"recipe.time": "{min} min",
|
||||
"scan.analyzing": "Analizowanie …",
|
||||
"scan.barcode": "Kod kreskowy",
|
||||
"scan.coach.body": "Take a photo of the whole shelf or fridge. Keep the phone steady and make sure the light is good.",
|
||||
"scan.coach.button": "Rozumiem",
|
||||
"scan.coach.tip1": "Make sure all items are visible",
|
||||
"scan.coach.tip2": "Avoid flash and shadows",
|
||||
"scan.coach.tip3": "Confirm uncertain items in the next step",
|
||||
"scan.coach.title": "Pierwsze skanowanie",
|
||||
"scan.expiry": "Data przydatności",
|
||||
"scan.failed": "Analiza się nie powiodła. Spróbuj ponownie lub dodaj ręcznie.",
|
||||
"scan.freezer": "Zamrażarka",
|
||||
@@ -318,12 +324,12 @@
|
||||
"scan.title": "Skanuj",
|
||||
"shopping.addPlaceholder": "Dodaj produkt …",
|
||||
"shopping.complete": "Zakończ zakupy",
|
||||
"shopping.completeNote": "Odhaczone produkty trafiają do spiżarni.",
|
||||
"shopping.completedBody_few": "{count} produkty trafiły do spiżarni.",
|
||||
"shopping.completedBody_many": "{count} produktów trafiło do spiżarni.",
|
||||
"shopping.completedBody_one": "1 produkt trafił do spiżarni.",
|
||||
"shopping.completedBody_other": "{count} produktu trafiło do spiżarni.",
|
||||
"shopping.completedTitle": "Gotowe!",
|
||||
"shopping.completeNote": "Odhaczone produkty trafiają do spiżarni.",
|
||||
"shopping.empty": "Lista jest pusta.",
|
||||
"shopping.estimated": "ok. {amount}",
|
||||
"shopping.estimatedTotal": "Szacowana suma: ok. {amount}",
|
||||
|
||||
@@ -191,9 +191,9 @@
|
||||
"onboarding.mode": "Que nível de precisão quer?",
|
||||
"onboarding.modeExact": "Modo exato",
|
||||
"onboarding.modeExactDesc": "Pese a comida, introduza gramas, controlo total dos números.",
|
||||
"onboarding.modesCombine": "Os modos podem combinar-se: simples no dia a dia, exato quando quiser.",
|
||||
"onboarding.modeSimple": "Modo simples",
|
||||
"onboarding.modeSimpleDesc": "Fotografe a comida, aceite estimativas, zero complicações.",
|
||||
"onboarding.modesCombine": "Os modos podem combinar-se: simples no dia a dia, exato quando quiser.",
|
||||
"onboarding.notMedical": "{brand} dá orientações – não aconselhamento médico.",
|
||||
"onboarding.stepBSubtitle": "Personaliza o teu perfil para melhores sugestões.",
|
||||
"onboarding.stepBTitle": "Continuar configuração",
|
||||
@@ -270,6 +270,12 @@
|
||||
"recipe.time": "{min} min",
|
||||
"scan.analyzing": "A analisar …",
|
||||
"scan.barcode": "Código de barras",
|
||||
"scan.coach.body": "Take a photo of the whole shelf or fridge. Keep the phone steady and make sure the light is good.",
|
||||
"scan.coach.button": "Entendi",
|
||||
"scan.coach.tip1": "Make sure all items are visible",
|
||||
"scan.coach.tip2": "Avoid flash and shadows",
|
||||
"scan.coach.tip3": "Confirm uncertain items in the next step",
|
||||
"scan.coach.title": "Primeira verificação",
|
||||
"scan.expiry": "Data de validade",
|
||||
"scan.failed": "A análise falhou. Tente novamente ou adicione manualmente.",
|
||||
"scan.freezer": "Congelador",
|
||||
@@ -306,10 +312,10 @@
|
||||
"scan.title": "Digitalizar",
|
||||
"shopping.addPlaceholder": "Adicionar artigo …",
|
||||
"shopping.complete": "Terminar as compras",
|
||||
"shopping.completeNote": "Os artigos marcados vão para a despensa.",
|
||||
"shopping.completedBody_one": "1 produto foi adicionado à despensa.",
|
||||
"shopping.completedBody_other": "{count} produtos foram adicionados à despensa.",
|
||||
"shopping.completedTitle": "Feito!",
|
||||
"shopping.completeNote": "Os artigos marcados vão para a despensa.",
|
||||
"shopping.empty": "A lista está vazia.",
|
||||
"shopping.estimated": "aprox. {amount}",
|
||||
"shopping.estimatedTotal": "Total estimado: aprox. {amount}",
|
||||
|
||||
@@ -191,9 +191,9 @@
|
||||
"onboarding.mode": "Hur noggrann vill du vara?",
|
||||
"onboarding.modeExact": "Exakt läge",
|
||||
"onboarding.modeExactDesc": "Väg mat, ange gram, full kontroll på siffrorna.",
|
||||
"onboarding.modesCombine": "Lägena kan kombineras – enkelt till vardags, exakt när du vill.",
|
||||
"onboarding.modeSimple": "Enkelt läge",
|
||||
"onboarding.modeSimpleDesc": "Fota maten, acceptera uppskattningar, minimalt pyssel.",
|
||||
"onboarding.modesCombine": "Lägena kan kombineras – enkelt till vardags, exakt när du vill.",
|
||||
"onboarding.notMedical": "{brand} ger vägledning – inte medicinsk rådgivning.",
|
||||
"onboarding.stepBSubtitle": "Anpassa din profil för bättre förslag.",
|
||||
"onboarding.stepBTitle": "Fortsätt konfiguration",
|
||||
@@ -270,6 +270,12 @@
|
||||
"recipe.time": "{min} min",
|
||||
"scan.analyzing": "Analyserar …",
|
||||
"scan.barcode": "Streckkod",
|
||||
"scan.coach.body": "Fota hela hyllan eller kylen. Håll telefonen stilla och se till att ljuset är bra.",
|
||||
"scan.coach.button": "Jag förstår",
|
||||
"scan.coach.tip1": "Se till att alla varor syns",
|
||||
"scan.coach.tip2": "Undvik blixt och skuggor",
|
||||
"scan.coach.tip3": "Bekräfta osäkra rader i nästa steg",
|
||||
"scan.coach.title": "Första skanningen",
|
||||
"scan.expiry": "Bäst före-datum",
|
||||
"scan.failed": "Analysen misslyckades. Försök igen eller registrera manuellt.",
|
||||
"scan.freezer": "Frys",
|
||||
@@ -306,10 +312,10 @@
|
||||
"scan.title": "Skanna",
|
||||
"shopping.addPlaceholder": "Lägg till vara …",
|
||||
"shopping.complete": "Avsluta köprundan",
|
||||
"shopping.completeNote": "Bockade varor läggs in i matlagret.",
|
||||
"shopping.completedBody_one": "1 vara lades in i matlagret.",
|
||||
"shopping.completedBody_other": "{count} varor lades in i matlagret.",
|
||||
"shopping.completedTitle": "Klart!",
|
||||
"shopping.completeNote": "Bockade varor läggs in i matlagret.",
|
||||
"shopping.empty": "Listan är tom.",
|
||||
"shopping.estimated": "ca {amount}",
|
||||
"shopping.estimatedTotal": "Uppskattad totalsumma: ca {amount}",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
-- FAS 1b: Progressive onboarding (3-layer flow)
|
||||
-- Step A = immediate value, Step B = after first value, Step C = contextual/completed
|
||||
ALTER TABLE "users" ADD COLUMN "onboarding_step" text NOT NULL DEFAULT 'a';
|
||||
ALTER TABLE "users" ADD COLUMN IF NOT EXISTS "onboarding_step" text NOT NULL DEFAULT 'a';
|
||||
|
||||
-- Add feature flag for progressive onboarding rollout
|
||||
INSERT INTO "feature_flags" ("key", "enabled", "rollout_percent", "description_sv")
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
-- FAS 1b: Household activation milestones (spec §4.2)
|
||||
ALTER TABLE "households" ADD COLUMN IF NOT EXISTS "first_scan_completed_at" timestamp with time zone;
|
||||
ALTER TABLE "households" ADD COLUMN IF NOT EXISTS "fifth_item_confirmed_at" timestamp with time zone;
|
||||
ALTER TABLE "households" ADD COLUMN IF NOT EXISTS "first_recipe_recommendation_viewed_at" timestamp with time zone;
|
||||
ALTER TABLE "households" ADD COLUMN IF NOT EXISTS "first_recipe_saved_or_started_at" timestamp with time zone;
|
||||
ALTER TABLE "households" ADD COLUMN IF NOT EXISTS "activated_at" timestamp with time zone;
|
||||
ALTER TABLE "households" ADD COLUMN IF NOT EXISTS "first_cooking_session_completed_at" timestamp with time zone;
|
||||
ALTER TABLE "households" ADD COLUMN IF NOT EXISTS "inventory_updated_after_cooking_at" timestamp with time zone;
|
||||
ALTER TABLE "households" ADD COLUMN IF NOT EXISTS "value_confirmed_at" timestamp with time zone;
|
||||
@@ -0,0 +1,8852 @@
|
||||
{
|
||||
"id": "f41270f3-91e6-42b4-abdd-4eb8d7aff05b",
|
||||
"prevId": "b39083cb-bff1-4598-ac65-63e27ee32ac4",
|
||||
"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
|
||||
},
|
||||
"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_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'"
|
||||
},
|
||||
"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
|
||||
},
|
||||
"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_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.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
|
||||
},
|
||||
"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": {}
|
||||
}
|
||||
},
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"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
|
||||
},
|
||||
"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_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
|
||||
},
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"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": {}
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,20 @@
|
||||
"when": 1785947000212,
|
||||
"tag": "0003_low_dreadnoughts",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 4,
|
||||
"version": "7",
|
||||
"when": 1785952349917,
|
||||
"tag": "0004_progressive_onboarding",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 5,
|
||||
"version": "7",
|
||||
"when": 1785954010000,
|
||||
"tag": "0005_household_activation",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
import { eq } from "drizzle-orm";
|
||||
import type { NodePgDatabase } from "drizzle-orm/node-postgres";
|
||||
import * as schema from "./schema/index.js";
|
||||
|
||||
type Db = NodePgDatabase<typeof schema>;
|
||||
|
||||
export interface ActivationState {
|
||||
firstScanCompletedAt: Date | null;
|
||||
fifthItemConfirmedAt: Date | null;
|
||||
firstRecipeRecommendationViewedAt: Date | null;
|
||||
firstRecipeSavedOrStartedAt: Date | null;
|
||||
activatedAt: Date | null;
|
||||
firstCookingSessionCompletedAt: Date | null;
|
||||
inventoryUpdatedAfterCookingAt: Date | null;
|
||||
valueConfirmedAt: Date | null;
|
||||
}
|
||||
|
||||
function milestoneColumn(milestone: keyof ActivationState) {
|
||||
return schema.households[milestone] as (typeof schema.households)["firstScanCompletedAt"];
|
||||
}
|
||||
|
||||
const activationMilestones: Array<keyof ActivationState> = [
|
||||
"firstScanCompletedAt",
|
||||
"fifthItemConfirmedAt",
|
||||
"firstRecipeRecommendationViewedAt",
|
||||
"firstRecipeSavedOrStartedAt",
|
||||
"activatedAt",
|
||||
"firstCookingSessionCompletedAt",
|
||||
"inventoryUpdatedAfterCookingAt",
|
||||
"valueConfirmedAt",
|
||||
];
|
||||
|
||||
function isActivationMilestone(m: string): m is keyof ActivationState {
|
||||
return activationMilestones.includes(m as keyof ActivationState);
|
||||
}
|
||||
|
||||
/**
|
||||
* Markera en milstolpe och utvärdera aktiveringsstatus.
|
||||
* Endast den första bekräftelsen sparas; anrop är idempotenta.
|
||||
*/
|
||||
export async function markMilestone(
|
||||
db: Db,
|
||||
householdId: string,
|
||||
milestone: keyof ActivationState,
|
||||
): Promise<{ wasNew: boolean; activatedNow: boolean; valueConfirmedNow: boolean }> {
|
||||
if (!isActivationMilestone(milestone)) {
|
||||
throw new Error(`Unknown activation milestone: ${milestone}`);
|
||||
}
|
||||
|
||||
const [before] = await db
|
||||
.select({
|
||||
milestoneAt: milestoneColumn(milestone),
|
||||
firstScanCompletedAt: schema.households.firstScanCompletedAt,
|
||||
fifthItemConfirmedAt: schema.households.fifthItemConfirmedAt,
|
||||
firstRecipeRecommendationViewedAt: schema.households.firstRecipeRecommendationViewedAt,
|
||||
firstRecipeSavedOrStartedAt: schema.households.firstRecipeSavedOrStartedAt,
|
||||
activatedAt: schema.households.activatedAt,
|
||||
firstCookingSessionCompletedAt: schema.households.firstCookingSessionCompletedAt,
|
||||
inventoryUpdatedAfterCookingAt: schema.households.inventoryUpdatedAfterCookingAt,
|
||||
valueConfirmedAt: schema.households.valueConfirmedAt,
|
||||
})
|
||||
.from(schema.households)
|
||||
.where(eq(schema.households.id, householdId));
|
||||
|
||||
if (!before) {
|
||||
throw new Error(`Household not found: ${householdId}`);
|
||||
}
|
||||
|
||||
const wasNew = before.milestoneAt == null;
|
||||
const wasActivated = before.activatedAt != null;
|
||||
const wasValueConfirmed = before.valueConfirmedAt != null;
|
||||
|
||||
if (!wasNew) {
|
||||
return { wasNew: false, activatedNow: false, valueConfirmedNow: false };
|
||||
}
|
||||
|
||||
const isActivatedNow =
|
||||
before.firstScanCompletedAt != null &&
|
||||
before.fifthItemConfirmedAt != null &&
|
||||
before.firstRecipeRecommendationViewedAt != null &&
|
||||
(before.firstRecipeSavedOrStartedAt != null || milestone === "firstRecipeSavedOrStartedAt");
|
||||
|
||||
const activatedNow = !wasActivated && isActivatedNow;
|
||||
|
||||
const isValueConfirmedNow =
|
||||
isActivatedNow &&
|
||||
(before.firstCookingSessionCompletedAt != null || milestone === "firstCookingSessionCompletedAt") &&
|
||||
(before.inventoryUpdatedAfterCookingAt != null || milestone === "inventoryUpdatedAfterCookingAt");
|
||||
|
||||
const valueConfirmedNow = !wasValueConfirmed && isValueConfirmedNow;
|
||||
|
||||
const updates: Partial<typeof schema.households.$inferInsert> = {
|
||||
[milestone]: new Date(),
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
|
||||
if (activatedNow) {
|
||||
updates.activatedAt = new Date();
|
||||
}
|
||||
if (valueConfirmedNow) {
|
||||
updates.valueConfirmedAt = new Date();
|
||||
}
|
||||
|
||||
await db.update(schema.households).set(updates).where(eq(schema.households.id, householdId));
|
||||
|
||||
return { wasNew, activatedNow, valueConfirmedNow };
|
||||
}
|
||||
|
||||
export async function getActivationState(db: Db, householdId: string): Promise<ActivationState> {
|
||||
const [row] = await db
|
||||
.select({
|
||||
firstScanCompletedAt: schema.households.firstScanCompletedAt,
|
||||
fifthItemConfirmedAt: schema.households.fifthItemConfirmedAt,
|
||||
firstRecipeRecommendationViewedAt: schema.households.firstRecipeRecommendationViewedAt,
|
||||
firstRecipeSavedOrStartedAt: schema.households.firstRecipeSavedOrStartedAt,
|
||||
activatedAt: schema.households.activatedAt,
|
||||
firstCookingSessionCompletedAt: schema.households.firstCookingSessionCompletedAt,
|
||||
inventoryUpdatedAfterCookingAt: schema.households.inventoryUpdatedAfterCookingAt,
|
||||
valueConfirmedAt: schema.households.valueConfirmedAt,
|
||||
})
|
||||
.from(schema.households)
|
||||
.where(eq(schema.households.id, householdId));
|
||||
|
||||
return row ?? {
|
||||
firstScanCompletedAt: null,
|
||||
fifthItemConfirmedAt: null,
|
||||
firstRecipeRecommendationViewedAt: null,
|
||||
firstRecipeSavedOrStartedAt: null,
|
||||
activatedAt: null,
|
||||
firstCookingSessionCompletedAt: null,
|
||||
inventoryUpdatedAfterCookingAt: null,
|
||||
valueConfirmedAt: null,
|
||||
};
|
||||
}
|
||||
@@ -3,3 +3,4 @@ export * as schema from "./schema/index.js";
|
||||
export * from "./schema/index.js";
|
||||
export * from "./analytics-gdpr.js";
|
||||
export * from "./release-gates.js";
|
||||
export * from "./activation.js";
|
||||
|
||||
@@ -21,6 +21,24 @@ export const households = pgTable(
|
||||
name: text("name").notNull(),
|
||||
inviteCode: text("invite_code").notNull(),
|
||||
weeklyBudgetMinor: integer("weekly_budget_minor"),
|
||||
/** Fas 1b: aktiveringsmått (spec §4.2) */
|
||||
firstScanCompletedAt: timestamp("first_scan_completed_at", { withTimezone: true }),
|
||||
fifthItemConfirmedAt: timestamp("fifth_item_confirmed_at", { withTimezone: true }),
|
||||
firstRecipeRecommendationViewedAt: timestamp(
|
||||
"first_recipe_recommendation_viewed_at",
|
||||
{ withTimezone: true },
|
||||
),
|
||||
firstRecipeSavedOrStartedAt: timestamp("first_recipe_saved_or_started_at", {
|
||||
withTimezone: true,
|
||||
}),
|
||||
activatedAt: timestamp("activated_at", { withTimezone: true }),
|
||||
firstCookingSessionCompletedAt: timestamp("first_cooking_session_completed_at", {
|
||||
withTimezone: true,
|
||||
}),
|
||||
inventoryUpdatedAfterCookingAt: timestamp("inventory_updated_after_cooking_at", {
|
||||
withTimezone: true,
|
||||
}),
|
||||
valueConfirmedAt: timestamp("value_confirmed_at", { withTimezone: true }),
|
||||
/** ISO 4217. Pengagränsen går vid hushållet (i18n-spec §20) – alla belopp i hushållet tolkas i denna valuta. */
|
||||
currencyCode: varchar("currency_code", { length: 3 }).notNull().default("SEK"),
|
||||
createdAt: createdAt(),
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
import { describe, expect, it, beforeAll, afterAll } from "vitest";
|
||||
import { eq, inArray } from "drizzle-orm";
|
||||
import { createDatabase, closeDatabase, markMilestone, getActivationState, schema } from "@app/database";
|
||||
|
||||
describe("activation", () => {
|
||||
const { db } = createDatabase();
|
||||
const emails = ["act-test-1@example.invalid", "act-test-2@example.invalid"];
|
||||
const inviteCodes = ["ACT123", "ACT223"];
|
||||
|
||||
async function cleanup() {
|
||||
await db.delete(schema.householdMembers).where(
|
||||
inArray(
|
||||
schema.householdMembers.householdId,
|
||||
db.select({ id: schema.households.id }).from(schema.households).where(inArray(schema.households.inviteCode, inviteCodes)),
|
||||
),
|
||||
);
|
||||
await db.delete(schema.households).where(inArray(schema.households.inviteCode, inviteCodes));
|
||||
await db.delete(schema.users).where(inArray(schema.users.email, emails));
|
||||
}
|
||||
|
||||
beforeAll(cleanup);
|
||||
afterAll(async () => {
|
||||
await cleanup();
|
||||
await closeDatabase();
|
||||
});
|
||||
|
||||
it("marks first scan and activates household", async () => {
|
||||
const [user] = await db
|
||||
.insert(schema.users)
|
||||
.values({ email: emails[0]!, displayName: "Act" })
|
||||
.returning();
|
||||
const [household] = await db
|
||||
.insert(schema.households)
|
||||
.values({ name: "Act family", inviteCode: inviteCodes[0]! })
|
||||
.returning();
|
||||
await db.insert(schema.householdMembers).values({
|
||||
householdId: household.id,
|
||||
userId: user.id,
|
||||
role: "owner",
|
||||
});
|
||||
|
||||
const r1 = await markMilestone(db, household.id, "firstScanCompletedAt");
|
||||
expect(r1.wasNew).toBe(true);
|
||||
expect(r1.activatedNow).toBe(false);
|
||||
|
||||
await markMilestone(db, household.id, "fifthItemConfirmedAt");
|
||||
await markMilestone(db, household.id, "firstRecipeRecommendationViewedAt");
|
||||
const r2 = await markMilestone(db, household.id, "firstRecipeSavedOrStartedAt");
|
||||
expect(r2.activatedNow).toBe(true);
|
||||
|
||||
const state = await getActivationState(db, household.id);
|
||||
expect(state.activatedAt).not.toBeNull();
|
||||
expect(state.valueConfirmedAt).toBeNull();
|
||||
|
||||
await markMilestone(db, household.id, "firstCookingSessionCompletedAt");
|
||||
const r3 = await markMilestone(db, household.id, "inventoryUpdatedAfterCookingAt");
|
||||
expect(r3.valueConfirmedNow).toBe(true);
|
||||
|
||||
const state2 = await getActivationState(db, household.id);
|
||||
expect(state2.valueConfirmedAt).not.toBeNull();
|
||||
});
|
||||
|
||||
it("idempotent: does not move timestamps backward", async () => {
|
||||
const [user] = await db
|
||||
.insert(schema.users)
|
||||
.values({ email: emails[1]!, displayName: "Act2" })
|
||||
.returning();
|
||||
const [household] = await db
|
||||
.insert(schema.households)
|
||||
.values({ name: "Act2 family", inviteCode: inviteCodes[1]! })
|
||||
.returning();
|
||||
await db.insert(schema.householdMembers).values({
|
||||
householdId: household.id,
|
||||
userId: user.id,
|
||||
role: "owner",
|
||||
});
|
||||
|
||||
await markMilestone(db, household.id, "firstScanCompletedAt");
|
||||
const state1 = await getActivationState(db, household.id);
|
||||
await new Promise((r) => setTimeout(r, 20));
|
||||
await markMilestone(db, household.id, "firstScanCompletedAt");
|
||||
const state2 = await getActivationState(db, household.id);
|
||||
expect(state2.firstScanCompletedAt?.getTime()).toBe(state1.firstScanCompletedAt?.getTime());
|
||||
});
|
||||
});
|
||||
@@ -75,6 +75,8 @@ export const ANALYTICS_HOUSEHOLD_EVENT_NAMES = [
|
||||
"household_invite_sent",
|
||||
"household_invite_accepted",
|
||||
"second_member_first_action",
|
||||
"household_activated",
|
||||
"household_value_confirmed",
|
||||
] as const;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user