Projektbasierte Organisation mit Einstellungen, Berechtigungen, Workflows und Reviews #11

Closed
opened 2026-03-30 19:17:51 +00:00 by David · 2 comments
Collaborator

Beschreibung

Das gesamte System (Repos, AppSignal-Apps, Tickets) soll projektbasiert organisiert werden.
Aktuell sind alle Tickets, Repos und Einstellungen global — es gibt keine Möglichkeit,
verschiedene Kunden/Projekte (z.B. "Henke") mit eigenen Konfigurationen zu betreiben.

Ein neues Projekt-Konzept soll eingeführt werden, das als zentraler Organisationsrahmen dient:
Jedes Projekt bündelt Repos, Tickets, Benutzer, Workflows und Review-Konfigurationen.

Hintergrund

Wenn Bruno für mehrere Kunden/Projekte eingesetzt wird, braucht jedes Projekt:

  • Eigene Repos und AppSignal-Apps
  • Eigene Scoring-Schwellwerte und Pipeline-Einstellungen
  • Eigene Benutzer mit unterschiedlichen Rollen
  • Eigene Workflow-Abfolgen (z.B. Projekt A braucht 2-stufige Review, Projekt B nur 1-stufig)
  • Eigene Review-Qualitätskriterien und -ausprägungen

Ohne diese Trennung können verschiedene Projekte nicht sauber parallel betrieben werden.

Konzept (basierend auf PROJEKT-SPEC.md)

1. Neues Datenmodell: Project

class Project(Base):
    __tablename__ = "projects"
    
    id = Column(Integer, primary_key=True)
    name = Column(String, unique=True, nullable=False)       # z.B. "Henke"
    slug = Column(String, unique=True, nullable=False)        # z.B. "henke"
    description = Column(Text)
    active = Column(Boolean, default=True)
    created_at = Column(DateTime, default=func.now())
    
    # Beziehungen
    repos = relationship("Repo", back_populates="project")
    tickets = relationship("Ticket", back_populates="project")
    members = relationship("ProjectMember", back_populates="project")
    workflow_config = relationship("WorkflowConfig", uselist=False, back_populates="project")
    review_config = relationship("ReviewConfig", uselist=False, back_populates="project")

2. Einstellungen pro Projekt: ProjectSettings

class ProjectSettings(Base):
    __tablename__ = "project_settings"
    
    id = Column(Integer, primary_key=True)
    project_id = Column(Integer, ForeignKey("projects.id"), unique=True)
    
    # Scoring-Schwellwerte (überschreiben globale Werte)
    score_threshold_autopilot = Column(Integer, default=70)
    score_threshold_reject = Column(Integer, default=30)
    
    # Polling
    odoo_poll_interval = Column(Integer, default=5)
    appsignal_poll_interval = Column(Integer, default=5)
    appsignal_auto_pipeline = Column(Boolean, default=False)
    
    # Claude Code Limits
    claude_timeout = Column(Integer, default=600)
    claude_max_turns = Column(Integer, default=50)
    
    # Odoo-Zuordnung
    odoo_assigned_user_id = Column(Integer)
    odoo_stage_names = Column(JSON)

3. Berechtigungen/User: ProjectMember

class ProjectMember(Base):
    __tablename__ = "project_members"
    
    id = Column(Integer, primary_key=True)
    project_id = Column(Integer, ForeignKey("projects.id"))
    username = Column(String, nullable=False)
    role = Column(String, nullable=False)
    # Rollen: "owner" | "admin" | "member" | "viewer"
    #   owner  — Vollzugriff, kann Projekt löschen
    #   admin  — Einstellungen ändern, Workflows konfigurieren
    #   member — Tickets bearbeiten, Enrichment, Pipeline starten
    #   viewer — Nur lesen, Dashboard ansehen
    created_at = Column(DateTime, default=func.now())

4. Workflow-Konfiguration: WorkflowConfig

