15 KiB
AHK-Ehrenamtsmanagement — Entscheidungsprotokoll und bisheriger Aufbau
Stand: 2026-09-16
Projekt: AHK-Ehrenamtsmanagement
Technischer Name:ahk-ehrenamtsmanagement
Status: Greenfield-Neustart; Phase 0 vollständig geplant, Phase 1 vollständig entschieden, Phase 2 begonnen
Verbindliche Grundlage:PROJECT_DESIGN.md
Visuelle Referenz:PROJECT_DESIGN_VOR_ORT_MOCKUP.png
Arbeitsprinzip: WennPROJECT_DESIGN.mdetwas nicht entscheidet, wird nicht geraten, sondern bewusst gemeinsam entschieden.
1. Grundsatzentscheidungen
1.1 Greenfield statt Legacy-Umbau
Der bestehende Prototyp wird nicht migriert.
- kein erhaltenswerter Produktivdatenbestand
- nur ein einzelner Testnutzer ohne relevante Inhalte
- Ziel ist ausdrücklich, den Aufbau der App von Grund auf zu lernen
- kein Legacy-Code wird nur deshalb übernommen, weil er bereits existiert
- keine Legacy-Datenmigration
- keine Kompatibilitätsschicht
- keine Datenrettung aus der alten SQLite-Datenbank
Falls alte Implementierungen später als Referenz dienen, werden sie zuerst gegen PROJECT_DESIGN.md geprüft.
1.2 Projektname
Sichtbarer Produktname:
AHK-Ehrenamtsmanagement
Technischer Name:
ahk-ehrenamtsmanagement
Der technische Name soll konsistent für Repository, App-Verzeichnis, Stack-Verzeichnis und Container verwendet werden.
1.3 Entwicklungsumgebung
Betriebssystem: EndeavourOS / Arch Linux
Shell: Bash
Python-Verwaltung: pyenv
Virtuelle Umgebungen: pyenv-virtualenv
Python-Serie: 3.14
Fish ist nicht mehr Standardshell.
1.4 Python-Environment
Eigenes pyenv-virtualenv:
ahk-ehrenamtsmanagement
Projektbindung:
pyenv local ahk-ehrenamtsmanagement
.python-version wird versioniert. Das eigentliche Virtualenv liegt außerhalb des Repositories in der pyenv-Verwaltung.
2. Phase 0 — Greenfield-Projektbasis
Ziel
Am Ende von Phase 0 existiert eine minimale, lauffähige und getestete Flask-Anwendung.
Noch nicht enthalten:
- Datenbankmodelle
- Auth
- Organisationen
- Teams
- Rollen
- Templates
- Branding
- Docker
- Fachmodule
2.1 Git-Grundlage
Initiale Struktur:
ahk-ehrenamtsmanagement/
├── PROJECT_DESIGN.md
├── PROJECT_DESIGN_VOR_ORT_MOCKUP.png
└── .gitignore
Initialisierung:
mkdir ahk-ehrenamtsmanagement
cd ahk-ehrenamtsmanagement
git init
Minimale .gitignore:
# Python
__pycache__/
*.py[cod]
# Optional local virtual environments
.venv/
venv/
# Local application data
instance/
# Environment / secrets
.env
# OS / editor noise
.DS_Store
Baseline-Commit:
git add .
git status
git commit -m "Initialize AHK-Ehrenamtsmanagement project"
Ein Remote ist in Phase 0 nicht Pflicht.
2.2 Abhängigkeiten
Runtime und Development werden getrennt.
requirements.txt:
Flask
requirements-dev.txt:
-r requirements.txt
pytest
Installation:
python -m pip install -r requirements-dev.txt
Bevorzugte Form:
python -m pip ...
2.3 Erste Flask-Struktur
Verbindlich:
- Flask App Factory
- Blueprints
- eigener Core-Bereich für
/health
Struktur:
app/
├── __init__.py
└── core/
├── __init__.py
└── health/
├── __init__.py
└── routes.py
app/core/health/__init__.py:
from flask import Blueprint
bp = Blueprint("health", __name__)
from app.core.health import routes
app/core/health/routes.py:
from app.core.health import bp
@bp.get("/health")
def health():
return {"status": "ok"}, 200
Initiale app/__init__.py:
from flask import Flask
def create_app():
app = Flask(__name__)
from app.core.health import bp as health_bp
app.register_blueprint(health_bp)
return app
Start:
flask --app 'app:create_app' run --debug
Healthcheck:
curl -i http://127.0.0.1:5000/health
Erwartung:
{"status": "ok"}
mit HTTP 200.
2.4 Erste Testbasis
tests/test_health.py:
from app import create_app
def test_health_endpoint():
app = create_app()
client = app.test_client()
response = client.get("/health")
assert response.status_code == 200
assert response.get_json() == {"status": "ok"}
Ausführen:
python -m pytest
3. Phase 1 — Technisches App-Fundament
Reihenfolge
1.1 Architektur-Gerüst
1.2 Konfigurationssystem
1.3 Flask Extensions
1.4 SQLAlchemy + SQLite
1.5 Flask-Migrate / Alembic
1.6 Teststruktur / Fixtures
1.7 Foundation-Tests
1.8 Checkpoint
3.1 Architektur-Gerüst
Die vollständige Ordnerstruktur wird als Gerüst angelegt.
Wichtig:
- nur Ordner/Pakete vorbereiten
- nicht vorsorglich überall
models.py,routes.py,services.py,permissions.pyoderforms.pyanlegen - solche Dateien entstehen erst bei echter Implementierung
app/
├── __init__.py
├── core/
│ ├── __init__.py
│ ├── health/
│ ├── auth/
│ ├── accounts/
│ ├── organizations/
│ ├── teams/
│ ├── memberships/
│ ├── invitations/
│ ├── permissions/
│ ├── branding/
│ ├── storage/
│ ├── notifications/
│ ├── dashboard/
│ ├── profiles/
│ └── audit/
├── modules/
│ ├── __init__.py
│ ├── scheduling/
│ ├── documents/
│ └── chat/
├── templates/
│ ├── core/
│ └── components/
└── static/
├── css/
├── js/
└── avatars/
tests/
instance/
Git speichert keine leeren Ordner. Keine künstliche .gitkeep-Orgie.
3.2 config.py
Liegt im Projektroot.
Zunächst:
class Config:
pass
class DevelopmentConfig(Config):
DEBUG = True
class TestingConfig(Config):
TESTING = True
Noch nicht vorsorglich anlegen:
ProductionConfigSECRET_KEY- SMTP
- Uploadlimits
- Auth-Zeiten
- Polling-Intervalle
3.3 App-Factory-Interface
Festgelegt:
def create_app(
config_class=DevelopmentConfig,
config_overrides=None,
):
...
Reihenfolge:
Factory starten
→ Config-Klasse laden
→ optionale Overrides
→ Extensions initialisieren
→ Blueprints registrieren
→ später Module registrieren
Sinngemäß:
from flask import Flask
from config import DevelopmentConfig
def create_app(
config_class=DevelopmentConfig,
config_overrides=None,
):
app = Flask(__name__)
app.config.from_object(config_class)
if config_overrides:
app.config.from_mapping(config_overrides)
return app
3.4 Zentrale Flask-Extensions
Datei:
app/extensions.py
Nur Extension-Instanzen, keine Models, keine Businesslogik, keine Konfiguration.
Zunächst:
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
Später:
from flask_migrate import Migrate
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
migrate = Migrate()
In der Factory:
db.init_app(app)
migrate.init_app(app, db)
3.5 SQLite-Strategie
Development:
instance/app.db
Sinngemäß:
SQLALCHEMY_DATABASE_URI = "sqlite:///app.db"
Tests:
- pro Test eigene temporäre echte SQLite-Datei
- keine In-Memory-Datenbank
- keine gemeinsam genutzte Testdatenbank
- nie
instance/app.db
DevelopmentConfig → instance/app.db
TestingConfig → temporäre SQLite-Datei pro Test
Production später → /app/instance/app.db
3.6 Flask-Migrate / Alembic
Runtime-Abhängigkeiten:
Flask
Flask-SQLAlchemy
Flask-Migrate
Initialisierung bereits in Phase 1:
flask --app 'app:create_app' db init
Ergebnis:
migrations/
├── alembic.ini
├── env.py
├── script.py.mako
└── versions/
Festgelegt:
migrations/wird versioniert- keine künstliche leere erste Migration
- erste echte Migration erst mit erstem Domain-Model in Phase 2
db.create_all()ist keine Produktionsstrategie
3.7 pytest-Fixtures
tests/
├── conftest.py
└── test_health.py
In Phase 1 nur:
appclient- temporäre SQLite-Testdatenbank
Noch keine Domain-Fixtures.
3.8 Foundation-Tests
Mindestens:
/healthliefert 200 und{"status": "ok"}TestingConfigist aktivconfig_overridesüberschreibt Config-Werte- SQLAlchemy erreicht die temporäre SQLite-DB (
SELECT 1) - Tests verwenden niemals
instance/app.db
3.9 Phase-1-Checkpoint
Phase 1 ist fertig, wenn:
- Architektur-Gerüst vorhanden
- Root-
config.pyvorhanden - Factory-Interface umgesetzt
app/extensions.pyvorhanden- SQLAlchemy integriert
- Development-SQLite auf
instance/app.db - Tests je eigene temporäre SQLite-Datei
- Flask-Migrate/Alembic initialisiert
- keine künstliche Migration erzeugt
/healthfunktioniert- alle Foundation-Tests grün
- keine Secrets oder lokalen DB-Daten versioniert
- sauber committed
Beispiel:
git add .
git status
git commit -m "Establish application foundation"
4. Phase 2 — Plattform-Core / Datenmodell
Reihenfolge
2.1 Organization
2.2 Account
2.3 BrandProfile
2.4 Team
2.5 OrganizationMembership
2.6 TeamMembership
2.7 Permission-Grundlagen
2.8 TeamInvitation
2.9 OrganizationAdminInvitation
2.10 TeamModule
2.11 AuditLog
2.12 Core-Integritätstests
2.13 Checkpoint
BrandProfile wird bewusst vor Team gebaut, damit Team.brand_profile_id von Anfang an auf ein reales Modell zeigt.
5. Globale Model-Konventionen
5.1 Primary Keys
Normale Domain-Models verwenden:
Integer Primary Keys
Opaque/kryptographische Werte werden dort verwendet, wo sie tatsächlich nötig sind, z. B. Auth-Tokens oder Storage Keys.
5.2 Statusfelder
Begrenzte Statusfelder:
- Python-Enums
- klare Stringwerte in der Datenbank
Beispiele:
active
archived
disabled
5.3 Zeitstempel
Global:
- intern UTC
- Python-Seite timezone-aware
- lokale Zeitzone erst bei Darstellung
- keine lokale Serverzeit als Domain-Wahrheit
6. Organization
Beschlossen:
Organization
├── id Integer PK
├── name required
├── slug required, unique, stabil
├── status active | archived
└── created_at UTC timestamp
Slug
Ergänzt, weil Organisations-URLs Slugs verwenden:
Aidshilfe Köln e.V.
→ aidshilfe-koeln
URL:
/org/aidshilfe-koeln/manage/
Der Slug ist eine stabile technische Kennung und folgt Namensänderungen nicht automatisch.
Betriebsrealität
Die Architektur behält Organization, aber der reale Betrieb ist auf genau eine Organisation ausgerichtet:
Aidshilfe Köln e.V.
Es ist nicht geplant, die App anderen Aidshilfen oder externen Organisationen bereitzustellen.
7. Account
Beschlossen:
Account
├── id Integer PK
├── email required, unique, normalized
├── display_name optional
├── status
│ ├── active
│ └── disabled
├── created_at UTC
└── last_login_at optional, UTC
E-Mail-Normalisierung
Beispiel:
" Patsy@Example.DE "
→ "patsy@example.de"
Regeln:
- Whitespace entfernen
- Groß-/Kleinschreibung für Account-Identität nicht unterscheiden
- gleiche Normalisierung vor Suche und Speicherung
- zentrale Normalisierungsfunktion
Status
Neue verifizierte/eingeladene Accounts starten als:
active
Kein zusätzlicher pending-Accountstatus.
display_name
Optional, weil Account vor abgeschlossenem Onboarding existieren kann.
last_login_at
Initial NULL.
Nur nach erfolgreicher Authentifizierung aktualisieren, nicht bei jedem Seitenaufruf oder bei normaler Nutzung einer gültigen Device Session.
disabled
Wirkt sofort:
Account → disabled
→ alle aktiven DeviceSessions widerrufen
→ bestehende Sitzungen verlieren Zugriff
→ neue Logins nicht zugelassen
Bei späterer Reaktivierung:
- alte Sessions bleiben widerrufen
- neue Authentifizierung nötig
- neue Device Session wird erzeugt
8. BrandProfile
Aktueller Stand:
BrandProfile
├── id
├── organization_id
├── name
├── logo_storage_key optional
├── primary_color
├── primary_strong
├── accent_color
├── accent_strong
├── heading_font
└── body_font
8.1 Name
Da die App real nur für eine Organisation betrieben wird:
name
wird global eindeutig gehalten.
organization_id bleibt trotzdem Teil des Architekturmodells.
8.2 Logo
Logo ist optional.
Gespeichert wird:
logo_storage_key
Kein Dateipfad, kein Originaldateiname.
Der zentrale Storage-Service löst den opaque Key später auf.
8.3 Farben
Validierte und normalisierte HEX-Strings:
#RRGGBB
Beispiele:
#00858A
#005F61
#52E0CF
#2596BE
Nicht als freie Brandwerte:
rgb(...)rgba(...)hsl(...)#RRGGBBAA- beliebige CSS-Ausdrücke
Semantische Ableitungen und Transparenzen entstehen im zentralen Designsystem.
8.4 Fonts
Font-Auswahl über Code-Registry.
Die Datenbank speichert nur stabile Keys:
heading_font = "big-shoulders-display"
body_font = "noto-sans"
Keine freien Fontnamen, URLs oder CSS-Fragmente.
8.5 Font-Auslieferung
Fonts werden lokal mit der Anwendung ausgeliefert.
Keine Pflichtabhängigkeit von Google Fonts, CDN oder anderen externen Font-Diensten.
Vor Nutzung einer Schrift wird geprüft, ob die Lizenz lokales Webhosting erlaubt.
8.6 Offene Entscheidung
Noch nicht entschieden:
created_at
updated_at
für BrandProfile.
Variante A:
- beide ergänzen
Variante B:
- keine Zeitstempel
- Änderungsnachvollziehbarkeit nur über
AuditLog
Dies ist der aktuelle nächste offene Punkt.
9. Noch nicht im Detail entschieden
TeamOrganizationMembershipTeamMembership- Permission-Helper
- Einladungsmodelle
TeamModuleAuditLogAuthChallengeDeviceSession- Passwordless-Flow
- BrandProfile-Seed für Vor-Ort
- konkrete Font-Registry-Implementierung
- Core Storage
- UI-Komponenten
- Dashboard
- Scheduling
- Documents
- Chat
- Deployment
- SMTP
- Docker
- Caddy
- Backup/Restore
Alle diese Punkte bleiben an PROJECT_DESIGN.md gebunden und werden schrittweise entschieden.
10. Arbeitsmethode
Für jede Phase:
1. PROJECT_DESIGN.md lesen
2. bereits Festgelegtes übernehmen
3. echte offene Entscheidungen identifizieren
4. gemeinsam entscheiden
5. erst danach Tasks ableiten
6. implementieren
7. testen
8. Checkpoint / Commit
Regel:
TASKS.mddarf keine neue Architektur erfinden.
11. Aktueller nächster Schritt
Aktueller Stand:
Phase 2.3 — BrandProfile
Nächste Entscheidung:
Soll BrandProfile created_at und updated_at erhalten?
Danach wird BrandProfile abgeschlossen und Phase 2.4 Team begonnen.