Salta al contenuto principale
Web App / Architettura SaaS · Sotto NDA

Marketplace SaaS multi-ruolo RBAC area-based, audit doppio, commissioni, multi-lingua. Tre anni di sviluppo continuo.

Una piattaforma multi-tenant in produzione per un settore regolamentato e high-riskin Svizzera. NDA attivo: il cliente e il verticale restano riservati. L'architettura sotto descritta è invece pubblica e riusabile su qualunque marketplace verticale a quattro ruoli — immobiliare, tutoring, artigiani, classified.

56

Modelli Prisma

3 anni

Sviluppo continuo

1.940

Ore tracciate

4

Ruoli con RBAC

4

Tier abbonamento

5

Lingue native

Il contesto: un verticale high-risk e poco competente

Il cliente operava in un settore regolamentato in Svizzera dove la concorrenza digitale era ferma a un'altra epoca: portali datati in PHP, pagamenti via bonifico, zero verifiche identità, UX da forum del 2010. Una nicchia con ricavi importanti e qualità tecnica fra i più bassi del mercato europeo.

La richiesta era chiara: costruire da zero un marketplace SaaS multi-tenant con qualità da prodotto consumer moderno — verifica identità seria, abbonamenti ricorrenti, sistema commissioni per agenti di vendita, contenuti monetizzabili, multi-lingua nativa, audit completo per le richieste regolatorie. Tutto in conformità nLPD svizzera, con dati e backup esclusivamente in Svizzera.

“Tre anni di lavoro continuo. 56 modelli Prisma. Quattro ruoli, tre livelli di admin, due tabelle di audit. Quello che doveva essere un MVP da sei mesi è diventato un sistema operativo per un'intera categoria di business.”

Note tecniche dal post-mortem interno

Quattro ruoli, un solo schema

Il cuore del sistema è un modello Usersingolo che assume comportamenti diversi in base al ruolo. Niente tabelle separate per “professionista” e “cliente”: stessa identità, capability diverse. Questo rende possibile cambiare ruolo (un utente che diventa professionista) senza migrazioni dolorose.

Utente finale

Browse, ricerca filtrata, messaggistica, recensioni, preferiti. Permessi: lettura globale, scrittura sulle proprie risorse.

Professionista

Dashboard editoriale completa: media gallery, storie a tempo, abbonamento attivo, statistiche viste, gestione conversazioni.

Club / multi-account

Profilo contenitore con N profili affiliati. Gestione fatture cumulative, dashboard aggregata, permessi delegati.

Admin (3 livelli)

Super-admin, moderatore, finance. Permessi granulari per area: ban, refund, edit profili, audit completo. Ogni azione tracciata.

Diagramma 01 — Schema dati semplificato (estratto da 56 modelli)

Userid · email · roleverifiedAt · localeProfiletier · area · status⤷ Photo · Video⤷ Story · Service⤷ CharacteristicSubscriptiontier · period⤷ Payment⤷ CreditBalanceCommissionsalesAgentId · rate⤷ Payout (batch)AuditLogUserActivityLog+ AdminAuditLog+ 51 modelli ausiliari (Conversation, Message, Report, Notification, Banner, EmailTemplate…)

Lo schema completo conta 56 modelli. Il diagramma sopra mostra i quattro pilastri: User al centro, Profile come aggregato di contenuti monetizzabili, Subscription / Commission per il flusso economico, AuditLog per la governance.

RBAC area-based, non page-based

La maggior parte degli RBAC che vediamo in giro è page-based: un middleware controlla chi può accedere a una rotta. Funziona finché l'applicazione è semplice. Quando i ruoli iniziano a sovrapporsi — un admin moderatore può modificare contenuti ma non rimborsare, un club può gestire i propri profili ma non quelli altrui — il page-based diventa un albero di if annidati.

La nostra scelta: RBAC area-based. Una matrice esplicita di (ruolo, area, livello). Le aree sono concettuali (Profilo, Contenuti, Abbonamenti, Moderazione, Sistema), i livelli sono tre (lettura, scrittura sulle proprie risorse, scrittura globale). Ogni endpoint dichiara di che area-livello ha bisogno; il middleware fa il resto.

Il risultato: aggiungere un nuovo ruolo costa 1 riga in matrice, non un refactor. E gli audit di compliance hanno una mappa leggibile in due minuti.

