Compare commits
5
Commits
2401991870
...
0a5ef715eb
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0a5ef715eb | ||
|
|
6df1027699 | ||
|
|
67ec85b6cc | ||
|
|
70fb592cf7 | ||
|
|
6c78351a95 |
@@ -0,0 +1,24 @@
|
||||
# TarninaHub — backend runtime configuration example.
|
||||
# Copy to `.env` (not tracked) or provide as environment variables.
|
||||
# Never commit real secrets.
|
||||
|
||||
ASPNETCORE_ENVIRONMENT=Production
|
||||
ASPNETCORE_URLS=http://+:8080
|
||||
|
||||
# SurrealDB — embedded RocksDB by default.
|
||||
SurrealDb__Namespace=hubmi
|
||||
SurrealDb__Database=hubmi
|
||||
SurrealDb__DataDirectory=data
|
||||
|
||||
# SurrealDB — remote mode (optional; leave DataDirectory unused when set).
|
||||
# SurrealDb__RemoteUrl=ws://127.0.0.1:8010/rpc
|
||||
# SurrealDb__Username=
|
||||
# SurrealDb__Password=
|
||||
|
||||
# LLM (F-8, matchmaking) — runtime secret only.
|
||||
# LLM__ApiKey=
|
||||
|
||||
# Observability (optional).
|
||||
# Logging__LogLevel__Default=Information
|
||||
# OTEL_EXPORTER_OTLP_ENDPOINT=
|
||||
# OTEL_SERVICE_NAME=tarninahub
|
||||
@@ -0,0 +1,50 @@
|
||||
# CD workflow — DISABLED.
|
||||
# No Gitea Actions runner is attached to this repository yet, so the workflow
|
||||
# below is fully commented out to avoid failing runs. Uncomment everything once
|
||||
# a runner is registered (repository Settings -> Actions -> Runners).
|
||||
|
||||
# name: CD
|
||||
#
|
||||
# on:
|
||||
# push:
|
||||
# branches: [main]
|
||||
# tags: ["v*"]
|
||||
#
|
||||
# jobs:
|
||||
# publish:
|
||||
# runs-on: ubuntu-latest
|
||||
# steps:
|
||||
# - uses: actions/checkout@v4
|
||||
# - name: Read version
|
||||
# id: version
|
||||
# run: |
|
||||
# VERSION=$(sed -n 's:.*<Version>\(.*\)</Version>.*:\1:p' backend/TarninaHub.csproj | head -1)
|
||||
# echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
||||
# - name: Log in to registry
|
||||
# run: echo "${{ secrets.GITEA_TOKEN }}" | docker login git.rtynski.pl -u "${{ github.actor }}" --password-stdin
|
||||
# - name: Build image
|
||||
# run: |
|
||||
# docker build \
|
||||
# --build-arg APP_VERSION=${{ steps.version.outputs.version }} \
|
||||
# -t git.rtynski.pl/rtynski/hy2026:${{ steps.version.outputs.version }} \
|
||||
# -t git.rtynski.pl/rtynski/hy2026:latest .
|
||||
# - name: Push image
|
||||
# run: |
|
||||
# docker push git.rtynski.pl/rtynski/hy2026:${{ steps.version.outputs.version }}
|
||||
# docker push git.rtynski.pl/rtynski/hy2026:latest
|
||||
#
|
||||
# # Deploy demo runs after the image is published from `main`.
|
||||
# # Configure DEPLOY_HOST / DEPLOY_USER / DEPLOY_SSH_KEY as CI secrets.
|
||||
# # deploy:
|
||||
# # needs: publish
|
||||
# # if: github.ref == 'refs/heads/main'
|
||||
# # runs-on: ubuntu-latest
|
||||
# # steps:
|
||||
# # - name: Deploy over SSH
|
||||
# # run: |
|
||||
# # echo "${{ secrets.DEPLOY_SSH_KEY }}" > key && chmod 600 key
|
||||
# # ssh -i key -o StrictHostKeyChecking=no \
|
||||
# # "${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }}" \
|
||||
# # "cd /opt/tarninahub && APP_VERSION=latest docker compose pull && docker compose up -d"
|
||||
# # - name: Smoke test
|
||||
# # run: curl -fsS "http://${{ secrets.DEPLOY_HOST }}:8075/api/health"
|
||||
@@ -0,0 +1,46 @@
|
||||
# CI workflow — DISABLED.
|
||||
# No Gitea Actions runner is attached to this repository yet, so the workflow
|
||||
# below is fully commented out to avoid failing runs. Uncomment everything once
|
||||
# a runner is registered (repository Settings -> Actions -> Runners).
|
||||
|
||||
# name: CI
|
||||
#
|
||||
# on:
|
||||
# push:
|
||||
# branches: [main]
|
||||
# pull_request:
|
||||
# branches: [main]
|
||||
#
|
||||
# jobs:
|
||||
# backend:
|
||||
# runs-on: ubuntu-latest
|
||||
# steps:
|
||||
# - uses: actions/checkout@v4
|
||||
# - uses: actions/setup-dotnet@v4
|
||||
# with:
|
||||
# dotnet-version: "10.0.x"
|
||||
# - name: Restore
|
||||
# run: dotnet restore backend/TarninaHub.csproj
|
||||
# - name: Build
|
||||
# run: dotnet build backend/TarninaHub.csproj --configuration Release --no-restore
|
||||
# - name: Format check
|
||||
# run: dotnet format backend/TarninaHub.csproj --verify-no-changes
|
||||
# # Tests: enabled once test projects are added (`dotnet test`).
|
||||
#
|
||||
# frontend:
|
||||
# runs-on: ubuntu-latest
|
||||
# defaults:
|
||||
# run:
|
||||
# working-directory: client
|
||||
# steps:
|
||||
# - uses: actions/checkout@v4
|
||||
# - uses: actions/setup-node@v4
|
||||
# with:
|
||||
# node-version: "22"
|
||||
# cache: npm
|
||||
# cache-dependency-path: client/package-lock.json
|
||||
# - name: Install
|
||||
# run: npm ci
|
||||
# - name: Build (typecheck + bundle)
|
||||
# run: npm run build
|
||||
# # Lint: enabled once ESLint is wired up (`npm run lint`).
|
||||
@@ -84,6 +84,31 @@ kopiuje go do `backend/wwwroot`, buduje backend, buduje obraz Docker i wypycha g
|
||||
do `git.rtynski.pl/rtynski/hy2026` jako `<wersja>` oraz `latest`. Nadpisanie
|
||||
sprawdzenia: `-Force`.
|
||||
|
||||
## CI/CD
|
||||
|
||||
Pipeline działa w **Gitea Actions** (`.gitea/workflows/`):
|
||||
|
||||
> **Uwaga:** workflow są zakomentowane w całości — do repozytorium nie jest podpięty runner Gitea. Odkomentuj po zarejestrowaniu runera (Settings → Actions → Runners).
|
||||
|
||||
- `ci.yml` — na `push`/PR do `main`: build backendu (.NET 10), `dotnet format --verify-no-changes` oraz build klienta (Node 22, `vue-tsc` + bundle).
|
||||
- `cd.yml` — na `main`/tag `v*`: build obrazu i push do `git.rtynski.pl/rtynski/hy2026` (`<wersja>` + `latest`); deploy demo opcjonalnie przez sekrety CI.
|
||||
|
||||
Sekrety CI: `GITEA_TOKEN` (`write:package`), opcjonalnie `DEPLOY_HOST`/`DEPLOY_USER`/`DEPLOY_SSH_KEY`. Klucz `LLM__ApiKey` przekazujemy wyłącznie na runtime — nigdy do repo ani warstw obrazu.
|
||||
|
||||
## Środowiska i konfiguracja
|
||||
|
||||
- **dev** — `appsettings.Development.json`, dane w `backend/data`.
|
||||
- **demo** — obraz z rejestru, `docker-compose.yaml.example`, port `TARNINAHUB_PORT` (8075), wersja `APP_VERSION`.
|
||||
- **staging** (opcjonalne) — kandydat wydania przed demo.
|
||||
|
||||
Konfiguracja przez zmienne środowiskowe (`SurrealDb__*`, `LLM__ApiKey`, `OTEL_*`); szablon: [`.env.example`](.env.example).
|
||||
|
||||
## Observability
|
||||
|
||||
- Healthcheck: `GET /api/health` (używany też przez Docker healthcheck).
|
||||
- Logi strukturalne (JSON) do `stdout`/`stderr`, z correlation ID.
|
||||
- Docelowo: `/metrics` (Prometheus) i OpenTelemetry (traces).
|
||||
|
||||
## Struktura
|
||||
|
||||
```
|
||||
|
||||
@@ -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<string>;
|
||||
DEFINE FIELD IF NOT EXISTS geometry ON TABLE location TYPE option<geometry>;
|
||||
DEFINE FIELD IF NOT EXISTS parent ON TABLE location TYPE option<record<location>>;
|
||||
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<record<location>>;
|
||||
DEFINE FIELD IF NOT EXISTS contact ON TABLE organization TYPE option<object>;
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- 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<string>;
|
||||
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<record<organization>>;
|
||||
DEFINE FIELD IF NOT EXISTS password ON TABLE user TYPE string;
|
||||
DEFINE FIELD IF NOT EXISTS updated_at ON TABLE user TYPE option<datetime> 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<datetime>;
|
||||
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<social_challenge>;
|
||||
DEFINE FIELD IF NOT EXISTS location ON TABLE problem TYPE record<location>;
|
||||
DEFINE FIELD IF NOT EXISTS author ON TABLE problem TYPE record<user>;
|
||||
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<record<tag>> DEFAULT [];
|
||||
DEFINE FIELD IF NOT EXISTS updated_at ON TABLE problem TYPE option<datetime> 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<datetime>;
|
||||
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<problem>;
|
||||
DEFINE FIELD IF NOT EXISTS submitted_by ON TABLE submission TYPE record<user>;
|
||||
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<string> 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<datetime> 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<datetime>;
|
||||
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<social_challenge>;
|
||||
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<string>;
|
||||
DEFINE FIELD IF NOT EXISTS contact ON TABLE innovation TYPE option<object>;
|
||||
DEFINE FIELD IF NOT EXISTS media ON TABLE innovation TYPE array<object> DEFAULT [];
|
||||
DEFINE FIELD IF NOT EXISTS embedding ON TABLE innovation TYPE option<array<float>>;
|
||||
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<datetime> 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<user>;
|
||||
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<datetime> 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<datetime>;
|
||||
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<idea>;
|
||||
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<string>;
|
||||
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<grant_call>;
|
||||
DEFINE FIELD IF NOT EXISTS idea ON TABLE application TYPE record<idea>;
|
||||
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<innovation>;
|
||||
DEFINE FIELD IF NOT EXISTS author ON TABLE test_feedback TYPE record<user>;
|
||||
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<user>;
|
||||
DEFINE FIELD IF NOT EXISTS subject ON TABLE partnership TYPE string;
|
||||
DEFINE FIELD IF NOT EXISTS partners ON TABLE partnership TYPE array<record<organization>> 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<record<user>> 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<thread>;
|
||||
DEFINE FIELD IF NOT EXISTS author ON TABLE message TYPE record<user>;
|
||||
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<user>;
|
||||
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<datetime>;
|
||||
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<user>;
|
||||
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<user>;
|
||||
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<record<social_challenge>>;
|
||||
DEFINE FIELD IF NOT EXISTS source_url ON TABLE knowledge_item TYPE option<string>;
|
||||
DEFINE FIELD IF NOT EXISTS attachments ON TABLE knowledge_item TYPE array<object> 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<datetime> 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<user>;
|
||||
DEFINE FIELD IF NOT EXISTS archived ON TABLE document TYPE bool DEFAULT false;
|
||||
DEFINE FIELD IF NOT EXISTS related ON TABLE document TYPE option<string>;
|
||||
DEFINE FIELD IF NOT EXISTS updated_at ON TABLE document TYPE option<datetime> 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<datetime>;
|
||||
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<user>;
|
||||
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<datetime>;
|
||||
DEFINE FIELD IF NOT EXISTS revoked_at ON TABLE consent TYPE option<datetime>;
|
||||
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<record<user>>;
|
||||
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;
|
||||
@@ -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' }
|
||||
];
|
||||
@@ -0,0 +1,15 @@
|
||||
-- TarninaHub — migration 0003: additive schema for demo/UI
|
||||
-- Idempotent, additive. Required before the demo seed (0004).
|
||||
|
||||
-- Human-friendly label for synthetic demo profiles (never personal data).
|
||||
DEFINE FIELD IF NOT EXISTS display_name ON TABLE user TYPE option<string>;
|
||||
|
||||
-- Innovations are taggable (matchmaking filters), mirroring `problem.tags`.
|
||||
DEFINE FIELD IF NOT EXISTS tags ON TABLE innovation TYPE array<record<tag>> DEFAULT [];
|
||||
|
||||
-- Free-form payloads must be FLEXIBLE (SCHEMAFULL otherwise rejects nested keys).
|
||||
DEFINE FIELD OVERWRITE contact ON TABLE organization TYPE option<object> FLEXIBLE;
|
||||
DEFINE FIELD OVERWRITE content ON TABLE application TYPE object FLEXIBLE DEFAULT {};
|
||||
DEFINE FIELD OVERWRITE media ON TABLE innovation TYPE array<object> FLEXIBLE DEFAULT [];
|
||||
DEFINE FIELD OVERWRITE attachments ON TABLE knowledge_item TYPE array<object> FLEXIBLE DEFAULT [];
|
||||
DEFINE FIELD OVERWRITE payload ON TABLE notification TYPE object FLEXIBLE DEFAULT {};
|
||||
@@ -0,0 +1,117 @@
|
||||
-- TarninaHub — migration 0004: synthetic demo seed (step 09)
|
||||
-- Idempotent (INSERT IGNORE). ALL DATA IS SYNTHETIC — no real personal or
|
||||
-- sensitive data. Personas come from the public ROPS Map of Social Challenges
|
||||
-- and are explicitly synthetic. Phone numbers use the +48 000 reserved range.
|
||||
-- Passwords are generated demo hashes for a throwaway password.
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- Organizations
|
||||
-- ---------------------------------------------------------------------------
|
||||
|
||||
INSERT IGNORE INTO organization [
|
||||
{ id: organization:ngo_dobry_sasiad, name: 'Stowarzyszenie Dobry Sąsiad (demo)', type: 'ngo', location: location:krakow, contact: { email: 'kontakt@example.test' } },
|
||||
{ id: organization:mops_krakow, name: 'MOPS Kraków (demo)', type: 'jst', location: location:krakow, contact: { email: 'mops@example.test' } },
|
||||
{ id: organization:instytut_zdrowia, name: 'Instytut Zdrowia Społecznego (demo)', type: 'institution', location: location:krakow, contact: { email: 'instytut@example.test' } },
|
||||
{ id: organization:firma_care, name: 'CareTech sp. z o.o. (demo)', type: 'company', location: location:tarnow, contact: { email: 'caretech@example.test' } },
|
||||
{ id: organization:rops_krakow, name: 'ROPS Kraków (demo)', type: 'institution', location: location:krakow, contact: { email: 'rops@example.test' } }
|
||||
];
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- Users (synthetic personas + demo staff roles)
|
||||
-- ---------------------------------------------------------------------------
|
||||
|
||||
INSERT IGNORE INTO user [
|
||||
{ id: user:ania, display_name: 'Ania (persona)', phone: '+48000000001', email: 'ania@example.test', password: crypto::argon2::generate('Demo1234!'), role: 'user', organization: organization:ngo_dobry_sasiad },
|
||||
{ id: user:kuba, display_name: 'Kuba (persona)', phone: '+48000000002', email: 'kuba@example.test', password: crypto::argon2::generate('Demo1234!'), role: 'user' },
|
||||
{ id: user:krystian, display_name: 'Krystian (persona)', phone: '+48000000003', email: 'krystian@example.test', password: crypto::argon2::generate('Demo1234!'), role: 'user' },
|
||||
{ id: user:tomek, display_name: 'Tomek (persona)', phone: '+48000000004', email: 'tomek@example.test', password: crypto::argon2::generate('Demo1234!'), role: 'user' },
|
||||
{ id: user:swietlana, display_name: 'Swietłana (persona)', phone: '+48000000005', email: 'swietlana@example.test', password: crypto::argon2::generate('Demo1234!'), role: 'user' },
|
||||
{ id: user:stanislaw, display_name: 'Stanisław (persona)', phone: '+48000000006', email: 'stanislaw@example.test', password: crypto::argon2::generate('Demo1234!'), role: 'user' },
|
||||
{ id: user:mateusz, display_name: 'Mateusz (persona)', phone: '+48000000007', email: 'mateusz@example.test', password: crypto::argon2::generate('Demo1234!'), role: 'user' },
|
||||
{ id: user:karina, display_name: 'Karina (persona)', phone: '+48000000008', email: 'karina@example.test', password: crypto::argon2::generate('Demo1234!'), role: 'user' },
|
||||
{ id: user:janina, display_name: 'Janina (persona)', phone: '+48000000009', email: 'janina@example.test', password: crypto::argon2::generate('Demo1234!'), role: 'user' },
|
||||
{ id: user:jst_krakow, display_name: 'Pracownik JST (demo)', phone: '+48000000010', email: 'jst@example.test', password: crypto::argon2::generate('Demo1234!'), role: 'jst', organization: organization:mops_krakow },
|
||||
{ id: user:rops_operator, display_name: 'Koordynator ROPS (demo)', phone: '+48000000011', email: 'operator@example.test', password: crypto::argon2::generate('Demo1234!'), role: 'operator', organization: organization:rops_krakow },
|
||||
{ id: user:rops_admin, display_name: 'Administrator ROPS (demo)', phone: '+48000000012', email: 'admin@example.test', password: crypto::argon2::generate('Demo1234!'), role: 'admin', organization: organization:rops_krakow },
|
||||
{ id: user:ekspert_zdrowie, display_name: 'Ekspert zdrowia (demo)', phone: '+48000000013', email: 'expert@example.test', password: crypto::argon2::generate('Demo1234!'), role: 'manager', organization: organization:instytut_zdrowia }
|
||||
];
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- Problems + submissions (matchmaking input, one per challenge)
|
||||
-- ---------------------------------------------------------------------------
|
||||
|
||||
INSERT IGNORE INTO problem [
|
||||
{ id: problem:rodzina_rodzenstwo, title: 'Rodzeństwo rozdzielane przy pieczy zastępczej', description: 'Brak miejsc, które pozwalają utrzymać rodzeństwo razem w pieczy rodzinnej.', challenge: social_challenge:rodzina, location: location:krakow, author: user:ania, visibility: 'public', tags: [tag:rodziny, tag:koordynacja_miedzyinstytucjonalna] },
|
||||
{ id: problem:mloda_bezdomnosc, title: 'Młoda bezdomność bez wsparcia po placówce', description: 'Osoby 18–25 lat po opuszczeniu placówki nie mają oferty wsparcia.', challenge: social_challenge:bezdomnosc, location: location:krakow, author: user:kuba, visibility: 'public', tags: [tag:mlodziez, tag:dostep_do_uslug] },
|
||||
{ id: problem:praca_on, title: 'Niska aktywność zawodowa osób z niepełnosprawnościami', description: 'Trudność w znalezieniu i utrzymaniu pracy mimo kwalifikacji.', challenge: social_challenge:niepelnosprawnosc, location: location:tarnow, author: user:krystian, visibility: 'public', tags: [tag:osoby_z_niepelnosprawnosciami, tag:usluga] },
|
||||
{ id: problem:ubostwo_energetyczne, title: 'Ubóstwo energetyczne i żywnościowe seniorów', description: 'Starsze osoby ograniczają ogrzewanie i posiłki z powodu kosztów.', challenge: social_challenge:ubostwo, location: location:nowy_sacz, author: user:tomek, visibility: 'public', tags: [tag:seniorzy, tag:dostep_do_uslug] },
|
||||
{ id: problem:integracja_jezyk, title: 'Bariera językowa w dostępie do usług', description: 'Cudzoziemcy nie znają procedur i potrzebują asysty kulturowej.', challenge: social_challenge:integracja_cudzoziemcow, location: location:krakow, author: user:swietlana, visibility: 'public', tags: [tag:dostep_do_uslug, tag:wykluczenie_cyfrowe] },
|
||||
{ id: problem:samotnosc_seniorow, title: 'Samotność i izolacja seniorów', description: 'Mieszkańcy 70+ nie mają kontaktu z otoczeniem i dostępu do informacji.', challenge: social_challenge:zdrowie, location: location:krakow, author: user:stanislaw, visibility: 'public', tags: [tag:seniorzy, tag:samotnosc] },
|
||||
{ id: problem:zdrowie_psychiczne_mlodziez, title: 'Kryzys zdrowia psychicznego wśród młodzieży', description: 'Wzrost zaburzeń i trudny dostęp do wsparcia środowiskowego.', challenge: social_challenge:zdrowie_psychiczne, location: location:krakow, author: user:mateusz, visibility: 'public', tags: [tag:mlodziez, tag:zdrowie_psychiczne] },
|
||||
{ id: problem:powrot_na_rynek, title: 'Powrót na rynek pracy po kryzysie psychicznym', description: 'Stigma i brak elastycznych form zatrudnienia utrudniają powrót.', challenge: social_challenge:zdrowie_psychiczne, location: location:tarnow, author: user:karina, visibility: 'public', tags: [tag:zdrowie_psychiczne, tag:usluga] }
|
||||
];
|
||||
|
||||
INSERT IGNORE INTO submission [
|
||||
{ id: submission:rodzina_rodzenstwo, problem: problem:rodzina_rodzenstwo, submitted_by: user:ania, status: 'matched', keywords: ['rodzeństwo', 'piecza', 'rodzina'] },
|
||||
{ id: submission:mloda_bezdomnosc, problem: problem:mloda_bezdomnosc, submitted_by: user:kuba, status: 'triaged', keywords: ['bezdomność', 'młodzież', 'aktywizacja'] },
|
||||
{ id: submission:praca_on, problem: problem:praca_on, submitted_by: user:krystian, status: 'matched', keywords: ['praca', 'niepełnosprawność', 'edukacja'] },
|
||||
{ id: submission:ubostwo_energetyczne, problem: problem:ubostwo_energetyczne, submitted_by: user:tomek, status: 'new', keywords: ['ubóstwo', 'seniorzy', 'energia'] },
|
||||
{ id: submission:integracja_jezyk, problem: problem:integracja_jezyk, submitted_by: user:swietlana, status: 'matched', keywords: ['cudzoziemcy', 'integracja', 'język'] },
|
||||
{ id: submission:samotnosc_seniorow, problem: problem:samotnosc_seniorow, submitted_by: user:stanislaw, status: 'matched', keywords: ['samotność', 'seniorzy', 'cyfryzacja'] },
|
||||
{ id: submission:zdrowie_psychiczne_mlodziez, problem: problem:zdrowie_psychiczne_mlodziez, submitted_by: user:mateusz, status: 'matched', keywords: ['zdrowie psychiczne', 'młodzież', 'profilaktyka'] },
|
||||
{ id: submission:powrot_na_rynek, problem: problem:powrot_na_rynek, submitted_by: user:karina, status: 'in_progress', keywords: ['zdrowie psychiczne', 'praca', 'reintegracja'] }
|
||||
];
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- Innovations (catalogue, one or more per challenge)
|
||||
-- ---------------------------------------------------------------------------
|
||||
|
||||
INSERT IGNORE INTO innovation [
|
||||
{ id: innovation:rodziny_grupy, title: 'Grupy wsparcia dla rodzin zastępczych', summary: 'Sieć sąsiedzka utrzymująca rodzeństwo razem.', description: 'Lokalne grupy rodzin zastępczych dzielą się opieką i wsparciem, ograniczając rozdzielanie rodzeństwa.', challenge: social_challenge:rodzina, stage: 'pilot', published: true, tags: [tag:rodziny, tag:koordynacja_miedzyinstytucjonalna] },
|
||||
{ id: innovation:hostel_mlodzi, title: 'Hostel aktywizacyjny dla młodych dorosłych', summary: 'Mieszkanie z programem usamodzielniania.', description: 'Połączenie schronienia i treningu kompetencji społecznych dla osób 18–25 lat.', challenge: social_challenge:bezdomnosc, stage: 'scaling', published: true, tags: [tag:mlodziez, tag:dostep_do_uslug] },
|
||||
{ id: innovation:praca_wspierana, title: 'Praca wspierana z doradcą zawodowym', summary: 'Model zatrudnienia wspieranego w tandemie z trenerem pracy.', description: 'Trener pracy wspiera zatrudnienie osoby z niepełnosprawnością u lokalnego pracodawcy.', challenge: social_challenge:niepelnosprawnosc, stage: 'service', published: true, tags: [tag:osoby_z_niepelnosprawnosciami, tag:usluga] },
|
||||
{ id: innovation:spizarnia, title: 'Sąsiedzka spizarnia i wymiana energii', summary: 'Zbiórki żywności i wsparcie kosztów ogrzewania.', description: 'Lokalne spizarnie społeczne i sąsiedzka pomoc w kosztach energii dla seniorów.', challenge: social_challenge:ubostwo, stage: 'pilot', published: true, tags: [tag:seniorzy, tag:dostep_do_uslug] },
|
||||
{ id: innovation:asysta_cudzoziemcy, title: 'Asysta kulturowa dla cudzoziemców', summary: 'Tłumacz-asystent prowadzący przez procedury.', description: 'Asystent kulturowy pomaga w urzędzie, szkole i służbie zdrowia.', challenge: social_challenge:integracja_cudzoziemcow, stage: 'service', published: true, tags: [tag:dostep_do_uslug, tag:wykluczenie_cyfrowe] },
|
||||
{ id: innovation:telefon_zaufania_senior, title: 'Telefon i wizyta sąsiedzka dla seniorów', summary: 'Regularny kontakt i pomoc w cyfryzacji.', description: 'Wolontariusze dzwonią i odwiedzają seniorów, ucząc korzystania z usług online.', challenge: social_challenge:zdrowie, stage: 'scaling', published: true, tags: [tag:seniorzy, tag:samotnosc] },
|
||||
{ id: innovation:mlodzi_glowy_szkola, title: 'Program wsparcia psychicznego w szkołach', summary: 'Warsztaty i grupy rówieśnicze.', description: 'Szkolne grupy wsparcia i warsztaty profilaktyczne dla młodzieży.', challenge: social_challenge:zdrowie_psychiczne, stage: 'pilot', published: true, tags: [tag:mlodziez, tag:zdrowie_psychiczne] },
|
||||
{ id: innovation:zatrudnienie_elastyczne, title: 'Elastyczne zatrudnienie po kryzysie', summary: 'Stopniowy powrót na rynek pracy.', description: 'Elastyczne formy pracy i wsparcie mentora dla osób po kryzysie psychicznym.', challenge: social_challenge:zdrowie_psychiczne, stage: 'service', published: true, tags: [tag:zdrowie_psychiczne, tag:usluga] }
|
||||
];
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- Knowledge base (metadata + public links only, no copied content)
|
||||
-- ---------------------------------------------------------------------------
|
||||
|
||||
INSERT IGNORE INTO knowledge_item [
|
||||
{ id: knowledge_item:raport_rodzina, title: 'Piecza zastępcza w Polsce — raport GUS', kind: 'report', challenge: social_challenge:rodzina, source_url: 'https://stat.gov.pl/obszary-tematyczne/dzieci-i-rodzina/dzieci/piecza-zastepcza-w-2023-roku,1,8.html', published: true },
|
||||
{ id: knowledge_item:raport_bezdomnosc, title: 'Młoda bezdomność — raport Fundacji po DRUGIE', kind: 'report', challenge: social_challenge:bezdomnosc, source_url: 'https://podrugie.pl/wp-content/uploads/2023/04/RAPORT-DODAJ-MNIE.pdf', published: true },
|
||||
{ id: knowledge_item:raport_on, title: 'Badanie potrzeb osób niepełnosprawnych — PFRON 2024', kind: 'report', challenge: social_challenge:niepelnosprawnosc, source_url: 'https://www.pfron.org.pl/instytucje/badania-i-analizy-naukowe/raport-koncowy-z-badania-potrzeb-osob-niepelnosprawnych/', published: true },
|
||||
{ id: knowledge_item:raport_ubostwo, title: 'Poverty Watch — EAPN Polska', kind: 'report', challenge: social_challenge:ubostwo, source_url: 'https://www.eapn.org.pl/eapn/uploads/2023/10/poverty_watch_23_v12_10_v2_ost.pdf', published: true },
|
||||
{ id: knowledge_item:mapa_wyzwan, title: 'Mapa Wyzwań Społecznych — ROPS Kraków', kind: 'map', challenge: social_challenge:integracja_cudzoziemcow, source_url: 'https://rops.krakow.pl/mpliki/IS/IWS_20/za._nr_2._Mapa_Wyzwa_Spoecznych.pdf', published: true },
|
||||
{ id: knowledge_item:raport_samotnosc, title: 'Raport o samotności — Szlachetna Paczka', kind: 'report', challenge: social_challenge:zdrowie, source_url: 'https://www.szlachetnapaczka.pl/wp-content/uploads/2021/03/raport_o_samotnosci_2021.pdf', published: true },
|
||||
{ id: knowledge_item:raport_mlode_glowy, title: 'Młode Głowy — raport o zdrowiu psychicznym', kind: 'report', challenge: social_challenge:zdrowie_psychiczne, source_url: 'https://mlodeglowy.pl/wp-content/uploads/2023/04/MLODE-GLOWY.-Otwarcie-o-zdrowiu-psychicznym_-Raport-final.pdf', published: true },
|
||||
{ id: knowledge_item:raport_seniorzy, title: 'Sytuacja osób starszych w Polsce — GUS 2022', kind: 'report', challenge: social_challenge:seniorzy, source_url: 'https://stat.gov.pl/obszary-tematyczne/osoby-starsze/osoby-starsze/sytuacja-osob-starszych-w-polscie-w-2022-roku,2,5.html', published: true }
|
||||
];
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- Grant calls + ideas + test feedback
|
||||
-- ---------------------------------------------------------------------------
|
||||
|
||||
INSERT IGNORE INTO grant_call [
|
||||
{ id: grant_call:innowacje_2026, name: 'Innowacje społeczne 2026 (demo)', opens_at: d'2026-09-01T00:00:00Z', closes_at: d'2026-12-31T23:59:59Z', status: 'open', external_id: 'DEMO/5.1/2026-01' },
|
||||
{ id: grant_call:seniorzy_2026, name: 'Włączenie seniorów 2026 (demo)', opens_at: d'2026-10-01T00:00:00Z', closes_at: d'2026-11-30T23:59:59Z', status: 'open', external_id: 'DEMO/5.1/2026-02' }
|
||||
];
|
||||
|
||||
INSERT IGNORE INTO idea [
|
||||
{ id: idea:klub_sasiedzki, title: 'Klub sąsiedzki dla seniorów', description: 'Miejsce spotkań i nauki cyfrowej dla osób starszych.', author: user:janina, status: 'submitted' },
|
||||
{ id: idea:rower_terapeutyczny, title: 'Rower cargo dla wsparcia psychicznego', description: 'Wspólne wyjazdy jako forma terapii młodzieży.', author: user:mateusz, status: 'in_review' },
|
||||
{ id: idea:doradca_online, title: 'Doradca online dla osób z niepełnosprawnościami', description: 'Zdalne wsparcie w szukaniu pracy.', author: user:krystian, status: 'draft' }
|
||||
];
|
||||
|
||||
INSERT IGNORE INTO application [
|
||||
{ id: application:klub_sasiedzki, grant_call: grant_call:seniorzy_2026, idea: idea:klub_sasiedzki, content: { budget: 'syntetyczny', scope: 'klub sąsiedzki' }, status: 'submitted' }
|
||||
];
|
||||
|
||||
INSERT IGNORE INTO test_feedback [
|
||||
{ id: test_feedback:grupy_rodzinne_1, innovation: innovation:rodziny_grupy, author: user:ania, rating: 5, comment: 'Model dobrze łączy rodziny.', improvements: 'Potrzebna koordynacja powiatowa.' },
|
||||
{ id: test_feedback:mlodzi_glowy_1, innovation: innovation:mlodzi_glowy_szkola, author: user:karina, rating: 4, comment: 'Warsztaty potrzebne i trafne.', improvements: 'Zwiększyć liczbę grup rówieśniczych.' }
|
||||
];
|
||||
@@ -0,0 +1,42 @@
|
||||
# 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_schema.surql` | dodatki schematu pod demo: `user.display_name`, `innovation.tags`, pola `FLEXIBLE` |
|
||||
| `0004_demo_seed.surql` | dane syntetyczne (persony, problemy, innowacje, wiedza, nabory) |
|
||||
|
||||
## 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.
|
||||
Reference in New Issue
Block a user