ScarriffleandClaude Opus 5 be267e70a5 Seitenleiste: Eintraege ausblendbar (Backend)
Zwei Ebenen in einer Tabelle, nach dem Vorbild von DashboardLayout:
user_id NULL ist die Vorgabe des Administrators, eine Zeile mit user_id
die zusaetzliche Auswahl eines Kontos. Beide Listen gelten zusammen - wer
etwas versteckt, versteckt es zusaetzlich; einblenden, was der
Administrator ausgeblendet hat, kann niemand. Das erspart einen dritten
Zustand je Eintrag.

Als Schluessel dient der Routenpfad; der ist ohnehin eindeutig, eine
zweite Benennung waere nur eine weitere Stelle zum Auseinanderlaufen.

Drei Routen: lesen (Vorgabe, eigene Auswahl und die fertige Vereinigung
in einem Zug), eigene Auswahl setzen, Vorgabe setzen (nur Admin).

Die Eindeutigkeit laeuft ueber einen Index auf coalesce(user_id, -1).
Ein schlichtes UNIQUE(user_id) haette zwei Vorgabezeilen zugelassen, weil
NULL in SQL nie gleich NULL ist - das war im Entwurf noch falsch.

Versteckt wird nur der Eintrag, nicht die Seite: Lesezeichen,
Dashboard-Karten und Lagerort-QR sollen weiter funktionieren.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-28 20:27:14 +01:00
2026-07-23 17:35:45 +02:00

Vorrania – Selfhostbare Vorrats- und Gegenstandsverwaltung

Ein selbst gehosteter Dienst, um sowohl deinen Lebensmittelvorrat als auch allgemeine Gegenstände (Haushalt, Elektronik, Kleidung, Verbrauchsmaterial …) zu verwalten: Ein-/Auslagern per Barcode, Chargen mit MHD, Einzelstücke mit eigener UID/QR, Mindestbestände, automatische Einkaufsliste, verschachtelte Lagerorte, Gruppen, Kategorien und Dashboards – mit Web-UI und nativer iOS-App, die beide dieselbe REST-API nutzen.

Was Vorrania verwaltet

Lebensmittel laufen als Chargen mit Mindesthaltbarkeitsdatum. Beim Auslagern wird automatisch die zuerst ablaufende Charge entnommen (FEFO).

Gegenstände kennen drei Arten:

  • Menge je Lagerort – reines Zählen (z. B. Unterhosen, Batterien).
  • Einzelstücke – jedes Exemplar mit eigener UID/QR, Kaufdatum, Garantie, Bezugsquelle und Beleg (z. B. Powerbank, Kamera).
  • Verbrauchsgegenstand – wie ein Lebensmittel als Charge mit Menge und Einheit geführt (z. B. Sonnencreme in ml), nur ohne eindeutigen Code.