class WorkflowConfig(Base):
    __tablename__ = "workflow_configs"
    
    id = Column(Integer, primary_key=True)
    project_id = Column(Integer, ForeignKey("projects.id"), unique=True)
    
    # Workflow-Schritte als geordnete Liste
    steps = Column(JSON)
    # Beispiel:
    # [
    #   {"name": "scoring", "enabled": true, "auto": true},
    #   {"name": "enrichment", "enabled": true, "auto": false},
    #   {"name": "preparation", "enabled": true, "auto": true},
    #   {"name": "claude_run", "enabled": true, "auto": true},
    #   {"name": "review", "enabled": true, "auto": true, "rounds": 3},
    #   {"name": "mr_creation", "enabled": true, "auto": true},
    #   {"name": "manual_approval", "enabled": false, "auto": false}
    # ]
    
    auto_start_autopilot = Column(Boolean, default=True)
    auto_start_klarfall = Column(Boolean, default=False)
    
    notify_on_klarfall = Column(Boolean, default=True)
    notify_on_failure = Column(Boolean, default=True)
    notify_on_mr_created = Column(Boolean, default=False)

5. Review-Konfiguration: ReviewConfig

class ReviewConfig(Base):
    __tablename__ = "review_configs"
    
    id = Column(Integer, primary_key=True)
    project_id = Column(Integer, ForeignKey("projects.id"), unique=True)
    
    review_enabled = Column(Boolean, default=True)
    max_review_rounds = Column(Integer, default=3)
    
    # Review-Strenge
    review_strictness = Column(String, default="normal")
    # "minimal"  — Nur compiliert + Tests laufen
    # "normal"   — Code-Qualität, Tests, Style
    # "streng"   — Zusätzlich: Security, Performance, Edge Cases
    
    custom_review_criteria = Column(Text)
    auto_approve_if_clean = Column(Boolean, default=False)
    
    checklist = Column(JSON)
    # ["Tests vorhanden", "Type Hints", "Docstrings", "Keine Secrets"]

6. Betroffene bestehende Models

Ticket, Repo, AppSignalApp erhalten jeweils:

project_id = Column(Integer, ForeignKey("projects.id"), nullable=True)

nullable=True für Rückwärtskompatibilität.

7. Neue API-Endpoints

GET    /api/projects                       — Liste aller Projekte
POST   /api/projects                       — Neues Projekt anlegen
GET    /api/projects/{slug}                 — Projektdetails
PUT    /api/projects/{slug}                 — Projekt bearbeiten
DELETE /api/projects/{slug}                 — Projekt löschen

GET    /api/projects/{slug}/settings        — Einstellungen laden
PUT    /api/projects/{slug}/settings        — Einstellungen ändern

GET    /api/projects/{slug}/members         — Mitglieder auflisten
POST   /api/projects/{slug}/members         — Mitglied hinzufügen
PUT    /api/projects/{slug}/members/{id}    — Rolle ändern
DELETE /api/projects/{slug}/members/{id}    — Mitglied entfernen

GET    /api/projects/{slug}/workflow        — Workflow-Config laden
PUT    /api/projects/{slug}/workflow        — Workflow-Config ändern

GET    /api/projects/{slug}/review-config   — Review-Config laden
PUT    /api/projects/{slug}/review-config   — Review-Config ändern

Bestehende Endpoints erhalten optionalen ?project=slug Filter.

8. Frontend: Neue Seiten

  • ProjectList.tsx — Projektübersicht mit Kacheln
  • ProjectDetail.tsx — Tabs: Einstellungen, Mitglieder, Workflow, Reviews
  • ProjectSelector — Dropdown/Sidebar zum Projekt-Wechsel
  • Alle bestehenden Seiten werden projekt-gefiltert

Akzeptanzkriterien

  • Neues Project Model mit Alembic-Migration
  • ProjectSettings, ProjectMember, WorkflowConfig, ReviewConfig Models
  • Ticket, Repo, AppSignalApp haben project_id FK (nullable für Rückwärtskompatibilität)
  • CRUD API-Endpoints für Projekte und alle Sub-Entities
  • Pipeline liest Einstellungen aus Projekt-Settings statt aus globaler Config
  • Review-Agent nutzt projekt-spezifische Review-Kriterien und Strenge
  • Workflow-Engine respektiert projekt-spezifische Schrittfolge
  • Frontend: Projektauswahl, Dashboard gefiltert nach Projekt
  • Frontend: Projekt-Einstellungsseite mit allen Konfigurationstabs
  • Bestehende Tickets/Repos ohne Projekt funktionieren weiterhin (Fallback auf globale Settings)

