Skip to content

BDD-118 — Création d'une source depuis un formulaire API

1. User Story

En tant qu'administrateur, je veux pouvoir créer une source à partir des données d'un formulaire récupérées via une connexion API, afin d'exploiter ces données comme n'importe quelle autre source dans l'application.

2. Critères d'acceptation

#CritèreStatut
1L'utilisateur peut créer une nouvelle source basée sur les données d'un formulaire API
2La nouvelle source est visible et exploitable comme les autres sources (liste, détail, export)
3En cas d'erreur lors de la création : un message explicite est affiché et la source n'est pas créée

Détail fonctionnel des critères

  1. Création depuis un formulaire API

    • Deux points d'entrée :
      • depuis /connections/:uuid/browse (après sélection d'un formulaire) → bouton « Créer une source » qui pré-remplit l'assistant (connectionUuid, formUuid, formTitle) et amène directement à l'étape Nommer.
      • depuis /sources/create → toggle Depuis un fichier / Depuis une API à l'étape 1 ; en mode API : sélection d'une connexion ACTIVE puis d'un formulaire.
    • Étapes du wizard identiques aux sources fichier : choix de la source → nom + nom de table → preview + typage des colonnes → import.
  2. Source exploitable

    • La source apparaît dans /sources avec un badge d'origine « API Formulaire » (vs « Fichier »).
    • Détail (/sources/:uuid), export CSV / XLSX, suppression : comportement identique aux sources document.
    • Le bouton Recharger est désactivé pour les sources API_FORM avec un tooltip explicite (rechargement API hors périmètre — ticket suivant).
  3. Erreurs explicites

    • Connexion ou formulaire introuvable / supprimé → message dédié.
    • Connexion non supportée (autre que FORMULAIRES) → exception métier 400.
    • Formulaire sans colonnes exploitables → SourceApiFormNoColumnsException.
    • Clé API révoquée → message déjà géré par BDD-117 (markConnectionAsError).
    • Nom de table déjà utilisé → bannière d'avertissement (remplacement explicite).
    • En cas d'échec après preview, la source n'est pas persistée (transaction métier dans createSource).

3. Périmètre fonctionnel

Base de données

FichierRôle
sql/tool_14_add_api_form_sources.sqlMigration : enum SourceType (DOCUMENT / API_FORM), document_id nullable, colonnes api_connection_id, form_uuid, form_title, source_type, index
back/prisma/schema.prismaEnum SourceType, sources.document_id nullable, relation sourcesapi_connections, colonnes API

Backend

FichierRôle
back/src/api/sources/sources.controller.tsPOST /sources unifié (discriminant source), GET /sources/table-check (document ou connexion), GET /sources/api-preview
back/src/api/sources/sources.service.tscreateSource unifiée (helpers buildDocumentSourceContext / buildApiFormSourceContext), checkTable unifiée, getApiFormPreview, blocage reloadSource sur API_FORM
back/src/api/sources/dto/source.dto.tsCreateSourceDto unifié (source: 'document' | 'api') avec @ValidateIf ; SourceResponseDto enrichi (sourceType, connectionUuid, connectionName, formUuid, formTitle)
back/src/api/sources/source.presenter.tsMapping document nullable + relation api_connection
back/src/api/sources/sources.module.tsImport de ConnectionsModule (injection ConnectionsService + ConnectionBrowseService)
back/src/api/connections/connections.module.tsExport de ConnectionBrowseService pour réutilisation par SourcesModule
back/src/exceptions/source.exceptions.tsSourceApiFormNoColumnsException, SourceApiReloadNotSupportedException

Frontend

FichierRôle
front/src/pages/admin/sources/AdminSourceCreatePage.vueWizard 3 étapes, toggle document/API, pré-remplissage via query, branche preview/submit selon le mode
front/src/pages/admin/sources/AdminSourcesPage.vueBadge d'origine (Fichier / API Formulaire), bouton Recharger désactivé pour API_FORM, contexte super-admin enrichi (connexion → formulaire)
front/src/pages/connections/ConnectionBrowsePage.vueBouton « Créer une source » dans la barre de métriques, redirige vers /sources/create?connectionUuid=…&formUuid=…&formTitle=…
front/src/stores/source-store.jscreateSource unifiée, checkTableName({ tableName, documentUuid? | connectionUuid? }), getApiSourcePreview
front/src/stores/connection-browse-store.jsAction resetForms (réinitialisation lors d'un changement de connexion)

4. Endpoints API

MéthodeCheminGuardsDescription
POST/sourcesAuthGuard, AdminGuardCrée une source (source: 'document' ou 'api')
GET/sources/table-checkAuthGuard, AdminGuardVérifie disponibilité du nom de table (documentUuid ou connectionUuid exclusifs)
GET/sources/api-previewAuthGuard, AdminGuardAperçu typé d'un formulaire API (20 premières lignes)

Corps POST /sources — mode document

json
{
  "source": "document",
  "name": "RH Effectifs 2026",
  "tableName": "rh_effectifs_2026",
  "documentUuid": "uuid-doc",
  "columns": [
    { "name": "nom", "selectedType": "text" },
    { "name": "naissance", "selectedType": "date", "dateFormat": "DD/MM/YYYY" }
  ]
}

Corps POST /sources — mode API

json
{
  "source": "api",
  "name": "Inscriptions 2026",
  "tableName": "inscriptions_2026",
  "connectionUuid": "uuid-connexion",
  "formUuid": "uuid-formulaire",
  "formTitle": "Inscriptions 2026",
  "columns": [
    { "name": "email", "selectedType": "text" }
  ]
}

Réponse POST /sources (extrait, mode API)

json
{
  "uuid": "...",
  "name": "Inscriptions 2026",
  "tableName": "inscriptions_2026",
  "schemaName": "schema_aaaaaaaa...",
  "rowCount": 42,
  "status": "ACTIVE",
  "sourceType": "API_FORM",
  "documentUuid": null,
  "folderUuid": null,
  "connectionUuid": "...",
  "connectionName": "API Formulaires prod",
  "formUuid": "...",
  "formTitle": "Inscriptions 2026",
  "enterpriseName": "Acme Corp"
}

Erreurs courantes

CodeCas
400Body invalide (source manquant, documentUuid/connectionUuid incohérent), nom de table invalide, formulaire sans colonnes exploitables, connexion non supportée
400POST /sources/:uuid/reload sur une source API_FORM (SourceApiReloadNotSupportedException)
401Jeton absent / invalide
403Rôle non admin
404Document, connexion ou formulaire introuvable (ou hors périmètre entreprise)

5. Règles métier implémentées

  • Périmètre entreprise
    • administrateur : création limitée aux documents / connexions de son enterprise_id.
    • super-admin : voit toutes les entreprises (logique inchangée vs sources fichier).
  • Modèle unifié :
    • sources.source_type (DOCUMENT par défaut) ; document_id nullable ; api_connection_id, form_uuid, form_title renseignés uniquement en API_FORM.
    • Une source est rattachée à exactement une origine (XOR enforced par les helpers de service).
  • Création en mode API :
    • Récupération des réponses via ConnectionBrowseService.loadFormResponses (clé API déchiffrée côté serveur, jamais exposée).
    • Seules les connexions de type FORMULAIRES ACTIVE sont éligibles.
    • Réutilisation du typage de colonnes existant (text / number / date / boolean) avec correction manuelle possible avant import.
    • Nom de table contraint au regex ^[a-z][a-z0-9_]{0,62}$ (idem sources fichier).
  • Rechargement :
    • POST /sources/:uuid/reload sur une source API_FORM → 400 SourceApiReloadNotSupportedException (bouton désactivé côté front).
    • Le rechargement API sera traité dans un ticket dédié (BDD-119).
  • Suppression : aucun changement, hard delete côté table métier (la connexion d'origine peut être supprimée — la source survit, l'origine devient orpheline).

6. Migration

À exécuter côté base tool après merge :

bash
psql "$DATABASE_URL_TOOL" -f sql/tool_14_add_api_form_sources.sql
yarn --cwd back db:generate

La migration :

  • crée l'enum SourceType,
  • ajoute source_type, api_connection_id, form_uuid, form_title à sources,
  • rend document_id nullable,
  • ajoute les index idx_sources_api_connection_id et idx_sources_source_type,
  • ajoute la FK api_connection_id → api_connections.id.

Les sources existantes restent en source_type = 'DOCUMENT'.

7. Points de vérification

  • Depuis /connections/:uuid/browse : sélectionner un formulaire chargé → bouton « Créer une source » visible → arrive sur /sources/create étape Nommer pré-remplie (connexion + formulaire affichés en récapitulatif).
  • Depuis /sources/create : toggle Depuis une API → liste des connexions ACTIVE chargée → sélection d'une connexion → liste des formulaires chargée.
  • Création complète mode API : aperçu typé, ajustement type / format de date, import, redirection /sources, notification « Source "X" créée — N lignes chargées ».
  • Liste /sources : badge API Formulaire sur la nouvelle source ; bouton Recharger désactivé avec tooltip « Rechargement API bientôt disponible ».
  • Détail /sources/:uuid : affichage des données, export CSV / XLSX fonctionnels.
  • Suppression d'une connexion API : la source API_FORM rattachée reste exploitable (lecture seule de la donnée déjà chargée).
  • Tentative de rechargement (POST /sources/:uuid/reload) via API directe sur une source API_FORM → 400 avec message explicite.
  • Erreurs : formulaire introuvable, clé API révoquée, nom de table invalide → messages clairs côté front, pas d'enregistrement partiel.

8. Tests associés

Backend (unitaires)

  • back/src/api/sources/sources.service.spec.ts
    • createSource mode document : happy path, document introuvable, périmètre entreprise.
    • createSource mode API : happy path, connectionUuid manquant (400), connexion type non supporté, connexion introuvable, formulaire sans colonnes (SourceApiFormNoColumnsException), formTitle vide accepté.
    • checkTable mode document et mode API (XOR documentUuid / connectionUuid).
    • reloadSourceSourceApiReloadNotSupportedException si source_type === 'API_FORM'.
    • getApiFormPreview : inférence des colonnes, limite à 20 lignes, connexion non supportée, formulaire sans colonnes.

Backend (e2e)

  • back/test/e2e/sources-controller.e2e-spec.ts
    • POST /sources mode document : 400 (source manquant), 401, 403, 404 document inexistant, 201 happy path.
    • POST /sources mode API (à compléter avec mock ConnectionBrowseService) : 400 (formulaire vide), 404 connexion inconnue, 201.
    • GET /sources/table-check avec documentUuid et avec connectionUuid.

Frontend (unitaires)

  • front/src/__tests__/stores/source-store.spec.js
    • createSource document & API (signature unifiée).
    • checkTableName avec documentUuid, avec connectionUuid, sans UUID.
    • getApiSourcePreview (avec / sans formTitle).
  • front/src/__tests__/stores/connection-browse-store.spec.js
    • Action resetForms.
  • front/src/__tests__/pages/admin/sources/AdminSourceCreatePage.spec.js
    • Mode document inchangé (super-admin / admin, checkTableName, getSourcePreview).
    • Bascule en mode API via le toggle → chargement des connexions, sélection → resetForms + fetchForms.
    • Pré-remplissage depuis la query string → étape 2 directe, fetchConnection appelé.
    • checkTableName avec connectionUuid, getApiSourcePreview au passage à l'étape 3.
    • submit : payload source: 'document' vs source: 'api'.
  • front/src/__tests__/pages/admin/sources/AdminSourcesPage.spec.js
    • Badge Fichier / API Formulaire.
    • Bouton Recharger désactivé pour API_FORM, actif pour DOCUMENT.
    • Super-admin : contexte connexion → formulaire pour les sources API.
  • front/src/__tests__/pages/connections/ConnectionBrowsePage.spec.js
    • Bouton Créer une source redirige vers /sources/create avec connectionUuid, formUuid, formTitle.
    • formTitle omis si titre vide.

Frontend (e2e Playwright)

  • front/e2e/pages/connections/connections.spec.js
    • Bouton Créer une source → redirection avec les bons query params.
    • Bouton absent tant qu'aucun formulaire n'est sélectionné.

9. Notes d'implémentation

  • Unification volontaire des endpoints (POST /sources, GET /sources/table-check) via un discriminant source — évite la divergence entre les deux flux (preview, typage, validation, persistance partagés).
  • Le toggle à l'étape 1 a été préféré à des routes séparées (/sources/create/document vs /sources/create/api) pour conserver un seul wizard et une seule logique côté front.
  • Le bouton « Créer une source » sur ConnectionBrowsePage réutilise le style des actions de la barre de métriques (icône table_chart, tooltip) — il n'apparaît qu'après chargement des réponses, garantissant qu'on a un jeu de données valide à importer.
  • Le presenter toSourceResponse renvoie systématiquement les deux blocs d'origine (documentUuid / folderUuid ou connectionUuid / formUuid / formTitle), avec null pour le bloc non applicable — permet aux pages front de gérer les deux types sans branche conditionnelle au niveau de l'appel.

10. Hors périmètre (ticket suivant : BDD-119)

  • Rechargement d'une source API_FORM (refetch des réponses depuis Formulaires).
  • Ré-attribution d'une source à une autre connexion / formulaire après suppression.
  • Synchronisation automatique périodique des sources API (cron / webhook).
  • Rotation de clé API sur connexion existante.