Diagramma 02 — RBAC area-based (4 ruoli × 5 aree)

ProfiloContenutiAbbonamentiModerazioneSistemaUtenteRRRProfessionistaW (own)W (own)W (own)ClubW (own)W (own)W (own)RAdminW (all)W (all)W (all)W (all)W (all)R · letturaW (own) · scrittura sulle proprie risorseW (all) · scrittura globale

La matrice reale ha 12 ruoli (4 utente-side + 3 admin × 3 livelli + 2 sales agent) ed è derivata da un singolo file TypeScript con type-safety end-to-end. Il diagramma mostra una versione compressa.

Audit log doppio: utenti e admin non condividono la stessa tabella

Errore comune: una singola tabella audit_logdove finisce qualunque azione, di qualunque attore. Sembra ordinato finché non arriva il primo subpoena con cui devi distinguere “cosa ha fatto questo utente” da “cosa abbiamo fatto noi sull'utente”. A quel punto la tabella unica diventa un incubo di filtri, indici e false positive.

La nostra scelta è stata netta: due tabelle, due pipeline.UserActivityLog ha retention 90 giorni, schema leggero, ottimizzata per analytics. AdminAuditLog ha retention forever, cattura il diff JSON before/after di ogni mutazione, è scritta da un wrapper obbligatorio che falla la build se manca.

Diagramma 03 — Audit log doppio (utente vs admin)

User actionlogin · purchase · postMiddlewareredact PII · IP hashUserActivityLogretention 90gg · immutabileAdmin actionban · refund · editWrapper RBACcapture diff · target IDAdminAuditLogbefore/after JSON · for-everTabelle separate: storage diverso, retention diversa, query plan diverso. Le compliance review le ringraziano.

Il wrapper RBAC è non-bypassabile: tutti gli endpoint admin passano da una factory che cattura automaticamente il diff e scrive su AdminAuditLog prima di committare la transazione. Atomicità garantita.

Sistema commissioni e sales agent

Il marketplace ha un canale di acquisizione tramite sales agent geografici: persone reali che firmano clienti su una zona e ricevono una percentuale ricorrente per tutta la durata dell'abbonamento. Sembra semplice. Non lo è quando metti insieme retry su pagamenti falliti, refund parziali, change tier mid-cycle, e una commissione che varia in base al tier acquistato.

La regola d'oro che ci siamo dati: una commissione esiste se e solo se esiste un payment confermato. Niente record speculativi. Le commissioni vengono calcolate al webhook Stripe/TWINT, non in fase di subscription create. Idempotenza sul payment ID: ogni evento processa al massimo una volta, anche su replay.

Il batch payout mensile genera CSV pronto per il bonifico bancario, con tracciamento degli importi pagati. Zero double-spending: una volta marcata paidOut, la commissione non rientra in batch successivi.

Diagramma 04 — Flusso commissioni & sales agent

PaymentStripe · TWINTSubscriptiontier 1-4 attivataMatch agentreferralCode · areaCommissionrecord creatoBatch payoutcron mensile · CSVIdempotenza sulle commissioni: ogni payment processa al massimo una volta, anche su retry.

Verifica identità: il livello di trust che il settore non aveva

Verifica identità in tre passi: documento d'identità, foto vivente con un codice univoco generato server-side al momento dell'upload (anti-foto-recycled), revisione manuale entro 24-48 ore da un team interno di moderazione. Niente provider KYC esterni: i dati restano in Svizzera, l'onboarding costa zero per transazione e il team può escalare senza dipendere da un'API di terze parti.

Il badge “verificato” è una proprietà del profilo, non un campo testo. Compare in tutta la UI come segnale di trust ed è il prerequisito per accedere ai tier più alti. Quando un documento scade, il sistema flagga automaticamente il profilo per re-verifica.

Sicurezza, privacy e compliance svizzera

In un settore regolamentato non ci sono opzioni: o hai compliance documentabile, o non sei un fornitore serio. Quattro pilastri non negoziabili.

Dati in Svizzera (nLPD)

Hosting e DB su datacenter CH. Per il settore di applicazione la residenza dati non è preferenza, è obbligo.

KYC con revisione manuale

