add route moderation script
This commit is contained in:
@@ -17,6 +17,42 @@ Die API-Basisadresse ist standardmäßig `https://wegwichtel.de`. Alle Skripte u
|
||||
|
||||
## Routen
|
||||
|
||||
### Lokale Moderation auf dem Server
|
||||
|
||||
`moderate_routes.py` greift direkt auf die bestehende SQLite-Datenbank zu und
|
||||
benötigt keine API-Verbindung. Ohne Parameter zeigt es alle Strecken mit ID,
|
||||
Name, Status und Freigabezeitpunkt an:
|
||||
|
||||
```bash
|
||||
python3 tools/python/moderate_routes.py
|
||||
python3 tools/python/moderate_routes.py --enable 12
|
||||
python3 tools/python/moderate_routes.py --disable 12
|
||||
```
|
||||
|
||||
Alternativ aus dem Projektverzeichnis über npm:
|
||||
|
||||
```bash
|
||||
npm run moderate
|
||||
npm run moderate -- -e 12
|
||||
npm run moderate -- -d 12
|
||||
```
|
||||
|
||||
Das `--` trennt die npm-Optionen von den Parametern des Moderationsskripts.
|
||||
|
||||
Kurzformen: `-e 12` und `-d 12`. `--enanble` wird als Alias für `--enable`
|
||||
unterstützt. Freigeben setzt `activated_at` auf den aktuellen UTC-Zeitstempel;
|
||||
Zurückziehen setzt es auf `NULL`. Gelöschte Strecken werden nicht freigegeben.
|
||||
|
||||
Das Skript liest `.env` im Projektverzeichnis, unabhängig vom Arbeitsverzeichnis.
|
||||
`DATA_DIR` bestimmt das Datenverzeichnis, standardmäßig `data`; darin liegt
|
||||
`wegwichtel.sqlite`. Bereits gesetzte Umgebungsvariablen haben Vorrang wie beim
|
||||
App-Start. Die `.env` unterstützt einzeilige Werte, Anführungszeichen, Kommentare
|
||||
und `export`; sie wird nicht als Shell-Skript ausgeführt. Eine fehlende Datenbank
|
||||
wird nicht neu angelegt. Der aufrufende Benutzer benötigt die entsprechenden
|
||||
Dateirechte für Datenbank und Datenverzeichnis.
|
||||
|
||||
### Bearbeitung über die API
|
||||
|
||||
```bash
|
||||
python3 tools/python/create_route.py
|
||||
python3 tools/python/update_route.py
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Strecken direkt in der lokalen Wegwichtel-Datenbank moderieren."""
|
||||
|
||||
import argparse
|
||||
from datetime import datetime, timezone
|
||||
import os
|
||||
from pathlib import Path
|
||||
import shlex
|
||||
import sqlite3
|
||||
import sys
|
||||
|
||||
PROJECT_ROOT = Path(__file__).resolve().parents[2]
|
||||
|
||||
|
||||
def database_path(project_root):
|
||||
"""Liest die .env; bereits gesetzte Umgebungsvariablen haben Vorrang."""
|
||||
settings = {}
|
||||
env_file = project_root / ".env"
|
||||
if env_file.is_file():
|
||||
for number, line in enumerate(env_file.read_text(encoding="utf-8-sig").splitlines(), 1):
|
||||
line = line.strip()
|
||||
if not line or line.startswith("#"):
|
||||
continue
|
||||
if line.startswith("export "):
|
||||
line = line[7:].lstrip()
|
||||
key, separator, value = line.partition("=")
|
||||
if not separator:
|
||||
raise ValueError(f"Ungültige .env-Angabe in Zeile {number}.")
|
||||
try:
|
||||
settings[key.strip()] = " ".join(shlex.split(value, comments=True))
|
||||
except ValueError as error:
|
||||
raise ValueError(f"Ungültige .env-Anführungszeichen in Zeile {number}.") from error
|
||||
data_dir = os.environ.get("DATA_DIR", settings.get("DATA_DIR", "")) or "data"
|
||||
return (project_root / data_dir / "wegwichtel.sqlite").resolve()
|
||||
|
||||
|
||||
def route_id(value):
|
||||
result = int(value)
|
||||
if result < 1:
|
||||
raise argparse.ArgumentTypeError("Die Strecken-ID muss eine positive Zahl sein.")
|
||||
return result
|
||||
|
||||
|
||||
def arguments(argv):
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
group = parser.add_mutually_exclusive_group()
|
||||
group.add_argument("--enable", "--enanble", "-e", type=route_id, metavar="ID",
|
||||
help="Strecke mit dem aktuellen Zeitstempel freigeben")
|
||||
group.add_argument("--disable", "-d", type=route_id, metavar="ID",
|
||||
help="Freigabe zurückziehen (activated_at wird NULL)")
|
||||
return parser.parse_args(argv)
|
||||
|
||||
|
||||
def list_routes(database):
|
||||
rows = database.execute("SELECT id, name, status, activated_at FROM routes ORDER BY id").fetchall()
|
||||
if not rows:
|
||||
print("Keine Strecken vorhanden.")
|
||||
return
|
||||
print("ID\tStatus\tFreigegeben am (UTC)\tName")
|
||||
for row in rows:
|
||||
name = row["name"].replace("\n", " ").replace("\r", " ").replace("\t", " ")
|
||||
print(f'{row["id"]}\t{row["status"]}\t{row["activated_at"] or "nicht freigegeben"}\t{name}')
|
||||
|
||||
|
||||
def moderate(database, identifier, enable):
|
||||
activated_at = datetime.now(timezone.utc).isoformat(timespec="milliseconds").replace("+00:00", "Z") if enable else None
|
||||
with database:
|
||||
database.execute("BEGIN IMMEDIATE")
|
||||
row = database.execute("SELECT status FROM routes WHERE id = ?", (identifier,)).fetchone()
|
||||
if row is None:
|
||||
raise ValueError(f"Strecke {identifier} existiert nicht.")
|
||||
if enable and row["status"] != "active":
|
||||
raise ValueError(f"Strecke {identifier} ist gelöscht und kann nicht freigegeben werden.")
|
||||
database.execute("UPDATE routes SET activated_at = ? WHERE id = ?", (activated_at, identifier))
|
||||
if enable:
|
||||
print(f"Strecke {identifier} freigegeben: {activated_at}")
|
||||
else:
|
||||
print(f"Freigabe von Strecke {identifier} zurückgezogen.")
|
||||
|
||||
|
||||
def main(argv=None, project_root=None):
|
||||
args = arguments(argv)
|
||||
try:
|
||||
path = database_path(project_root or PROJECT_ROOT)
|
||||
mode = "ro" if args.enable is None and args.disable is None else "rw"
|
||||
database = sqlite3.connect(f"{path.as_uri()}?mode={mode}", uri=True, timeout=10)
|
||||
try:
|
||||
database.row_factory = sqlite3.Row
|
||||
if mode == "ro":
|
||||
list_routes(database)
|
||||
else:
|
||||
identifier = args.enable if args.enable is not None else args.disable
|
||||
moderate(database, identifier, args.enable is not None)
|
||||
finally:
|
||||
database.close()
|
||||
except (OSError, ValueError, sqlite3.Error) as error:
|
||||
print(f"Moderation fehlgeschlagen: {error}", file=sys.stderr)
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user