Skip to main content

Architecture

Patrimo est une application full-Next.js 16 (App Router) monolithique, multi-utilisateurs, exécutée en Docker sur un serveur on-premise via Coolify. Pas de backend séparé, pas de message broker, pas de cache distribué.

Historique

L'EB.md initial mentionnait Express / Firebase / MongoDB / MariaDB. Ces technologies ont été remplacées — voir ADR-001 et ADR-003. L'auth single-user par variables d'environnement a été remplacée par Authentik OIDC + Credentials multi-users — voir ADR-006.

Stack

CoucheTechnologieVersion
FrameworkNext.js (App Router)16.2.4
RuntimeReact19.2.4
LangageTypeScript^5
Base de donnéesPostgreSQL16 (Alpine)
ORMPrisma^7.7.0
Driver DB@prisma/adapter-pg + pg^7.7.0 / ^8.20
AuthNextAuth v5 beta^5.0.0-beta.31
CSSTailwind CSS^4
ChartsRecharts^3.8.1
UI primitivesRadix UImultiple
Dev containerDocker Compose
ProdCoolify (Docker Compose override)
TestsJest + Testing Library^30 / ^16

Couches applicatives

┌─────────────────────────────────────────────────────────┐
│ Browser (React 19) │
│ - Server Components par défaut │
│ - Client Components pour interactions (Sidebar, forms) │
└───────────┬─────────────────────────────────────────────┘
│ HTTP + Server Actions (fetch POST chiffré)
┌───────────▼─────────────────────────────────────────────┐
│ Next.js App Router │
│ - src/app/login (public) │
│ - src/app/(app)/* (authenticated route group) │
│ - src/app/api/* (route handlers) │
│ - src/app/actions/* ('use server' — API principale) │
└───────────┬─────────────────────────────────────────────┘
│ Prisma Client (singleton, adapter pg)
┌───────────▼─────────────────────────────────────────────┐
│ PostgreSQL 16 (Docker volume `postgres_data`) │
│ - 12 modèles Prisma │
│ - Migrations versionnées │
└─────────────────────────────────────────────────────────┘

Flux de requête

Connexion via Authentik OIDC

Browser → GET /login
→ src/app/login/page.tsx (Client Component)
→ clic bouton "Se connecter avec Authentik"
→ redirect GET /api/auth/signin/authentik
→ redirect vers Authentik (IDP externe)
→ callback GET /api/auth/callback/authentik
→ src/lib/auth.ts : PrismaAdapter crée/lie OAuthAccount
→ JWT signé (AUTH_SECRET), token.id + token.isAdmin propagés
→ cookie HttpOnly
→ redirect /dashboard

Connexion via Credentials

Browser → GET /login
→ src/app/login/page.tsx (Client Component)
→ formulaire email + mot de passe
→ POST /api/auth/callback/credentials
→ src/lib/auth.ts : authorize()
→ prisma.user.findUnique(email)
→ guard : passwordHash obligatoire (users OIDC exclus)
→ bcrypt.compare(password, passwordHash)
→ JWT signé (AUTH_SECRET), token.id + token.isAdmin propagés
→ cookie HttpOnly
→ redirect /dashboard

Page authentifiée

Browser → GET /(app)/accounts
→ src/app/(app)/layout.tsx (Server Component)
→ await auth() → session JWT
→ si absente : redirect /login
→ rendu <Sidebar> + children
→ src/app/(app)/accounts/page.tsx (Server Component)
→ prisma.account.findMany(...)
→ HTML streamed

Mutation via Server Action

Browser (form submit)
→ Server Action 'use server' dans src/app/actions/accounts.ts
→ auth() guard (ownership)
→ validation Zod
→ prisma.account.create(...)
→ revalidatePath('/accounts')
→ Browser rerender (RSC payload)

Voir Server Actions pour la convention détaillée.

Authentification

Source : src/lib/auth.ts

NextAuth v5 beta, deux providers coexistants, stratégie de session JWT (maxAge 30 jours).

Providers

ProviderTypeRôle
AuthentikOIDCProvider principal, délègue l'authentification à Authentik auto-hébergé
CredentialsEmail + bcryptFallback pour tous les utilisateurs ayant un passwordHash en base

PrismaAdapter — workaround collision de noms

Source : src/lib/auth.ts:33-36

Le modèle financier Account occupe le nom que PrismaAdapter attend pour les comptes OAuth. Un proxy client redirige le delegate account vers prisma.oAuthAccount (@@map("auth_accounts")) :

const prismaForAdapter = {
...prisma,
account: prisma.oAuthAccount,
} as unknown as Parameters<typeof PrismaAdapter>[0];

Modèles NextAuth dans le schéma Prisma

Modèle PrismaTable SQLUsage
OAuthAccountauth_accountsComptes OIDC liés à un User
AuthSessionauth_sessionsSessions (réservé — stratégie JWT, donc non utilisé activement)
VerificationTokenverification_tokensTokens de vérification email

JWT callbacks

Source : src/lib/auth.ts:83-101

À chaque génération de token (premier login ou refresh), token.id et token.isAdmin sont alimentés depuis la base. isAdmin est toujours lu en DB car le provider Authentik ne transmet pas cette information.

Pages personnalisées

  • signIn/login
  • error/login

La vérification de session dans la zone (app)/ est effectuée côté serveur via await auth() dans le layout.

Variables d'environnement (voir ADR-006)

VariableObligatoireDescription
AUTH_SECRETouiClé de signature JWT
AUTH_URLnonURL de base NextAuth (défaut http://localhost:4000)
AUTHENTIK_CLIENT_IDouiClient ID de l'application Authentik
AUTHENTIK_CLIENT_SECRETouiSecret client Authentik
AUTHENTIK_ISSUERouiURL de l'issuer OIDC Authentik

Docker

Développement (docker-compose.yml) :

  • db : postgres:16-alpine, healthcheck pg_isready, volume postgres_data, port 5432
  • app : build stage dev, bind-mount source (hot-reload), port 4000, exécute prisma db push avant npm run dev

Production (Dockerfile stage runner + docker-compose.prod.yml) :

StageBaseRôle
basenode:22-alpineVariables communes, WORKDIR
depsbasenpm ci
devbase + depsHot-reload, bind-mount
builderbase + depsprisma generate + next build (standalone)
runnernode:22-alpineImage finale minimale, user nextjs non-root

Le mode standalone de Next.js (output: "standalone" dans next.config.ts) produit un server.js autonome. L'entrypoint (entrypoint.sh) exécute prisma db push puis node server.js.

L'override prod :

  • Cible le stage runner
  • Supprime les bind-mounts
  • Healthcheck GET /api/health (curl, 30 s interval, start_period 40 s)
  • restart: always

Variables prod supplémentaires

VariableObligatoireDescription
POSTGRES_PASSWORDouiMot de passe PostgreSQL
POSTGRES_DBnonNom DB (défaut patrimo)
POSTGRES_USERnonUtilisateur DB (défaut patrimo)
GEMINI_API_KEYnonClé API Gemini (catégorisation IA)

Scripts npm (racine)

CommandeAction
npm run devNext.js sur le port 4000
npm run buildBuild de production (standalone)
npm run type-checktsc --noEmit
npm testJest
npm run test:coverageJest avec coverage
npm run db:migrateprisma migrate dev
npm run db:migrate:prodprisma migrate deploy
npm run db:seedprisma db seed
npm run db:studioPrisma Studio
npm run hash-passwordHash bcrypt d'un mot de passe
npm run generate-secretGénère un AUTH_SECRET aléatoire

Décisions techniques

Voir les ADRs :

Pour approfondir :