diff --git a/backend/migrations/0001_schema.surql b/backend/migrations/0001_schema.surql new file mode 100644 index 0000000..d1d59bc --- /dev/null +++ b/backend/migrations/0001_schema.surql @@ -0,0 +1,339 @@ +-- TarninaHub schema (SurrealDB 3.x) — migration 0001 +-- Idempotent: uses IF NOT EXISTS, safe to re-run. Backend database is the +-- source of truth; the frontend (WASM) applies the same subset of definitions. + +-- --------------------------------------------------------------------------- +-- Reference / dictionaries +-- --------------------------------------------------------------------------- + +DEFINE TABLE IF NOT EXISTS social_challenge SCHEMAFULL + PERMISSIONS FOR select FULL, FOR create, update, delete NONE; +DEFINE FIELD IF NOT EXISTS name ON TABLE social_challenge TYPE string ASSERT string::len($value) > 0; +DEFINE FIELD IF NOT EXISTS description ON TABLE social_challenge TYPE string DEFAULT ""; +DEFINE FIELD IF NOT EXISTS order ON TABLE social_challenge TYPE int DEFAULT 0; +DEFINE INDEX IF NOT EXISTS social_challenge_name ON TABLE social_challenge FIELDS name UNIQUE; + +DEFINE TABLE IF NOT EXISTS tag SCHEMAFULL + PERMISSIONS FOR select FULL, FOR create, update, delete NONE; +DEFINE FIELD IF NOT EXISTS name ON TABLE tag TYPE string ASSERT string::len($value) > 0; +DEFINE FIELD IF NOT EXISTS group ON TABLE tag TYPE string + ASSERT $value IN ['audience', 'topic', 'stage', 'sector', 'cross']; +DEFINE INDEX IF NOT EXISTS tag_name_group ON TABLE tag FIELDS name, group UNIQUE; + +DEFINE TABLE IF NOT EXISTS location SCHEMAFULL + PERMISSIONS FOR select FULL, FOR create, update, delete NONE; +DEFINE FIELD IF NOT EXISTS name ON TABLE location TYPE string; +DEFINE FIELD IF NOT EXISTS kind ON TABLE location TYPE string + ASSERT $value IN ['gmina', 'powiat', 'wojewodztwo']; +DEFINE FIELD IF NOT EXISTS teryt ON TABLE location TYPE option; +DEFINE FIELD IF NOT EXISTS geometry ON TABLE location TYPE option; +DEFINE FIELD IF NOT EXISTS parent ON TABLE location TYPE option>; +DEFINE INDEX IF NOT EXISTS location_teryt ON TABLE location FIELDS teryt UNIQUE; + +DEFINE TABLE IF NOT EXISTS organization SCHEMAFULL + PERMISSIONS FOR select FULL, + FOR create, update WHERE $auth.role IN ['operator', 'admin'], + FOR delete NONE; +DEFINE FIELD IF NOT EXISTS name ON TABLE organization TYPE string; +DEFINE FIELD IF NOT EXISTS type ON TABLE organization TYPE string + ASSERT $value IN ['ngo', 'jst', 'institution', 'company']; +DEFINE FIELD IF NOT EXISTS location ON TABLE organization TYPE option>; +DEFINE FIELD IF NOT EXISTS contact ON TABLE organization TYPE option; + +-- --------------------------------------------------------------------------- +-- Identity (shared across departments) +-- --------------------------------------------------------------------------- + +DEFINE TABLE IF NOT EXISTS user SCHEMAFULL + PERMISSIONS + FOR select WHERE id = $auth.id OR $auth.role IN ['operator', 'manager', 'admin'], + FOR create, update WHERE id = $auth.id, + FOR delete NONE; +DEFINE FIELD IF NOT EXISTS phone ON TABLE user TYPE string; +DEFINE FIELD IF NOT EXISTS email ON TABLE user TYPE option; +DEFINE FIELD IF NOT EXISTS role ON TABLE user TYPE string + ASSERT $value IN ['user', 'jst', 'operator', 'manager', 'admin']; +DEFINE FIELD IF NOT EXISTS organization ON TABLE user TYPE option>; +DEFINE FIELD IF NOT EXISTS password ON TABLE user TYPE string; +DEFINE FIELD IF NOT EXISTS updated_at ON TABLE user TYPE option DEFAULT time::now(); +DEFINE FIELD IF NOT EXISTS version ON TABLE user TYPE int DEFAULT 1; +DEFINE FIELD IF NOT EXISTS deleted_at ON TABLE user TYPE option; +DEFINE INDEX IF NOT EXISTS user_phone ON TABLE user FIELDS phone UNIQUE; +DEFINE INDEX IF NOT EXISTS user_email ON TABLE user FIELDS email UNIQUE; + +-- Record access: users sign up / sign in and receive a JWT. +DEFINE ACCESS IF NOT EXISTS account ON DATABASE TYPE RECORD + SIGNUP ( CREATE user SET phone = $phone, email = $email, password = crypto::argon2::generate($password), role = 'user' ) + SIGNIN ( SELECT * FROM user WHERE phone = $phone AND crypto::argon2::compare(password, $password) ) + DURATION FOR TOKEN 15m, FOR SESSION 12h; + +-- --------------------------------------------------------------------------- +-- Matchmaking (F-1): problems, submissions, innovations +-- --------------------------------------------------------------------------- + +DEFINE TABLE IF NOT EXISTS problem SCHEMAFULL + PERMISSIONS + FOR select WHERE author = $auth.id OR visibility != 'private' OR $auth.role IN ['operator', 'manager', 'admin'], + FOR create, update WHERE author = $auth.id OR $auth.role IN ['operator', 'admin'], + FOR delete WHERE author = $auth.id OR $auth.role = 'admin'; +DEFINE FIELD IF NOT EXISTS title ON TABLE problem TYPE string; +DEFINE FIELD IF NOT EXISTS description ON TABLE problem TYPE string; +DEFINE FIELD IF NOT EXISTS challenge ON TABLE problem TYPE record; +DEFINE FIELD IF NOT EXISTS location ON TABLE problem TYPE record; +DEFINE FIELD IF NOT EXISTS author ON TABLE problem TYPE record; +DEFINE FIELD IF NOT EXISTS visibility ON TABLE problem TYPE string + ASSERT $value IN ['private', 'shared', 'public'] DEFAULT 'private'; +DEFINE FIELD IF NOT EXISTS tags ON TABLE problem TYPE array> DEFAULT []; +DEFINE FIELD IF NOT EXISTS updated_at ON TABLE problem TYPE option DEFAULT time::now(); +DEFINE FIELD IF NOT EXISTS version ON TABLE problem TYPE int DEFAULT 1; +DEFINE FIELD IF NOT EXISTS deleted_at ON TABLE problem TYPE option; +DEFINE INDEX IF NOT EXISTS problem_challenge ON TABLE problem FIELDS challenge; +DEFINE INDEX IF NOT EXISTS problem_location ON TABLE problem FIELDS location; + +DEFINE TABLE IF NOT EXISTS submission SCHEMAFULL + PERMISSIONS + FOR select WHERE submitted_by = $auth.id OR $auth.role IN ['operator', 'admin'], + FOR create WHERE submitted_by = $auth.id, + FOR update WHERE $auth.role IN ['operator', 'admin'], + FOR delete NONE; +DEFINE FIELD IF NOT EXISTS problem ON TABLE submission TYPE record; +DEFINE FIELD IF NOT EXISTS submitted_by ON TABLE submission TYPE record; +DEFINE FIELD IF NOT EXISTS status ON TABLE submission TYPE string + ASSERT $value IN ['new', 'triaged', 'matched', 'in_progress', 'closed', 'rejected'] DEFAULT 'new'; +DEFINE FIELD IF NOT EXISTS keywords ON TABLE submission TYPE array DEFAULT []; +DEFINE FIELD IF NOT EXISTS created_at ON TABLE submission TYPE datetime DEFAULT time::now(); +DEFINE FIELD IF NOT EXISTS updated_at ON TABLE submission TYPE option DEFAULT time::now(); +DEFINE FIELD IF NOT EXISTS version ON TABLE submission TYPE int DEFAULT 1; +DEFINE FIELD IF NOT EXISTS deleted_at ON TABLE submission TYPE option; +DEFINE INDEX IF NOT EXISTS submission_status ON TABLE submission FIELDS status; +DEFINE INDEX IF NOT EXISTS submission_problem ON TABLE submission FIELDS problem; + +DEFINE TABLE IF NOT EXISTS innovation SCHEMAFULL + PERMISSIONS FOR select FULL, + FOR create, update WHERE $auth.role IN ['operator', 'manager', 'admin'], + FOR delete NONE; +DEFINE FIELD IF NOT EXISTS title ON TABLE innovation TYPE string; +DEFINE FIELD IF NOT EXISTS summary ON TABLE innovation TYPE string DEFAULT ""; +DEFINE FIELD IF NOT EXISTS description ON TABLE innovation TYPE string DEFAULT ""; +DEFINE FIELD IF NOT EXISTS challenge ON TABLE innovation TYPE record; +DEFINE FIELD IF NOT EXISTS stage ON TABLE innovation TYPE string + ASSERT $value IN ['idea', 'pilot', 'scaling', 'service'] DEFAULT 'idea'; +DEFINE FIELD IF NOT EXISTS cost ON TABLE innovation TYPE option; +DEFINE FIELD IF NOT EXISTS contact ON TABLE innovation TYPE option; +DEFINE FIELD IF NOT EXISTS media ON TABLE innovation TYPE array DEFAULT []; +DEFINE FIELD IF NOT EXISTS embedding ON TABLE innovation TYPE option>; +DEFINE FIELD IF NOT EXISTS published ON TABLE innovation TYPE bool DEFAULT false; +DEFINE FIELD IF NOT EXISTS updated_at ON TABLE innovation TYPE option DEFAULT time::now(); +DEFINE FIELD IF NOT EXISTS version ON TABLE innovation TYPE int DEFAULT 1; +DEFINE INDEX IF NOT EXISTS innovation_challenge ON TABLE innovation FIELDS challenge; +DEFINE INDEX IF NOT EXISTS innovation_embedding ON TABLE innovation FIELDS embedding + HNSW DIMENSION 1536 DIST COSINE TYPE F32; + +-- --------------------------------------------------------------------------- +-- Idea creator (F-4) +-- --------------------------------------------------------------------------- + +DEFINE TABLE IF NOT EXISTS idea SCHEMAFULL + PERMISSIONS + FOR select WHERE author = $auth.id OR $auth.role IN ['manager', 'operator', 'admin'], + FOR create, update WHERE author = $auth.id, + FOR delete WHERE author = $auth.id; +DEFINE FIELD IF NOT EXISTS title ON TABLE idea TYPE string; +DEFINE FIELD IF NOT EXISTS description ON TABLE idea TYPE string DEFAULT ""; +DEFINE FIELD IF NOT EXISTS author ON TABLE idea TYPE record; +DEFINE FIELD IF NOT EXISTS status ON TABLE idea TYPE string + ASSERT $value IN ['draft', 'submitted', 'in_review', 'accepted', 'rejected'] DEFAULT 'draft'; +DEFINE FIELD IF NOT EXISTS updated_at ON TABLE idea TYPE option DEFAULT time::now(); +DEFINE FIELD IF NOT EXISTS version ON TABLE idea TYPE int DEFAULT 1; +DEFINE FIELD IF NOT EXISTS deleted_at ON TABLE idea TYPE option; +DEFINE INDEX IF NOT EXISTS idea_author ON TABLE idea FIELDS author; +DEFINE INDEX IF NOT EXISTS idea_status ON TABLE idea FIELDS status; + +DEFINE TABLE IF NOT EXISTS idea_card SCHEMAFULL + PERMISSIONS + FOR select WHERE idea.author = $auth.id OR $auth.role IN ['manager', 'operator', 'admin'], + FOR create, update WHERE idea.author = $auth.id, + FOR delete NONE; +DEFINE FIELD IF NOT EXISTS idea ON TABLE idea_card TYPE record; +DEFINE FIELD IF NOT EXISTS essence ON TABLE idea_card TYPE string DEFAULT ""; +DEFINE FIELD IF NOT EXISTS audience ON TABLE idea_card TYPE string DEFAULT ""; +DEFINE FIELD IF NOT EXISTS stage ON TABLE idea_card TYPE string + ASSERT $value IN ['idea', 'pilot', 'scaling', 'service'] DEFAULT 'idea'; +DEFINE INDEX IF NOT EXISTS idea_card_idea ON TABLE idea_card FIELDS idea UNIQUE; + +DEFINE TABLE IF NOT EXISTS grant_call SCHEMAFULL + PERMISSIONS FOR select FULL, + FOR create, update, delete WHERE $auth.role IN ['operator', 'admin']; +DEFINE FIELD IF NOT EXISTS name ON TABLE grant_call TYPE string; +DEFINE FIELD IF NOT EXISTS opens_at ON TABLE grant_call TYPE datetime; +DEFINE FIELD IF NOT EXISTS closes_at ON TABLE grant_call TYPE datetime; +DEFINE FIELD IF NOT EXISTS status ON TABLE grant_call TYPE string + ASSERT $value IN ['open', 'closed', 'planned'] DEFAULT 'planned'; +DEFINE FIELD IF NOT EXISTS external_id ON TABLE grant_call TYPE option; +DEFINE INDEX IF NOT EXISTS grant_call_status ON TABLE grant_call FIELDS status; + +DEFINE TABLE IF NOT EXISTS application SCHEMAFULL + PERMISSIONS + FOR select WHERE idea.author = $auth.id OR $auth.role IN ['operator', 'admin'], + FOR create WHERE idea.author = $auth.id, + FOR update WHERE idea.author = $auth.id OR $auth.role IN ['operator', 'admin'], + FOR delete NONE; +DEFINE FIELD IF NOT EXISTS grant_call ON TABLE application TYPE record; +DEFINE FIELD IF NOT EXISTS idea ON TABLE application TYPE record; +DEFINE FIELD IF NOT EXISTS content ON TABLE application TYPE object DEFAULT {}; +DEFINE FIELD IF NOT EXISTS status ON TABLE application TYPE string + ASSERT $value IN ['draft', 'submitted', 'accepted', 'rejected'] DEFAULT 'draft'; +DEFINE FIELD IF NOT EXISTS created_at ON TABLE application TYPE datetime DEFAULT time::now(); + +-- --------------------------------------------------------------------------- +-- Tester (F-5) +-- --------------------------------------------------------------------------- + +DEFINE TABLE IF NOT EXISTS test_feedback SCHEMAFULL + PERMISSIONS + FOR select WHERE author = $auth.id OR $auth.role IN ['manager', 'operator', 'admin'], + FOR create WHERE author = $auth.id, + FOR update, delete WHERE author = $auth.id; +DEFINE FIELD IF NOT EXISTS innovation ON TABLE test_feedback TYPE record; +DEFINE FIELD IF NOT EXISTS author ON TABLE test_feedback TYPE record; +DEFINE FIELD IF NOT EXISTS rating ON TABLE test_feedback TYPE int ASSERT $value >= 1 AND $value <= 5; +DEFINE FIELD IF NOT EXISTS comment ON TABLE test_feedback TYPE string DEFAULT ""; +DEFINE FIELD IF NOT EXISTS improvements ON TABLE test_feedback TYPE string DEFAULT ""; +DEFINE FIELD IF NOT EXISTS created_at ON TABLE test_feedback TYPE datetime DEFAULT time::now(); +DEFINE INDEX IF NOT EXISTS test_feedback_innovation ON TABLE test_feedback FIELDS innovation; + +-- --------------------------------------------------------------------------- +-- Communication (F-6) +-- --------------------------------------------------------------------------- + +DEFINE TABLE IF NOT EXISTS partnership SCHEMAFULL + PERMISSIONS + FOR select WHERE initiator = $auth.id OR $auth.role IN ['operator', 'admin'], + FOR create, update WHERE initiator = $auth.id OR $auth.role IN ['operator', 'admin'], + FOR delete NONE; +DEFINE FIELD IF NOT EXISTS initiator ON TABLE partnership TYPE record; +DEFINE FIELD IF NOT EXISTS subject ON TABLE partnership TYPE string; +DEFINE FIELD IF NOT EXISTS partners ON TABLE partnership TYPE array> DEFAULT []; +DEFINE FIELD IF NOT EXISTS status ON TABLE partnership TYPE string + ASSERT $value IN ['proposed', 'active', 'closed'] DEFAULT 'proposed'; + +DEFINE TABLE IF NOT EXISTS thread SCHEMAFULL + PERMISSIONS + FOR select WHERE $auth.id IN participants OR $auth.role IN ['operator', 'admin'], + FOR create WHERE $auth.id IN participants, + FOR update WHERE $auth.id IN participants, + FOR delete NONE; +DEFINE FIELD IF NOT EXISTS subject ON TABLE thread TYPE string; +DEFINE FIELD IF NOT EXISTS participants ON TABLE thread TYPE array> DEFAULT []; +DEFINE FIELD IF NOT EXISTS created_at ON TABLE thread TYPE datetime DEFAULT time::now(); + +DEFINE TABLE IF NOT EXISTS message SCHEMAFULL + PERMISSIONS + FOR select WHERE thread.participants CONTAINS $auth.id OR $auth.role IN ['operator', 'admin'], + FOR create WHERE thread.participants CONTAINS $auth.id, + FOR update, delete WHERE author = $auth.id; +DEFINE FIELD IF NOT EXISTS thread ON TABLE message TYPE record; +DEFINE FIELD IF NOT EXISTS author ON TABLE message TYPE record; +DEFINE FIELD IF NOT EXISTS body ON TABLE message TYPE string; +DEFINE FIELD IF NOT EXISTS created_at ON TABLE message TYPE datetime DEFAULT time::now(); +DEFINE INDEX IF NOT EXISTS message_thread ON TABLE message FIELDS thread; + +DEFINE TABLE IF NOT EXISTS notification SCHEMAFULL + PERMISSIONS + FOR select WHERE recipient = $auth.id, + FOR create WHERE $auth.role IN ['operator', 'admin'], + FOR update WHERE recipient = $auth.id, + FOR delete NONE; +DEFINE FIELD IF NOT EXISTS recipient ON TABLE notification TYPE record; +DEFINE FIELD IF NOT EXISTS type ON TABLE notification TYPE string; +DEFINE FIELD IF NOT EXISTS payload ON TABLE notification TYPE object DEFAULT {}; +DEFINE FIELD IF NOT EXISTS read_at ON TABLE notification TYPE option; +DEFINE FIELD IF NOT EXISTS created_at ON TABLE notification TYPE datetime DEFAULT time::now(); +DEFINE INDEX IF NOT EXISTS notification_recipient ON TABLE notification FIELDS recipient, read_at; + +DEFINE TABLE IF NOT EXISTS comment SCHEMAFULL + PERMISSIONS + FOR select FULL, + FOR create WHERE author = $auth.id, + FOR update, delete WHERE author = $auth.id; +DEFINE FIELD IF NOT EXISTS target ON TABLE comment TYPE string; +DEFINE FIELD IF NOT EXISTS author ON TABLE comment TYPE record; +DEFINE FIELD IF NOT EXISTS body ON TABLE comment TYPE string; +DEFINE FIELD IF NOT EXISTS created_at ON TABLE comment TYPE datetime DEFAULT time::now(); +DEFINE INDEX IF NOT EXISTS comment_target ON TABLE comment FIELDS target; + +DEFINE TABLE IF NOT EXISTS reaction SCHEMAFULL + PERMISSIONS + FOR select FULL, + FOR create, delete WHERE author = $auth.id, + FOR update NONE; +DEFINE FIELD IF NOT EXISTS target ON TABLE reaction TYPE string; +DEFINE FIELD IF NOT EXISTS author ON TABLE reaction TYPE record; +DEFINE FIELD IF NOT EXISTS kind ON TABLE reaction TYPE string; +DEFINE INDEX IF NOT EXISTS reaction_target ON TABLE reaction FIELDS target; + +-- --------------------------------------------------------------------------- +-- Knowledge base (F-3) +-- --------------------------------------------------------------------------- + +DEFINE TABLE IF NOT EXISTS knowledge_item SCHEMAFULL + PERMISSIONS FOR select FULL, + FOR create, update WHERE $auth.role IN ['operator', 'admin'], + FOR delete WHERE $auth.role = 'admin'; +DEFINE FIELD IF NOT EXISTS title ON TABLE knowledge_item TYPE string; +DEFINE FIELD IF NOT EXISTS kind ON TABLE knowledge_item TYPE string + ASSERT $value IN ['report', 'map', 'education', 'dataset', 'canvas', 'media']; +DEFINE FIELD IF NOT EXISTS challenge ON TABLE knowledge_item TYPE option>; +DEFINE FIELD IF NOT EXISTS source_url ON TABLE knowledge_item TYPE option; +DEFINE FIELD IF NOT EXISTS attachments ON TABLE knowledge_item TYPE array DEFAULT []; +DEFINE FIELD IF NOT EXISTS published ON TABLE knowledge_item TYPE bool DEFAULT false; +DEFINE FIELD IF NOT EXISTS updated_at ON TABLE knowledge_item TYPE option DEFAULT time::now(); + +DEFINE ANALYZER IF NOT EXISTS ascii TOKENIZERS class FILTERS lowercase, ascii; +DEFINE INDEX IF NOT EXISTS knowledge_item_ft ON TABLE knowledge_item FIELDS title + FULLTEXT ANALYZER ascii BM25; + +-- --------------------------------------------------------------------------- +-- Documents, consent, audit (privacy) +-- --------------------------------------------------------------------------- + +DEFINE TABLE IF NOT EXISTS document SCHEMAFULL + PERMISSIONS + FOR select WHERE owner = $auth.id OR $auth.role IN ['operator', 'admin'], + FOR create WHERE owner = $auth.id, + FOR update WHERE owner = $auth.id, + FOR delete NONE; +DEFINE FIELD IF NOT EXISTS title ON TABLE document TYPE string; +DEFINE FIELD IF NOT EXISTS doc_type ON TABLE document TYPE string; +DEFINE FIELD IF NOT EXISTS processing_purpose ON TABLE document TYPE string; +DEFINE FIELD IF NOT EXISTS scope ON TABLE document TYPE string + ASSERT $value IN ['local', 'partial', 'full', 'anonymized'] DEFAULT 'local'; +DEFINE FIELD IF NOT EXISTS owner ON TABLE document TYPE record; +DEFINE FIELD IF NOT EXISTS archived ON TABLE document TYPE bool DEFAULT false; +DEFINE FIELD IF NOT EXISTS related ON TABLE document TYPE option; +DEFINE FIELD IF NOT EXISTS updated_at ON TABLE document TYPE option DEFAULT time::now(); +DEFINE FIELD IF NOT EXISTS version ON TABLE document TYPE int DEFAULT 1; +DEFINE FIELD IF NOT EXISTS deleted_at ON TABLE document TYPE option; +DEFINE INDEX IF NOT EXISTS document_owner ON TABLE document FIELDS owner; + +DEFINE TABLE IF NOT EXISTS consent SCHEMAFULL + PERMISSIONS + FOR select WHERE user = $auth.id OR $auth.role IN ['operator', 'admin'], + FOR create WHERE user = $auth.id, + FOR update WHERE user = $auth.id, + FOR delete NONE; +DEFINE FIELD IF NOT EXISTS user ON TABLE consent TYPE record; +DEFINE FIELD IF NOT EXISTS scope ON TABLE consent TYPE string; +DEFINE FIELD IF NOT EXISTS granted_at ON TABLE consent TYPE datetime DEFAULT time::now(); +DEFINE FIELD IF NOT EXISTS expires_at ON TABLE consent TYPE option; +DEFINE FIELD IF NOT EXISTS revoked_at ON TABLE consent TYPE option; +DEFINE INDEX IF NOT EXISTS consent_user ON TABLE consent FIELDS user; + +DEFINE TABLE IF NOT EXISTS audit_log SCHEMAFULL + PERMISSIONS FOR select WHERE $auth.role = 'admin', FOR create, update, delete NONE; +DEFINE FIELD IF NOT EXISTS actor ON TABLE audit_log TYPE option>; +DEFINE FIELD IF NOT EXISTS action ON TABLE audit_log TYPE string; +DEFINE FIELD IF NOT EXISTS target ON TABLE audit_log TYPE string; +DEFINE FIELD IF NOT EXISTS context ON TABLE audit_log TYPE object DEFAULT {}; +DEFINE FIELD IF NOT EXISTS at ON TABLE audit_log TYPE datetime DEFAULT time::now(); +DEFINE INDEX IF NOT EXISTS audit_log_at ON TABLE audit_log FIELDS at; diff --git a/backend/migrations/0002_taxonomy_seed.surql b/backend/migrations/0002_taxonomy_seed.surql new file mode 100644 index 0000000..3dcfa82 --- /dev/null +++ b/backend/migrations/0002_taxonomy_seed.surql @@ -0,0 +1,42 @@ +-- TarninaHub taxonomy seed — migration 0002 +-- Idempotent. Mirrors the 8 social challenges from the ROPS Map of +-- Social Challenges plus grouped tags and a sample of locations. + +INSERT IGNORE INTO social_challenge [ + { id: social_challenge:rodzina, name: 'Rodzina i piecza zastępcza', order: 1 }, + { id: social_challenge:bezdomnosc, name: 'Bezdomność', order: 2 }, + { id: social_challenge:niepelnosprawnosc, name: 'Niepełnosprawność', order: 3 }, + { id: social_challenge:ubostwo, name: 'Ubóstwo', order: 4 }, + { id: social_challenge:integracja_cudzoziemcow, name: 'Integracja cudzoziemców', order: 5 }, + { id: social_challenge:zdrowie, name: 'Zdrowie', order: 6 }, + { id: social_challenge:zdrowie_psychiczne, name: 'Zdrowie psychiczne', order: 7 }, + { id: social_challenge:seniorzy, name: 'Seniorzy', order: 8 } +]; + +INSERT IGNORE INTO tag [ + { id: tag:seniorzy, name: 'seniorzy', group: 'audience' }, + { id: tag:mlodziez, name: 'młodzież', group: 'audience' }, + { id: tag:rodziny, name: 'rodziny', group: 'audience' }, + { id: tag:osoby_z_niepelnosprawnosciami, name: 'osoby z niepełnosprawnościami', group: 'audience' }, + { id: tag:zdrowie_psychiczne, name: 'zdrowie psychiczne', group: 'topic' }, + { id: tag:samotnosc, name: 'samotność', group: 'topic' }, + { id: tag:transport, name: 'transport', group: 'topic' }, + { id: tag:pomysl, name: 'pomysł', group: 'stage' }, + { id: tag:pilotaz, name: 'pilotaż', group: 'stage' }, + { id: tag:skalowanie, name: 'skalowanie', group: 'stage' }, + { id: tag:usluga, name: 'usługa', group: 'stage' }, + { id: tag:ngo, name: 'NGO', group: 'sector' }, + { id: tag:jst, name: 'JST', group: 'sector' }, + { id: tag:instytucja, name: 'instytucja', group: 'sector' }, + { id: tag:firma, name: 'firma', group: 'sector' }, + { id: tag:wykluczenie_cyfrowe, name: 'wykluczenie cyfrowe', group: 'cross' }, + { id: tag:koordynacja_miedzyinstytucjonalna, name: 'koordynacja międzyinstytucjonalna', group: 'cross' }, + { id: tag:dostep_do_uslug, name: 'dostęp do usług', group: 'cross' } +]; + +INSERT IGNORE INTO location [ + { id: location:malopolska, name: 'Małopolska', kind: 'wojewodztwo', teryt: '12' }, + { id: location:krakow, name: 'Kraków', kind: 'powiat', teryt: '1261' }, + { id: location:tarnow, name: 'Tarnów', kind: 'powiat', teryt: '1263' }, + { id: location:nowy_sacz, name: 'Nowy Sącz', kind: 'powiat', teryt: '1262' } +]; diff --git a/backend/migrations/README.md b/backend/migrations/README.md new file mode 100644 index 0000000..97e9afd --- /dev/null +++ b/backend/migrations/README.md @@ -0,0 +1,41 @@ +# Migracje bazy danych (SurrealDB) + +Migracje w SurrealQL, wersjonowane w git. Backend (plik/RocksDB) jest źródłem prawdy; frontend (WASM) stosuje ten sam podzbiór definicji przy starcie. + +## Pliki + +| Plik | Zawartość | +|---|---| +| `0001_schema.surql` | tabele, pola, indeksy (w tym HNSW + pełnotekstowy), `ACCESS`, `PERMISSIONS`, `ASSERT` | +| `0002_taxonomy_seed.surql` | 8 obszarów (`social_challenge`), tagi, próbka lokalizacji | +| `0003_demo_seed.surql` | dane syntetyczne (dodawane w kroku 09) | + +## Zasady + +- Numeracja `NNNN_nazwa.surql`, stosowana w kolejności rosnącej. +- Migracje **idempotentne** (`DEFINE ... OVERWRITE`, `INSERT IGNORE`), addytywne. +- Zmiany schematu wyłącznie przez nowy plik migracji — bez ręcznych zmian na środowisku. +- Brak danych osobowych/wrażliwych w seedach (krok 09). + +## Aplikowanie + +SurrealDB CLI 3.x. `surreal sql` nie ma już flagi `--file` — przekaż plik przez stdin z `--multi` (średniki rozdzielają instrukcje): + +```pwsh +Get-Content -Raw backend/migrations/0001_schema.surql | ` + surreal sql -e http://127.0.0.1:18010 -u root -p root --ns hubmi --db hubmi --hide-welcome --multi +Get-Content -Raw backend/migrations/0002_taxonomy_seed.surql | ` + surreal sql -e http://127.0.0.1:18010 -u root -p root --ns hubmi --db hubmi --hide-welcome --multi +``` + +Alternatywnie `surreal import` — wymaga `OPTION IMPORT;` jako pierwszej instrukcji pliku (wyłącza przetwarzanie pól). + +Embedded RocksDB: uruchom `pwsh ./scripts/surrealdb-start.ps1` (tryb zdalny) albo zastosuj migracje przez backend przy starcie (krok 10). + +## Weryfikacja + +Zwalidowano na **SurrealDB 3.2.4** (instancja in-memory): schemat i seed aplikują się bez błędów, ponowne uruchomienie jest idempotentne (22 tabele, 8 obszarów, 18 tagów, 4 lokalizacje), a CRUD na encjach (`create`/`update`/`select`/`delete`) działa. + +## Frontend (WASM) + +Frontend stosuje podzbiór `0001_schema.surql` do `indxdb://tarninahub` (bez serwerowych `PERMISSIONS` i `ACCESS`). Reguły synchronizacji: patrz `10_ZalozeniaProjektowe/todo08/BAZA_DANYCH.md` §6.