Technische Hinweise

  • Betroffene Dateien (neu):

    • backend/models/project.py
    • backend/api/projects.py
    • frontend/src/pages/ProjectList.tsx, ProjectDetail.tsx
    • frontend/src/components/ProjectSelector.tsx
  • Betroffene Dateien (ändern):

    • backend/models/ticket.py — project_id FK
    • backend/models/repo.py — project_id FK
    • backend/models/appsignal_app.py — project_id FK
    • backend/services/pipeline.py — Projekt-Settings laden
    • backend/services/scoring_engine.py — Schwellwerte aus Projekt
    • backend/services/review_agent.py — Review-Kriterien aus Projekt
    • backend/api/tickets.py — project-Filter
    • backend/api/repos.py — project-Filter
    • backend/config.py — Globale Settings als Fallback
    • backend/main.py — Router registrieren
    • frontend/src/App.tsx — Neue Routen
    • frontend/src/api/client.ts — Neue API-Funktionen
  • Migration nötig: ja (Alembic: neue Tabellen + FKs)

  • Ansatz: Schrittweise — erst Models + API, dann Pipeline-Integration, dann Frontend

Aufwand: XL

Neuer Service-Bereich, 15+ Dateien betroffen, mehrere Tage Implementierung.
Empfehlung: In Sub-Issues aufteilen (Models → API → Pipeline-Integration → Frontend).

