From 91964b7954f27247051c97688001f3256144d9e2 Mon Sep 17 00:00:00 2001 From: Ingrihka Date: Fri, 2 Oct 2026 14:34:34 +0200 Subject: [PATCH] setup: databaseskjema og tilkoblingspool for PostgreSQL (#7) --- backend/.env.example | 2 +- backend/db/migrations/001_init.sql | 73 ++++++++++++++++++++++++++++++ backend/src/db.ts | 10 ++++ 3 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 backend/db/migrations/001_init.sql create mode 100644 backend/src/db.ts diff --git a/backend/.env.example b/backend/.env.example index 13c79cb..caddef9 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -1,3 +1,3 @@ PORT=3001 -DATABASE_URL=postgres://brukernavn:passord@localhost:5432/handlelappen +DATABASE_URL=postgres://localhost:5432/handlelapp KASSALAPP_API_KEY= diff --git a/backend/db/migrations/001_init.sql b/backend/db/migrations/001_init.sql new file mode 100644 index 0000000..3569193 --- /dev/null +++ b/backend/db/migrations/001_init.sql @@ -0,0 +1,73 @@ +-- Databaseskjema for Handlelappen (issue #7) +-- Kjør fra backend/: psql handlelapp -f db/migrations/001_init.sql +-- Kan kjøres flere ganger uten feil (IF NOT EXISTS). + +CREATE EXTENSION IF NOT EXISTS pg_trgm; + +-- Kategoritre fra Kassalapp (/categories). parent_id er NULL for toppnivå. +CREATE TABLE IF NOT EXISTS categories ( + id INTEGER PRIMARY KEY, + parent_id INTEGER REFERENCES categories (id), + name TEXT NOT NULL +); + +-- Én rad per vare (EAN). Samme vare i flere kjeder deler rad. +-- category_id er NULL for varer uten kategori («Ukategorisert»). +CREATE TABLE IF NOT EXISTS products ( + ean TEXT PRIMARY KEY, + name TEXT NOT NULL, + brand TEXT, + vendor TEXT, + image_url TEXT, + description TEXT, + ingredients TEXT, + weight NUMERIC, + weight_unit TEXT, + nutrition JSONB, + allergens JSONB, + labels TEXT[] NOT NULL DEFAULT '{}', -- f.eks. {debio,nyt_norge} + category_id INTEGER REFERENCES categories (id) +); + +-- Én rad per vare per kjede. +CREATE TABLE IF NOT EXISTS prices ( + ean TEXT NOT NULL REFERENCES products (ean) ON DELETE CASCADE, + store_code TEXT NOT NULL, -- f.eks. KIWI, MENY_NO + price NUMERIC(10, 2) NOT NULL, + unit_price NUMERIC(10, 2), + url TEXT, + updated_at TIMESTAMPTZ, + PRIMARY KEY (ean, store_code) +); + +-- Brukergenererte data. owner_id er den anonyme bruker-ID-en fra nettleseren. +CREATE TABLE IF NOT EXISTS shopping_lists ( + id SERIAL PRIMARY KEY, + owner_id TEXT NOT NULL, + name TEXT NOT NULL, + status TEXT NOT NULL DEFAULT 'ACTIVE' CHECK (status IN ('ACTIVE', 'DONE')), + in_use BOOLEAN NOT NULL DEFAULT false, + created_at TIMESTAMPTZ NOT NULL DEFAULT now(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT now(), + completed_at TIMESTAMPTZ +); + +CREATE TABLE IF NOT EXISTS list_items ( + id SERIAL PRIMARY KEY, + list_id INTEGER NOT NULL REFERENCES shopping_lists (id) ON DELETE CASCADE, + ean TEXT NOT NULL REFERENCES products (ean), + quantity INTEGER NOT NULL DEFAULT 1 CHECK (quantity > 0), + note TEXT, + checked BOOLEAN NOT NULL DEFAULT false, + UNIQUE (list_id, ean) +); + +-- Indekser for søk, sortering og filtrering +CREATE INDEX IF NOT EXISTS products_name_trgm_idx ON products USING gin (name gin_trgm_ops); +CREATE INDEX IF NOT EXISTS products_brand_idx ON products (brand); +CREATE INDEX IF NOT EXISTS products_category_idx ON products (category_id); +CREATE INDEX IF NOT EXISTS products_labels_idx ON products USING gin (labels); +CREATE INDEX IF NOT EXISTS prices_price_idx ON prices (price); +CREATE INDEX IF NOT EXISTS prices_store_idx ON prices (store_code); +CREATE INDEX IF NOT EXISTS shopping_lists_owner_idx ON shopping_lists (owner_id); +CREATE INDEX IF NOT EXISTS list_items_list_idx ON list_items (list_id); diff --git a/backend/src/db.ts b/backend/src/db.ts new file mode 100644 index 0000000..f8034d6 --- /dev/null +++ b/backend/src/db.ts @@ -0,0 +1,10 @@ +import 'dotenv/config'; +import pg from 'pg'; + +const connectionString = process.env.DATABASE_URL; +if (!connectionString) { + throw new Error('DATABASE_URL mangler i backend/.env (se .env.example)'); +} + +// Én felles tilkoblingspool for hele backenden. +export const pool = new pg.Pool({ connectionString });