Aller au contenu principal

HeartCo SaaS Architecture

Table of Contents

Overview

HeartCo is a multi-tenant SaaS platform built on Next.js 15 App Router with tRPC for type-safe RPC, Prisma 7 for ORM, PostgreSQL (Supabase) for data, NextAuth v5 for authentication, and Mistral AI for intelligent features. The platform includes web (React), mobile (Expo), and specialized verticals (Payroll, Accounting) with Stripe billing, Redis-cached RBAC, and comprehensive audit trails.


System Architecture Diagram

┌─────────────────┐     ┌────────────────────────┐     ┌──────────────────┐
│  Browser (SPA)  │────▶│  Next.js 15            │────▶│  PostgreSQL      │
│  React + tRPC   │◀────│  App Router + Middleware│◀────│  (Supabase)      │
└─────────────────┘     │                        │     └──────────────────┘
                        │  ┌──────────────────┐  │     ┌──────────────────┐
┌─────────────────┐     │  │ tRPC Procedures  │  │────▶│  Redis / Upstash │
│  Expo Mobile    │────▶│  │ (staffProcedure, │  │◀────│  (Cache + Queues)│
│  App            │◀────│  │  requirePermit) │  │     └──────────────────┘
└─────────────────┘     │  └──────────────────┘  │     ┌──────────────────┐
                        │                        │────▶│  Stripe API      │
┌─────────────────┐     │  ┌──────────────────┐  │     └──────────────────┘
│  External APIs  │────▶│  │ Prisma 7 Client  │  │     ┌──────────────────┐
│  (Stripe,       │◀────│  │ (org-scoped via  │  │────▶│  Resend / Email  │
│   Mistral, etc) │     │  │  $extends)       │  │     └──────────────────┘
└─────────────────┘     │  └──────────────────┘  │     ┌──────────────────┐
                        │                        │────▶│  Mistral AI      │
                        │  ┌──────────────────┐  │     └──────────────────┘
                        │  │ Webhooks Handler │  │     ┌──────────────────┐
                        │  │ (Stripe, Bridge) │  │────▶│  Pusher / Queues │
                        │  └──────────────────┘  │     └──────────────────┘
                        └────────────────────────┘

Authentication & Authorization Flow

1. User Login
   └─→ NextAuth v5 Provider (CredentialsProvider or OAuth)
       └─→ Verify credentials / OAuth token
           └─→ Create JWT session (signed, httpOnly cookie)

2. Middleware (src/middleware.ts)
   └─→ Check session validity + refresh if needed
       └─→ Enforce route protection (public/private/admin)
           └─→ Inject session into context

3. tRPC Procedure Call
   └─→ Check procedure guard (protectedProcedure, staffProcedure, etc.)
       └─→ Load user permissions from Redis (cached)
           └─→ DENY-by-default: check requirePermission() guards
               └─→ Execute procedure with organizationId context

4. Data Access (Prisma)
   └─→ Prisma $extends auto-filters by organizationId
       └─→ Query results scoped to caller's organization only

Stripe Billing Flow

1. User selects plan → Checkout Page
   └─→ Redirect to Stripe Checkout Session
       └─→ User completes payment

2. Stripe Webhook → /api/stripe/webhook
   └─→ Verify HMAC signature (timingSafeEqual)
       └─→ Handle events:
           • checkout.session.completed → create Subscription record
           • customer.subscription.updated → update plan + features
           • customer.subscription.deleted → downgrade to FREE
           • invoice.payment_failed → trigger dunning flow (email)

3. Database Update
   └─→ Update Subscription model (plan, features, periodEnd)
       └─→ Reset monthly usage counters if needed
           └─→ Send confirmation email (Resend)

4. Customer Portal
   └─→ Link in dashboard → manage.stripe.com (session creation)
       └─→ Update payment method, cancel subscription

Mobile (Expo) Integration

1. Mobile Auth
   └─→ JWT endpoint → /api/auth/mobile-token
       └─→ Exchange mobile credentials → return JWT
           └─→ Store in SecureStore (React Native)

2. tRPC from Mobile
   └─→ Same backend API (no separate mobile endpoint)
       └─→ React Query / TRPC client for offline queue
           └─→ Queue mutations locally (SQLite)

