/* |-------------------------------------------------------------------------- | Routes file |-------------------------------------------------------------------------- | | Toutes les routes sont sous /api/v1/. Les groupes auth et account sont | importés depuis le contrôleur généré par Tuyau pour garantir le contrat | client typé (cf. docs/tech/backend.md §8). | */ import { middleware } from '#start/kernel' import router from '@adonisjs/core/services/router' import { controllers } from '#generated/controllers' router .group(() => { /** * Health check — public, utilisé par les sondes K3s (startup/liveness/ * readiness) et le healthcheck Docker. Retourne 200 sans toucher la DB * pour rester rapide ; la DB est implicitement vérifiée au boot par * le init-container migrate. */ router.get('health', () => ({ status: 'ok', uptime: process.uptime() })).as('health') }) .prefix('/api/v1') router .group(() => { /** * Auth — public. /refresh utilise le cookie httpOnly `rubis_refresh` * posé par signup/login pour émettre une nouvelle AuthSession. * /google/* → SSO via Ally, callback pose aussi le refresh cookie. */ router .group(() => { router.post('signup', [controllers.NewAccount, 'store']).as('signup') router.post('login', [controllers.AccessTokens, 'store']).as('login') router.post('refresh', [controllers.Refresh, 'handle']).as('refresh') router .get('google/redirect', [controllers.AuthGoogle, 'redirect']) .as('google.redirect') router .get('google/callback', [controllers.AuthGoogle, 'callback']) .as('google.callback') }) .prefix('auth') .as('auth') /** * Check-in — public (pas d'auth Bearer). Token signé en URL, * lookup hash en DB. Redirige vers le SPA avec ?checkin=... pour * que l'UI affiche un toast. */ router .group(() => { router .get(':token/paid', [controllers.Checkin, 'respondPaid']) .as('paid') router .get(':token/pending', [controllers.Checkin, 'respondPending']) .as('pending') }) .prefix('checkin') .as('checkin') /** * Compte courant — auth requise. */ router .group(() => { router.get('profile', [controllers.Profile, 'show']).as('profile.show') router.patch('profile', [controllers.Profile, 'update']).as('profile.update') router.post('logout', [controllers.AccessTokens, 'destroy']).as('logout') }) .prefix('account') .as('account') .use(middleware.auth()) /** * Organisation rattachée à l'utilisateur courant — auth requise. */ router .group(() => { router.get('me', [controllers.Organizations, 'show']).as('show') router.patch('me', [controllers.Organizations, 'update']).as('update') }) .prefix('organizations') .as('organizations') .use(middleware.auth()) /** * Clients — auth requise, scope par organization de l'utilisateur courant. */ router .group(() => { router.get('', [controllers.Clients, 'index']).as('index') router.post('', [controllers.Clients, 'store']).as('store') router.get(':id', [controllers.Clients, 'show']).as('show').where('id', router.matchers.uuid()) router.patch(':id', [controllers.Clients, 'update']).as('update').where('id', router.matchers.uuid()) }) .prefix('clients') .as('clients') .use(middleware.auth()) /** * IA — auth requise. Génération de templates de relance avec Mistral * pour le wizard de création de plan custom. */ router .group(() => { router.post('generate-relance', [controllers.Ai, 'generateRelance']).as('generate-relance') }) .prefix('ai') .as('ai') .use(middleware.auth()) /** * Plans — auth requise. Lookup par slug (stable et lisible : * /parametres/plans/standard-30j). POST = création d'un plan custom, * slug auto-généré depuis le name. */ router .group(() => { router.get('', [controllers.Plans, 'index']).as('index') router.post('', [controllers.Plans, 'store']).as('store') router.get(':slug', [controllers.Plans, 'show']).as('show') router.patch(':slug', [controllers.Plans, 'update']).as('update') }) .prefix('plans') .as('plans') .use(middleware.auth()) /** * Dashboard — auth requise. Calculs agrégés on-the-fly (pas de cache V1). */ router .group(() => { router.get('kpis', [controllers.Dashboard, 'kpis']).as('kpis') router.get('activity', [controllers.Dashboard, 'activity']).as('activity') router.get('top-late', [controllers.Dashboard, 'topLate']).as('top-late') }) .prefix('dashboard') .as('dashboard') .use(middleware.auth()) /** * Invoices — auth requise. Ordre IMPORTANT : les routes statiques * (/upload, /counts, /import-batch/...) sont déclarées AVANT /:id * sinon `:id` matche les segments littéraux. */ router .group(() => { router.get('', [controllers.Invoices, 'index']).as('index') router.post('', [controllers.Invoices, 'store']).as('store') router.get('counts', [controllers.Invoices, 'counts']).as('counts') // OCR / Import batch (cf. ImportBatchesController) router.post('upload', [controllers.ImportBatches, 'upload']).as('upload') router .get('import-batch/:id', [controllers.ImportBatches, 'show']) .as('import-batch.show') .where('id', router.matchers.uuid()) router .post('import-batch/:id/drafts/:draftId/validate', [ controllers.ImportBatches, 'validateDraft', ]) .as('import-batch.draft.validate') .where('id', router.matchers.uuid()) .where('draftId', router.matchers.uuid()) router .post('import-batch/:id/drafts/:draftId/skip', [ controllers.ImportBatches, 'skipDraft', ]) .as('import-batch.draft.skip') .where('id', router.matchers.uuid()) .where('draftId', router.matchers.uuid()) router .delete('import-batch/:id', [controllers.ImportBatches, 'destroy']) .as('import-batch.destroy') .where('id', router.matchers.uuid()) router.get(':id', [controllers.Invoices, 'show']).as('show').where('id', router.matchers.uuid()) router .post(':id/mark-paid', [controllers.Invoices, 'markPaid']) .as('mark-paid') .where('id', router.matchers.uuid()) }) .prefix('invoices') .as('invoices') .use(middleware.auth()) }) .prefix('/api/v1')