feat(admin): crée un ticket Forgejo depuis un message de contact (#81) #125

Merged
kyuno merged 1 commit from feat/forgejo-issue-from-contact-message into main 2026-09-08 20:30:09 +02:00
Owner

Depuis /admin/messages (issue #80), un admin peut créer une issue Forgejo à partir d'un message utilisateur en un geste : un bouton ouvre une modale pré-remplie (titre + corps éditables), l'admin ajuste et confirme, l'app crée l'issue via l'API Forgejo et mémorise le lien sur le message (#N, le bouton disparaît). Première intégration Forgejo d'apps/api.

Dépend de #80 (mergé). 1 PR.

Contenu

Modèle — delta sur ContactMessage : forgejoIssueNumber / forgejoIssueUrl nullables (migration 20260908130000_contact_message_forgejo_link, aucun backfill).

Config (config/env.ts + .env.example + ci.yml) — FORGEJO_BASE_URL (.url()), FORGEJO_TOKEN (secret), FORGEJO_REPO (owner/repo), toutes .optional(). ForgejoService.isConfigured() les vérifie ensemble ; sans les trois, le bouton est masqué et l'endpoint répond 503 (même posture fail-closed que ADMIN_JWT_SECRET).

ForgejoService (lib/forgejo/forgejo.service.ts, singleton forgejo) — createIssue({ title, body }) → POST {BASE}/api/v1/repos/{REPO}/issues, en-tête Authorization: token <pat> (schéma PAT Forgejo), lit { number, html_url }. Via fetch global (comme mobile.service.ts). Erreur réseau / non-2xx / JSON invalide ⇒ HttpError 502 FORGEJO_REQUEST_FAILED.

admin-contact (issue #80, étendu) —

  • POST /admin/api/contact/:id/forgejo-issue (createForgejoIssueSchema) : ordre 503 → 404 CONTACT_MESSAGE_NOT_FOUND → 409 CONTACT_MESSAGE_ALREADY_LINKED → appel Forgejo → écriture BDD. Un échec Forgejo (502) ne touche pas la BDD — pas d'état partiel. Renvoie le ContactMessageAdminView à jour.
  • GET /admin/api/contact/forgejo/status → { enabled }.

Contrat partagé — createForgejoIssueSchema (title 1–255 = limite Forgejo, body 1–60000), ContactMessageAdminView + forgejoIssueNumber/forgejoIssueUrl, ForgejoIntegrationStatus, ErrorCode : FORGEJO_NOT_CONFIGURED=5001, FORGEJO_REQUEST_FAILED=5002, CONTACT_MESSAGE_ALREADY_LINKED=4026, CONTACT_MESSAGE_NOT_FOUND=4055 + labels dans errors.*.

Front — MessagesPage sonde forgejo/status au montage ; colonne « Ticket » : lien #N (target="_blank") si lié, bouton « Créer un ticket » si enabled, note « intégration Forgejo non configurée » sinon. CreateForgejoIssueModal (via le primitif Dialog) + helpers purs messages.ts (buildIssueTitle = [Bug] <1ʳᵉ ligne>, buildIssueBody = texte + bloc --- contexte + pied). Aucun label auto (décision #81). Erreurs via ErrorMessageService, jamais err.message.

Tests

  • apps/api/test/forgejo.service.test.ts — globalThis.fetch réassigné : URL / en-tête token / corps {title, body} ; 502 sur non-2xx et sur erreur réseau.
  • apps/api/test/admin-contact.test.ts (étendu) — status ; 503 (env absent) ; nominal (stocke le lien, renvoie la vue) ; 404 ; 409 ; 502 + ligne intacte ; 400 titre vide.
  • apps/web/cypress/e2e/admin-messages.cy.ts (étendu) — modale pré-remplie, #N après création, pas de bouton si enabled:false (+ note), lien affiché pour un message déjà lié.

Vérifié localement

pnpm lint ✅ · pnpm build (tsc api + web, vite) ✅ · Cypress admin-messages + admin-corrections + admin-layout : 11/11 ✅. Mocha → CI (pas de Postgres local).

Closes #81.

🤖 Generated with Claude Code

Depuis `/admin/messages` (issue #80), un admin peut créer une **issue Forgejo** à partir d'un message utilisateur en un geste : un bouton ouvre une modale pré-remplie (titre + corps éditables), l'admin ajuste et confirme, l'app crée l'issue via l'API Forgejo et **mémorise le lien** sur le message (`#N`, le bouton disparaît). Première intégration Forgejo d'`apps/api`. Dépend de #80 (mergé). 1 PR. ## Contenu **Modèle** — delta sur `ContactMessage` : `forgejoIssueNumber` / `forgejoIssueUrl` nullables (migration `20260908130000_contact_message_forgejo_link`, aucun backfill). **Config** (`config/env.ts` + `.env.example` + `ci.yml`) — `FORGEJO_BASE_URL` (`.url()`), `FORGEJO_TOKEN` (secret), `FORGEJO_REPO` (`owner/repo`), **toutes `.optional()`**. `ForgejoService.isConfigured()` les vérifie ensemble ; sans les trois, le bouton est masqué et l'endpoint répond `503` (même posture fail-closed que `ADMIN_JWT_SECRET`). **`ForgejoService`** (`lib/forgejo/forgejo.service.ts`, singleton `forgejo`) — `createIssue({ title, body })` → `POST {BASE}/api/v1/repos/{REPO}/issues`, en-tête `Authorization: token <pat>` (schéma PAT Forgejo), lit `{ number, html_url }`. Via `fetch` global (comme `mobile.service.ts`). Erreur réseau / non-2xx / JSON invalide ⇒ `HttpError 502 FORGEJO_REQUEST_FAILED`. **`admin-contact`** (issue #80, étendu) — - `POST /admin/api/contact/:id/forgejo-issue` (`createForgejoIssueSchema`) : ordre `503` → `404 CONTACT_MESSAGE_NOT_FOUND` → `409 CONTACT_MESSAGE_ALREADY_LINKED` → appel Forgejo → écriture BDD. **Un échec Forgejo (`502`) ne touche pas la BDD** — pas d'état partiel. Renvoie le `ContactMessageAdminView` à jour. - `GET /admin/api/contact/forgejo/status` → `{ enabled }`. **Contrat partagé** — `createForgejoIssueSchema` (title 1–255 = limite Forgejo, body 1–60000), `ContactMessageAdminView` + `forgejoIssueNumber`/`forgejoIssueUrl`, `ForgejoIntegrationStatus`, `ErrorCode` : `FORGEJO_NOT_CONFIGURED`=5001, `FORGEJO_REQUEST_FAILED`=5002, `CONTACT_MESSAGE_ALREADY_LINKED`=4026, `CONTACT_MESSAGE_NOT_FOUND`=4055 + labels dans `errors.*`. **Front** — `MessagesPage` sonde `forgejo/status` au montage ; colonne « Ticket » : lien `#N` (`target="_blank"`) si lié, bouton « Créer un ticket » si `enabled`, note « intégration Forgejo non configurée » sinon. `CreateForgejoIssueModal` (via le primitif `Dialog`) + helpers purs `messages.ts` (`buildIssueTitle` = `[Bug] <1ʳᵉ ligne>`, `buildIssueBody` = texte + bloc `---` contexte + pied). **Aucun label auto** (décision #81). Erreurs via `ErrorMessageService`, jamais `err.message`. ## Tests - `apps/api/test/forgejo.service.test.ts` — `globalThis.fetch` réassigné : URL / en-tête `token` / corps `{title, body}` ; `502` sur non-2xx et sur erreur réseau. - `apps/api/test/admin-contact.test.ts` (étendu) — status ; `503` (env absent) ; nominal (stocke le lien, renvoie la vue) ; `404` ; `409` ; `502` **+ ligne intacte** ; `400` titre vide. - `apps/web/cypress/e2e/admin-messages.cy.ts` (étendu) — modale pré-remplie, `#N` après création, pas de bouton si `enabled:false` (+ note), lien affiché pour un message déjà lié. ## Vérifié localement `pnpm lint` ✅ · `pnpm build` (tsc api + web, vite) ✅ · Cypress `admin-messages` + `admin-corrections` + `admin-layout` : 11/11 ✅. Mocha → CI (pas de Postgres local). Closes #81. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
kyuno added 1 commit 2026-09-08 20:11:31 +02:00
feat(admin): crée un ticket Forgejo depuis un message de contact (#81)
All checks were successful
CI / lint (push) Successful in 3m16s
CI / build (push) Successful in 3m29s
CI / intent-service-test (push) Successful in 4m39s
CI / e2e (push) Successful in 10m24s
CI / test (push) Successful in 18m48s
CI / build-android (push) Has been skipped
30f975d29a
Depuis /admin/messages, un admin peut créer une issue Forgejo à partir
d'un message utilisateur : bouton « Créer un ticket » → modale pré-remplie
éditable → l'issue est créée via l'API Forgejo et le lien (#N) est
mémorisé sur le message. Première intégration Forgejo d'apps/api.

- Modèle : `forgejoIssueNumber` / `forgejoIssueUrl` sur `ContactMessage`
  (migration `20260908130000_contact_message_forgejo_link`, 2 colonnes
  nullables, aucun backfill).
- Config : `FORGEJO_BASE_URL` / `FORGEJO_TOKEN` / `FORGEJO_REPO`, toutes
  `.optional()` — **fail-closed** (même posture que `ADMIN_JWT_SECRET`) :
  bouton masqué + endpoint `503` tant que les trois ne sont pas là.
  Documentées dans `.env.example` ; valeurs de test dans `ci.yml`.
- `ForgejoService` (`lib/forgejo/forgejo.service.ts`, singleton `forgejo`) :
  `isConfigured()` + `createIssue({ title, body })` → `POST .../issues`,
  en-tête `Authorization: token <pat>`, lit `{ number, html_url }` ;
  erreur réseau / non-2xx / JSON invalide ⇒ `HttpError 502
  FORGEJO_REQUEST_FAILED`.
- `admin-contact` : `POST /admin/api/contact/:id/forgejo-issue`
  (`createForgejoIssueFromMessage` — ordre 503 → 404 → 409 → Forgejo →
  écriture BDD ; un échec Forgejo ne touche pas la BDD) et
  `GET /admin/api/contact/forgejo/status`.
- Contrat partagé : `createForgejoIssueSchema` (title 1–255, body 1–60000),
  `ContactMessageAdminView` étendu, `ForgejoIntegrationStatus`, nouveaux
  `ErrorCode` (`FORGEJO_NOT_CONFIGURED`=5001, `FORGEJO_REQUEST_FAILED`=5002,
  `CONTACT_MESSAGE_ALREADY_LINKED`=4026, `CONTACT_MESSAGE_NOT_FOUND`=4055)
  + labels i18n.
- Front : `MessagesPage` — sonde `forgejo/status` au montage, colonne
  « Ticket » (lien `#N` si lié, bouton sinon, « non configurée » sinon),
  `CreateForgejoIssueModal` + helpers purs `messages.ts`
  (`buildIssueTitle` / `buildIssueBody`).
- Tests : `forgejo.service.test.ts` (stub `globalThis.fetch`),
  `admin-contact.test.ts` étendu (nominal / 503 / 404 / 409 / 502 +
  ligne intacte / 400), `admin-messages.cy.ts` étendu (modale pré-remplie,
  lien après création, masquage si non configuré).
- Docs : `backend-architecture.md`, `batch-cooking-modele.md`.

Closes #81.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
kyuno merged commit 46c23c24ab into main 2026-09-08 20:30:09 +02:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: kyuno/batchCooking#125
No description provided.