3. Sync on Reconnect
   └─→ Check connection status
       └─→ Flush outbox queue → tRPC mutations
           └─→ Sync remote data (pull changes)
               └─→ Push notifications (Expo Notification Service)

4. Deep Linking
   └─→ Expo.Linking → route to in-app screen with parameters

Directory Structure

/src
  /app                          # Next.js App Router (pages + layouts)
    /api                        # Route handlers (webhooks, auth, crons)
    /dashboard                  # Protected dashboard routes
    /(marketing)                # Public landing & marketing pages
    /auth                       # Auth flows (login, signup, reset)

  /server
    /api
      /routers                  # 76 tRPC routers
        /crm.ts                 # Contacts, deals, activities, tasks
        /facturation.ts         # Invoicing, payments, dunning
        /comptabilite.ts        # Accounting, journal entries, GL
        /appointments.ts        # Scheduling, calendars, resources
        /automation.ts          # Workflows, triggers, email campaigns
        /chatbot.ts             # AI chatbot, conversation management
        /analytics.ts           # Custom dashboards, KPIs
        /bank.ts                # Bank integration (Bridge API), reconciliation
        [+25 more]
      /root.ts                  # Router registry (combines all)
      /trpc.ts                  # Procedure guards + middleware
      /abtest.ts                # A/B testing utilities

  /lib
    /permissions
      /matrix.ts                # RBAC matrix (source of truth)
      /permissions.ts           # Helper functions
    /prisma-org-scope.ts        # Prisma $extends for auto-scoping
    /auth/                       # NextAuth config, callbacks
    /stripe/                     # Stripe utilities
    /ai/                         # Mistral AI integration
    /freemium/                   # Usage tracking & quotas
    /cache.ts                    # Redis wrapper
    /validation.ts              # Zod schemas
    /utils.ts                   # Helpers (cn(), etc.)

  /components
    /ui                         # shadcn/Radix primitives (Button, Card, etc.)
    /layout                     # Sidebar, navbar, main layout
    /auth                       # Login, signup, password forms
    /crm                        # CRM widgets
    /dashboard                  # Dashboard charts, tables
    /landing                    # Landing page sections

  /modules
    /bridge/                    # Bank API integration
    /facturx/                   # Factur-X e-invoice (certified)
    /iopole/                    # Compliance gateway
    /payment/                   # Payment processing

  /hooks                        # Custom React hooks (useOrgDb, usePermissions, etc.)
  /contexts                     # React Context providers (theme, auth, notifications)
  /stores                       # Client state (Zustand, if used)
  /types                        # TypeScript type definitions
  /constants                    # Env values, feature flags
  /test                         # Test utilities, mocks
  /__tests__                    # Integration/security tests
  /middleware.ts                # Next.js middleware (auth, ratelimit)
  /env.js                       # Environment validation (@t3-oss/env-nextjs)

/apps
  /mobile                       # Expo React Native app
  /app-terrain                  # Field worker app (specialized)
  /app-temps                    # Temp staffing app (specialized)

/packages
  /shared-types                 # Monorepo shared TS types (if used)

/prisma
  /schema.prisma               # Prisma schema (models + relations)
  /migrations/                 # Auto-generated migration files

/generated
  /prisma/                     # Prisma client (generated, not in node_modules)

/e2e
  /tests/                      # Playwright E2E tests
  /.auth/user.json             # Saved auth state (gitignored)


tRPC Procedure Hierarchy

LevelNameRequirementUse Case
0publicProcedureNonePublic routes, webhooks (no auth)
1protectedProcedureValid session (any role)Client portal, personal data
2staffProcedureValid session + not CLIENT roleInternal tools, staff features
3managerProcedureADMIN, DIRECTION, MANAGER, HRPayroll, reports, team management
4adminProcedureADMIN role onlySystem settings, user management
5requirePermission("res:act")staffProcedure + specific RBAC permissionGranular access (e.g., "invoice:export")

Pattern: Always start with the most restrictive guard needed. Stack guards with requirePermission() for fine-grained control.


Data Scoping & Multi-Tenancy

// Prisma $extends auto-filters all reads by organizationId
const ctx.orgDb = ctx.db.$extends(
  orgScope({ organizationId: ctx.session.user.organizationId })
);
 
// Usage:
const contacts = await ctx.orgDb.contact.findMany(); // auto-filtered by org
 
