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>
8.1 KiB
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:
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:
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(): LiefertNonefür SysAdmin/AppAdmin (kein Filter), sonstcurrent_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:
- Die Modell-Funktion
get_xxx(...)einen optionalenuser_id-Parameter erhalten. Ist dieser gesetzt, wird per Subquery aufprojekt_usersgefiltert. - Die Modell-Funktion
get_xxx_by_id(id, user_id)bei gesetztemuser_iddie Projekt-Zuordnung prüfen und bei Fehlen 404 werfen. - Die Lese-Endpunkte in
crm/routes.py_get_visible_user_id()aufrufen und das Ergebnis an die Modell-Funktion übergeben. - Die Schreib-Endpunkte
_require_projekt_access(projekt_id)aufrufen, bevor die Änderung durchgeführt wird. Bei create/update stehtprojekt_idim JSON-Body (request.json["projekt_id"]bzw.request.json["id"]); bei delete muss die Entität ggf. erst mit Sichtbarkeits-Check geladen werden, um dieprojekt_idzu 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.