docs: README für Beteiligungsportal
This commit is contained in:
@@ -0,0 +1,286 @@
|
||||
# MeinVLN – Beteiligungsportal
|
||||
|
||||
Token-basiertes **Beteiligungsportal** des VLN Manager. Behörden, Träger öffentlicher
|
||||
Belange (TöB) und Einzelpersonen erhalten über einen persönlichen Zugriffscode – **ohne
|
||||
Login** – Einsicht in bereitgestellte Dokumente und können einen Fragenkatalog
|
||||
beantworten. Jeder Seitenaufruf und jeder Dateidownload wird revisionssicher
|
||||
protokolliert.
|
||||
|
||||
Erreichbar unter **https://beteiligung.vlnsachsen.de** (eigener nginx-Container
|
||||
`nginx-beteiligung`). Angelegt, gepflegt und ausgewertet werden Verfahren im
|
||||
geschützten Self-Service-Portal **mein.vlnsachsen.de** sowie über die **REST-API v2**.
|
||||
|
||||
> **Hinweis:** Dieses Repository dokumentiert das Beteiligungsmodul. Der produktive
|
||||
> Code lebt im Monorepo des VLN Manager und verteilt sich auf mehrere Verzeichnisse
|
||||
> (siehe [Codebestandteile](#codebestandteile)).
|
||||
|
||||
---
|
||||
|
||||
## Inhalt
|
||||
|
||||
- [Was es ist & wofür](#was-es-ist--wofür)
|
||||
- [Rollen & Ablauf](#rollen--ablauf)
|
||||
- [Codebestandteile](#codebestandteile)
|
||||
- [Datenmodell](#datenmodell)
|
||||
- [Fragenkatalog](#fragenkatalog)
|
||||
- [REST-API v2](#rest-api-v2)
|
||||
- [Berechtigungen](#berechtigungen)
|
||||
- [Betrieb & Deployment](#betrieb--deployment)
|
||||
- [Sicherheitshinweise](#sicherheitshinweise)
|
||||
|
||||
---
|
||||
|
||||
## Was es ist & wofür
|
||||
|
||||
Im Flurbereinigungsverfahren müssen regelmäßig Behörden und Beteiligte angehört werden.
|
||||
Das Beteiligungsportal löst dafür den klassischen Postweg ab:
|
||||
|
||||
- Eine **Teilnehmergemeinschaft (TG)** oder ein Verbandsmitarbeiter legt ein
|
||||
**Beteiligungsverfahren** an (Titel, Frist, VKZ).
|
||||
- Pro **Empfänger** wird ein eindeutiger, kryptographisch sicherer **Zugriffscode**
|
||||
erzeugt (`uniqidReal()`, 13 Zeichen, Großbuchstaben-Hex).
|
||||
- Der Empfänger öffnet seinen Link `…/?code=<CODE>`, lädt die freigegebenen Dokumente
|
||||
(einzeln oder als ZIP) herunter und beantwortet den Fragenkatalog.
|
||||
- Die TG wertet Rückläufe und Zugriffe aus.
|
||||
|
||||
Kein Empfänger-Account, kein Passwort – der **Code ist das Token**. Codes werden niemals
|
||||
geraten (Zufallsquelle `random_bytes`/`openssl_random_pseudo_bytes`).
|
||||
|
||||
---
|
||||
|
||||
## Rollen & Ablauf
|
||||
|
||||
| Rolle | Ort | Aktion |
|
||||
|-------|-----|--------|
|
||||
| **Verband / TG-Vorsitz / TG-Sachbearbeiter** | `mein.vlnsachsen.de` | Verfahren anlegen, Empfänger/Fragen/Dateien pflegen, auswerten |
|
||||
| **Empfänger** (Behörde, TöB, Person) | `beteiligung.vlnsachsen.de/?code=…` | Dokumente einsehen/herunterladen, Fragen beantworten |
|
||||
| **System / Integration** | `api.…/v2/beteiligung` | Verfahren per API anlegen, Public-Read/Submit per Token |
|
||||
|
||||
```
|
||||
Anlegen (meinVLN) Beteiligung (öffentlich) Auswertung (meinVLN)
|
||||
───────────────── ───────────────────────── ────────────────────
|
||||
beteiligung_create.php ──► index.php?code=<TOKEN> ──► beteiligung_auswertung.php
|
||||
beteiligung_edit.php ├─ Dateien (Einzel/ZIP) ├─ Antworten + Aggregate
|
||||
├─ Stammdaten └─ antwort.php (Fragen) └─ Zugriffsverlauf (Audit)
|
||||
├─ Empfänger (→ Code)
|
||||
├─ Fragen
|
||||
└─ Dateien
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Codebestandteile
|
||||
|
||||
| Pfad | Zweck |
|
||||
|------|-------|
|
||||
| `0_beteiligung/index.php` | **Public-Entry**: Datei-/ZIP-Download, Seite mit Fragenformular |
|
||||
| `0_beteiligung/antwort.php` | **Public-Submit**: speichert Antworten (Pflichtfeld-Validierung, Typ-Normalisierung) |
|
||||
| `0_beteiligung/templates/` | Mustache-Templates des Public-Portals (`login`, `main`, `header`, `footer`) |
|
||||
| `0_beteiligung/set.php` | Hilfsskript: listet Upload-Verzeichnis (Debug) |
|
||||
| `require/require-beteiligungsverfahren.php` | **Domänenklasse** `BETEILIGUNGSVERFAHREN` + Factory `get_BETEILIGUNGSVERFAHREN()` |
|
||||
| `0_meinvln/beteiligung_create.php` | Verfahren anlegen (JSON-Endpoint) |
|
||||
| `0_meinvln/beteiligung_edit.php` | CRUD-Dispatcher (`?action=meta\|empfaenger_*\|frage_save\|frage_del\|datei_upload\|datei_del\|verfahren_del`) |
|
||||
| `0_meinvln/beteiligung_auswertung.php` | Antworten-Auswertung + Zugriffsverlauf |
|
||||
| `0_meinvln/templates/beteiligung_{edit,auswertung}.mustache` | Admin-Oberflächen |
|
||||
| `0_api/v2/plugins/beteiligung.php` | **REST-API v2** (List/Create + Public Read/Submit) |
|
||||
|
||||
Anlegen erfolgt zusätzlich über ein Modal in `0_meinvln/templates/meinvln_main.mustache`,
|
||||
das auf `beteiligung_create.php` postet.
|
||||
|
||||
---
|
||||
|
||||
## Datenmodell
|
||||
|
||||
Zwei Tabellen, keine ORM – rohes MySQLi über den `DB`-Wrapper.
|
||||
|
||||
### `BETEILIGUNGSVERFAHREN`
|
||||
|
||||
| Spalte | Typ | Bedeutung |
|
||||
|--------|-----|-----------|
|
||||
| `id` | INT, PK, auto | Verfahrens-ID |
|
||||
| `VKZ` | INT | Verfahrenskennzeichen (TG-Zuordnung) |
|
||||
| `von` | INT (Unix-TS) | Beginn des Beteiligungszeitraums |
|
||||
| `bis` | INT (Unix-TS) | Frist (Zugriff/Antwort enden 23:59:59 dieses Tages) |
|
||||
| `CODES` | JSON | Empfänger inkl. Code, Stammdaten, Antworten |
|
||||
| `DATEN` | JSON | Array der freigegebenen Datei-IDs |
|
||||
| `SETTING` | JSON | Titel, Ersteller, **Fragenkatalog** |
|
||||
|
||||
Indizes: `idx_vkz`, `idx_bis`, `idx_vkz_bis`.
|
||||
|
||||
**`CODES`** – Map `Code → Empfänger`:
|
||||
|
||||
```json
|
||||
{
|
||||
"A1B2C3D4E5F60": {
|
||||
"name": "Landratsamt Musterkreis",
|
||||
"anrede": "Damen und Herren",
|
||||
"email": "post@lra-muster.de",
|
||||
"z": [],
|
||||
"antworten": { "f1": "Ja", "f2": ["A", "C"], "f3": 4 },
|
||||
"antwort_ts": 1718524800
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**`SETTING`**:
|
||||
|
||||
```json
|
||||
{
|
||||
"titel": "Anhörung Wegebau Gewann Süd",
|
||||
"created_by": 62,
|
||||
"created_at": 1718438400,
|
||||
"fragen": [ /* siehe Fragenkatalog */ ]
|
||||
}
|
||||
```
|
||||
|
||||
**`DATEN`**: `["10231", "10232", "10240"]` – IDs aus dem zentralen Dateisystem
|
||||
(`DATEI`/`ORDNER`). Physische Ablage im Ordnerbaum unter `<VKZ>/Beteiligung/<verfahren_id>`.
|
||||
|
||||
### `BETEILIGUNGSVERFAHREN_ZUGRIFF` (Audit-Log)
|
||||
|
||||
Append-only, race-condition-frei – ein `INSERT` pro Zugriff. Ersetzt das frühere
|
||||
JSON-Logging in `CODES[code].{a,z}` (seit 2026-05-23; **nie wieder** Zugriffslogs in die
|
||||
JSON-Spalte schreiben).
|
||||
|
||||
| Spalte | Typ | Bedeutung |
|
||||
|--------|-----|-----------|
|
||||
| `verfahren_id` | INT | FK auf `BETEILIGUNGSVERFAHREN.id` |
|
||||
| `code` | VARCHAR | Empfänger-Code |
|
||||
| `typ` | ENUM(`page`,`file`,`zip`) | Art des Zugriffs |
|
||||
| `file_id` | INT NULL | bei `typ=file` die Datei-ID |
|
||||
| `ts` | INT (Unix-TS) | Zeitpunkt |
|
||||
| `ip` | VARBINARY | via `INET6_ATON()` (IPv4/IPv6) |
|
||||
| `user_agent` | VARCHAR(255) | gekürzter UA-String |
|
||||
|
||||
Aggregation für die Auswertung über `BETEILIGUNGSVERFAHREN::get_zugriffsstats()`
|
||||
(GROUP BY `code, typ`), Detailverlauf per JOIN auf `DATA_FILE` in
|
||||
`beteiligung_auswertung.php`.
|
||||
|
||||
---
|
||||
|
||||
## Fragenkatalog
|
||||
|
||||
Fragen liegen in `SETTING.fragen`. Jede Frage hat eine stabile ID (`f1`, `f2`, …),
|
||||
einen Typ und ein `pflicht`-Flag. Erlaubte Typen:
|
||||
|
||||
| Typ | Bedeutung | Zusatzfelder | Antwortformat |
|
||||
|-----|-----------|--------------|---------------|
|
||||
| `single` | Einfachauswahl | `optionen` (≥ 2) | String (eine Option) |
|
||||
| `multi` | Mehrfachauswahl | `optionen` (≥ 2) | String-Array |
|
||||
| `text` | Freitext | – | String |
|
||||
| `zahl` | Zahl | – | Number |
|
||||
| `datum` | Datum | – | parsebarer Datums-String |
|
||||
| `skala` | Skala | `min`, `max` (`max > min`) | Integer in `[min, max]` |
|
||||
|
||||
```json
|
||||
[
|
||||
{ "id": "f1", "typ": "single", "frage": "Bestehen Einwände?",
|
||||
"pflicht": true, "optionen": ["Ja", "Nein"] },
|
||||
{ "id": "f2", "typ": "multi", "frage": "Betroffene Belange",
|
||||
"optionen": ["Natur", "Wasser", "Verkehr"] },
|
||||
{ "id": "f3", "typ": "skala", "frage": "Dringlichkeit", "min": 1, "max": 5 }
|
||||
]
|
||||
```
|
||||
|
||||
Eingehende Antworten werden **serverseitig gegen den Katalog validiert** (Optionen
|
||||
müssen existieren, Skala im Bereich, Pflichtfragen vorhanden) – sowohl in `antwort.php`
|
||||
als auch im API-Endpoint.
|
||||
|
||||
---
|
||||
|
||||
## REST-API v2
|
||||
|
||||
Basis: `https://api.…/v2/beteiligung`. Admin-Routen erfordern einen API-Key
|
||||
(`auth: true`), Public-Routen nutzen den Empfänger-Token.
|
||||
|
||||
| Methode | Route | Zugriff | Zweck |
|
||||
|---------|-------|---------|-------|
|
||||
| `GET` | `/beteiligung` | Admin | Verfahren listen (`?vkz=`, `?status=aktiv\|abgelaufen\|alle`) |
|
||||
| `POST` | `/beteiligung` | Admin | Verfahren anlegen (inkl. Empfänger + Fragen) |
|
||||
| `GET` | `/beteiligung/{token}` | Public | Verfahren + Dateien + Fragen + bisherige Antwort |
|
||||
| `POST` | `/beteiligung/{token}/antworten` | Public | Antworten speichern (Frist + Validierung) |
|
||||
| `POST` | `/beteiligung/{id}/empfaenger` | Admin | Weiteren Empfänger anhängen |
|
||||
|
||||
**Verfahren anlegen:**
|
||||
|
||||
```bash
|
||||
curl -X POST https://api.…/v2/beteiligung \
|
||||
-H "Authorization: Bearer <API_KEY>" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"vkz": 1234,
|
||||
"bis": "2026-07-31",
|
||||
"empfaenger": [
|
||||
{ "name": "Landratsamt Musterkreis", "anrede": "Damen und Herren", "email": "post@lra-muster.de" }
|
||||
],
|
||||
"fragen": [
|
||||
{ "typ": "single", "frage": "Einwände?", "pflicht": true, "optionen": ["Ja","Nein"] }
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
**Antworten einreichen (öffentlich, per Token):**
|
||||
|
||||
```bash
|
||||
curl -X POST https://api.…/v2/beteiligung/A1B2C3D4E5F60/antworten \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{ "antworten": { "f1": "Nein" } }'
|
||||
```
|
||||
|
||||
Fehlercodes: `400` (ungültige Eingabe), `404` (Token/Verfahren unbekannt),
|
||||
`410` (Frist abgelaufen), `422` (Pflichtfragen fehlen).
|
||||
|
||||
---
|
||||
|
||||
## Berechtigungen
|
||||
|
||||
- **Admin-Seiten** (`mein.vlnsachsen.de`): Modul-Gate `MODUL_BETEILIGUNG`
|
||||
(`$RIGHT->darf_modul(...)`) – Verbandsmitarbeiter sowie freigeschaltete
|
||||
TG-Vorsitzende/Stellvertreter.
|
||||
- **Pro Verfahren** zusätzlich `tg_access($VKZ)`: TG-Nutzer sehen nur Verfahren
|
||||
*ihrer* VKZ, der Verband sieht alle.
|
||||
- **Public-Portal**: keine Anmeldung; Autorisierung allein über den Code in `CODES`.
|
||||
Dateien werden nur über `jumpfile()`/`jumpzip()` ausgeliefert (kein direkter
|
||||
Pfadzugriff), Downloads vor der Auslieferung protokolliert.
|
||||
|
||||
---
|
||||
|
||||
## Betrieb & Deployment
|
||||
|
||||
Eigener nginx-Container, getrennt von den anderen Subdomains.
|
||||
|
||||
- **docker-compose:** `/daten/dockervorlagen/manager/docker-compose.beteiligung.yaml`
|
||||
(Service `nginxbeteiligung`, Container `nginx-beteiligung`, Routing via Traefik auf
|
||||
`beteiligung.vlnsachsen.de`, TLS über `myresolver`).
|
||||
- **nginx-Conf:** `/daten/dockervorlagen/manager/nginx_beteiligung.conf`
|
||||
(Webroot `0_beteiligung/`, PHP-FPM `phpworker:9000`).
|
||||
- **URL-Konstante:** `URL_BETEILIGUNG = "https://beteiligung.vlnsachsen.de"`
|
||||
(in `require/require-main.php`).
|
||||
|
||||
```bash
|
||||
# Container (neu) starten
|
||||
cd /daten/dockervorlagen/manager
|
||||
docker compose -f docker-compose.beteiligung.yaml up -d
|
||||
|
||||
# PHP-Syntaxcheck (PHP läuft nur im Container)
|
||||
docker exec php-worker php -l /var/www/html/require/require-beteiligungsverfahren.php
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Sicherheitshinweise
|
||||
|
||||
- **Codes sind Geheimnisse.** Ein Code gewährt vollen Lese-/Antwortzugriff auf ein
|
||||
Verfahren – nur über sichere Kanäle versenden.
|
||||
- **Zugriffslogs gehören in die Audit-Tabelle** `BETEILIGUNGSVERFAHREN_ZUGRIFF`,
|
||||
nie zurück in die `CODES`-JSON-Spalte.
|
||||
- **Frist (`bis`) ist hart**: Nach Ablauf verweigert die API das Speichern (`410 Gone`).
|
||||
- **Eingaben werden serverseitig validiert** – Client-Werte (Optionen, Skala, Pflicht)
|
||||
nie ungeprüft übernehmen.
|
||||
- **Dateiauslieferung nur indirekt** über die Domänenklasse; keine Pfade aus
|
||||
Client-Input.
|
||||
|
||||
---
|
||||
|
||||
*Teil des VLN Manager (Flurbereinigung Sachsen). Domänenklasse:
|
||||
`require/require-beteiligungsverfahren.php`.*
|
||||
Reference in New Issue
Block a user