## Beschreibung Das gesamte System (Repos, AppSignal-Apps, Tickets) soll projektbasiert organisiert werden. Aktuell sind alle Tickets, Repos und Einstellungen global — es gibt keine Möglichkeit, verschiedene Kunden/Projekte (z.B. "Henke") mit eigenen Konfigurationen zu betreiben. Ein neues **Projekt-Konzept** soll eingeführt werden, das als zentraler Organisationsrahmen dient: Jedes Projekt bündelt Repos, Tickets, Benutzer, Workflows und Review-Konfigurationen. ## Hintergrund Wenn Bruno für mehrere Kunden/Projekte eingesetzt wird, braucht jedes Projekt: - Eigene Repos und AppSignal-Apps - Eigene Scoring-Schwellwerte und Pipeline-Einstellungen - Eigene Benutzer mit unterschiedlichen Rollen - Eigene Workflow-Abfolgen (z.B. Projekt A braucht 2-stufige Review, Projekt B nur 1-stufig) - Eigene Review-Qualitätskriterien und -ausprägungen Ohne diese Trennung können verschiedene Projekte nicht sauber parallel betrieben werden. ## Konzept (basierend auf PROJEKT-SPEC.md) ### 1. Neues Datenmodell: `Project` ```python class Project(Base): __tablename__ = "projects" id = Column(Integer, primary_key=True) name = Column(String, unique=True, nullable=False) # z.B. "Henke" slug = Column(String, unique=True, nullable=False) # z.B. "henke" description = Column(Text) active = Column(Boolean, default=True) created_at = Column(DateTime, default=func.now()) # Beziehungen repos = relationship("Repo", back_populates="project") tickets = relationship("Ticket", back_populates="project") members = relationship("ProjectMember", back_populates="project") workflow_config = relationship("WorkflowConfig", uselist=False, back_populates="project") review_config = relationship("ReviewConfig", uselist=False, back_populates="project") ``` ### 2. Einstellungen pro Projekt: `ProjectSettings` ```python class ProjectSettings(Base): __tablename__ = "project_settings" id = Column(Integer, primary_key=True) project_id = Column(Integer, ForeignKey("projects.id"), unique=True) # Scoring-Schwellwerte (überschreiben globale Werte) score_threshold_autopilot = Column(Integer, default=70) score_threshold_reject = Column(Integer, default=30) # Polling odoo_poll_interval = Column(Integer, default=5) appsignal_poll_interval = Column(Integer, default=5) appsignal_auto_pipeline = Column(Boolean, default=False) # Claude Code Limits claude_timeout = Column(Integer, default=600) claude_max_turns = Column(Integer, default=50) # Odoo-Zuordnung odoo_assigned_user_id = Column(Integer) odoo_stage_names = Column(JSON) ``` ### 3. Berechtigungen/User: `ProjectMember` ```python class ProjectMember(Base): __tablename__ = "project_members" id = Column(Integer, primary_key=True) project_id = Column(Integer, ForeignKey("projects.id")) username = Column(String, nullable=False) role = Column(String, nullable=False) # Rollen: "owner" | "admin" | "member" | "viewer" # owner — Vollzugriff, kann Projekt löschen # admin — Einstellungen ändern, Workflows konfigurieren # member — Tickets bearbeiten, Enrichment, Pipeline starten # viewer — Nur lesen, Dashboard ansehen created_at = Column(DateTime, default=func.now()) ``` ### 4. Workflow-Konfiguration: `WorkflowConfig` ```python class WorkflowConfig(Base): __tablename__ = "workflow_configs" id = Column(Integer, primary_key=True) project_id = Column(Integer, ForeignKey("projects.id"), unique=True) # Workflow-Schritte als geordnete Liste steps = Column(JSON) # Beispiel: # [ # {"name": "scoring", "enabled": true, "auto": true}, # {"name": "enrichment", "enabled": true, "auto": false}, # {"name": "preparation", "enabled": true, "auto": true}, # {"name": "claude_run", "enabled": true, "auto": true}, # {"name": "review", "enabled": true, "auto": true, "rounds": 3}, # {"name": "mr_creation", "enabled": true, "auto": true}, # {"name": "manual_approval", "enabled": false, "auto": false} # ] auto_start_autopilot = Column(Boolean, default=True) auto_start_klarfall = Column(Boolean, default=False) notify_on_klarfall = Column(Boolean, default=True) notify_on_failure = Column(Boolean, default=True) notify_on_mr_created = Column(Boolean, default=False) ``` ### 5. Review-Konfiguration: `ReviewConfig` ```python class ReviewConfig(Base): __tablename__ = "review_configs" id = Column(Integer, primary_key=True) project_id = Column(Integer, ForeignKey("projects.id"), unique=True) review_enabled = Column(Boolean, default=True) max_review_rounds = Column(Integer, default=3) # Review-Strenge review_strictness = Column(String, default="normal") # "minimal" — Nur compiliert + Tests laufen # "normal" — Code-Qualität, Tests, Style # "streng" — Zusätzlich: Security, Performance, Edge Cases custom_review_criteria = Column(Text) auto_approve_if_clean = Column(Boolean, default=False) checklist = Column(JSON) # ["Tests vorhanden", "Type Hints", "Docstrings", "Keine Secrets"] ``` ### 6. Betroffene bestehende Models **Ticket, Repo, AppSignalApp** erhalten jeweils: ```python project_id = Column(Integer, ForeignKey("projects.id"), nullable=True) ``` `nullable=True` für Rückwärtskompatibilität. ### 7. Neue API-Endpoints ``` GET /api/projects — Liste aller Projekte POST /api/projects — Neues Projekt anlegen GET /api/projects/{slug} — Projektdetails PUT /api/projects/{slug} — Projekt bearbeiten DELETE /api/projects/{slug} — Projekt löschen GET /api/projects/{slug}/settings — Einstellungen laden PUT /api/projects/{slug}/settings — Einstellungen ändern GET /api/projects/{slug}/members — Mitglieder auflisten POST /api/projects/{slug}/members — Mitglied hinzufügen PUT /api/projects/{slug}/members/{id} — Rolle ändern DELETE /api/projects/{slug}/members/{id} — Mitglied entfernen GET /api/projects/{slug}/workflow — Workflow-Config laden PUT /api/projects/{slug}/workflow — Workflow-Config ändern GET /api/projects/{slug}/review-config — Review-Config laden PUT /api/projects/{slug}/review-config — Review-Config ändern ``` Bestehende Endpoints erhalten optionalen `?project=slug` Filter. ### 8. Frontend: Neue Seiten - **ProjectList.tsx** — Projektübersicht mit Kacheln - **ProjectDetail.tsx** — Tabs: Einstellungen, Mitglieder, Workflow, Reviews - **ProjectSelector** — Dropdown/Sidebar zum Projekt-Wechsel - Alle bestehenden Seiten werden projekt-gefiltert ## Akzeptanzkriterien - [ ] Neues `Project` Model mit Alembic-Migration - [ ] `ProjectSettings`, `ProjectMember`, `WorkflowConfig`, `ReviewConfig` Models - [ ] Ticket, Repo, AppSignalApp haben `project_id` FK (nullable für Rückwärtskompatibilität) - [ ] CRUD API-Endpoints für Projekte und alle Sub-Entities - [ ] Pipeline liest Einstellungen aus Projekt-Settings statt aus globaler Config - [ ] Review-Agent nutzt projekt-spezifische Review-Kriterien und Strenge - [ ] Workflow-Engine respektiert projekt-spezifische Schrittfolge - [ ] Frontend: Projektauswahl, Dashboard gefiltert nach Projekt - [ ] Frontend: Projekt-Einstellungsseite mit allen Konfigurationstabs - [ ] Bestehende Tickets/Repos ohne Projekt funktionieren weiterhin (Fallback auf globale Settings) ## Technische Hinweise - Betroffene Dateien (neu): - `backend/models/project.py` - `backend/api/projects.py` - `frontend/src/pages/ProjectList.tsx`, `ProjectDetail.tsx` - `frontend/src/components/ProjectSelector.tsx` - Betroffene Dateien (ändern): - `backend/models/ticket.py` — project_id FK - `backend/models/repo.py` — project_id FK - `backend/models/appsignal_app.py` — project_id FK - `backend/services/pipeline.py` — Projekt-Settings laden - `backend/services/scoring_engine.py` — Schwellwerte aus Projekt - `backend/services/review_agent.py` — Review-Kriterien aus Projekt - `backend/api/tickets.py` — project-Filter - `backend/api/repos.py` — project-Filter - `backend/config.py` — Globale Settings als Fallback - `backend/main.py` — Router registrieren - `frontend/src/App.tsx` — Neue Routen - `frontend/src/api/client.ts` — Neue API-Funktionen - Migration nötig: ja (Alembic: neue Tabellen + FKs) - Ansatz: Schrittweise — erst Models + API, dann Pipeline-Integration, dann Frontend ## Aufwand: XL Neuer Service-Bereich, 15+ Dateien betroffen, mehrere Tage Implementierung. Empfehlung: In Sub-Issues aufteilen (Models → API → Pipeline-Integration → Frontend).
Basti added
doku
and removed
in work
labels 2026-03-30 21:36:37 +00:00
Owner

