Bei Neuinstallation erzeugt create_all bereits das gesamte Schema,
so dass nachfolgende ALTER-Table-Migrationen fehlschlagen ("duplicate
column name"). V01_04 prüft nun die Spalten-Existenz per PRAGMA
table_info. checkupdates läuft wieder sequentiell ohne Überspringen.
Konvention in AGENTS.md dokumentiert.
Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
82 lines
8.1 KiB
Markdown
82 lines
8.1 KiB
Markdown
# Architektur der Anwendung
|
||
Beachte die strukturellen Vorgaben, die in ai-context/AI-operative-Vorgaben.md definiert sind.
|
||
In den Programmen sollen aussagekräftige Kommentare in deutsch erstellt werden.
|
||
|
||
## Vorgaben git Versionsverwaltung
|
||
Ein neuer Branch darf nur auf ausdrückliche Anweisung hin erstellt werden. Ohne diese Anweisung werden Änderungen immer in main gemacht.
|
||
|
||
## Vorgaben für die Typescript und mithril.js Entwicklung
|
||
Verwende bei der GUI primar die Widgets und Komponenten, die es in Client/Components/widgets und Client/Components gibt. Wenn Widgets fehlen, gib eines Hinweis und erstelle einen Vorschlag für ein entsprechendes neues Widget.
|
||
Es soll immer ein responsive Design implmentiert werden, da die App sowohl auf PCs als auch auf mobilen Geräten laufen soll.
|
||
Verwende für Attribute der GUI-Oberfläche nach Möglichkeit Klassen aus NGINX_Server/html/static/w3.css, insbesondere wenn es darum geht, ein Respnosive Design zu implentieren.
|
||
Für Farben (foreground und background) verwende priorisiert die Klassen aus NGINX_Server/html/static/app-theme.css
|
||
View-Container werden mit `w3-container.w3-auto` gewrappt, damit sie auf kleinen Bildschirmen 100% und auf großen bis max. 1140px (bzw. 95%) zentriert werden.
|
||
|
||
Bei Mithril `m(...)`-Aufrufen mit längeren Attributlisten (z.B. mehrere `style`-Eigenschaften oder mehrere Attribute) schreibe die Attribute untereinander, sobald sie in einer Zeile nicht mehr gut lesbar sind (Faustregel: mehr als 2–3 Attribute oder ein mehrzeiliges `style`-Objekt). Kurze Attributlisten (1–2 Attribute, einzeiliges `style`) dürfen in einer Zeile bleiben. Formatbeispiel:
|
||
```typescript
|
||
m("div.w3-bar-block.w3-border", {
|
||
style: {
|
||
width: '95%',
|
||
'max-height': '220px',
|
||
'overflow-y': 'auto',
|
||
position: 'absolute',
|
||
'z-index': '1000'
|
||
}
|
||
})
|
||
```
|
||
|
||
Für Symbole auf Buttons und ähnlichen Steuerelementen verwende Symbole aus den Google Material Icons aus der NGINX_Server/html/static/Google_Symbols.woff2 Datei.
|
||
Erstelle die Datenmodelle für die Typescript-Entwicklung im Verzeichnis Client/Models. Erstelle für die verschiedenen Datenmodelle (Kunde, Projekt usw.) eigene .ts-Dateien. Erstelle in der jeweiligen Datei auch den Aufruf der REST-Api für die Server-Seite.
|
||
|
||
Beim Aufruf einer Bearbeitungsmaske werden in den Route-Params nur die ID (und ggf. zum Vorbelegen notwendige Eltern-FKs wie `kunden_id`) übergeben — nicht die vollständigen Felder des Objekts. Die Maske lädt die Daten frisch vom Server via `fetchXxxById(id)` im `oninit`. So wird sichergestellt, dass die Daten aktuell sind und zwischenzeitliche Änderungen anderer Nutzer nicht mit veralteten Werten aus der Liste überschrieben werden. Beim Neuanlegen (`id === -1`) entfällt der Fetch; vorbelegte Werte (z.B. `kunden_id`) kommen aus den Route-Params.
|
||
|
||
Jeder `m.request`-Aufruf (REST-Endpunkt) muss `.catch((e) => showServerError(e))` anhängen, damit Serverfehler (z.B. 401 bei fehlender Autorisierung) dem Nutzer als Modal angezeigt werden. Die Navigation (`m.route.set`) steht im `.then`-Zweig, damit sie bei Fehler nicht ausgeführt wird und der Nutzer auf der Maske bleibt. `showServerError` wird aus `./auth/authmessages` importiert.
|
||
|
||
Bei `m.ClosureComponent` den Closure-Parameter weglassen (`() =>`) und Attribute ausschließlich über das `vnode` des jeweiligen Lifecycle-Hooks (`view`, `oninit`, `onupdate` etc.) lesen. Das `vnode` der Closure-Funktion ist ein Snapshot zum Erzeugungszeitpunkt und wird bei Redraws nicht aktualisiert — daraus gelesene `attrs` sind veraltet. Initialisierung, die `attrs` braucht, gehört nach `oninit`. Beispiel:
|
||
```typescript
|
||
export const Select: m.ClosureComponent<iSelect> = () => {
|
||
// closure-State ohne Referenz auf attrs
|
||
let search = ""
|
||
return {
|
||
oninit: (vnode) => {
|
||
// vnode.attrs hier initial lesen
|
||
search = vnode.attrs.value
|
||
},
|
||
view: (vnode) => {
|
||
// vnode.attrs enthält die aktuellen Werte je Render-Durchlauf
|
||
const options = vnode.attrs.options
|
||
...
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
Nach jeder Änderung an `.ts`-Dateien unter `Client/` ist der Build-Prozess (tsc + rollup) auszuführen, damit die deploybaren Bundles in `NGINX_Server/html/static/` aktuell sind. Verwende dafür den `client-build`-Skill.
|
||
|
||
## Vorgaben für die Server-seitige Entwicklung unter Python
|
||
|
||
### Projektbezogene Sichtbarkeit und Schreibschutz
|
||
Projekte sind nutzerbezogen: Ein Nutzer sieht nur Projekte, denen er über die Tabelle `projekt_users` zugeordnet ist. Diese Sichtbarkeits-Filterung muss für **jede** projektbezogene Entität (Projekt selbst und alle davon abhängigen Entitäten wie Kontaktadresse, CallLog) in den Lese- und Schreib-Endpunkten umgesetzt werden.
|
||
|
||
Die Prüfung erfolgt rollenbasiert, nicht flagbasiert: SysAdmin und AppAdmin (geprüft via `current_user.is_SysAdmin()` bzw. `current_user.has_permission(app.config["APPID"], [APP_ADMIN])`) sehen alle Projekte. Alle anderen Nutzer werden über ihre `current_user.id` gefiltert.
|
||
|
||
Dafür werden in `crm/routes.py` zwei Hilfsfunktionen bereitgestellt:
|
||
- `_get_visible_user_id()`: Liefert `None` für SysAdmin/AppAdmin (kein Filter), sonst `current_user.id`. Wird an die Modell-Funktionen der Lese-Endpunkte (list/get) übergeben.
|
||
- `_require_projekt_access(projekt_id)`: Prüft vor Schreiboperationen (create/update/delete), ob der Nutzer dem Projekt zugeordnet ist. Bei fehlender Zuordnung wird 404 (nicht 403) geworfen, um keine Rückschlüsse auf die Existenz des Projekts zu ermöglichen.
|
||
|
||
Die `user_id` wird als Parameter übergeben statt `current_user` direkt im Modell zu lesen, weil `None` als Signalwert für Admins (kein Filter) nur so sauber transportiert werden kann und die Modell-Funktionen frei von Flask-Request-Kontext bleiben — das wahrt die Schichtentrennung und Testbarkeit.
|
||
|
||
Bei neuen projektbezogenen Entitäten muss:
|
||
1. Die Modell-Funktion `get_xxx(...)` einen optionalen `user_id`-Parameter erhalten. Ist dieser gesetzt, wird per Subquery auf `projekt_users` gefiltert.
|
||
2. Die Modell-Funktion `get_xxx_by_id(id, user_id)` bei gesetztem `user_id` die Projekt-Zuordnung prüfen und bei Fehlen 404 werfen.
|
||
3. Die Lese-Endpunkte in `crm/routes.py` `_get_visible_user_id()` aufrufen und das Ergebnis an die Modell-Funktion übergeben.
|
||
4. Die Schreib-Endpunkte `_require_projekt_access(projekt_id)` aufrufen, bevor die Änderung durchgeführt wird. Bei create/update steht `projekt_id` im JSON-Body (`request.json["projekt_id"]` bzw. `request.json["id"]`); bei delete muss die Entität ggf. erst mit Sichtbarkeits-Check geladen werden, um die `projekt_id` zu ermitteln.
|
||
|
||
## Generierte Dateien nicht beachten
|
||
Das Verzeichnis `Client/tsout/` enthält die JavaScript-Ausgabe der TypeScript-Kompilation und wird durch den Build-Prozess (tsc + rollup) erzeugt. Diese Dateien sind nicht manuell zu bearbeiten und brauchen bei der Codeanalyse nicht berücksichtigt zu werden. Ebenso sind die JavaScript-Dateien in `NGINX_Server/html/static/` (z.B. `kbp_crm_00.00.01.js`) die Ausgabe des Rollup-Builds und nicht zu untersuchen.
|
||
|
||
## Vorgaben für DB-Migrationen (checkdb.py)
|
||
Die DB-Versionierung erfolgt über `_db_Vxx_xx`-Funktionen in `auth/model/checkdb.py` und `crm/model/checkdb.py`. Beim Start wird `checkupdates()` aufgerufen und führt alle Funktionen aus, deren Versionsnummer größer ist als die in der Systeminfo-Tabelle vermerkte. Bei einer Neuinstallation (leere Datenbank) erzeugt `_db_V01_00` über `create_all` bereits das **gesamte aktuelle Schema** aus den Models — inklusive aller Spalten, Tabellen und Indizes, die in späteren Migrationen erst nachträglich ergänzt wurden. Die inkrementellen Migrationen laufen danach dennoch sequentiell nach.
|
||
|
||
Daher gilt die Konvention: **Jede `_db_Vxx_xx`-Migration muss idempotent sein** — sie muss fehlerfrei durchlaufen, auch wenn das von ihr geänderte Schema-Element bereits durch `create_all` existiert. Insbesondere müssen `ALTER TABLE ADD COLUMN`-Anweisungen vorab prüfen, ob die Spalte bereits existiert (z.B. via `PRAGMA table_info`). `CREATE INDEX` verwendet `IF NOT EXISTS`. Daten-UPDATEs auf leeren Tabellen matchen nichts und sind somit unbedenklich. Migrationen, die Seed-Daten einfügen, müssen vorab prüfen, ob diese bereits vorhanden sind.
|