diff --git a/apps/api/src/lib/helpers.ts b/apps/api/src/lib/helpers.ts index 4d15a26..bcec8a2 100644 --- a/apps/api/src/lib/helpers.ts +++ b/apps/api/src/lib/helpers.ts @@ -47,6 +47,41 @@ export async function requireMembership( return { role: member.role, portionFactor: member.portionFactor }; } +/** Skapa hushåll med standard-förvaringsplatser (spec §7–8). Återanvänds av + * både POST /v1/households och onboarding auto-create. */ +export async function createHouseholdWithDefaults( + db: Database, + { + userId, + name, + size, + }: { + userId: string; + name: string; + size?: number; + }, +): Promise<{ id: string; name: string }> { + const [household] = await db + .insert(schema.households) + .values({ + name, + inviteCode: generateInviteCode(), + size: size ?? null, + }) + .returning(); + await db.insert(schema.householdMembers).values({ + householdId: household!.id, + userId, + role: "owner", + }); + await db.insert(schema.storageLocations).values([ + { householdId: household!.id, type: "fridge", name: "Kylen", sortOrder: 0 }, + { householdId: household!.id, type: "freezer", name: "Frysen", sortOrder: 1 }, + { householdId: household!.id, type: "pantry", name: "Skafferiet", sortOrder: 2 }, + ]); + return { id: household!.id, name: household!.name }; +} + /** Hämta användarens aktiva hushåll (första medlemskapet) eller null. */ export async function getActiveHouseholdId(db: Database, userId: string): Promise { const [member] = await db diff --git a/apps/api/src/lib/i18n.ts b/apps/api/src/lib/i18n.ts new file mode 100644 index 0000000..9e2d7f8 --- /dev/null +++ b/apps/api/src/lib/i18n.ts @@ -0,0 +1,31 @@ +import { readFileSync } from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); + +const cache: Record> = {}; + +/** + * Minimal server-side i18n lookup into the mobile app's common.json files. + * Falls back to the requested key if the translation is missing. + */ +export function t(key: string, languageTag: string): string { + const lang = (languageTag.split("-")[0] ?? languageTag).toLowerCase(); + let dict = cache[lang]; + if (!dict) { + try { + const file = path.resolve( + __dirname, + "../../../../apps/mobile/src/locales", + lang, + "common.json", + ); + dict = JSON.parse(readFileSync(file, "utf8")) as Record; + } catch { + dict = {}; + } + cache[lang] = dict; + } + return dict[key] ?? key; +} diff --git a/apps/api/src/routes/households.ts b/apps/api/src/routes/households.ts index 4188e34..f572c34 100644 --- a/apps/api/src/routes/households.ts +++ b/apps/api/src/routes/households.ts @@ -13,7 +13,13 @@ import { } from "@app/validation"; import { loadEntitlements } from "../lib/entitlements.js"; import { errors, parse } from "../lib/errors.js"; -import { audit, emitEvent, generateInviteCode, requireMembership } from "../lib/helpers.js"; +import { + audit, + createHouseholdWithDefaults, + emitEvent, + generateInviteCode, + requireMembership, +} from "../lib/helpers.js"; /** Hushåll (spec §7): delat lager/plan/lista, individuella mål och roller. */ export async function householdRoutes(app: FastifyInstance) { @@ -34,23 +40,20 @@ export async function householdRoutes(app: FastifyInstance) { app.post("/v1/households", auth, async (req, reply) => { const input = parse(createHouseholdInputSchema, req.body); - const [household] = await app.db - .insert(schema.households) - .values({ - name: input.name, - inviteCode: generateInviteCode(), - weeklyBudgetMinor: input.weeklyBudgetMinor ?? null, - ...(input.currencyCode ? { currencyCode: input.currencyCode } : {}), - }) - .returning(); - await app.db - .insert(schema.householdMembers) - .values({ householdId: household!.id, userId: req.userId, role: "owner" }); - await app.db.insert(schema.storageLocations).values([ - { householdId: household!.id, type: "fridge", name: "Kylen", sortOrder: 0 }, - { householdId: household!.id, type: "freezer", name: "Frysen", sortOrder: 1 }, - { householdId: household!.id, type: "pantry", name: "Skafferiet", sortOrder: 2 }, - ]); + const household = await createHouseholdWithDefaults(app.db, { + userId: req.userId, + name: input.name, + }); + if (input.weeklyBudgetMinor != null || input.currencyCode) { + await app.db + .update(schema.households) + .set({ + weeklyBudgetMinor: input.weeklyBudgetMinor ?? null, + ...(input.currencyCode ? { currencyCode: input.currencyCode } : {}), + updatedAt: new Date(), + }) + .where(eq(schema.households.id, household.id)); + } return reply.status(201).send(household); }); diff --git a/apps/api/src/routes/onboarding.ts b/apps/api/src/routes/onboarding.ts index 9b0173b..8347965 100644 --- a/apps/api/src/routes/onboarding.ts +++ b/apps/api/src/routes/onboarding.ts @@ -4,7 +4,13 @@ import { z } from "zod"; import { schema } from "@app/database"; import { quickStartInputSchema, onboardingInputSchema } from "@app/validation"; import { errors, parse } from "../lib/errors.js"; -import { audit, generateInviteCode, getActiveHouseholdId } from "../lib/helpers.js"; +import { + audit, + createHouseholdWithDefaults, + getActiveHouseholdId, +} from "../lib/helpers.js"; + +import { t } from "../lib/i18n.js"; import { KNOWN_FLAGS } from "@app/feature-flags"; /** @@ -87,6 +93,24 @@ export async function onboardingRoutes(app: FastifyInstance) { }, }); + // Auto-create a household so the user can scan, shop, plan and budget + // immediately instead of hitting the "no household" wall in 21 endpoints. + let householdId = await getActiveHouseholdId(app.db, req.userId); + if (!householdId) { + const [user] = await app.db + .select({ locale: schema.users.locale }) + .from(schema.users) + .where(eq(schema.users.id, req.userId)) + .limit(1); + const householdName = t("onboarding.householdDefaultName", user?.locale ?? "sv-SE"); + const household = await createHouseholdWithDefaults(app.db, { + userId: req.userId, + name: householdName, + size: input.persons, + }); + householdId = household.id; + } + // Advance to step B const [user] = await app.db .update(schema.users) @@ -101,7 +125,7 @@ export async function onboardingRoutes(app: FastifyInstance) { await audit(app.db, { actorUserId: req.userId, action: "onboarding.quick_start", - metadata: { goals, primaryGoal, precisionMode: input.precisionMode }, + metadata: { goals, primaryGoal, precisionMode: input.precisionMode, householdId }, ip: req.ip, correlationId: req.correlationId, }); @@ -110,6 +134,7 @@ export async function onboardingRoutes(app: FastifyInstance) { ok: true, step: user!.onboardingStep, onboardingCompleted: user!.onboardingCompleted, + householdId, }; }); @@ -128,22 +153,20 @@ export async function onboardingRoutes(app: FastifyInstance) { } let householdId: string | null = await getActiveHouseholdId(app.db, req.userId); - if (input.householdChoice.kind === "create" && !householdId) { - const [household] = await app.db - .insert(schema.households) - .values({ name: input.householdChoice.name, inviteCode: generateInviteCode() }) - .returning(); - await app.db.insert(schema.householdMembers).values({ - householdId: household!.id, - userId: req.userId, - role: "owner", - }); - await app.db.insert(schema.storageLocations).values([ - { householdId: household!.id, type: "fridge", name: "Kylen", sortOrder: 0 }, - { householdId: household!.id, type: "freezer", name: "Frysen", sortOrder: 1 }, - { householdId: household!.id, type: "pantry", name: "Skafferiet", sortOrder: 2 }, - ]); - householdId = household!.id; + if (input.householdChoice.kind === "create") { + if (householdId) { + // Auto-created household already exists; update name if user provided a custom one. + await app.db + .update(schema.households) + .set({ name: input.householdChoice.name, updatedAt: new Date() }) + .where(eq(schema.households.id, householdId)); + } else { + const household = await createHouseholdWithDefaults(app.db, { + userId: req.userId, + name: input.householdChoice.name, + }); + householdId = household.id; + } } else if (input.householdChoice.kind === "join") { const [household] = await app.db .select() diff --git a/apps/api/test/recommendations.test.ts b/apps/api/test/recommendations.test.ts index 33e1e18..5eb5e0c 100644 --- a/apps/api/test/recommendations.test.ts +++ b/apps/api/test/recommendations.test.ts @@ -1,5 +1,6 @@ +import "./setup-env.js"; import { describe, expect, it, beforeAll, afterAll } from "vitest"; -import { eq, inArray } from "drizzle-orm"; +import { and, eq, inArray } from "drizzle-orm"; import { buildServer } from "../src/server.js"; import { loadConfig } from "../src/config.js"; import { createDatabase, closeDatabase, schema } from "@app/database"; @@ -9,22 +10,32 @@ import { createDatabase, closeDatabase, schema } from "@app/database"; * created a household yet (empty pantry, single-person context). */ describe("what-to-eat without household", () => { - const testDb = createDatabase(process.env.TEST_DATABASE_URL); - const config = loadConfig({ - ...process.env, - DATABASE_URL: process.env.TEST_DATABASE_URL!, - }); + const testDb = createDatabase(process.env.TEST_DATABASE_URL!); + const config = loadConfig(); let app: Awaited>; let accessToken: string; const userEmail = "what-to-eat-repro@example.invalid"; async function cleanup() { - const emails = [userEmail, "goals-multi@example.invalid"]; + const emails = [userEmail, "goals-multi@example.invalid", "auto-household@example.invalid"]; const existing = await testDb.db .select({ id: schema.users.id }) .from(schema.users) .where(inArray(schema.users.email, emails)); for (const u of existing) { + await testDb.db.delete(schema.householdMembers).where(eq(schema.householdMembers.userId, u.id)); + const ownedHouseholds = await testDb.db + .select({ id: schema.households.id }) + .from(schema.households) + .innerJoin( + schema.householdMembers, + eq(schema.householdMembers.householdId, schema.households.id), + ) + .where(and(eq(schema.householdMembers.userId, u.id), eq(schema.householdMembers.role, "owner"))); + for (const h of ownedHouseholds) { + await testDb.db.delete(schema.storageLocations).where(eq(schema.storageLocations.householdId, h.id)); + await testDb.db.delete(schema.households).where(eq(schema.households.id, h.id)); + } await testDb.db.delete(schema.userPreferences).where(eq(schema.userPreferences.userId, u.id)); await testDb.db.delete(schema.users).where(eq(schema.users.id, u.id)); } @@ -101,4 +112,38 @@ describe("what-to-eat without household", () => { expect(prefs?.goals).toEqual(["cook_more", "less_waste"]); expect(prefs?.primaryGoal).toBe("cook_more"); }); + + it("quick-start auto-creates a household with default storage locations", async () => { + const registerRes = await app.inject({ + method: "POST", + url: "/v1/auth/register", + payload: { email: "auto-household@example.invalid", password: "Password123!", displayName: "Auto" }, + }); + const { accessToken: token } = JSON.parse(registerRes.body) as { accessToken: string }; + const userId = (JSON.parse(atob(token.split(".")[1]!)) as { sub: string }).sub; + + const quickRes = await app.inject({ + method: "POST", + url: "/v1/onboarding/quick-start", + headers: { authorization: `Bearer ${token}` }, + payload: { goals: ["cook_more"], persons: 2, precisionMode: "simple" }, + }); + expect(quickRes.statusCode).toBe(200); + const { householdId } = JSON.parse(quickRes.body) as { householdId: string }; + expect(householdId).toBeTruthy(); + + const [household] = await testDb.db + .select() + .from(schema.households) + .where(eq(schema.households.id, householdId)) + .limit(1); + expect(household).toBeTruthy(); + expect(household!.size).toBe(2); + + const locations = await testDb.db + .select() + .from(schema.storageLocations) + .where(eq(schema.storageLocations.householdId, householdId)); + expect(locations.map((l) => l.type).sort()).toEqual(["freezer", "fridge", "pantry"]); + }); }); diff --git a/apps/api/test/setup-env.ts b/apps/api/test/setup-env.ts new file mode 100644 index 0000000..94483c1 --- /dev/null +++ b/apps/api/test/setup-env.ts @@ -0,0 +1,15 @@ +/** + * Hermetic test environment for API integration tests. + * Must run BEFORE any application module is imported so that `loadConfig` + * sees deterministic values instead of whatever happens to be in `.env`. + */ +process.env.NODE_ENV = "test"; +process.env.AAMOS_MODE = "mock"; +process.env.EMAIL_MODE = "log"; +process.env.S3_MODE = "mock"; +process.env.LOG_LEVEL = "error"; + +// Local test database fallback – tests still need a Postgres instance, but +// the connection string is not a secret and the value is predictable. +process.env.TEST_DATABASE_URL ||= "postgres://app_user:app_dev_password@localhost:5432/cibello_test"; +process.env.DATABASE_URL = process.env.TEST_DATABASE_URL; diff --git a/apps/api/vitest.config.ts b/apps/api/vitest.config.ts new file mode 100644 index 0000000..3614e4e --- /dev/null +++ b/apps/api/vitest.config.ts @@ -0,0 +1,13 @@ +import { defineConfig } from "vitest/config"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); + +export default defineConfig({ + test: { + environment: "node", + globals: false, + setupFiles: [path.resolve(__dirname, "test/setup-env.ts")], + }, +}); diff --git a/infrastructure/migrations/0006_wakeful_virginia_dare.sql b/infrastructure/migrations/0006_wakeful_virginia_dare.sql new file mode 100644 index 0000000..99c91d7 --- /dev/null +++ b/infrastructure/migrations/0006_wakeful_virginia_dare.sql @@ -0,0 +1 @@ +ALTER TABLE "households" ADD COLUMN "size" integer; \ No newline at end of file diff --git a/infrastructure/migrations/meta/0006_snapshot.json b/infrastructure/migrations/meta/0006_snapshot.json new file mode 100644 index 0000000..e2ff9be --- /dev/null +++ b/infrastructure/migrations/meta/0006_snapshot.json @@ -0,0 +1,8858 @@ +{ + "id": "135e7c6d-c037-48b9-a512-2b0e895be4c7", + "prevId": "f41270f3-91e6-42b4-abdd-4eb8d7aff05b", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.admin_totp": { + "name": "admin_totp", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "secret_base32": { + "name": "secret_base32", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enabled_at": { + "name": "enabled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_used_step": { + "name": "last_used_step", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "admin_totp_user_id_users_id_fk": { + "name": "admin_totp_user_id_users_id_fk", + "tableFrom": "admin_totp", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.email_verification_tokens": { + "name": "email_verification_tokens", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "used_at": { + "name": "used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "email_verification_tokens_user_idx": { + "name": "email_verification_tokens_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "email_verification_tokens_hash_unique": { + "name": "email_verification_tokens_hash_unique", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "email_verification_tokens_user_id_users_id_fk": { + "name": "email_verification_tokens_user_id_users_id_fk", + "tableFrom": "email_verification_tokens", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.password_reset_tokens": { + "name": "password_reset_tokens", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "used_at": { + "name": "used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "password_reset_tokens_user_idx": { + "name": "password_reset_tokens_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "password_reset_tokens_hash_unique": { + "name": "password_reset_tokens_hash_unique", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "password_reset_tokens_user_id_users_id_fk": { + "name": "password_reset_tokens_user_id_users_id_fk", + "tableFrom": "password_reset_tokens", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.refresh_tokens": { + "name": "refresh_tokens", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "family_id": { + "name": "family_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "replaced_by_token_id": { + "name": "replaced_by_token_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ip": { + "name": "ip", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "refresh_tokens_user_idx": { + "name": "refresh_tokens_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "refresh_tokens_hash_unique": { + "name": "refresh_tokens_hash_unique", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "refresh_tokens_user_id_users_id_fk": { + "name": "refresh_tokens_user_id_users_id_fk", + "tableFrom": "refresh_tokens", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_consents": { + "name": "user_consents", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "consent_kind", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "consent_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "granted_at": { + "name": "granted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "user_consents_user_id_users_id_fk": { + "name": "user_consents_user_id_users_id_fk", + "tableFrom": "user_consents", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "user_consents_user_id_kind_pk": { + "name": "user_consents_user_id_kind_pk", + "columns": [ + "user_id", + "kind" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_credentials": { + "name": "user_credentials", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "password_hash": { + "name": "password_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "password_updated_at": { + "name": "password_updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "user_credentials_user_id_users_id_fk": { + "name": "user_credentials_user_id_users_id_fk", + "tableFrom": "user_credentials", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_health_profiles": { + "name": "user_health_profiles", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "birth_year": { + "name": "birth_year", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "sex": { + "name": "sex", + "type": "sex", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "height_cm": { + "name": "height_cm", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "weight_kg": { + "name": "weight_kg", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "target_weight_kg": { + "name": "target_weight_kg", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "activity_level": { + "name": "activity_level", + "type": "activity_level", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'moderate'" + }, + "training_sessions_per_week": { + "name": "training_sessions_per_week", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "training_types": { + "name": "training_types", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "user_health_profiles_user_id_users_id_fk": { + "name": "user_health_profiles_user_id_users_id_fk", + "tableFrom": "user_health_profiles", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_preferences": { + "name": "user_preferences", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "primary_goal": { + "name": "primary_goal", + "type": "goal_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "goals": { + "name": "goals", + "type": "goal_type[]", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "diet_pattern": { + "name": "diet_pattern", + "type": "diet_pattern", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'omnivore'" + }, + "religious_rule": { + "name": "religious_rule", + "type": "religious_rule", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "allergens": { + "name": "allergens", + "type": "allergen[]", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "intolerances": { + "name": "intolerances", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "avoid_ingredient_ids": { + "name": "avoid_ingredient_ids", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "favorite_cuisines": { + "name": "favorite_cuisines", + "type": "cuisine[]", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "disliked_dishes": { + "name": "disliked_dishes", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "spice_level_max": { + "name": "spice_level_max", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 3 + }, + "weekly_budget_minor": { + "name": "weekly_budget_minor", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "max_cooking_minutes_weekday": { + "name": "max_cooking_minutes_weekday", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "equipment": { + "name": "equipment", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "default_portions": { + "name": "default_portions", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 2 + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "user_preferences_user_id_users_id_fk": { + "name": "user_preferences_user_id_users_id_fk", + "tableFrom": "user_preferences", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_verified_at": { + "name": "email_verified_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "user_role", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'user'" + }, + "locale": { + "name": "locale", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'sv-SE'" + }, + "precision_mode": { + "name": "precision_mode", + "type": "precision_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'simple'" + }, + "onboarding_completed": { + "name": "onboarding_completed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "onboarding_step": { + "name": "onboarding_step", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'a'" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "users_email_unique": { + "name": "users_email_unique", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_locale_preferences": { + "name": "user_locale_preferences", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "language_tag": { + "name": "language_tag", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'sv-SE'" + }, + "region_code": { + "name": "region_code", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'SE'" + }, + "time_zone": { + "name": "time_zone", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'Europe/Stockholm'" + }, + "measurement_system": { + "name": "measurement_system", + "type": "measurement_system", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'METRIC'" + }, + "temperature_unit": { + "name": "temperature_unit", + "type": "temperature_unit", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'CELSIUS'" + }, + "currency_code": { + "name": "currency_code", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'SEK'" + }, + "first_day_of_week": { + "name": "first_day_of_week", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "use_24_hour_time": { + "name": "use_24_hour_time", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "user_locale_preferences_user_id_users_id_fk": { + "name": "user_locale_preferences_user_id_users_id_fk", + "tableFrom": "user_locale_preferences", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.household_members": { + "name": "household_members", + "schema": "", + "columns": { + "household_id": { + "name": "household_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "household_role", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'member'" + }, + "portion_factor": { + "name": "portion_factor", + "type": "double precision", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "joined_at": { + "name": "joined_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "household_members_user_idx": { + "name": "household_members_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "household_members_household_id_households_id_fk": { + "name": "household_members_household_id_households_id_fk", + "tableFrom": "household_members", + "tableTo": "households", + "columnsFrom": [ + "household_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "household_members_user_id_users_id_fk": { + "name": "household_members_user_id_users_id_fk", + "tableFrom": "household_members", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "household_members_household_id_user_id_pk": { + "name": "household_members_household_id_user_id_pk", + "columns": [ + "household_id", + "user_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.households": { + "name": "households", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "invite_code": { + "name": "invite_code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "weekly_budget_minor": { + "name": "weekly_budget_minor", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "first_scan_completed_at": { + "name": "first_scan_completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "fifth_item_confirmed_at": { + "name": "fifth_item_confirmed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "first_recipe_recommendation_viewed_at": { + "name": "first_recipe_recommendation_viewed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "first_recipe_saved_or_started_at": { + "name": "first_recipe_saved_or_started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "activated_at": { + "name": "activated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "first_cooking_session_completed_at": { + "name": "first_cooking_session_completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "inventory_updated_after_cooking_at": { + "name": "inventory_updated_after_cooking_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "value_confirmed_at": { + "name": "value_confirmed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "size": { + "name": "size", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "currency_code": { + "name": "currency_code", + "type": "varchar(3)", + "primaryKey": false, + "notNull": true, + "default": "'SEK'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "households_invite_code_unique": { + "name": "households_invite_code_unique", + "columns": [ + { + "expression": "invite_code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.storage_locations": { + "name": "storage_locations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "household_id": { + "name": "household_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "storage_location_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sublocations": { + "name": "sublocations", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "storage_locations_household_idx": { + "name": "storage_locations_household_idx", + "columns": [ + { + "expression": "household_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "storage_locations_household_id_households_id_fk": { + "name": "storage_locations_household_id_households_id_fk", + "tableFrom": "storage_locations", + "tableTo": "households", + "columnsFrom": [ + "household_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.canonical_ingredients": { + "name": "canonical_ingredients", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name_sv": { + "name": "name_sv", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name_en": { + "name": "name_en", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "aliases": { + "name": "aliases", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "default_unit": { + "name": "default_unit", + "type": "unit", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "density_g_per_ml": { + "name": "density_g_per_ml", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "grams_per_piece": { + "name": "grams_per_piece", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "allergens": { + "name": "allergens", + "type": "allergen[]", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "is_vegan": { + "name": "is_vegan", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_vegetarian": { + "name": "is_vegetarian", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "contains_gluten": { + "name": "contains_gluten", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "contains_lactose": { + "name": "contains_lactose", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_pork": { + "name": "is_pork", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_beef": { + "name": "is_beef", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_alcohol": { + "name": "is_alcohol", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "nutrition_per_100": { + "name": "nutrition_per_100", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "nutrition_provenance": { + "name": "nutrition_provenance", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "peak_seasons": { + "name": "peak_seasons", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "shelf_life_guidance": { + "name": "shelf_life_guidance", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "default_price_minor_per_kg": { + "name": "default_price_minor_per_kg", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "canonical_ingredients_category_idx": { + "name": "canonical_ingredients_category_idx", + "columns": [ + { + "expression": "category", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.substitutions": { + "name": "substitutions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "from_ingredient_id": { + "name": "from_ingredient_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "to_ingredient_id": { + "name": "to_ingredient_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "ratio": { + "name": "ratio", + "type": "double precision", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "instructions_sv": { + "name": "instructions_sv", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "best_for": { + "name": "best_for", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "not_recommended_for": { + "name": "not_recommended_for", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "flavor_impact_sv": { + "name": "flavor_impact_sv", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "texture_impact_sv": { + "name": "texture_impact_sv", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": { + "substitutions_from_idx": { + "name": "substitutions_from_idx", + "columns": [ + { + "expression": "from_ingredient_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "substitutions_pair_unique": { + "name": "substitutions_pair_unique", + "columns": [ + { + "expression": "from_ingredient_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "to_ingredient_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "substitutions_from_ingredient_id_canonical_ingredients_id_fk": { + "name": "substitutions_from_ingredient_id_canonical_ingredients_id_fk", + "tableFrom": "substitutions", + "tableTo": "canonical_ingredients", + "columnsFrom": [ + "from_ingredient_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "substitutions_to_ingredient_id_canonical_ingredients_id_fk": { + "name": "substitutions_to_ingredient_id_canonical_ingredients_id_fk", + "tableFrom": "substitutions", + "tableTo": "canonical_ingredients", + "columnsFrom": [ + "to_ingredient_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.products": { + "name": "products", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "gtin": { + "name": "gtin", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "brand": { + "name": "brand", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "canonical_ingredient_id": { + "name": "canonical_ingredient_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "package_size_value": { + "name": "package_size_value", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "package_size_unit": { + "name": "package_size_unit", + "type": "unit", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "ingredients_text": { + "name": "ingredients_text", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "allergens": { + "name": "allergens", + "type": "allergen[]", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "may_contain_allergens": { + "name": "may_contain_allergens", + "type": "allergen[]", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "nutrition": { + "name": "nutrition", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "image_urls": { + "name": "image_urls", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "language": { + "name": "language", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'sv'" + }, + "market": { + "name": "market", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'SE'" + }, + "data_source": { + "name": "data_source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "verification_status": { + "name": "verification_status", + "type": "verification_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'unverified'" + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "valid_from": { + "name": "valid_from", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "valid_to": { + "name": "valid_to", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "products_gtin_version_unique": { + "name": "products_gtin_version_unique", + "columns": [ + { + "expression": "gtin", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"products\".\"gtin\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "products_gtin_current_idx": { + "name": "products_gtin_current_idx", + "columns": [ + { + "expression": "gtin", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"products\".\"valid_to\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "products_canonical_idx": { + "name": "products_canonical_idx", + "columns": [ + { + "expression": "canonical_ingredient_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "products_canonical_ingredient_id_canonical_ingredients_id_fk": { + "name": "products_canonical_ingredient_id_canonical_ingredients_id_fk", + "tableFrom": "products", + "tableTo": "canonical_ingredients", + "columnsFrom": [ + "canonical_ingredient_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.inventory_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": {} + } +} \ No newline at end of file diff --git a/infrastructure/migrations/meta/_journal.json b/infrastructure/migrations/meta/_journal.json index 910df6d..bfe9e5b 100644 --- a/infrastructure/migrations/meta/_journal.json +++ b/infrastructure/migrations/meta/_journal.json @@ -43,6 +43,13 @@ "when": 1785954010000, "tag": "0005_household_activation", "breakpoints": true + }, + { + "idx": 6, + "version": "7", + "when": 1786028467254, + "tag": "0006_wakeful_virginia_dare", + "breakpoints": true } ] -} +} \ No newline at end of file diff --git a/packages/database/src/schema/households.ts b/packages/database/src/schema/households.ts index 7ae21ca..f62f46c 100644 --- a/packages/database/src/schema/households.ts +++ b/packages/database/src/schema/households.ts @@ -39,6 +39,8 @@ export const households = pgTable( withTimezone: true, }), valueConfirmedAt: timestamp("value_confirmed_at", { withTimezone: true }), + /** Antal personer i hushållet – anges tidigt i onboarding för att styra portionsant och budget. */ + size: integer("size"), /** 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(), diff --git a/packages/validation/src/profile.ts b/packages/validation/src/profile.ts index 472ab1e..04488d2 100644 --- a/packages/validation/src/profile.ts +++ b/packages/validation/src/profile.ts @@ -77,6 +77,8 @@ export const quickStartInputSchema = z.object({ primaryGoal: z.enum(GOAL_TYPES).optional(), // Multi-select goals (new UI). First goal is considered primary. goals: z.array(z.enum(GOAL_TYPES)).max(11).optional(), + // Household size for the auto-created household (default 1 if omitted). + persons: z.coerce.number().int().min(1).max(50).optional(), precisionMode: z.enum(PRECISION_MODES).default("simple"), }); export type QuickStartInput = z.infer;