Features

  • Barcode-Lookup über Open Food Facts und Open Products Facts mit lokalem Fallback; unbekannte Produkte selbst anlegen, Bild wird automatisch geholt.
  • Chargen mit MHD & FEFO, Mengen in Packungen oder Teilmengen (Basiseinheit Stück / Gramm / Milliliter plus Packungsgröße/Gebinde).
  • Lagerorte beliebig verschachtelt (Keller → Regal 2 → Fach A), mit QR-Etiketten, Inhaltsansicht („was liegt hier?“), Umlagern und Entfernen mit Grund. Chargen und Einzelstücke sind einem Lagerort zugeordnet.
  • Einzelstücke verleihen mit Name und Grund als Freitext und optionalem Rückgabedatum – auch direkt nach dem Scannen des QR/NFC-Tags. Der Lagerort bleibt stehen (dorthin gehört das Stück zurück), als verliehen zählt es aber nicht mehr zum Bestand. Zurückgegebene Verleihe bleiben als Verlauf erhalten; überfällige Rückgaben sind in Liste und Dashboard rot.
  • Kategorien verschachtelt und pro Typ (Lebensmittel/Gegenstand); für Gegenstände mit eigenen Feldern (z. B. Kapazität, Größe, Farbe).
  • Gruppen rechnen Bestände mehrerer Marken/Artikel zusammen (z. B. „5 kg Mehl“) mit eigenem Mindestbestand und Einheit.
  • Mindestbestände je Produkt und Gruppe – gesamt und je Lagerort – mit zentraler Übersichtsseite, daraus die automatische Einkaufsliste und Ablaufwarnung für bald ablaufende Chargen.
  • Übergreifende Listen für Produkte (getrennt Lebensmittel/Gegenstände), Einzelstücke und Chargen – filter-, sortier- und spaltenkonfigurierbar; bei Chargen lässt sich der Lagerort mehrerer auf einmal setzen.
  • Dashboards mit konfigurierbaren Karten und Diagrammen, Verlauf (wer hat wann was ein-/ausgelagert), Stammdaten (Einheiten, Gebinde, Shops) und Import/Export.
  • Mehrbenutzer mit Rollen: Admin verwaltet Stammdaten und Benutzer, Nutzer lagert nur ein/aus und sieht Bestände; alle Bewegungen werden protokolliert.
  • Single Sign-on (optional) über OpenID Connect, z. B. gegen ein selbst gehostetes Authentik. Der Passwort-Login bleibt daneben bestehen – siehe Single Sign-on.
  • Sitzungsdauer einstellbar (Einstellungen → Anmeldung): 7 Tage bis 1 Jahr oder unbegrenzt, für Website und App gemeinsam. Die Frist zählt ab dem letzten Besuch, nicht ab dem Anmelden.

In der iOS-App zusätzlich

  • Scannen von Barcode, MHD/Datum per Texterkennung (auch nur Tag+Monat ohne Jahr bei Frischware), Lagerort-QR und Name/Marke per Text-Scan.
  • Verleihen direkt nach dem Scannen – QR, eingenähtes NFC-Tag oder angetipptes Tag landen alle im Einzelstück, wo Herausgeben und Zurücknehmen gleich oben stehen.
  • Foto direkt beim Anlegen (Kamera/Galerie), Push-Benachrichtigungen bei ablaufenden Produkten, lokale Dashboards und Home-Screen-Schnellaktionen.

Details zur App: ios/README.md.

Architektur

Teil Technik
Backend Python · FastAPI · SQLAlchemy
Anmeldung Passwort (JWT) und optional OpenID Connect
DB PostgreSQL (SQLite für Tests/lokal)
Web-UI React · Vite (via nginx ausgeliefert)
iOS Native SwiftUI-App (VisionKit-Scanner)
Deploy Docker Compose · install.sh
backend/   FastAPI-App + Tests
web/        React/Vite SPA
ios/        Native SwiftUI-App (XcodeGen-Projekt)
deploy/     docker-compose.yml, install.sh, update.sh, .env.example
docs/       Roadmap

Das Schema wird beim Start per idempotenter ALTER TABLE-Migration in backend/app/main.py nachgezogen (kein Alembic).

Installation (selfhosted, Linux)

Voraussetzung: eine Linux-Maschine (Server, NAS, Raspberry Pi …). Docker wird bei Bedarf automatisch installiert.

git clone https://git.scarriffle.com/Scarriffle/vorrania.git
cd vorrania/deploy
chmod +x install.sh
./install.sh            # interaktiv – fragt Port & Admin-Passwort
# oder vollautomatisch mit generiertem Admin-Passwort:
# ./install.sh --yes

Ist das Gitea-Repo privat, fragt git clone nach Benutzername + Access-Token. Falls git fehlt: apt update && apt install -y git.

Nach dem Start:

  • Web-UI: http://<server-ip>:8080 (Port konfigurierbar)
  • Anmeldung mit dem im Installer angezeigten Admin-Benutzer/Passwort.

