897 lines
15 KiB
Markdown
897 lines
15 KiB
Markdown
# 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:** Wenn `PROJECT_DESIGN.md` etwas 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:
|
|
|
|
```text
|
|
AHK-Ehrenamtsmanagement
|
|
```
|
|
|
|
Technischer Name:
|
|
|
|
```text
|
|
ahk-ehrenamtsmanagement
|
|
```
|
|
|
|
Der technische Name soll konsistent für Repository, App-Verzeichnis, Stack-Verzeichnis und Container verwendet werden.
|
|
|
|
## 1.3 Entwicklungsumgebung
|
|
|
|
```text
|
|
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:
|
|
|
|
```text
|
|
ahk-ehrenamtsmanagement
|
|
```
|
|
|
|
Projektbindung:
|
|
|
|
```bash
|
|
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:
|
|
|
|
```text
|
|
ahk-ehrenamtsmanagement/
|
|
├── PROJECT_DESIGN.md
|
|
├── PROJECT_DESIGN_VOR_ORT_MOCKUP.png
|
|
└── .gitignore
|
|
```
|
|
|
|
Initialisierung:
|
|
|
|
```bash
|
|
mkdir ahk-ehrenamtsmanagement
|
|
cd ahk-ehrenamtsmanagement
|
|
git init
|
|
```
|
|
|
|
Minimale `.gitignore`:
|
|
|
|
```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:
|
|
|
|
```bash
|
|
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`:
|
|
|
|
```text
|
|
Flask
|
|
```
|
|
|
|
`requirements-dev.txt`:
|
|
|
|
```text
|
|
-r requirements.txt
|
|
|
|
pytest
|
|
```
|
|
|
|
Installation:
|
|
|
|
```bash
|
|
python -m pip install -r requirements-dev.txt
|
|
```
|
|
|
|
Bevorzugte Form:
|
|
|
|
```text
|
|
python -m pip ...
|
|
```
|
|
|
|
## 2.3 Erste Flask-Struktur
|
|
|
|
Verbindlich:
|
|
|
|
- Flask App Factory
|
|
- Blueprints
|
|
- eigener Core-Bereich für `/health`
|
|
|
|
Struktur:
|
|
|
|
```text
|
|
app/
|
|
├── __init__.py
|
|
└── core/
|
|
├── __init__.py
|
|
└── health/
|
|
├── __init__.py
|
|
└── routes.py
|
|
```
|
|
|
|
`app/core/health/__init__.py`:
|
|
|
|
```python
|
|
from flask import Blueprint
|
|
|
|
bp = Blueprint("health", __name__)
|
|
|
|
from app.core.health import routes
|
|
```
|
|
|
|
`app/core/health/routes.py`:
|
|
|
|
```python
|
|
from app.core.health import bp
|
|
|
|
@bp.get("/health")
|
|
def health():
|
|
return {"status": "ok"}, 200
|
|
```
|
|
|
|
Initiale `app/__init__.py`:
|
|
|
|
```python
|
|
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:
|
|
|
|
```bash
|
|
flask --app 'app:create_app' run --debug
|
|
```
|
|
|
|
Healthcheck:
|
|
|
|
```bash
|
|
curl -i http://127.0.0.1:5000/health
|
|
```
|
|
|
|
Erwartung:
|
|
|
|
```json
|
|
{"status": "ok"}
|
|
```
|
|
|
|
mit HTTP 200.
|
|
|
|
## 2.4 Erste Testbasis
|
|
|
|
`tests/test_health.py`:
|
|
|
|
```python
|
|
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:
|
|
|
|
```bash
|
|
python -m pytest
|
|
```
|
|
|
|
---
|
|
|
|
# 3. Phase 1 — Technisches App-Fundament
|
|
|
|
## Reihenfolge
|
|
|
|
```text
|
|
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.py` oder `forms.py` anlegen
|
|
- solche Dateien entstehen erst bei echter Implementierung
|
|
|
|
```text
|
|
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:
|
|
|
|
```python
|
|
class Config:
|
|
pass
|
|
|
|
class DevelopmentConfig(Config):
|
|
DEBUG = True
|
|
|
|
class TestingConfig(Config):
|
|
TESTING = True
|
|
```
|
|
|
|
Noch nicht vorsorglich anlegen:
|
|
- `ProductionConfig`
|
|
- `SECRET_KEY`
|
|
- SMTP
|
|
- Uploadlimits
|
|
- Auth-Zeiten
|
|
- Polling-Intervalle
|
|
|
|
## 3.3 App-Factory-Interface
|
|
|
|
Festgelegt:
|
|
|
|
```python
|
|
def create_app(
|
|
config_class=DevelopmentConfig,
|
|
config_overrides=None,
|
|
):
|
|
...
|
|
```
|
|
|
|
Reihenfolge:
|
|
|
|
```text
|
|
Factory starten
|
|
→ Config-Klasse laden
|
|
→ optionale Overrides
|
|
→ Extensions initialisieren
|
|
→ Blueprints registrieren
|
|
→ später Module registrieren
|
|
```
|
|
|
|
Sinngemäß:
|
|
|
|
```python
|
|
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:
|
|
|
|
```text
|
|
app/extensions.py
|
|
```
|
|
|
|
Nur Extension-Instanzen, keine Models, keine Businesslogik, keine Konfiguration.
|
|
|
|
Zunächst:
|
|
|
|
```python
|
|
from flask_sqlalchemy import SQLAlchemy
|
|
|
|
db = SQLAlchemy()
|
|
```
|
|
|
|
Später:
|
|
|
|
```python
|
|
from flask_migrate import Migrate
|
|
from flask_sqlalchemy import SQLAlchemy
|
|
|
|
db = SQLAlchemy()
|
|
migrate = Migrate()
|
|
```
|
|
|
|
In der Factory:
|
|
|
|
```python
|
|
db.init_app(app)
|
|
migrate.init_app(app, db)
|
|
```
|
|
|
|
## 3.5 SQLite-Strategie
|
|
|
|
Development:
|
|
|
|
```text
|
|
instance/app.db
|
|
```
|
|
|
|
Sinngemäß:
|
|
|
|
```python
|
|
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`
|
|
|
|
```text
|
|
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:
|
|
|
|
```text
|
|
Flask
|
|
Flask-SQLAlchemy
|
|
Flask-Migrate
|
|
```
|
|
|
|
Initialisierung bereits in Phase 1:
|
|
|
|
```bash
|
|
flask --app 'app:create_app' db init
|
|
```
|
|
|
|
Ergebnis:
|
|
|
|
```text
|
|
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
|
|
|
|
```text
|
|
tests/
|
|
├── conftest.py
|
|
└── test_health.py
|
|
```
|
|
|
|
In Phase 1 nur:
|
|
- `app`
|
|
- `client`
|
|
- temporäre SQLite-Testdatenbank
|
|
|
|
Noch keine Domain-Fixtures.
|
|
|
|
## 3.8 Foundation-Tests
|
|
|
|
Mindestens:
|
|
|
|
1. `/health` liefert 200 und `{"status": "ok"}`
|
|
2. `TestingConfig` ist aktiv
|
|
3. `config_overrides` überschreibt Config-Werte
|
|
4. SQLAlchemy erreicht die temporäre SQLite-DB (`SELECT 1`)
|
|
5. Tests verwenden niemals `instance/app.db`
|
|
|
|
## 3.9 Phase-1-Checkpoint
|
|
|
|
Phase 1 ist fertig, wenn:
|
|
|
|
- Architektur-Gerüst vorhanden
|
|
- Root-`config.py` vorhanden
|
|
- Factory-Interface umgesetzt
|
|
- `app/extensions.py` vorhanden
|
|
- SQLAlchemy integriert
|
|
- Development-SQLite auf `instance/app.db`
|
|
- Tests je eigene temporäre SQLite-Datei
|
|
- Flask-Migrate/Alembic initialisiert
|
|
- keine künstliche Migration erzeugt
|
|
- `/health` funktioniert
|
|
- alle Foundation-Tests grün
|
|
- keine Secrets oder lokalen DB-Daten versioniert
|
|
- sauber committed
|
|
|
|
Beispiel:
|
|
|
|
```bash
|
|
git add .
|
|
git status
|
|
git commit -m "Establish application foundation"
|
|
```
|
|
|
|
---
|
|
|
|
# 4. Phase 2 — Plattform-Core / Datenmodell
|
|
|
|
## Reihenfolge
|
|
|
|
```text
|
|
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:
|
|
|
|
```text
|
|
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:
|
|
|
|
```text
|
|
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:
|
|
|
|
```text
|
|
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:
|
|
|
|
```text
|
|
Aidshilfe Köln e.V.
|
|
→ aidshilfe-koeln
|
|
```
|
|
|
|
URL:
|
|
|
|
```text
|
|
/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:
|
|
|
|
```text
|
|
Aidshilfe Köln e.V.
|
|
```
|
|
|
|
Es ist nicht geplant, die App anderen Aidshilfen oder externen Organisationen bereitzustellen.
|
|
|
|
---
|
|
|
|
# 7. `Account`
|
|
|
|
Beschlossen:
|
|
|
|
```text
|
|
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:
|
|
|
|
```text
|
|
" 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:
|
|
|
|
```text
|
|
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:
|
|
|
|
```text
|
|
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:
|
|
|
|
```text
|
|
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:
|
|
|
|
```text
|
|
name
|
|
```
|
|
|
|
wird global eindeutig gehalten.
|
|
|
|
`organization_id` bleibt trotzdem Teil des Architekturmodells.
|
|
|
|
## 8.2 Logo
|
|
|
|
Logo ist optional.
|
|
|
|
Gespeichert wird:
|
|
|
|
```text
|
|
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:
|
|
|
|
```text
|
|
#RRGGBB
|
|
```
|
|
|
|
Beispiele:
|
|
|
|
```text
|
|
#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:
|
|
|
|
```text
|
|
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:
|
|
|
|
```text
|
|
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
|
|
|
|
- `Team`
|
|
- `OrganizationMembership`
|
|
- `TeamMembership`
|
|
- Permission-Helper
|
|
- Einladungsmodelle
|
|
- `TeamModule`
|
|
- `AuditLog`
|
|
- `AuthChallenge`
|
|
- `DeviceSession`
|
|
- 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:
|
|
|
|
```text
|
|
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.md` darf keine neue Architektur erfinden.
|
|
|
|
---
|
|
|
|
# 11. Aktueller nächster Schritt
|
|
|
|
Aktueller Stand:
|
|
|
|
```text
|
|
Phase 2.3 — BrandProfile
|
|
```
|
|
|
|
Nächste Entscheidung:
|
|
|
|
```text
|
|
Soll BrandProfile created_at und updated_at erhalten?
|
|
```
|
|
|
|
Danach wird `BrandProfile` abgeschlossen und Phase 2.4 `Team` begonnen.
|