Documento ID + foto vivente con codice univoco generato server-side. Approvazione 24-48h da un team interno, non automatica.

Moderazione attiva + DMCA

Coda di segnalazioni con priorità, takedown notice in 24h. Watermark non rimovibile sui media caricati.

PCI-DSS by proxy

Zero card data sui nostri server. Stripe e TWINT gestiscono il PCI scope. Webhook firmati e replay-protected.

Cinque lingue native, non plug-in

La Svizzera è quattro lingue ufficiali più l'inglese di servizio. Un Weglot o un traduttore automatico non bastano: i contenuti monetizzabili, le email transazionali, i template legali e l'onboarding del professionista devono essere localizzati frase per frase. Abbiamo usatonext-intl con bundle separati per locale e routing dedicato (/fr/...,/de/...).

Anche i campi user-generated supportano traduzioni multiple sullo stesso record (es. una bio in IT + DE + FR), con fallback intelligente sulla lingua preferita dell'utente che legge.

Cosa abbiamo imparato

Tre anni su un singolo prodotto cambiano il modo in cui pensi alle architetture. La lezione più importante non è tecnica, è strategica: i settori high-risk sono sotto-serviti dal punto di vista tecnico. Adult tech, gambling, classified, exchange crypto — sono mercati pieni di ricavi e vuoti di sviluppatori seri, perché molti studi “perbenisti” ci girano alla larga.

Tecnicamente, le scelte che hanno retto bene tre anni di evoluzione sono tre: schema Prisma normalizzato (no shortcut su tabelle “event blob”), RBAC esplicito anziché if annidati, audit log separato per attore. Quelle che invece abbiamo dovuto rifare almeno una volta: la pipeline upload media (passata da S3 diretto a Cloudflare R2 con CDN edge) e la gestione storie/contenuti effimeri (initially in DB, oggi su storage object con scadenza nativa).

Sul piano del business: una piattaforma multi-ruolo con verifica identità, abbonamenti ricorrenti e sistema commissioni è uno schema riusabile. Lo stesso scheletro può servire un marketplace immobiliare, una rete di tutor, una directory di artigiani, un classified verticale. Il valore non è il dominio: è la capacità di reggere quattro tipi di utenti, tre livelli di permessi e un flusso pagamenti senza buchi.

Stai costruendo un marketplace multi-ruolo?

Tre anni di esperienza concreta su uno stesso schema architetturale. RBAC area-based, audit doppio, pipeline pagamenti, KYC interno, multi-lingua. Lo schema è trasferibile a qualunque verticale — immobiliare, tutoring, artigiani, classified. Possiamo discutere la tua piattaforma senza vincoli, sotto NDA.

Parliamone sotto NDA

Dettagli Progetto

Cliente

Riservato (NDA)

Periodo

3 anni · in corso

Categoria

Web App · SaaS Multi-tenant

Verticale

Settore regolamentato CH

Mercato

Svizzera (5 lingue)

Volume

1.940 ore tracciate

Ruoli gestiti

Utente / Pro / Club / Admin

Tecnologie

Next.js 14TypeScriptPrismaPostgreSQLNextAuthStripeTWINTCloudflare R2WebSocketTailwind CSSnext-intlTiptapSharpPlaywright

Cosa abbiamo costruito

Architettura SaaS multi-tenantRBAC area-basedAudit log doppioKYC internoPipeline pagamenti ricorrentiSistema commissioniMulti-lingua nativoCompliance nLPD / DMCA

Lo Stack Tecnologico

Stack scelto con un solo criterio: ogni componente deve reggere tre anni di evoluzione senza essere riscritto.

Next.js 14

App Router, SSR, ISR

Prisma + PostgreSQL

56 modelli, migration-driven

NextAuth + RBAC

Sessions JWT, area-based

Stripe + TWINT

Pagamenti ricorrenti, webhook

Cloudflare R2 / S3

Media storage, presigned URL

next-intl

5 lingue native, locale routing

WebSocket

Messaggistica real-time

Sharp + AVIF/WebP

Resize on-the-fly

Tiptap

Editor moderazione e blog

Playwright + Vitest

E2E + unit, CI bloccante

Torna al

Portfolio

Creiamo Qualcosa di Grande Insieme!

Hai un progetto in mente? Parliamone. La prima consulenza è gratuita.