Die iOS-App wird mit derselben Adresse verbunden (http://<server-ip>:8080).

Single Sign-on (optional)

Neben dem Passwort-Login kann sich Vorrania an einen OpenID-Connect-Dienst hängen (getestet mit Authentik). Ohne Konfiguration ändert sich nichts: Es gibt keinen SSO-Knopf, und der Passwort-Login bleibt der einzige Weg.

1. In Authentik zwei Anwendungen anlegen – Web-UI und App brauchen unterschiedliche Client-Typen, und ein Provider kann nur einer davon sein:

Für Client-Typ Redirect-URI
Web-UI Confidential (mit Secret) https://<deine-adresse>/api/auth/oidc/authentik/callback
iOS-App Public (ohne Secret, PKCE) vorrania://oauth/callback

Drei Punkte, an denen es sonst klemmt:

  • Das /api in der Web-Adresse ist kein Versehen: Es entsteht im mitgelieferten nginx und gehört deshalb in die bei Authentik hinterlegte Adresse.
  • Signing Key setzen (Provider ▸ Advanced protocol settings). Ohne ihn signiert Authentik mit HS256, und Vorrania prüft gegen den öffentlichen Schlüsselsatz – das Token würde abgelehnt.
  • Jede Anwendung hat in Authentik ihren eigenen Issuer (aus dem Application-Slug). Deshalb bekommt der App-Client eine eigene Discovery-Adresse: OIDC_AUTHENTIK_NATIVE_DISCOVERY_URL.

Damit die App ihre Anmeldung im Hintergrund erneuern kann, muss der öffentliche Client offline_access ausliefern dürfen (in Authentik seit 2024.2 ein eigenes Scope-Mapping) – sonst kommt kein Refresh-Token zurück.

2. deploy/.env ergänzen (vollständige Vorlage in deploy/.env.example):

PUBLIC_BASE_URL=https://vorrania.example.com
OIDC_PROVIDERS=authentik
OIDC_AUTHENTIK_DISPLAY_NAME=Authentik
OIDC_AUTHENTIK_DISCOVERY_URL=https://authentik.example.com/application/o/vorrania/.well-known/openid-configuration
OIDC_AUTHENTIK_CLIENT_ID=...
OIDC_AUTHENTIK_CLIENT_SECRET=...
OIDC_AUTHENTIK_NATIVE_CLIENT_IDS=vorrania-ios
OIDC_AUTHENTIK_NATIVE_DISCOVERY_URL=https://authentik.example.com/application/o/vorrania-ios/.well-known/openid-configuration

Danach ./update.sh (oder docker compose --env-file .env up -d). Ob es geklappt hat, verrät curl http://<server>:8080/api/auth/oidc/providers.

3. Bestehende Konten verknüpfen. Beim ersten SSO-Login zu einer noch unbekannten Kennung fragt Vorrania nach, statt still ein zweites Konto anzulegen:

Die Anmeldung über Authentik hat geklappt – zu diesem Zugang gibt es hier aber noch kein Konto. [ Bestehendes Konto verknüpfen ] oder [ Neues Konto anlegen ]

Das Verknüpfen verlangt einmalig Benutzername und Passwort des bestehenden Kontos. Dieser Nachweis ist der Grund, warum der Anmeldeweg selbst nie fragt „welches Konto?" – ohne ihn könnte sich jeder mit einem beliebigen Konto beim Anmeldedienst an ein fremdes Vorrania-Konto hängen. Rolle und Verlauf bleiben dabei erhalten.

Zwei Wege führen daran vorbei, weil sie das Konto schon vorher finden:

  • E-Mail-Adresse unter Benutzer eintragen – dieselbe wie in Authentik. Verknüpft wird nur bei bestätigter Adresse.
  • Vorab verknüpfen: angemeldet bleiben, unten in der Seitenleiste auf den eigenen Namen → Mein Konto → Verknüpfen. Dort lässt sich eine Verknüpfung auch wieder lösen – außer es wäre die letzte Anmeldemöglichkeit.

Mit OIDC_AUTHENTIK_AUTO_CREATE=false entfällt der Knopf „Neues Konto anlegen"; dann bleibt nur das Verknüpfen mit einem Konto, das der Admin angelegt hat.

Die App zeigt diesen Dialog nicht – dort gilt weiterhin: bekannte Kennung oder passende Adresse, sonst je nach AUTO_CREATE ein neues Konto oder eine Absage. Einmal im Browser verknüpft, funktioniert die App danach ohne Weiteres.

Weitere Anbieter sind reine Konfiguration: Kürzel in OIDC_PROVIDERS ergänzen und einen zweiten Block mit dessen Präfix anlegen (OIDC_<KÜRZEL>_…) – am Code ändert sich dafür nichts.

Was dabei bewusst so gebaut ist

  • Der Anmeldedienst ist nur für die Anmeldung zuständig. Danach stellt Vorrania sein eigenes Token aus, wie beim Passwort-Login – Rollen, API-Tokens und alle Endpunkte bleiben unverändert.
  • Rollen kommen nie aus dem Anbieter. Neu angelegte SSO-Konten sind immer Benutzer; Admin vergibt ein Admin von Hand.
  • Der Benutzername wird einmal beim Anlegen übernommen und gehört danach Vorrania – ein späterer Namenswechsel beim Anmeldedienst schlägt nicht durch. Ändern lässt er sich jederzeit unter Benutzer: Sitzungen, Verlauf und API-Tokens hängen an der Benutzer-ID, nicht am Namen.
  • Automatisch verknüpft wird nur bei bestätigter E-Mail-Adresse. Sonst könnte ein Konto im Anmeldedienst mit frei gewählter Adresse ein fremdes Vorrania-Konto übernehmen. Beim Verknüpfen über Mein Konto entfällt das, weil dort die eigene Sitzung bürgt – eine bereits anderweitig vergebene Kennung wird aber auch dort abgelehnt.
  • Die App meldet sich ausschließlich im System-Browser an (AppAuth mit PKCE), nie in einem eingebetteten WebView, und sendet nie ein Client-Secret.

Aktualisieren

cd vorrania/deploy
./update.sh                # holt den neuesten Stand aus Git und baut neu
./update.sh --prune        # zusätzlich alte Images aufräumen
./update.sh --no-pull      # nur neu bauen, ohne git pull

Das Skript ist von überall aufrufbar, nutzt die vorhandene deploy/.env und prüft am Ende, ob das Backend wieder erreichbar ist.

Verwaltung:

cd deploy
docker compose logs -f     # Logs ansehen
docker compose down        # stoppen
docker compose up -d       # starten

Die Konfiguration liegt in deploy/.env (Passwörter, Port, JWT-Secret). Für den Produktivbetrieb bitte hinter einen HTTPS-Reverse-Proxy (z. B. Caddy/Traefik) stellen.

Entwicklung (ohne Docker)

Backend:

cd backend
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
# lokale SQLite-DB nutzen:
export DATABASE_URL="sqlite:///./vorrania.db"
uvicorn app.main:app --reload

API-Doku dann unter http://localhost:8000/docs.

Web:

cd web
npm install
npm run dev     # http://localhost:5173, proxyt /api → localhost:8000

iOS: siehe ios/README.md (XcodeGen-Projekt, xcodegen generate).

Tests:

cd backend
pip install -r requirements.txt
DATABASE_URL="sqlite://" pytest

Weiteres

Fahrplan und Ideen: docs/ROADMAP.md.

S
Description
No description provided
Readme
8.7 MiB
Languages
Python 40.8%
JavaScript 29.1%
Swift 27%
CSS 2.5%
Shell 0.6%