Review bestanden

Test Status
Backend Tests (93/93) PASS
TypeScript Check PASS
API: Project CRUD PASS
API: Settings Update PASS
API: Members PASS
API: Workflow Config PASS
API: Review Config PASS
API: Projekt-gefiltertes Repos/Tickets/Stats PASS
API: Project Delete + 404 PASS
EffectiveSettings Resolver PASS
JWT Role Decoding PASS

Label auf doku gesetzt.

## Review bestanden | Test | Status | |------|--------| | Backend Tests (93/93) | PASS | | TypeScript Check | PASS | | API: Project CRUD | PASS | | API: Settings Update | PASS | | API: Members | PASS | | API: Workflow Config | PASS | | API: Review Config | PASS | | API: Projekt-gefiltertes Repos/Tickets/Stats | PASS | | API: Project Delete + 404 | PASS | | EffectiveSettings Resolver | PASS | | JWT Role Decoding | PASS | Label auf `doku` gesetzt.
Owner

Dokumentation aktualisiert. Issue wird geschlossen.

Geaenderte Docs:

  • README.md: Projektbasierte Organisation, API-Tabelle, Projektstruktur, Auth-Rollen
  • CLAUDE.md: Einschraenkungen (JWT, Projekte, Repo-Verzeichnisse)
  • Memory: feedback_jwt_role.md (Rolle aus JWT-Token lesen)
Dokumentation aktualisiert. Issue wird geschlossen. Geaenderte Docs: - `README.md`: Projektbasierte Organisation, API-Tabelle, Projektstruktur, Auth-Rollen - `CLAUDE.md`: Einschraenkungen (JWT, Projekte, Repo-Verzeichnisse) - Memory: feedback_jwt_role.md (Rolle aus JWT-Token lesen)
Basti closed this issue 2026-03-30 21:45:04 +00:00
Sign in to join this conversation.
No description provided.