Architektur
Hinata besteht aus zwei Teilen: einem Flutter-Client und einem Spring-Boot-Server. Beide sprechen über eine versionierte REST-API. Dahinter liegen MongoDB und S3-kompatibler Speicher.
Die Komponenten
Vom Client bis zum Speicher:
/api/v1SSE · Live-Updates- App (Flutter): eine Codebasis für sechs Ziele, nämlich Android, iOS, Web, macOS, Windows und Linux (eine native GTK-3-Desktop-App mit der Application-ID
com.ahmadre.hinata). State mit bloc/cubit, Routing mit go_router, Übersetzungen mit i18next (Englisch + Deutsch), Netzwerk über dio in einemApiClient. Diagramme zeichnet fl_chart. - Server (Spring Boot 4, Java 21): stellt die REST-API unter
/api/v1bereit, enthält die gesamte Geschäftslogik und Autorisierung und streamt Live-Updates. - MongoDB: hält alle Daten. In Produktion läuft ein Replica Set (2 Datenknoten + 1 Arbiter) mit TLS und X.509-Client-Authentifizierung.
- S3 / MinIO: Objektspeicher für Anhänge und Avatare, mit zufälligen Objektschlüsseln und vorsignierten Downloads.
- SMTP: ausgehende Mail (Verifizierung, Benachrichtigungen, Passwort-Reset). In der Entwicklung übernimmt das Mailpit.
- Hinata Connect Gateway: zentrales Relay für Push und Universal Links. So bedient die eine veröffentlichte App viele selbst gehostete Server.
Der Weg App → Server
Jeder Netzwerkaufruf der App läuft durch einen einzigen ApiClient auf Basis von dio. Er kümmert sich um Tokens und Header, die einzelnen Screens müssen das nicht:
- Er hängt das aktuelle Bearer-Access-Token an authentifizierte Anfragen.
- Er sendet
Accept-Language(enoderde), damit der Server Fehlermeldungen lokalisiert. - Bei
401ruft er den Refresh-Endpunkt auf, holt ein neues Access-Token und wiederholt die Anfrage einmal. Scheitert der Refresh, leert er die Sitzung und leitet zum Login.
Der Server bietet eine stabile, versionierte Schnittstelle unter /api/v1. Alle Endpunkte stehen in der API-Referenz.
Mehrere Server von Anfang an
Die native App hat keine fest eingebaute Server-URL. Nutzer speichern einen oder mehrere Server und wechseln zwischen ihnen. Access-Tokens gelten pro Server. Der Web-Build kann seine eigene Origin als Standard nutzen. Deshalb funktioniert dieselbe veröffentlichte App mit jedem Hinata-Server. Siehe Branding & eigene Clients.
Live-Updates mit SSE
Der Server schickt Änderungen per Server-Sent Events (SSE) an verbundene Clients, statt dass sie pollen. Beispiel Anhänge: Kommt an einem Vorgang eine Datei dazu oder fällt weg, bekommt jeder offene Client, der den Vorgang streamt, die Änderung sofort über:
GET /api/v1/issues/{issueId}/attachments/stream
SSE ist ein einseitiger, langlebiger HTTP-Stream. Das ist günstig, kommt gut durch Proxys und braucht kein WebSocket-Upgrade. Dein Reverse Proxy darf diese Antworten nicht puffern.
Ablauf einer Anfrage & Token-Refresh
Eine typische authentifizierte Anfrage:
- App: sendet die Anfrage über
ApiClient. dio hängt Bearer-Access-Token undAccept-Languagean. - Server: prüft das JWT (HS512), erzwingt Rate-Limits pro IP und prüft die Berechtigung (z. B. braucht
/api/v1/admin/**die RolleADMIN). - Controller → Service → Repository: Der Service wendet die Geschäftsregeln an und liest oder schreibt MongoDB. Die Dateien von Anhängen gehen an S3/MinIO.
- Antwort: stabiles, lokalisiertes JSON. Entweder die Nutzdaten oder ein Fehler aus
messages.propertiesin der Sprache des Clients, ohne Stacktraces.
Access-Tokens sind kurzlebig. Refresh-Tokens leben länger, werden aber für API-Zugriff abgelehnt und können nur neue Access-Tokens ausstellen. Läuft ein Access-Token mitten in der Sitzung ab, merkt der Nutzer vom Refresh nichts:
App ──GET /issues (abgelaufenes Access-Token)──▶ Server
App ◀──────────── 401 Unauthorized ──────────────── Server
App ──POST /auth/refresh (Refresh-Token)────────▶ Server
App ◀──────── neues Access-Token ────────────────── Server
App ──GET /issues (wiederholt, neues Token)─────▶ Server
App ◀──────────────── 200 OK ──────────────────────── Server
Das vollständige Token-Modell steht unter Authentifizierung.
Laufzeiteinstellungen in MongoDB
Der Großteil der Betriebskonfiguration liegt in MongoDB und wird im Adminbereich der App verwaltet. Beim Start fest in Umgebungsvariablen steht sie nicht. Dazu gehören SSO-Provider, E-Mail-Eingang (IMAP), Push, Git-OAuth-Apps und App-Einstellungen wie die minimale Client-Version.
Daraus folgen zwei Regeln:
- Die Datenbank überschreibt die Umgebung. Umgebungsvariablen wie
hinata.app.*sind Standardwerte. Ein Wert aus dem Adminbereich gewinnt. - Änderungen greifen ohne Neustart. Ein geänderter SSO-Provider oder ein Feature-Flag wirkt ab der nächsten Anfrage, ohne Redeploy oder Neustart des Containers.
Secrets sind write-only
In der Admin-API sind Secrets (OAuth-Client-Secrets, Tokens, Passwörter) write-only. Du kannst sie setzen, zurückgegeben werden sie nie. Gespeicherte Git-Access-Tokens liegen zusätzlich mit AES-GCM verschlüsselt in der Datenbank.
Für den Start reichen deshalb wenige Umgebungsvariablen (siehe Konfigurationsreferenz). Alles andere stellst du ein, sobald der Server läuft.
Lokalisierte Fehler
Fehlermeldungen entstehen auf dem Server aus Resource-Bundles: messages.properties (Englisch, Standard) und messages_de.properties (Deutsch). Maßgeblich ist der Accept-Language-Header des Clients. Der Server liefert einen stabilen, maschinenlesbaren Fehler, dessen Meldung schon in der richtigen Sprache ist. Der Client übersetzt nichts selbst.
Das Connect Gateway
Push-Benachrichtigungen und Universal Links laufen über das Hinata Connect Gateway, einen gehosteten Dienst des App-Herausgebers. Sie sind nicht in jeden Server eingebaut.
- Dein Server verbindet sich mit dem Gateway. Die Push-Zugangsdaten der App liegen dort, deshalb brauchen Self-Hoster kein eigenes Firebase-Projekt.
- Universal Links öffnen die App auf dem richtigen Server, egal woher Einladung oder Reset-Link stammen.
- Der App-Herausgeber betreibt und sichert das Gateway. Self-Hoster müssen es weder betreiben noch verwalten.
HINATA_GATEWAY_BASE_URLänderst du nur, wenn du eine eigene gebrandete App ausrollst.
Linux bekommt die Benachrichtigungen, aber kein Push
Push braucht einen Zustelldienst des Betriebssystems. Unter Linux gibt es keinen, firebase_messaging hat keine Linux-Implementierung. Ein Linux-Client registriert deshalb nie ein Push-Token, und das Gateway kann ihm nichts zustellen. Die Ereignisse erreichen den Nutzer trotzdem im Benachrichtigungscenter der App und per E-Mail. Der Push-Schalter in den Kontoeinstellungen bleibt aktiv. Die Einstellung gehört zum Konto und steuert weiter das Telefon derselben Person.
So bedient die eine veröffentlichte App beliebig viele unabhängige, selbst gehostete Hinata-Server. Siehe Hinata Connect Gateway.
Wohin als Nächstes
- Grundkonzepte: das Vokabular, auf dem API und UI aufbauen.
- Self-Hosting-Überblick: welche Container du für diese Komponenten deployst.
- Sicherheitsmodell: die Garantien hinter dem Anfragepfad oben.