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).
Inhalt
- Was es ist & wofür
- Rollen & Ablauf
- Codebestandteile
- Datenmodell
- Fragenkatalog
- REST-API v2
- Berechtigungen
- Betrieb & Deployment
- 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:
{
"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:
{
"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] |
[
{ "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:
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):
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-GateMODUL_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 überjumpfile()/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(Servicenginxbeteiligung, Containernginx-beteiligung, Routing via Traefik aufbeteiligung.vlnsachsen.de, TLS übermyresolver). - nginx-Conf:
/daten/dockervorlagen/manager/nginx_beteiligung.conf(Webroot0_beteiligung/, PHP-FPMphpworker:9000). - URL-Konstante:
URL_BETEILIGUNG = "https://beteiligung.vlnsachsen.de"(inrequire/require-main.php).
# 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 dieCODES-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.