- Use dedicated GiteaConfiguration entity instead of generic Setting table - Add token encryption with SodiumEncryptor + GITEA_ENCRYPTION_KEY - Split aggregated /gitea/info into separate /branches and /pull-requests endpoints - Fix branch pattern matching to avoid PROJ-420 matching PROJ-42 - Add error handling strategy with GiteaApiException and degraded UI state - Add slug generation via AsciiSlugger with 50 char limit - Add test connection endpoint - Extract TaskGitSection.vue component - Add frontend service layer (gitea.ts) - Add i18n consideration - Add "copy branch name" feature Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
152 lines
7.0 KiB
Markdown
152 lines
7.0 KiB
Markdown
# Intégration Gitea — Design Spec
|
|
|
|
## Objectif
|
|
|
|
Lier les tickets Lesstime à Gitea pour :
|
|
- Créer une branche depuis un ticket (avec choix du type : feature, fix, refactor, etc.)
|
|
- Voir les branches, commits, PRs et statut CI liés à un ticket
|
|
- Le tout à la demande (pas de webhook, pas de cron, pas de stockage git en base)
|
|
|
|
## Décisions
|
|
|
|
| Question | Décision |
|
|
|----------|----------|
|
|
| Interaction | Depuis Lesstime uniquement (bouton + affichage) |
|
|
| Repos | Un projet = un repo Gitea |
|
|
| Nommage branches | `<type>/PROJ-42-titre-en-slug` (type choisi par l'utilisateur) |
|
|
| Détection commits | Par la branche (tous les commits d'une branche liée au ticket) |
|
|
| Données affichées | Branches + commits + PRs + statut CI/CD |
|
|
| Config serveur | Globale (admin) : URL + token API |
|
|
| Config repo | Par projet : owner + repo name |
|
|
| Synchronisation | À la demande (appel API Gitea en temps réel) |
|
|
|
|
## Architecture
|
|
|
|
### Backend
|
|
|
|
#### Configuration globale — entité `GiteaConfiguration`
|
|
|
|
Entité dédiée (singleton en base, un seul enregistrement) :
|
|
|
|
```
|
|
id: int
|
|
url: string|null (ex: "https://git.malio.fr")
|
|
token: string|null (token API personnel Gitea, chiffré via SodiumEncryptor)
|
|
```
|
|
|
|
Le token est chiffré au repos avec une clé symétrique définie en variable d'environnement (`GITEA_ENCRYPTION_KEY`). L'endpoint GET ne retourne jamais le token en clair — seulement un booléen `hasToken`.
|
|
|
|
#### Entité `Project` — nouveaux champs
|
|
|
|
```
|
|
gitea_owner: string|null (ex: "malio")
|
|
gitea_repo: string|null (ex: "lesstime")
|
|
```
|
|
|
|
Nullable car tous les projets ne sont pas forcément liés à un repo.
|
|
|
|
#### Service `GiteaApiService`
|
|
|
|
Service Symfony qui encapsule les appels HTTP vers l'API Gitea REST v1.
|
|
|
|
**Configuration HTTP :**
|
|
- Timeout : 10s par requête
|
|
- En cas d'erreur Gitea (timeout, 5xx, réseau), le service lève une `GiteaApiException` avec un message clair. Le frontend affiche un état dégradé (message d'erreur dans la section Git, le reste de la TaskModal fonctionne normalement).
|
|
|
|
**Méthodes :**
|
|
- `testConnection(): bool` — appelle `GET /api/v1/version` pour vérifier la connexion
|
|
- `listRepositories(): array` — liste les repos accessibles (pour sélection dans le projet)
|
|
- `getDefaultBranch(project): string` — récupère la branche par défaut du repo via `GET /api/v1/repos/{owner}/{repo}`
|
|
- `createBranch(project, task, type, baseBranch): string` — crée une branche `<type>/CODE-NUM-slug` à partir d'une branche de base
|
|
- `listBranches(project, taskCode): array` — liste les branches matchant le pattern `*/CODE-NUM-*` ou `*/CODE-NUM` (délimité pour éviter de matcher PROJ-420 quand on cherche PROJ-42)
|
|
- `listCommits(project, branch): array` — liste les commits d'une branche (paginé, max 30)
|
|
- `listPullRequests(project, taskCode): array` — liste les PRs en filtrant par `head` branch (paramètre `?head=` supporté par Gitea)
|
|
- `getPullRequestChecks(project, prNumber): array` — statut CI/CD d'une PR
|
|
- `copyBranchName(task, type): string` — génère le nom de branche sans appeler Gitea (pour le bouton "copier")
|
|
|
|
**Slug :** généré côté backend avec `Symfony\Component\String\Slugger\AsciiSlugger` — gère les accents français, tronqué à 50 caractères max pour le slug (le nom complet de branche reste sous 80 chars).
|
|
|
|
#### Endpoints API Platform
|
|
|
|
Nouveaux endpoints :
|
|
|
|
- `GET /api/settings/gitea` — récupérer la config Gitea (admin only, retourne url + hasToken)
|
|
- `PUT /api/settings/gitea` — sauvegarder la config Gitea (admin only)
|
|
- `POST /api/settings/gitea/test` — tester la connexion Gitea (admin only)
|
|
- `GET /api/gitea/repositories` — lister les repos disponibles (pour config projet)
|
|
- `POST /api/tasks/{id}/gitea/branches` — créer une branche pour un ticket
|
|
- `GET /api/tasks/{id}/gitea/branches` — lister branches liées au ticket
|
|
- `GET /api/tasks/{id}/gitea/pull-requests` — lister PRs liées au ticket (avec statut CI inclus)
|
|
|
|
Les endpoints branches et PRs sont séparés pour permettre un chargement progressif côté frontend et éviter de fan-out trop de requêtes Gitea en un seul appel.
|
|
|
|
### Frontend
|
|
|
|
#### Service `frontend/services/gitea.ts`
|
|
|
|
Nouveau service API encapsulant les appels, cohérent avec le pattern existant (`frontend/services/`).
|
|
|
|
#### Admin — Config Gitea
|
|
|
|
Nouveau tab `GiteaAdminTab.vue` dans l'admin pour configurer :
|
|
- URL du serveur Gitea
|
|
- Token API (champ password, affiche seulement si un token est configuré)
|
|
- Bouton "Tester la connexion"
|
|
|
|
#### ProjectDrawer — Config repo
|
|
|
|
Ajout de champs dans le drawer de projet :
|
|
- Sélecteur de repo Gitea (dropdown alimenté par `GET /api/gitea/repositories`)
|
|
- Affiche `owner/repo` une fois sélectionné
|
|
|
|
#### TaskModal — Section Git
|
|
|
|
Nouveau composant `TaskGitSection.vue` intégré dans la TaskModal (visible et chargé uniquement si le projet a un repo configuré).
|
|
|
|
**Bouton "Créer une branche"** :
|
|
- Sélecteur de type : `feature`, `fix`, `refactor`, `hotfix`, `chore`
|
|
- Sélecteur de branche de base (default: branche par défaut du repo)
|
|
- Preview du nom : `feature/PROJ-42-titre-de-la-tache`
|
|
- Bouton de confirmation
|
|
- Bouton "Copier le nom" (génère le nom sans appeler Gitea, pour création locale)
|
|
|
|
**Affichage des infos Git** (chargement progressif) :
|
|
- Liste des branches liées (avec statut : active / mergée / supprimée)
|
|
- Pour chaque branche : derniers commits (hash court, message, auteur, date)
|
|
- PRs associées (titre, statut : open/merged/closed, reviewers)
|
|
- Statut CI/CD par PR (checks : success/failure/pending)
|
|
- Liens directs vers Gitea pour chaque élément
|
|
- En cas d'erreur Gitea : message d'erreur dans la section, le reste de la modal reste fonctionnel
|
|
|
|
### Sécurité
|
|
|
|
- Le token Gitea est chiffré en base via `SodiumEncryptor` avec clé `GITEA_ENCRYPTION_KEY`
|
|
- L'endpoint `GET /api/settings/gitea` retourne `url` + `hasToken: bool`, jamais le token en clair
|
|
- Seuls les `ROLE_ADMIN` peuvent configurer le serveur Gitea et les repos
|
|
- Les utilisateurs authentifiés peuvent créer des branches et voir les infos git pour les tâches qu'ils ont le droit de voir
|
|
|
|
### i18n
|
|
|
|
Toutes les chaînes UI (labels, messages d'erreur, types de branche) passent par le système i18n existant (`frontend/i18n/locales/fr.json` et `en.json`).
|
|
|
|
## API Gitea — Endpoints utilisés
|
|
|
|
| Action | Méthode Gitea API |
|
|
|--------|-------------------|
|
|
| Tester connexion | `GET /api/v1/version` |
|
|
| Info repo (branche défaut) | `GET /api/v1/repos/{owner}/{repo}` |
|
|
| Lister repos | `GET /api/v1/repos/search` |
|
|
| Lister branches | `GET /api/v1/repos/{owner}/{repo}/branches` |
|
|
| Créer branche | `POST /api/v1/repos/{owner}/{repo}/branches` |
|
|
| Lister commits | `GET /api/v1/repos/{owner}/{repo}/commits?sha={branch}&limit=30` |
|
|
| Lister PRs par branche | `GET /api/v1/repos/{owner}/{repo}/pulls?state=all&head={branch}` |
|
|
| Statut CI | `GET /api/v1/repos/{owner}/{repo}/commits/{sha}/statuses` |
|
|
|
|
## Hors scope
|
|
|
|
- Webhooks Gitea → Lesstime
|
|
- Stockage des commits/PRs en base
|
|
- Création de PR depuis Lesstime
|
|
- Lien multi-repo par projet
|
|
- Synchronisation périodique (cron/polling)
|