# CLAUDE.md — GitLab Bridge

Bridge FastAPI entre **OpenWebUI** et l'**API GitLab v4**.  
Auteur : Sylvain SCATTOLINI — Déployé sur `/var/www/html/gitlab-bridge`, port 8080.

---

## Rôle du service

Ce service est un intermédiaire conversationnel entre un LLM (via OpenWebUI) et GitLab.
Il centralise un token technique serveur, journalise chaque action en SQL, et expose des
endpoints "assistant" qui acceptent du langage naturel (résolution floue, détection
d'ambiguïté, confirmation en deux temps).

---

## Stack technique

| Composant | Choix |
|---|---|
| Runtime | Python 3.11+ |
| Framework | FastAPI (sync, 2 workers uvicorn) |
| Client HTTP | httpx (sync, connection pooling) |
| Validation | Pydantic v2 (`ConfigDict(extra='forbid')`) |
| ORM | SQLAlchemy 2.x (session synchrone) |
| Base de données | MariaDB/MySQL (audit uniquement) |
| Config | pydantic-settings + `.env` |
| Logging | JSON structuré vers stdout, X-Request-ID par requête |

---

## Architecture en couches

```
.env
 └─ app/core/config.py          Settings (singleton @lru_cache)
 └─ app/core/exceptions.py      Hiérarchie BridgeError → sous-classes métier
 └─ app/core/logging.py         SafeJsonFormatter, configure_logging()

app/clients/
 └─ gitlab_client.py            Client httpx partagé (_shared_client global)
                                 Retry sur [429,500-504], backoff exponentiel
                                 Pagination auto via X-Total-Pages

app/services/
 └─ auth_context_service.py     Normalise AuthContext (lowercase)
 └─ gitlab_user_service.py      Résout email→username→GitLab user
 └─ gitlab_permission_service.py Vérifie access_level (10/20/30/40/50)
 └─ gitlab_project_service.py   Résout project_path → projet GitLab
 └─ gitlab_issue_service.py     Wrapper création d'issue
 └─ gitlab_summary_service.py   Agrège issues ouvertes/fermées sur période
 └─ audit_log_service.py        Persiste en DB de façon défensive (jamais bloquant)

app/schemas/
 └─ common.py                   AuthContext, ApiErrorResponse
 └─ groups.py, projects.py, issues.py, milestones.py, users.py

app/api/v1/
 └─ health.py, groups.py, projects.py, issues.py, milestones.py

app/utils/helpers.py            normalize_text(), compute_similarity_score(), slugify()
app/db/                         Base, SessionLocal, AuditLog ORM
app/main.py                     Middleware logging HTTP, handlers d'erreurs, startup
```

---

## Patterns clés à respecter

### Modèles Pydantic
Toujours `ConfigDict(extra='forbid', str_strip_whitespace=True)`.  
Pas d'union implicite : typer précisément chaque champ.

### Endpoints "assistant" (résolution en deux temps)
1. Premier appel → le bridge résout le projet/groupe via scoring flou, retourne les candidats  
2. Si ambigu : `status='clarification_needed'` + `candidate_projects`  
3. Second appel avec `confirm=True` + `resolved_project_path` → exécute l'action

Ne pas casser ce pattern : OpenWebUI s'appuie dessus pour les conversations multi-tours.

### AuthContext
`{"username": "...", "email": "..."}` — présent dans les endpoints `IssueCreateRequest`,
absent des endpoints `IssueAssistantCreateRequest` (par conception, OpenWebUI fournit
le contexte utilisateur séparément).

### Résolution floue
`compute_similarity_score()` dans `helpers.py` (SequenceMatcher, 0-100).  
Seuils usuels : 65+ pour doublons issues, 40+ pour groupes, 50+ pour projets.  
**Ne pas dupliquer** : `groups.py` API a encore sa propre fonction `_compute_score()` —
c'est une dette à migrer vers `helpers.compute_similarity_score`.

### Gestion des erreurs
- Exceptions métier → hériter de `BridgeError` et définir `code` + `message`
- `handle_bridge_error` dans `main.py` les transforme en JSON homogène
- `AuditLogService.write_log()` ne lève jamais d'exception (rollback silencieux)

### Client GitLab
`_get_shared_client()` retourne un singleton `httpx.Client` par processus worker.  
Le retry utilise `time.sleep()` synchrone — acceptable car les workers sont sync.

---

## Configuration (.env)

Variables obligatoires :

```
GITLAB_BASE_URL        URL de l'instance GitLab
GITLAB_API_TOKEN       Token technique (glpat-...)
DB_HOST / DB_PORT / DB_NAME / DB_USER / DB_PASSWORD
```

Variables optionnelles :

```
CORS_ORIGINS           Liste JSON d'origines autorisées (défaut: ['https://ia.tarbouriech.tech'])
GITLAB_TIMEOUT         Timeout HTTP GitLab en secondes (défaut: 15)
GITLAB_VERIFY_SSL      Vérification SSL (défaut: true)
APP_ENV / APP_DEBUG    dev ou prod
```

---

## Points d'amélioration connus

### Bugs mineurs
- **HTTP 400 vs 404** : `ProjectNotFoundError` et `GitLabUserNotFoundError` retournent 400.
  Ils devraient retourner 404 — corriger dans `handle_bridge_error` de `main.py`.
- **`app_debug`** : lu dans `Settings` mais jamais passé à `FastAPI(debug=...)`.
- **`_compute_score()` dupliqué** dans `app/api/v1/groups.py` — doit utiliser
  `compute_similarity_score` de `helpers.py`.

### Deprecation
- `@app.on_event("startup")` est déprécié depuis FastAPI 0.93 — migrer vers
  `@asynccontextmanager` + `lifespan=` sur l'instance FastAPI.
- `_shared_client` n'est pas fermé à l'arrêt — ajouter un hook `shutdown` qui appelle
  `_shared_client.close()`.

### Feature manquante : `/api/v1/users/search`
Les schémas `UserSearchRequest / UserSearchResponse / UserItem` existent dans
`app/schemas/users.py` mais il n'y a **pas de router** `app/api/v1/users.py`.
C'est la seule ressource documentée dans les schémas sans endpoint correspondant.

### Health check partiel
`GET /health` vérifie GitLab mais pas la base de données.  
Ajouter un `SELECT 1` sur la session DB pour un diagnostic complet.

### Pas d'authentification côté bridge
Le service est protégé uniquement par réseau/reverse-proxy.  
Si l'exposition change, envisager un `X-API-Key` en middleware.

---

## Ce qui fonctionne bien (ne pas toucher)

- Retry + backoff exponentiel sur le client GitLab
- Pagination automatique `_list_all_pages` (X-Total-Pages)
- Sanitization des logs (`SENSITIVE_LOG_KEYS` dans `main.py`)
- Détection de doublons d'issues (score ≥ 80 → `status='duplicate_detected'`)
- `AuditLogService` défensif (jamais bloquant pour le flux métier)
- `slugify()` conforme aux contraintes GitLab
- `AccessDeniedError` → HTTP 403 (seul code d'erreur correctement mappé)

---

## Lancement local

```bash
cd /var/www/html/gitlab-bridge
source .venv/bin/activate
uvicorn app.main:app --reload --host 0.0.0.0 --port 8080
```

Production (systemd) : `systemctl status gitlab-bridge`  
Logs : `journalctl -u gitlab-bridge -f`

---

## Table d'audit SQL

`ai_gitlab_audit_logs` — créée automatiquement au démarrage via `Base.metadata.create_all`.  
Champs : `id, created_at, username, email, action, project_path, status,
request_payload (JSON), response_payload (JSON), error_message`.
