Init: RoggioApp Architecture, Prisma Schema, API MVP
This commit is contained in:
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
+9
@@ -0,0 +1,9 @@
|
||||
# RoggioApp - Vision & Kontext
|
||||
|
||||
## Die Grundidee (Ganz von vorne)
|
||||
* **Use-Case:** Verwaltung einer Ferienwohnungs-Vermietung.
|
||||
* **Betreiber-Struktur:** Das System wird nicht von einer klassischen "Firma" oder einem "Hotel-Manager" bedient, sondern von einem **Kollektiv**.
|
||||
|
||||
## Wichtige Implikationen (Claras Notizen)
|
||||
* *Rechte & Rollen:* Kollektiv bedeutet oft: flachere Hierarchien, geteilte Verantwortung, Transparenz. Ein starrer Admin/User-Schnitt (wie im alten System) passt hier vielleicht nicht. Jeder im Kollektiv muss vermutlich sehen, wer wann da ist und welche Aufgaben anstehen (z.B. Putzen, Check-In).
|
||||
* *Gäste-Kommunikation:* Die Interaktion mit den Gästen ist wahrscheinlich persönlicher oder kollaborativer als in einem Standard-Hotel.
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
# RoggioApp - Real World & Ökosystem
|
||||
|
||||
## 1. Die physische Welt (Real World / RW)
|
||||
Das System muss eine flexible, organisch wachsende Infrastruktur abbilden können:
|
||||
* **Gelände/Grundstück:** Viel Fläche, die sich entwickelt.
|
||||
* **Bestand:**
|
||||
* 2 feste Häuser.
|
||||
* 1 alter Zirkuswagen.
|
||||
* **Zukunft / Flexibilität:**
|
||||
* Camping-Möglichkeiten.
|
||||
* Platz für zukünftige Unterkünfte.
|
||||
* **Nutzungseinheiten (Granularität):**
|
||||
* In den Häusern gibt es verschiedene Wohnungen.
|
||||
* Es gibt Funktionsräume und Gemeinschaftsräume/Flächen.
|
||||
* *Kernanforderung:* Diese Einheiten müssen in verschiedenen Kombinationen vermietet oder belegt werden können (z.B. Wohnung A + Gemeinschaftsraum, oder nur Zirkuswagen).
|
||||
|
||||
## 2. Die digitale Welt (Ecosystem)
|
||||
Die neue Applikation existiert nicht im luftleeren Raum, sondern ist Teil einer Dreifaltigkeit:
|
||||
1. **Werbung & Frontend (Wordpress):** Externe Website für Marketing, Gästeinformationen und als Einstiegspunkt für Buchungsanfragen.
|
||||
2. **Dateien & Kommunikation (Nextcloud):** Bestehende Instanz für interne Datenverwaltung und Team-Kommunikation.
|
||||
3. **Verwaltung (RoggioApp):** Die neu zu bauende Kern-Applikation für Buchungen, Aufgaben (Tasks/Cleaning) und Finanzen.
|
||||
|
||||
## 3. Architektur-Ideen & Schnittstellen
|
||||
* **Single Sign-On (SSO):** Nextcloud als zentrales Auth-Backend (OIDC / SAML) für alle Tools, um die Benutzerverwaltung für das Kollektiv zentral an einem Ort zu halten.
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
# RoggioApp - Datenarchitektur & Normalisierung
|
||||
|
||||
## 1. Die Grundphilosophie (Single Source of Truth)
|
||||
* **Keine Redundanz:** Keine doppelte Datenhaltung und (noch wichtiger) keine doppelte Dateneingabe.
|
||||
* **Ableitung statt Hardcoding (Computed Values):**
|
||||
* Die Fläche einer Wohnung wird *nicht* als eigenes Feld in der Datenbank eingetragen. Sie wird berechnet (Summe der Flächen aller Räume, die zur Wohnung gehören).
|
||||
* Die Schlafplatzkapazität einer Einheit wird *nicht* als Zahl eingetragen. Sie ergibt sich aus: `Einheit -> hat Räume (Kategorie: Schlafzimmer) -> hat Betten -> Bett hat Schlafplätze (1 oder 2) = Summe X`.
|
||||
|
||||
## 2. Granularität & Hierarchie
|
||||
Alles im physischen Raum ist eine "Unit" (Einheit), die zueinander in Beziehung steht und strikt kategorisiert sein muss.
|
||||
|
||||
**Beispiel-Hierarchie für Kapazitäten:**
|
||||
1. **Unit: Wohnung** (z.B. Haus A, Wohnung 1)
|
||||
2. --> besteht aus **Unit: Raum** (z.B. Zimmer 1, Kategorie: Schlafzimmer)
|
||||
3. ----> enthält **Inventar/Unit: Bett** (Kategorie: Doppelbett, Kapazität: 2 Personen)
|
||||
4. --> besteht aus **Unit: Raum** (z.B. Zimmer 2, Kategorie: Schlafzimmer)
|
||||
5. ----> enthält **Inventar/Unit: Bett** (Kategorie: Einzelbett, Kapazität: 1 Person)
|
||||
|
||||
*Ergebnis:* Das System weiß automatisch, dass die Wohnung 3 Schlafplätze hat. Wenn ein Einzelbett ins Zimmer gestellt wird, erhöht sich die Kapazität der Wohnung automatisch auf 4, ohne dass jemand das Datenfeld "Wohnung_MaxPax" ändern muss.
|
||||
|
||||
## Claras Architektonische Ableitung
|
||||
Diese Anforderung schreit nach einem strikten, relationalen Baum-Modell (Tree/Graph) in PostgreSQL.
|
||||
* *Vorteil:* Absolute Konsistenz. Es gibt keine Widersprüche (z.B. "Wohnung hat 5 Betten, aber im System steht Kapazität 2").
|
||||
* *Herausforderung (auf die wir achten müssen):* Solche "Bottom-Up"-Berechnungen (Summen aus der Tiefe der Datenbank holen) können in SQL sehr komplex (viele JOINs) werden, wenn man z.B. nur schnell filtern will: "Zeig mir alle Wohnungen für 4 Personen".
|
||||
* *Lösungansatz:* Wir nutzen das normalisierte Modell für die *Struktur*, aber arbeiten beim Abfragen vielleicht mit Datenbank-Views (Materialized Views) oder Prisma-Computed-Fields, damit das Frontend nicht endlos rechnen muss.
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
# RoggioApp - Die Ultimative Abstraktion (Einheiten & Eigenschaften)
|
||||
|
||||
## 1. Die physische Welt ist ein Graph aus "Einheiten" (Units)
|
||||
Es gibt keine festen, harten Tabellen wie `Wohnungen`, `Räume` oder `Parkplätze`.
|
||||
Alles ist eine grundlegende **Unit** (Einheit) auf dem Gelände.
|
||||
|
||||
### Was definiert eine Unit?
|
||||
Eine Unit ist lediglich ein Knotenpunkt (Node), der über seine Eigenschaften (Properties/Traits) und seine Verbindungen (Parent/Child) definiert wird.
|
||||
|
||||
**Beispiele für Einheiten (Units):**
|
||||
* Das Grundstück selbst (Unit)
|
||||
* Das Haus A (Unit, liegt auf dem Grundstück)
|
||||
* Ein Zirkuswagen (Unit)
|
||||
* Ein Badezimmer (Unit, liegt in Haus A)
|
||||
* Ein Stück Rasen (Unit)
|
||||
|
||||
### Die Eigenschaften (Traits / Properties)
|
||||
Was die Unit *ist* oder was man mit ihr *tun* kann, wird über zugewiesene Eigenschaften bestimmt:
|
||||
* `is_rentable` (RentalUnit): Die Einheit kann gebucht werden (z.B. Wohnung, Zirkuswagen, Veranstaltungsraum, Parkplatz).
|
||||
* `needs_service` (Serviceable): Die Einheit erfordert Reinigung oder Instandhaltung (gilt für Mietobjekte, aber eben auch für Flure oder den privaten Putzraum).
|
||||
* `has_capacity` (Capacity): Definiert, wie viele Schlafplätze (oder Sitzplätze beim Veranstaltungsraum) sich durch die Untereinheiten ergeben.
|
||||
|
||||
## 2. Die Akteure (Personen & Gruppen)
|
||||
Auch hier wird stark normalisiert, um Doppelungen bei den Personendaten zu vermeiden (z.B. wenn Peter dieses Jahr mit Familie A kommt und nächstes Jahr mit Verein B).
|
||||
|
||||
* **Person (Die Basis):**
|
||||
* *Eindeutig / Fest:* Vorname, Nachname, Geburtsdatum, Ausweisnr., Mail, Mobil, Foto.
|
||||
* *Kategorisierung (Eigenschaft):* Ist diese Person Kollektiv-Mitglied, Support-Gang, Personal oder Gast/Kunde? (Kann sich überlappen!).
|
||||
* **Adresse (Die Location):**
|
||||
* *Nicht eindeutig / Shared:* Straße, Hausnummer, PLZ, Stadt, Land. (Mehrere Personen können dieselbe Adresse referenzieren).
|
||||
* **Gruppe (Die Entität, die bucht):**
|
||||
* Besteht aus $N$ Personen.
|
||||
* *Variabel:* Person X kann Mitglied in Gruppe A (Familienurlaub) und Gruppe B (Yoga-Retreat) sein.
|
||||
|
||||
## 3. Die Transaktionen (Buchungen & Aktionen)
|
||||
* **Buchungen (Bookings):**
|
||||
* Verbinden eine **Gruppe** mit einer **RentalUnit** (Unit mit `is_rentable=true`) über einen bestimmten Zeitraum.
|
||||
* **Aktionen (Tasks / Operations):**
|
||||
* Putzen, Einkaufen, Abholen.
|
||||
* Können an eine Buchung geknüpft sein (Endreinigung nach Gruppe A).
|
||||
* Oder an eine Unit (Grundreinigung des Flurs, der `needs_service` ist, aber nicht `is_rentable`).
|
||||
|
||||
## Claras architektonische Ableitung
|
||||
Das ist ein Entity-Component-System (ECS) oder ein Node-Graph, wie man ihn aus Game-Engines oder extrem skalierbaren ERP-Systemen kennt!
|
||||
Statt 20 verschiedener Tabellen (`flats`, `rooms`, `parking_spaces`) bauen wir einen großen `units`-Baum. Was die Unit kann, bestimmt eine Verknüpfungstabelle `unit_traits`.
|
||||
Das macht das System zukunftssicher: Wenn ihr morgen beschließt, Surfbretter zu verleihen, legt ihr einfach neue Units an, gebt ihnen den Trait `is_rentable` (RentalUnit) und sie tauchen sofort im Buchungskalender auf, ohne dass das Datenbankschema geändert werden muss.
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
# RoggioApp - Events, Aufgaben & Relationen
|
||||
|
||||
## 1. Die Universalität der Entitäten
|
||||
Nicht nur die physische Welt (Räume, Zirkuswagen), sondern **alles** ist eine grundlegende Entität, die über Eigenschaften (Traits) und Verknüpfungen definiert wird.
|
||||
* **Personen als Entitäten:** Können Rollen in Gruppen übernehmen.
|
||||
* **Dynamische Relationen (Verknüpfungen mit Eigenschaften):** Eine Person ist nicht einfach "in" einer Gruppe. Die Verbindung selbst hat eine Eigenschaft!
|
||||
* *Person A* ist über die Eigenschaft `is_comm_contact` mit *Gruppe X* verbunden.
|
||||
* *Person B* ist über die Eigenschaft `is_finance_contact` mit *Gruppe X* verbunden.
|
||||
|
||||
## 2. Buchungen und Aufgaben = "Events"
|
||||
Die höchste Form der Abstraktion betrifft die Zeit und die Aktionen. Es gibt keine starren Tabellen für "Buchungen" und "Putzpläne". Beide sind im Kern **Events**.
|
||||
|
||||
### Was ist ein Event?
|
||||
Ein Event ist ein Knotenpunkt in der Zeit, der andere Entitäten miteinander verbindet und einen Zustand hat.
|
||||
|
||||
* **Beispiel A: Die Buchung (Event-Typ: Rental)**
|
||||
* *Eigenschaften des Events:* Zeitraum (14.08 - 21.08), Status (Bestätigt).
|
||||
* *Relation 1:* Verknüpft mit Entität "Gruppe Familie Rossi" (Wer?).
|
||||
* *Relation 2:* Verknüpft mit Entität "Haus A" + "Zirkuswagen" (Wo?).
|
||||
* *Relation 3:* Finanzen/Rechnung (Geld).
|
||||
|
||||
* **Beispiel B: Das Putzen (Event-Typ: Task/Maintenance)**
|
||||
* *Eigenschaften des Events:* Fälligkeitsfenster (21.08 10:00 - 15:00), Status (Offen).
|
||||
* *Relation 1:* Verknüpft mit Entität "Haus A" (Was muss geputzt werden?).
|
||||
* *Relation 2:* Verknüpft mit Entität "Kollektiv-Mitglied Sarah" (Wer putzt?).
|
||||
* *Spezial-Relation (Event -> Event):* Getriggert durch / Abhängig von "Event A (Buchung Ende)".
|
||||
|
||||
## Claras architektonische Ableitung
|
||||
Das gesamte System besteht eigentlich nur aus drei riesigen Säulen:
|
||||
1. **Nodes (Entitäten):** Die Bausteine der Welt (Personen, Einheiten, etc.).
|
||||
2. **Edges (Relationen/Traits):** Wie die Nodes verbunden sind und was sie können.
|
||||
3. **Events (Zeitliche Knoten):** Das Klebeband, das Nodes auf einer Zeitachse zusammenführt (Buchungen, Aufgaben, Wartung).
|
||||
|
||||
*Herausforderung für die Datenbank:* So etwas baut man idealerweise mit einer Graphendatenbank (wie Neo4j) oder in PostgreSQL mit JSONB-Feldern für die Traits und extrem klugen Pivot-Tabellen (Edge-Tables) für die Beziehungen, damit man nicht den Verstand beim Queries-Schreiben verliert. Wir müssen einen Weg finden, das in Prisma sauber und typsicher zu modellieren, ohne die Performance zu töten.
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
# RoggioApp - Architektur & Schnittstellen-Strategie
|
||||
|
||||
## 1. Das Kern-System (Die absolute Source of Truth)
|
||||
Das Ziel ist eine unantastbare, zukunftssichere und hochflexible Datenstruktur, die Schnittstellen (Frontends, CMS, Apps) als austauschbare Plugins betrachtet.
|
||||
|
||||
* **Der Datenbank-Core:**
|
||||
* *Relationales Fundament (SQL):* Für absolute Konsistenz bei Transaktionen, Buchungen, Finanzen und den Kern-Entitäten (Nodes/Edges). Postgres ist hierfür ideal.
|
||||
* *NoSQL-Einflüsse / Flexibilität:* Eigenschaften (Traits/Properties), die sich ständig ändern können, werden als JSONB-Felder (in Postgres) oder in einer kooperierenden NoSQL-Struktur abgelegt.
|
||||
* *Caching/Performance:* Redis oder Memory-Caches vor den Berechnungen, damit der komplexe Graph nicht bei jedem Aufruf die SQL-DB auslastet.
|
||||
* *Die API:* Eine zentrale REST- oder GraphQL-API. *Nichts* redet direkt mit der Datenbank, alles geht durch diese API.
|
||||
|
||||
## 2. Die Satelliten (Schnittstellen & Konsumenten)
|
||||
Um dieses Kern-System kreisen verschiedene spezialisierte Werkzeuge, die über die API kommunizieren:
|
||||
|
||||
### A. Nextcloud (Das interne Werkzeug)
|
||||
* **Auth (RBAC):** Nextcloud fungiert als Identity Provider (OIDC). Rollen und Rechte des Kollektivs werden dort gepflegt.
|
||||
* **Ablage:** Dokumente, Rechnungs-PDFs und Verträge werden in Nextcloud gespeichert (das Roggio-Backend speichert nur den Link/die WebDAV-Referenz).
|
||||
* **Organisation:** Tasks (Putzen/Support) und interne Kalender-Syncs (CalDAV) für das Kollektiv.
|
||||
|
||||
### B. WordPress (Das Schaufenster)
|
||||
* **Aufgabe:** Marketing, Landing Pages, SEO.
|
||||
* **Integration:** Ein schlankes Plugin oder Widget greift per API auf das Roggio-Backend zu, liest die Verfügbarkeiten aus (Belegungskalender) und schickt Buchungsanfragen zurück ins Kern-System.
|
||||
|
||||
### C. WebApp / MobileApp (Das Cockpit)
|
||||
Eine (oder zwei getrennte) maßgeschneiderte Frontend-Applikationen (vermutlich React/React Native), die über die API mit dem Kern sprechen:
|
||||
* **Für das Kollektiv / Personal:** Das "Backend-Frontend". Ansicht von Rechnungen, Zuweisen von Tasks, Managen der Graphen (Einheiten erstellen).
|
||||
* **Für die Gäste (Kunden):** Eine Art "Guest-Portal" (Self-Service). Rechnungen einsehen, Services hinzubuchen (z.B. Sauna), Chat/Nachrichten, Check-in-Infos.
|
||||
Executable
+44
@@ -0,0 +1,44 @@
|
||||
# RoggioApp - Tech-Stack Empfehlung (Hetzner, Schmal, API-First)
|
||||
|
||||
## Prämissen
|
||||
1. **Hetzner-Ökosystem:** Performant, günstig, viel Bandbreite. Eigene VMs via Hetzner Cloud (Console) sind in Sekunden da.
|
||||
2. **So schmal wie möglich (Lean):** Keine Over-Engineering-Monster (wie Kubernetes), wenn Docker Compose reicht. Wenig Wartungsaufwand für das Kollektiv.
|
||||
3. **Zukunftssicher & Flexibel:** API-First, Graph-Datenmodell (ECS-Ansatz), Integration mit Nextcloud (WebDAV/OIDC) & WordPress.
|
||||
|
||||
## Der empfohlene Tech-Stack
|
||||
|
||||
### 1. Infrastruktur & Hosting (Die Basis)
|
||||
* **Server:** Hetzner Cloud (z.B. CX22 oder CPX21). Reicht völlig für den Anfang, skaliert in Sekunden.
|
||||
* **Orchestrierung:** **Docker Compose**. Kein K8s, kein Swarm. Eine einfache `docker-compose.yml`, die alle Core-Services hochzieht. Das ist extrem wartungsarm und portabel.
|
||||
* **Reverse Proxy:** **Nginx Proxy Manager** (oder Caddy). Extrem schmal, kümmert sich automatisch um Let's Encrypt SSL-Zertifikate und leitet Traffic auf die Docker-Container (wie wir es lokal schon erfolgreich getestet haben!).
|
||||
|
||||
### 2. Die Datenhaltung (Das Herzstück)
|
||||
* **Datenbank:** **PostgreSQL**.
|
||||
* *Warum:* Absoluter Industrie-Standard, bombensicher für Finanz-Transaktionen und durch die exzellente `JSONB`-Unterstützung perfekt für unser flexibles "Traits/Eigenschaften"-Modell.
|
||||
* **Caching / Message Broker (Optional, erst später zuschalten):** **Redis**.
|
||||
* *Warum:* Wenn später WordPress alle 2 Sekunden den Kalender anfragt, puffert Redis das, damit Postgres nicht schwitzt. Für den Start (MVP) können wir das aber noch weglassen ("so schmal wie möglich").
|
||||
|
||||
### 3. Das Backend (Die API)
|
||||
* **Sprache:** **Node.js mit TypeScript**.
|
||||
* *Warum:* Enorme Community, sehr schnell entwickelt, und (das ist der Hauptgrund) wir können Code und Typen zwischen Frontend und Backend teilen!
|
||||
* **ORM (Datenbank-Mapper):** **Prisma**.
|
||||
* *Warum:* Es gibt nichts Besseres für TypeScript und Postgres. Es generiert uns typsichere Abfragen, verwaltet die Datenbank-Migrationen und macht das Jonglieren mit unserem Graph/Node-Modell deutlich angenehmer als rohes SQL.
|
||||
* **Framework:** **NestJS** (oder leichtgewichtig: **Hono / Express**).
|
||||
* *Empfehlung für euch:* Wenn es sauber und "Enterprise-like" strukturiert sein soll: *NestJS*. Wenn es absolut schmal und pfeilschnell (Edge-ready) sein soll: *Hono*.
|
||||
|
||||
### 4. Die Frontends (Das Gesicht)
|
||||
* **Core-App (Kollektiv & Gäste Web-Portal):** **React** (gebaut mit **Vite** oder **Next.js**).
|
||||
* *Warum:* Absoluter Standard. Riesiges Ökosystem an Kalender- und UI-Bibliotheken (z.B. Tailwind CSS, shadcn/ui).
|
||||
* *Architektur:* Eine Single-Page-Application (SPA) oder statisch exportierte Seite, die direkt mit dem Node-Backend redet.
|
||||
* **Marketing (bestehend):** WordPress (bei Hetzner Webhosting).
|
||||
* *Warum:* Ihr habt es schon, jeder kann Inhalte einpflegen. Zieht die Buchungsdaten über eine simple REST-Schnittstelle von unserem Node-Backend.
|
||||
* **Dateien / Auth (bestehend):** Nextcloud (bei Hetzner Storage Share).
|
||||
* *Integration:* Das Node-Backend authentifiziert sich per OAuth2/OIDC an Nextcloud und speichert Rechnungen per WebDAV dort ab.
|
||||
|
||||
## Zusammenfassung des MVP-Setups (Schmalster Weg)
|
||||
1. **Eine Hetzner Cloud VM**
|
||||
2. Darauf läuft **Docker Compose**
|
||||
3. Darin laufen genau 3 Container:
|
||||
- **Nginx Proxy Manager** (für SSL und Domains)
|
||||
- **PostgreSQL** (Die Datenbank)
|
||||
- **Node.js (Hono/Prisma)** (Das Backend + liefert das kompilierte React-Frontend aus)
|
||||
@@ -0,0 +1,30 @@
|
||||
# RoggioApp - Langzeit-Vision & Hardware-Schnittstellen
|
||||
|
||||
## 1. Lokale Resilienz (Datenbank-Mirroring)
|
||||
* **Ziel:** Die Cloud (Hetzner) darf ausfallen oder das Internet auf dem Gelände streiken, ohne dass der Betrieb komplett lahmgelegt wird.
|
||||
* **Ansatz:** Ein lokales Mirror-System für die Datenbank.
|
||||
* *Architektur-Implication:* PostgreSQL erlaubt logische Replikation (Master-Slave oder Master-Master Konzepte). Wir sollten das Backend so bauen, dass es (später) Offline-First / Sync-Fähigkeiten bekommt oder zumindest ein lokaler Read-Replica Server auf dem Gelände läuft. (Für den Start bauen wir Single-Node, halten uns aber die Multi-Node Replikation in Postgres offen).
|
||||
|
||||
## 2. Zentrale User-Verwaltung (Identity & Access Management - IAM)
|
||||
* **Ziel:** Das Auth-System (Nextcloud / OIDC) soll in Zukunft der einzige Schlüssel für *alles* sein.
|
||||
* **Geplante Erweiterungen für User:**
|
||||
* WireGuard VPN-Profile
|
||||
* VoIP / SIP-Accounts (Telefonie)
|
||||
* E-Mail Accounts
|
||||
* NFC / RFID Zugangskontrolle (Türen)
|
||||
* *Architektur-Implication:* Nextcloud als OIDC-Provider ist hier bereits ein sehr guter Startpunkt. Wir dürfen in der RoggioApp keine eigenen, isolierten "User-Passwort"-Tabellen bauen. Die RoggioApp referenziert nur eine globale `user_id` (z.B. aus LDAP oder Nextcloud), an der die Hardware-Schlüssel hängen.
|
||||
|
||||
## 3. Home Automation & IoT (Der Campus)
|
||||
* **Ziel:** Smarte Steuerung und Überwachung der physischen Infrastruktur vor Ort.
|
||||
* **Core-Technologien:** HomeAssistant, MQTT, LoRaWAN.
|
||||
* **Kritische Systeme:**
|
||||
* PV / Solar Management (Strom)
|
||||
* Wasserspeicher (Infrastruktur)
|
||||
* Zugangskontrolle (Smart Locks)
|
||||
* *Architektur-Implication:* Unser Konzept der "Units" und "Traits" greift hier perfekt!
|
||||
* Eine Unit (z.B. Zirkuswagen) bekommt in unserer Datenbank den Trait `has_smart_lock`.
|
||||
* In diesem Trait speichern wir die MQTT-Topic-ID (`/house/zirkuswagen/lock`).
|
||||
* Wenn im Event "Buchung" der Status auf "Check-in" wechselt, feuert unser Node-Backend einen MQTT-Befehl an den lokalen HomeAssistant, um den Code des Gastes auf das Schloss zu spielen.
|
||||
|
||||
## Claras Fazit
|
||||
Die Weichenstellung ist klar: Das System ist kein reines "Software-Tool", sondern das Betriebssystem für ein physisches Gelände. Die strikte Trennung von Core-Logik, externem Auth-Provider und MQTT-fähigen Hardware-Layern sichert uns ab. Keine dieser Anforderungen sprengt unseren API-First / Node.js / Postgres Stack – im Gegenteil, MQTT und OIDC lassen sich in Node.js hervorragend nativ integrieren.
|
||||
@@ -0,0 +1,8 @@
|
||||
# RoggioApp - Nächster Schritt: Das Prisma Datenmodell (MVP)
|
||||
|
||||
Um vom Konzept in die Praxis zu kommen, sollten wir als Nächstes ein `schema.prisma` entwerfen, das unseren "Graph aus Units und Events" abbildet.
|
||||
|
||||
*Zu klären:*
|
||||
1. Wie bilden wir die flache "Unit"-Tabelle ab?
|
||||
2. Wie speichern wir die flexiblen "Traits" (als JSONB oder eigene Tabellen)?
|
||||
3. Wie verknüpfen wir Events (Buchungen/Aufgaben) typsicher?
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
# RoggioApp - System-Status & Paranoia-Checkpoint
|
||||
|
||||
## 1. Warum wir sicher sind (Keine Angst vor Token-Limits)
|
||||
* **Festplatten-Gedächtnis:** Alles, was wir bisher konzipiert haben (Die Node/Graph-Abstraktion, die Architektur-Entscheidungen), habe ich bereits als Markdown-Dateien in diesem Ordner abgelegt (`01_...` bis `09_...`).
|
||||
* **Infrastruktur existiert wirklich:** Die Docker-VM, der SMB-Share, das Let's Encrypt Zertifikat, die Prisma-Konfiguration und die laufende PostgreSQL-Datenbank sind **echt**. Sie laufen unabhängig von diesem Chatfenster.
|
||||
* **Der Code liegt auf der Platte:** Die `schema.prisma` und die Nginx-Konfigurationen sind nicht flüchtig im Chat-Verlauf, sondern fest gespeichert.
|
||||
|
||||
## 2. Wie wir nach einem Neustart (oder morgen) weitermachen
|
||||
Sollten wir morgen neu anfangen oder das Kontextfenster platzen, sagst du einfach:
|
||||
*"Clara, schau in den Ordner `/home/clara/.openclaw/workspace/projects/RoggioApp/Brainstorm/260426/`, lies dir die Architektur-Entscheidungen durch und lass uns am Prisma-Backend weiterarbeiten."*
|
||||
|
||||
Ich werde dann genau dort ansetzen, wo wir aufgehört haben, weil mein "Gedächtnis" auf SSD-Basis (in diesen Dateien) liegt, nicht nur im flüchtigen Chat-Protokoll.
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
# RoggioApp - Personen Entitäten & Nextcloud Sync (OIDC)
|
||||
|
||||
## Das Problem
|
||||
Wir wollen Personen (`Persons`) als zentrale Knoten im Graph (Datenbank) modellieren.
|
||||
Aber die "Wahrheit" über den Login (Passwort, Rollen) soll in der Nextcloud (als Identity Provider) liegen.
|
||||
Stellen wir uns ein Bein, wenn wir in der Datenbank jetzt Personen anlegen, ohne dass die Nextcloud steht?
|
||||
|
||||
## Die Lösung: Entkopplung von "Identity" und "Profile"
|
||||
Wir trennen das Konzept "Wer bist du technisch?" von "Wer bist du in der realen Welt?".
|
||||
|
||||
### 1. Das Roggio-Profil (Die Person-Entität)
|
||||
In unserer Postgres-DB (Prisma) liegt das Modell `Person`.
|
||||
Dieses Modell enthält **nur Profil- und Geschäftsdaten**:
|
||||
* `firstName`, `lastName`, `phone`
|
||||
* `traits` (JSONB) für alles andere (Körpergröße für Leihräder, Essensvorlieben für Gruppen).
|
||||
* **Der Anker:** Ein Feld `ssoId` (Single-Sign-On ID).
|
||||
|
||||
### 2. Die Nextcloud-Identität (Der Identity Provider)
|
||||
In der Nextcloud liegen die sensiblen Login-Daten:
|
||||
* `username` (z.B. "sev")
|
||||
* `password` (gehasht)
|
||||
* `email`
|
||||
* `groups` (z.B. "admin", "kollektiv")
|
||||
|
||||
### Der Workflow (Wie beides zusammenspielt)
|
||||
Wir können jetzt sofort Personen in der Datenbank anlegen (z.B. Gäste, die sowieso keinen Nextcloud-Account bekommen).
|
||||
Wenn sich später ein Kollektiv-Mitglied über Nextcloud einloggt (OIDC), passiert folgendes:
|
||||
1. Nextcloud sagt: *"Hier ist User 'sev' mit der OIDC-Sub-ID '12345' und der Email 'sev@...'."*
|
||||
2. Unser Backend schaut in der `Person`-Tabelle nach: *"Habe ich eine Person mit `ssoId = '12345'` oder `email = 'sev@...'`?"*
|
||||
3. Wenn Ja: Die Person ist authentifiziert und das Roggio-Profil wird mit der Sitzung verknüpft.
|
||||
4. Wenn Nein (erster Login): Das Backend erstellt automatisch eine neue `Person` mit der `ssoId='12345'`.
|
||||
|
||||
## Fazit für die Entwicklung
|
||||
Wir können das Datenmodell jetzt zu 100% fertigbauen und verfeinern!
|
||||
Gäste existieren ohnehin nur in Roggio. Für das Kollektiv lassen wir einfach das Feld `ssoId` vorerst leer. Wenn wir Nextcloud anbinden, füllen wir die ID nach oder lassen sie beim ersten Login auto-matchen.
|
||||
Kein Bein gestellt!
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
# RoggioApp - Das Kollektiv als eigene Entität
|
||||
|
||||
## Die Erkenntnis: Das Kollektiv ist kein abstrakter "Gott", sondern nur eine Gruppe
|
||||
Wenn man konsequent abstrahiert, ist die Betreiber-Struktur (das Kollektiv) nichts anderes als eine ganz normale Entität im Graphen.
|
||||
|
||||
### 1. Das Kollektiv ist eine `Group`
|
||||
In unserer Datenbank-Tabelle `Group` existieren Gruppen für "Familie Rossi" (Gäste).
|
||||
Genau dort existiert auch ein Datensatz für "Das Kollektiv".
|
||||
Unterschieden wird dies nur durch das JSONB-Feld `traits`:
|
||||
* *Familie Rossi:* `{"group_type": "guest", "preferences": "quiet"}`
|
||||
* *Das Kollektiv:* `{"group_type": "operator", "bank_account": "DE123...", "tax_id": "DE999..."}`
|
||||
|
||||
### 2. Die Mitglieder sind `Person`-Entitäten mit Relationen
|
||||
Die Menschen, die das System betreiben (Sev, Sarah, etc.), sind normale `Person`-Einträge.
|
||||
Ihre Zugehörigkeit zum Kollektiv wird durch die reguläre Pivot-Tabelle `GroupMembership` definiert:
|
||||
* *Person ID (Sev)* + *Group ID (Das Kollektiv)* + *Rolle (`finance_admin`)*
|
||||
* *Person ID (Sarah)* + *Group ID (Das Kollektiv)* + *Rolle (`cleaning_crew`)*
|
||||
|
||||
### 3. Warum das absolut genial ist: Mandantenfähigkeit ("Multi-Tenancy")
|
||||
Wenn wir das Kollektiv als einfache Gruppe speichern, ist das System sofort **mandantenfähig**.
|
||||
Beispiel: In 5 Jahren wollt ihr neben dem Haupt-Gelände noch ein zweites Haus im Nachbardorf verwalten, das aber von einer anderen Genossenschaft (oder GmbH) betrieben wird.
|
||||
Wir müssen *keinen einzigen* Datenbankumbau vornehmen! Wir legen einfach eine zweite Operator-Gruppe an, ordnen dieser Gruppe ein paar Personen und die neuen "Units" (Räume) zu, und fertig. Die Rechnungen ziehen sich dann automatisch die Bankdaten von Gruppe B statt von Gruppe A.
|
||||
|
||||
## Fazit
|
||||
Das System kennt keinen "Super-Admin-Hardcode". Es kennt nur den Graphen. Das Kollektiv ist ein normaler Knotenpunkt, der sich über seine Beziehungen definiert.
|
||||
|
||||
### 4. B2B / Firmen als Kunden (Organisationen)
|
||||
Genau wie "Das Kollektiv" eine Operator-Gruppe ist, sind auch B2B-Kunden (Firmen, NGOs, Vereine) einfache Einträge in der Tabelle `Group`.
|
||||
* *Verein e.V.:* `{"group_type": "b2b_guest", "tax_id": "DE123...", "invoice_notes": "Vereinsrabatt"}`
|
||||
* Die Verknüpfung der tatsächlichen Gäste erfolgt wieder über `GroupMembership`. (Z.B. ist Peter der `invoice_contact` für den Verein, und Sarah, Tim und Lisa sind `guest`).
|
||||
* Auch Rechnungen können somit direkt an die Entität `Group` ausgestellt werden, da die Firmen-/Vereinsadresse in deren Traits liegt, anstatt an einer einzelnen Person zu kleben.
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
# RoggioApp - Analyse: Die Person-Entität (Hard vs. Flexible)
|
||||
|
||||
## Die Herausforderung
|
||||
Was gehört zwingend als harte, durchsuchbare SQL-Spalte in die Tabelle `Person`, und was lagern wir als flexible Eigenschaft (Trait) in das JSONB-Feld oder in Relationen aus?
|
||||
|
||||
## Sevs Entwurf & Claras Analyse
|
||||
|
||||
### 1. Originär (Die Person selbst)
|
||||
* **Vorname & Nachname:** ✅ Absolut notwendig. Harte SQL-Spalten. (Ggf. um `nickname` als Spalte erweitern).
|
||||
* **Geburtsdatum:** ✅ Harte Spalte (für Altersberechnungen, Kurtaxe, Meldescheine extrem wichtig).
|
||||
* **Mobile-Nr & Private E-Mail:** ⚠️ *Diskussion:* Sind die eindeutig?
|
||||
* *Realität:* Oft buchen Ehepaare oder Familien mit nur *einer* gemeinsamen Mailadresse oder Handynummer.
|
||||
* *Lösung:* Wir dürfen `email` und `phone` **nicht** auf `UNIQUE` (Einzigartig) in der Datenbank setzen! Sie bleiben harte SQL-Spalten für die schnelle Suche, aber Doppelungen sind erlaubt.
|
||||
|
||||
### 2. Relational / Flexibel (Die Eigenschaften)
|
||||
* **Anrede (Pronouns / Title):** -> JSONB (`traits`). (Ändert sich gesellschaftlich, JSONB ist hier flexibler als ein harter SQL-Enum).
|
||||
* **Identity-Dokument (Perso/Passport):** -> JSONB (`traits`).
|
||||
* *Warum:* Die Felder variieren stark je nach Land (Ausgestellt in, Gültig bis, Behörde). Ein JSONB-Objekt `identity_document: { type: "passport", number: "...", valid_until: "..." }` ist hier perfekt.
|
||||
* **Geburtsort:** -> JSONB (`traits`).
|
||||
* **Adresse (Wohnort):** -> ⚠️ *Eigene Tabelle!*
|
||||
* *Warum:* Eine Adresse (Straße, Stadt, Land) kann von 5 Familienmitgliedern oder einer Firma geteilt werden. Wir bauen eine Tabelle `Address` und verknüpfen die Person(en) per ID damit. Das spart immense Redundanz.
|
||||
* **Finanz-Infos (IBAN etc.):** -> JSONB (`traits`).
|
||||
|
||||
### 3. Logisch durch den Graphen gelöst (Architektur-Bonus)
|
||||
* **Buchungsverlauf:** Wird *nicht* in der Person gespeichert. Ergibt sich zu 100% aus den `Event`-Knoten, die mit der Person (oder ihrer Gruppe) verknüpft sind.
|
||||
* **Loginrights:** Werden nicht gespeichert. Das übernimmt die Nextcloud (SSO). Wir speichern nur die `ssoId`.
|
||||
* **Type (Kollektiv / Client / Personal):** Wird *nicht* als hartes Feld gespeichert! Ergibt sich aus den `GroupMembership`-Verknüpfungen (z.B. wenn Person A Mitglied in der Operator-Gruppe ist, ist sie Personal).
|
||||
* **Groups & Groupfunction:** Wird 1:1 durch unsere Pivot-Tabelle `GroupMembership` (inkl. `role`) abgebildet.
|
||||
|
||||
## Claras Vorschlag für die finale Prisma-Tabelle `Person`
|
||||
```prisma
|
||||
model Person {
|
||||
id String @id @default(uuid())
|
||||
firstName String @db.VarChar(100)
|
||||
lastName String @db.VarChar(100)
|
||||
nickname String? @db.VarChar(100)
|
||||
birthDate DateTime? @db.Date
|
||||
|
||||
// Kontaktdaten (NICHT UNIQUE!)
|
||||
email String? @db.VarChar(255)
|
||||
phone String? @db.VarChar(50)
|
||||
|
||||
// Verknüpfung zur Authentifizierung
|
||||
ssoId String? @unique @db.VarChar(255)
|
||||
|
||||
// Die Wohnadresse (Relational)
|
||||
addressId String?
|
||||
address Address? @relation(fields: [addressId], references: [id])
|
||||
|
||||
// Alles andere (Pass, Finanzen, Anrede, Geburtsort, Allergien)
|
||||
traits Json @default("{}")
|
||||
|
||||
groupMemberships GroupMembership[]
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
}
|
||||
```
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
# RoggioApp - Units & Hierarchie (Beispiel: Wohnung im 1. Stock)
|
||||
|
||||
## 1. Die Hierarchie (Der Tree)
|
||||
Die Macht unseres Node-Graphen zeigt sich in der Hierarchie. Wir legen nicht eine fette Zeile in einer Tabelle "Wohnung" an, sondern stapeln Units ineinander (`parentId`).
|
||||
|
||||
**Beispiel-Baum:**
|
||||
* **Unit 1:** Haus A (Parent: Null)
|
||||
* **Unit 2:** 1. Stock (Parent: Unit 1)
|
||||
* **Unit 3:** Wohnung Sonnenaufgang (Parent: Unit 2, `is_rentable: true`)
|
||||
* **Unit 4:** Küche (Parent: Unit 3)
|
||||
* **Unit 5:** Bad (Parent: Unit 3)
|
||||
* **Unit 6:** Wohnzimmer (Parent: Unit 3)
|
||||
* **Unit 7:** Terrasse (Parent: Unit 3)
|
||||
* **Unit 8:** Schlafzimmer 1 (Parent: Unit 3)
|
||||
* **Unit 9:** Doppelbett (Parent: Unit 8, `capacity: 2`)
|
||||
* **Unit 10:** Schlafzimmer 2 (Parent: Unit 3)
|
||||
* **Unit 11:** Einzelbett A (Parent: Unit 10, `capacity: 1`)
|
||||
* **Unit 12:** Einzelbett B (Parent: Unit 10, `capacity: 1`)
|
||||
* **Unit 13:** Schlafzimmer 3 (Parent: Unit 3)
|
||||
* **Unit 14:** Einzelbett (Parent: Unit 13, `capacity: 1`)
|
||||
|
||||
## 2. Die Eigenschaften (Traits im JSONB)
|
||||
Wie unterscheiden wir nun ein Schlafzimmer von einem Bad, ohne für jedes eine eigene Datenbanktabelle zu bauen? Über das JSONB-Feld `traits`.
|
||||
|
||||
**Beispiele für das `traits`-Feld (JSON):**
|
||||
|
||||
* **Bei Unit 3 (Wohnung Sonnenaufgang):**
|
||||
```json
|
||||
{
|
||||
"type": "apartment",
|
||||
"is_rentable": true,
|
||||
"base_price": 85.00,
|
||||
"cleaning_fee": 40.00
|
||||
}
|
||||
```
|
||||
*(Hinweis: Fläche und Kapazität stehen hier absichtlich NICHT drin. Sie werden später live aus den Kindern berechnet!)*
|
||||
|
||||
* **Bei Unit 8 (Schlafzimmer 1):**
|
||||
```json
|
||||
{
|
||||
"type": "room",
|
||||
"room_category": "bedroom",
|
||||
"area_sqm": 18.5,
|
||||
"features": ["Meerblick", "Verdunkelungsrollos"]
|
||||
}
|
||||
```
|
||||
|
||||
* **Bei Unit 9 (Doppelbett):**
|
||||
```json
|
||||
{
|
||||
"type": "inventory",
|
||||
"inventory_category": "bed_double",
|
||||
"sleep_capacity": 2,
|
||||
"width_cm": 180
|
||||
}
|
||||
```
|
||||
|
||||
* **Bei Unit 5 (Bad):**
|
||||
```json
|
||||
{
|
||||
"type": "room",
|
||||
"room_category": "bathroom",
|
||||
"area_sqm": 8.0,
|
||||
"features": ["Dusche", "Badewanne", "Bidet", "Fenster"]
|
||||
}
|
||||
```
|
||||
|
||||
* **Bei Unit 4 (Küche):**
|
||||
```json
|
||||
{
|
||||
"type": "room",
|
||||
"room_category": "kitchen",
|
||||
"area_sqm": 12.0,
|
||||
"inventory": ["Kühlschrank", "Herd", "Kaffeemaschine", "Töpfe"]
|
||||
}
|
||||
```
|
||||
|
||||
## 3. Claras Fazit zur Flexibilität
|
||||
Wir müssen in Postgres nur EINE einzige Tabelle `Unit` abfragen, um das gesamte Gebäude zu kennen.
|
||||
Wenn jemand wissen will: *"Wie groß ist die Wohnung und wie viele Leute können dort schlafen?"*
|
||||
Dann fragt unser Backend: *"Summiere alle `area_sqm` von allen Units, deren direkter oder indirekter Parent die 'Wohnung Sonnenaufgang' ist. Und summiere alle `sleep_capacity` der Kinder."*
|
||||
Ergebnis: Fläche = 38.5qm, Schlafplätze = 5.
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
# RoggioApp - Inventar & Zustand (Soll vs. Ist)
|
||||
|
||||
## Das Problem
|
||||
Inventar (Löffel, Gasflaschen) ist nicht statisch. Es verbraucht sich, geht kaputt oder verschwindet. Wir müssen nicht nur wissen, *was* da sein sollte, sondern *wie viel* davon tatsächlich da ist, um Aktionen (Nachbestellen) zu triggern.
|
||||
|
||||
## Sevs Ansatz: Soll- vs. Ist-Anzahl
|
||||
Beispiele:
|
||||
* Küche: Löffel (Soll: 10, Ist: 5)
|
||||
* Butanlager: 5kg Flaschen (Soll: 10, Ist: 7)
|
||||
|
||||
## Claras architektonische Einordnung im Unit-Graphen
|
||||
Wir können Inventar nicht einfach als dumme Text-Strings in ein Array (`inventory: ["Löffel"]`) packen, wenn wir Soll/Ist-Zahlen erfassen wollen. Wir müssen dem Inventar eine kleine Struktur geben.
|
||||
|
||||
Da wir JSONB (`traits`) verwenden, ist das ein Kinderspiel und erfordert **keine** Änderung am Prisma-Schema!
|
||||
|
||||
### Lösung A: Inventar als strukturiertes Objekt im Raum-Trait
|
||||
Anstatt eines einfachen Arrays speichern wir ein Array aus Objekten im JSONB der Unit (z.B. der Küche oder dem Butanlager).
|
||||
|
||||
*Trait für die Unit "Butanlager":*
|
||||
```json
|
||||
{
|
||||
"type": "storage_room",
|
||||
"inventory_state": [
|
||||
{
|
||||
"item_id": "item_butan_5kg",
|
||||
"name": "5kg Butangas",
|
||||
"target_qty": 10,
|
||||
"actual_qty": 7,
|
||||
"alert_threshold": 3
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
*Vorteil:* Sehr schnell abfragbar.
|
||||
*Nachteil:* Die Endreinigungskräfte müssten theoretisch die Unit "Butanlager" bearbeiten, um die Zahl anzupassen.
|
||||
|
||||
### Lösung B: Inventar als eigene Sub-Units (Der reinrassige Graph-Weg)
|
||||
Anstatt die Gabeln als Text in die Küche zu schreiben, machen wir aus "Bestand: Löffel" eine eigene Unit, die als Child *unter* der Küche hängt.
|
||||
|
||||
*Unit "Löffel-Bestand" (Parent: Küche):*
|
||||
```json
|
||||
{
|
||||
"type": "inventory_item",
|
||||
"name": "Kaffeelöffel",
|
||||
"target_qty": 10,
|
||||
"actual_qty": 5
|
||||
}
|
||||
```
|
||||
*Vorteil:* Absolut sauber. Wenn eine Putzkraft nach der Reinigung einen "Schwund" meldet, erstellt das System ein `Event` ("Inventar-Korrektur") und verknüpft es direkt mit der Unit "Löffel-Bestand". Die Historie bleibt erhalten (Wer hat wann gemeldet, dass 5 Löffel fehlen?).
|
||||
|
||||
## Die Konsequenz für Aufgaben (Events)
|
||||
Wenn `actual_qty` kleiner wird als ein definierter Schwellenwert (z.B. bei Gasflaschen), feuert unser System automatisch ein neues `Event` vom Typ `TASK_BUY` (Einkaufen). Dieses Event taucht dann in Nextcloud auf der Einkaufsliste auf.
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
# RoggioApp - Beschluss: Weg 2 (Alles ist eine Unit)
|
||||
|
||||
## Der Entschluss
|
||||
Wir nutzen **Weg 2** für *jedes* Inventar, bei dem Bestände, Schwund oder Nachbestellungen (Soll/Ist) relevant sind.
|
||||
Das bedeutet: Wir verpacken Inventar nicht in unübersichtliche JSON-Arrays innerhalb eines Raums, sondern machen **jedes relevante Inventar zu einer eigenen Child-Unit**.
|
||||
|
||||
## Warum das die mächtigste Entscheidung ist
|
||||
1. **Historie (Audit-Trail):** Wenn "5 Löffel fehlen" als JSON in der Küche steht, weiß niemand, wann das passiert ist. Wenn der Löffel-Bestand eine eigene Unit ist, kann ein `Event` ("Inventur nach Abreise von Rossi") mit der Löffel-Unit verknüpft werden. Man sieht exakt: Am 26.04. hat Sarah 5 fehlende Löffel eingetragen.
|
||||
2. **Modularität:** Ein Grill (Unit) kann heute zur Wohnung 1 (Parent) gehören und morgen auf dem Zirkuswagen-Platz (neuer Parent) stehen. Hätten wir den Grill als JSON in Wohnung 1 geschrieben, müssten wir JSON manipulieren. So verschieben wir einfach die `parentId`.
|
||||
3. **Wartung & Defekte:** Eine Spülmaschine (Unit) kann den Trait `status: "broken"` bekommen. Das generiert automatisch ein `Event` (Task: Reparatur) für die Support-Gang.
|
||||
|
||||
## Das Datenmodell bleibt unberührt
|
||||
Es beweist die Stärke des Modells: Obwohl wir gerade die Verwaltung von Besteck, Gasflaschen und Spülmaschinen beschlossen haben, müssen wir in `schema.prisma` **nichts** ändern. Die `Unit`-Tabelle fängt das komplett ab.
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
# RoggioApp - Zeitachsen (Buchungen, Anwesenheit, Schichten)
|
||||
|
||||
## Die große Vereinheitlichung
|
||||
Genau wie bei den Units gibt es auch bei der Zeit keine harten Trennungen.
|
||||
**Eine Buchung für einen Gast, eine Schicht für Personal und die private Anwesenheit eines Kollektiv-Mitglieds sind technisch absolut identisch.**
|
||||
|
||||
Alles sind `Event`-Knoten auf einer Zeitachse.
|
||||
|
||||
### Der Aufbau eines Zeitachsen-Events
|
||||
Jedes dieser Ereignisse nutzt exakt dieselbe Tabelle (`Event`) in der Datenbank. Die Magie entsteht durch die Verknüpfung (Relationen) und das Etikett (Type).
|
||||
|
||||
**Beispiel 1: Die klassische Gast-Buchung**
|
||||
* `type`: "RENTAL"
|
||||
* `startTime`: 15.08.2026 15:00
|
||||
* `endTime`: 22.08.2026 10:00
|
||||
* `targetUnitId`: Haus A (Was ist belegt?)
|
||||
* `targetGroupId`: Familie Rossi (Wer belegt es?)
|
||||
|
||||
**Beispiel 2: Die Personal-Schicht (Putzen)**
|
||||
* `type`: "SHIFT_WORK"
|
||||
* `startTime`: 22.08.2026 10:00
|
||||
* `endTime`: 22.08.2026 14:00
|
||||
* `targetUnitId`: Haus A (Wo wird gearbeitet?)
|
||||
* `targetPersonId`: Putzkraft Maria (Wer arbeitet?)
|
||||
* *Abhängigkeit (`parentEventId`):* Verweist auf Beispiel 1 (Wird automatisch getriggert, wenn Rossi abreist).
|
||||
|
||||
**Beispiel 3: Anwesenheit Kollektiv (Workation/Urlaub)**
|
||||
* `type`: "PRESENCE_COLLECTIVE"
|
||||
* `startTime`: 01.09.2026
|
||||
* `endTime`: 14.09.2026
|
||||
* `targetUnitId`: Zirkuswagen (Wo schläft Sev?)
|
||||
* `targetPersonId`: Sev (Wer ist da?)
|
||||
|
||||
## Warum das Architektur in Perfektion ist
|
||||
Da alle drei Dinge in *derselben* Tabelle als Event mit Start- und Endzeit liegen, ist das Verhindern von Doppelbelegungen ("Clash-Detection") extrem einfach.
|
||||
Wenn ein Gast "Haus A" buchen will, fragt die Datenbank: *"Gibt es irgendein Event (egal ob RENTAL, SHIFT oder PRESENCE), das sich mit diesem Datum auf 'Haus A' überschneidet?"*
|
||||
Wenn Sev also für seine Workation das Haus A als "Anwesenheit" blockt, kann kein Gast es buchen. Man muss nicht drei verschiedene Tabellen (`Urlaub`, `Buchungen`, `Sperrzeiten`) abfragen!
|
||||
@@ -0,0 +1,29 @@
|
||||
# RoggioApp - Replikation & Hardcore-Datenschutz (Online vs. On-Premise)
|
||||
|
||||
## Die Anforderung
|
||||
Zwei aktive Datenbanken (Cloud bei Hetzner + On-Premise auf dem Gelände).
|
||||
**Das Ziel:** Sensible Daten (Ausweisnummern, IBANs, Kreditkarten) dürfen **niemals** in der Cloud-Datenbank liegen. Die Cloud hat nur Pointer (Referenzen) auf diese Daten. Die echte Entschlüsselung und Speicherung findet nur im Safe (On-Premise) statt.
|
||||
|
||||
## Die Lösungsarchitektur (Der Hybrid-Ansatz)
|
||||
|
||||
Wir können nicht einfach eine blinde PostgreSQL-Replikation (Master-Slave) einschalten. Wenn die Cloud der Master ist, hat sie alle Daten. Wenn wir das verhindern wollen, müssen wir die Applikations-Architektur anpassen.
|
||||
|
||||
### 1. Tokenisierung (Das Pointer-Konzept)
|
||||
Die Cloud-Datenbank (Hetzner) speichert keine JSONB-Traits mit sensiblen Daten.
|
||||
Anstatt: `traits: { iban: "DE1234...", passport: "X123" }`
|
||||
Speichert die Cloud: `traits: { secure_vault_id: "vault_abc123" }`
|
||||
|
||||
### 2. Der Vault (On-Premise)
|
||||
Auf dem Server im Gelände läuft ein isolierter "Vault-Service" (z.B. HashiCorp Vault oder eine eigene hochsichere Micro-Postgres-Instanz).
|
||||
Dort liegt die Tabelle `Vault_Data` mit den Inhalten für `vault_abc123`.
|
||||
|
||||
### 3. Wie die API damit umgeht
|
||||
* **Der Cloud-API-Server (Hetzner):** Handelt nur Termine, Namen (ohne Ausweise), Unit-Kapazitäten und Buchungs-Slots. Wenn ein Gast via WordPress bucht und seine IBAN eintippt, schickt die Cloud-API diese IBAN *direkt* durch einen verschlüsselten Tunnel (WireGuard) an den Vault On-Premise, holt sich eine generierte `vault_id` zurück und speichert *nur diese ID* in der Cloud-Datenbank.
|
||||
* **Der On-Premise-API-Server (Das Kollektiv-Cockpit):** Wenn du zu Hause im Netzwerk die Buchung aufmachst, lädt das Frontend die Buchung (aus der Cloud-DB) PLUS die sensiblen Daten (aus dem lokalen Vault) und fügt sie in deinem Browser zusammen.
|
||||
|
||||
### 4. Was passiert bei Internet-Ausfall?
|
||||
Wenn das Gelände offline ist, kann die Cloud immer noch fröhlich neue Buchungen über WordPress annehmen. Sie speichert die sensiblen Daten (z.B. Ausweisfotos) temporär hochverschlüsselt (asymmetrisch, z.B. mit PGP) zwischen. Sobald die On-Premise-Kiste wieder online geht, zieht sie sich die verschlüsselten Pakete, entschlüsselt sie mit ihrem privaten Offline-Key, speichert sie im Vault und sagt der Cloud: "Hab ich, du kannst das Original jetzt löschen."
|
||||
|
||||
## Claras Einschätzung
|
||||
Das ist Enterprise-Level Security (Zero Trust für die Cloud).
|
||||
Für Prisma bedeutet das: Wir trennen die sensiblen Traits strikt ab. Wir können das relativ elegant bauen, indem wir für sensible Traits eine eigene Prisma-Logik verwenden, die immer mit einem On-Prem-Service redet, statt sie ins normale Cloud-JSONB-Feld zu kippen.
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
# RoggioApp - Vault Implementierung (MVP Step 1)
|
||||
|
||||
## Die MVP-Strategie für den Datenschutz-Split
|
||||
Wir bauen nicht sofort eine zweite physische Datenbank und einen VPN-Tunnel auf. Das würde uns am Anfang massiv ausbremsen. Wir simulieren die Architektur zuerst *logisch*, bevor wir sie *physisch* trennen.
|
||||
|
||||
### Schritt 1 (Jetzt): Die logische Trennung (Separate Table)
|
||||
In unserer aktuellen Prisma-Datenbank (Hetzner/Cloud) legen wir eine **eigenständige Tabelle `Vault`** an.
|
||||
* **Warum:** So zwingen wir unseren Backend-Code vom ersten Tag an dazu, so zu tun, als lägen die Daten auf einem anderen Server. Die Logik "Hole Token -> Lade Daten aus Vault" wird in Code gegossen.
|
||||
* **Der Vorteil:** Wenn wir später den echten On-Premise-Server hinstellen, müssen wir den Backend-Code nicht mehr anfassen. Wir ändern nur die Datenbank-URL für die `Vault`-Tabelle auf deine lokale IP um.
|
||||
|
||||
### Schritt 2 (Später): Die physische Trennung
|
||||
Sobald der On-Premise-Server steht, lösen wir die Tabelle `Vault` aus Prisma heraus und schieben sie in eine eigene, kleine SQLite/Postgres-Instanz auf deinem HomeAssistant-Server.
|
||||
|
||||
## Anpassung für Prisma (Mock-Vault)
|
||||
```prisma
|
||||
// ============================================================================
|
||||
// DER VAULT (Logisch getrennt - Später On-Premise)
|
||||
// ============================================================================
|
||||
model VaultData {
|
||||
id String @id @default(uuid())
|
||||
entityType String @db.VarChar(50) // e.g. "Person", "Booking"
|
||||
entityId String // Die ID der Person oder Buchung in der Cloud
|
||||
|
||||
// Die sensiblen Daten (verschlüsselt!)
|
||||
secureData String @db.Text
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
}
|
||||
```
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
# RoggioApp - Nächster Schritt: Das Seed/CRUD Script
|
||||
|
||||
## Das Ziel
|
||||
Die graue Theorie der Graphen-Architektur (`Units`, `Traits`, `Events`) mit echtem Leben füllen.
|
||||
Wir brauchen einen visuellen / greifbaren Beweis, dass unser Prisma-Modell einen komplexen Baum (Wohnung im 1. Stock mit Inventar) abbilden kann und dass wir die Flächen/Kapazitäten rekursiv abfragen können.
|
||||
|
||||
## Der Plan: Ein Node.js Playground-Script
|
||||
Bevor wir ein riesiges Frontend bauen, schreiben wir ein simples `seed.ts` oder `playground.ts` Skript, das die Prisma-API nutzt.
|
||||
|
||||
### Was das Script tun soll:
|
||||
1. **Baum aufbauen:** Es erstellt das Grundstück, ein Haus, den 1. Stock und eine "Wohnung Sonnenaufgang" mit Küche, Bad und Schlafzimmern inklusive Betten und Besteck (als Child-Units).
|
||||
2. **Personen anlegen:** Es erstellt eine Test-Person (z.B. "Sev") und speichert die "Ausweisnummer" testweise in unserer neuen `VaultData`-Tabelle, um die Trennung zu simulieren.
|
||||
3. **Ein Event (Buchung) triggern:** Es verknüpft Sev mit der "Wohnung Sonnenaufgang" für ein Datum.
|
||||
4. **Die Graph-Abfrage (Der Test):** Das Script führt die "Magic-Query" aus, die beweist, dass unser Ansatz funktioniert: Es fragt die Datenbank nach der Wohnung und rechnet rekursiv die Flächen der untergeordneten Räume und die Schlafplätze der Betten zusammen.
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
# RoggioApp - Trennung von "Gehirn" und "Muskeln"
|
||||
|
||||
## Die Philosophie
|
||||
Die OpenClaw-Instanz (`192.168.20.199`) ist Claras Safe-Space. Hier läuft ihr Gedächtnis, ihre Konfiguration und die interne Steuerung.
|
||||
Projekte (wie der API-Server, Datenbanken) dürfen hier *nicht* ausgeführt werden.
|
||||
|
||||
## Zukünftiger Standard-Workflow
|
||||
1. Code wird von Clara in `/home/clara/.openclaw/workspace/projects` (was ein SMB-Mount auf die Docker-VM ist) geschrieben.
|
||||
2. Anstatt Code mit `npx tsx` auf Claras Instanz auszuführen, nutzt Clara *ausschließlich* SSH, um den Code auf der Docker-VM (`192.168.20.252`) auszuführen.
|
||||
3. Der Nginx Proxy Manager leitet Traffic immer auf die Docker-VM weiter.
|
||||
|
||||
*Folge:* Wenn ein API-Prototyp eine Endlosschleife hat oder RAM frisst, stürzt die Docker-VM ab. Clara bleibt aber zu 100% online und kann den Fehler beheben.
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
# RoggioApp - Infrastruktur: Gitea & Node.js Docker-Setup
|
||||
|
||||
## Die Notwendigkeit
|
||||
Projekte wie RoggioApp oder unser API-Server sollten nicht einfach per `nohup` oder in `tmux`-Sessions auf der Docker-VM laufen. Das ist fehleranfällig und schlecht wartbar.
|
||||
Zudem fehlt uns eine Versionskontrolle (Git) auf dem Server, um Code-Änderungen sicher nachverfolgen zu können, ohne Abhängigkeiten zu externen Anbietern (wie GitHub/GitLab) zu schaffen.
|
||||
|
||||
## Die Lösung: Das Infra-Compose-File
|
||||
Wir erweitern unsere Sandkiste (`192.168.20.252`) um ein sauberes Infrastruktur-Compose-Setup:
|
||||
|
||||
### 1. Gitea (Lokales GitHub)
|
||||
* *Warum:* Gitea ist extrem leichtgewichtig (geschrieben in Go), verbraucht kaum RAM und gibt uns eine professionelle Git-Oberfläche (Issues, Pull Requests, Code-Hosting) direkt im lokalen Netzwerk.
|
||||
* *Setup:* Ein Container in der Docker-VM. Wir erstellen einen NPM-Proxy (z.B. `git.h80.11112222.net`).
|
||||
|
||||
### 2. Node.js Production-Serving (PM2 im Docker)
|
||||
* *Warum:* Statt Node.js "nackt" auf dem Host (VM) laufen zu lassen, packen wir die Node.js-Applikationen in eigene Docker-Container.
|
||||
* *Wie:* Für Test-API-Server (wie `bookme`) schreiben wir kleine `Dockerfile`s, die den Code bauen und per Node starten. Diese Container hängen wir dann in das Netzwerk des Nginx Proxy Managers.
|
||||
|
||||
## Nächste Schritte
|
||||
1. Eine `docker-compose.yml` für Gitea anlegen und starten.
|
||||
2. Die Subdomain `git.h80.11112222.net` im Nginx Proxy Manager eintragen.
|
||||
3. Das RoggioApp-Projekt als erstes Repo initialisieren.
|
||||
Reference in New Issue
Block a user