// For writes, add organizationId manually:
await ctx.db.contact.create({
  data: {
    ...input,
    organizationId: ctx.session.user.organizationId, // REQUIRED
  },
});
 
// For deletes, ALWAYS include organizationId in where:
await ctx.db.contact.delete({
  where: {
    id: input.id,
    organizationId: ctx.session.user.organizationId, // REQUIRED for safety
  },
});

Rule: Every data model with business data must have organizationId String + @@index([organizationId]).


Key Design Decisions

  1. Multi-Tenant Isolation via Prisma $extends

    • Single database, one table per entity
    • Auto-filtering on reads (ctx.orgDb)
    • Manual checks on writes (organizationId in where clause)
    • IDOR prevention: respond with NOT_FOUND (never FORBIDDEN)
  2. RBAC with Redis-Cached Permissions

    • Permissions matrix in src/lib/permissions/matrix.ts (source of truth)
    • Loaded from Redis on every request (TTL: 5min)
    • DENY-by-default: all features blocked until explicitly granted
    • Stack guards: requirePermission("invoice:create") on top of staffProcedure
  3. Type Safety & Strictness

    • TypeScript strict mode enabled (noUncheckedIndexedAccess: true)
    • All type imports: import type { Foo } or import { type Foo }
    • No any without justification (use unknown + type guards)
    • Zod for runtime validation of tRPC inputs
  4. Prisma Client in generated/prisma/ (not node_modules)

    • Avoids bloated node_modules
    • Faster CI/CD
    • Explicit output directory in schema.prisma
  5. Tailwind v4 + shadcn/ui (Radix Primitives)

    • Semantic CSS variables (dark mode built-in)
    • Never hardcode colors
    • Class merging via cn() from utils.ts
  6. Freemium Model with Usage Tracking

    • Limits per plan in src/lib/freemium/freemium-limits.ts
    • Check before action: await checkFreemiumLimit(ctx, "feature")
    • Increment after success (not before)
    • Monthly reset via cron job

Security Essentials

  • Webhooks: Verify HMAC with crypto.timingSafeEqual (never ===)
  • Error Messages: Hide IDOR leaks with NOT_FOUND (not FORBIDDEN)
  • Session: httpOnly cookies, JWT signed, short expiry with refresh token
  • Middleware: Rate limit + session check on every request
  • Permissions: DENY-by-default, no wildcard grants
  • Audit Log: Every create/update/delete logged with user + timestamp + changes

Development Commands

pnpm dev              # Next.js dev server (Turbopack)
pnpm build            # Prod build
pnpm start            # Start prod server
 
# Code quality
pnpm lint             # ESLint check
pnpm lint:fix         # ESLint auto-fix
pnpm format:write     # Prettier format
pnpm typecheck        # tsc --noEmit
 
# Testing
pnpm test             # Vitest (unit tests)
pnpm test:security    # Permission/RBAC tests
pnpm test:watch       # Watch mode
 
# Database
npx prisma generate   # Regenerate Prisma client (→ generated/prisma/)
npx prisma migrate dev --name <name>  # Create + run migration
npx prisma studio    # Visual DB browser

Monitoring & Observability

  • Error Tracking: Sentry integration (env: SENTRY_DSN)
  • Analytics: Segment / Mixpanel custom events
  • Webhooks Delivery: Svix or Hookdeck (retry + logging)
  • Performance: Vercel Analytics (Web Vitals)
  • Logs: JSON structured logs (Winston / Pino)

Deployment

  • Primary: Vercel (Next.js native)
  • Database: Supabase PostgreSQL (auto-backups, point-in-time restore)
  • Cache: Upstash Redis (serverless)
  • Email: Resend (transactional)
  • Storage: Supabase Storage or S3 (documents, invoices)
  • Webhooks: Stripe, Bridge, custom integrations

All secrets stored in .env.local (never committed). Vercel Environment variables for production.


Next Steps

  1. Read /claude/rules/ for domain-specific conventions (Freemium, Security, Testing, Git)
  2. Run pnpm install && pnpm dev to start local development
  3. Review src/server/api/root.ts to understand router structure
  4. Check src/lib/permissions/matrix.ts for your role's capabilities
  5. Use pnpm test:security to verify RBAC enforcement