Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion backend/.env.example
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
PORT=3001
DATABASE_URL=postgres://brukernavn:passord@localhost:5432/handlelappen
DATABASE_URL=postgres://localhost:5432/handlelapp
KASSALAPP_API_KEY=
73 changes: 73 additions & 0 deletions backend/db/migrations/001_init.sql
Original file line number Diff line number Diff line change
@@ -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);
10 changes: 10 additions & 0 deletions backend/src/db.ts
Original file line number Diff line number Diff line change
@@ -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 });