diff --git a/lib/db/migrations/005_cross.sql b/lib/db/migrations/005_cross.sql new file mode 100644 index 0000000..2beaf82 --- /dev/null +++ b/lib/db/migrations/005_cross.sql @@ -0,0 +1,43 @@ +CREATE TABLE tags ( + id uuid PRIMARY KEY DEFAULT gen_random_uuid(), + name text NOT NULL UNIQUE, + description text, + color text, + created_at timestamptz NOT NULL DEFAULT now() +); + +CREATE TABLE entity_tags ( + entity_type text NOT NULL, + entity_id uuid NOT NULL, + tag_id uuid NOT NULL REFERENCES tags(id) ON DELETE CASCADE, + created_at timestamptz NOT NULL DEFAULT now(), + PRIMARY KEY (entity_type, entity_id, tag_id) +); + +CREATE TABLE entity_links ( + id uuid PRIMARY KEY DEFAULT gen_random_uuid(), + from_type text NOT NULL, + from_id uuid NOT NULL, + to_type text NOT NULL, + to_id uuid NOT NULL, + relation text NOT NULL DEFAULT 'attached', + created_at timestamptz NOT NULL DEFAULT now(), + UNIQUE (from_type, from_id, to_type, to_id, relation) +); + +CREATE TABLE attachments ( + id uuid PRIMARY KEY DEFAULT gen_random_uuid(), + entity_type text, + entity_id uuid, + filename text NOT NULL, + mime_type text, + size_bytes bigint, + blob_path text NOT NULL, + checksum text, + uploaded_at timestamptz NOT NULL DEFAULT now() +); + +CREATE INDEX idx_entity_tags_entity ON entity_tags(entity_type, entity_id); +CREATE INDEX idx_entity_links_from ON entity_links(from_type, from_id); +CREATE INDEX idx_entity_links_to ON entity_links(to_type, to_id); +CREATE INDEX idx_attachments_entity ON attachments(entity_type, entity_id); diff --git a/tests/db/migration_005.test.js b/tests/db/migration_005.test.js new file mode 100644 index 0000000..d7e328c --- /dev/null +++ b/tests/db/migration_005.test.js @@ -0,0 +1,16 @@ +import { describe, it, expect, beforeAll } from 'vitest'; +import { resetDb, withClient } from '../helpers/db.js'; +import { migrateUp } from '../../lib/db/migrate.js'; + +describe('migration 005 — cross', () => { + beforeAll(async () => { await resetDb(); await migrateUp(); }); + + it('creates tags, entity_tags, entity_links, attachments', async () => { + await withClient(async (c) => { + for (const t of ['tags','entity_tags','entity_links','attachments']) { + const { rows } = await c.query(`SELECT to_regclass('public.' || $1) AS t;`, [t]); + expect(rows[0].t).toBe(t); + } + }); + }); +});