# 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](https://world.openfoodfacts.org) 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](https://goauthentik.io). Der Passwort-Login bleibt daneben bestehen – siehe [Single Sign-on](#single-sign-on-optional). ### 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**. - **Foto direkt beim Anlegen** (Kamera/Galerie), **Push-Benachrichtigungen** bei ablaufenden Produkten, lokale Dashboards und Home-Screen-Schnellaktionen. Details zur App: [ios/README.md](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. ```bash 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://:8080` (Port konfigurierbar) - Anmeldung mit dem im Installer angezeigten Admin-Benutzer/Passwort. Die iOS-App wird mit derselben Adresse verbunden (`http://: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:///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`): ```bash 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://: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__…`) – 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 ```bash 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: ```bash 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:** ```bash 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:** ```bash cd web npm install npm run dev # http://localhost:5173, proxyt /api → localhost:8000 ``` **iOS:** siehe [ios/README.md](ios/README.md) (XcodeGen-Projekt, `xcodegen generate`). **Tests:** ```bash cd backend pip install -r requirements.txt DATABASE_URL="sqlite://" pytest ``` ## Weiteres Fahrplan und Ideen: [docs/ROADMAP.md](docs/ROADMAP.md).