System design, data model, and data flow for Tavola Romagna.
- High-Level Architecture
- Application Layers
- Domain Model
- Request Lifecycle
- Payment Flow (Stripe Connect)
- Recommendation Engine
- Dynamic Pricing Pipeline
- Authentication & Session Management
- Notification Architecture
- Deployment Architecture
- Data Model Summary
graph TB
subgraph Client
Browser[Browser / PWA]
Mobile[Mobile Browser]
end
subgraph "Vercel Edge (fra1)"
Next[Next.js 16<br/>App Router]
API[API Routes<br/>~60 endpoints]
SSR[Server Components<br/>React 19]
Cron[Vercel Cron<br/>4 scheduled jobs]
end
subgraph "Data Layer"
DB[(SQLite / Turso<br/>libSQL)]
Redis[(Upstash Redis<br/>Rate Limiting)]
Blob[Vercel Blob<br/>Image Storage]
end
subgraph "External Services"
Stripe[Stripe Connect<br/>Payments]
Resend[Resend<br/>Transactional Email]
OpenAI[OpenAI<br/>Recipe/Menu AI]
Sentry[Sentry<br/>Error Tracking]
WhatsApp[WhatsApp<br/>Business API]
end
Browser --> Next
Mobile --> Next
Next --> API
Next --> SSR
API --> DB
API --> Redis
API --> Blob
API --> Stripe
API --> Resend
API --> OpenAI
API --> Sentry
API --> WhatsApp
Cron --> API
graph LR
subgraph "Presentation"
Pages[App Router Pages<br/>50+ routes]
Components[React Components<br/>30+ shared]
end
subgraph "API Layer"
Routes[Route Handlers<br/>GET/POST/PATCH/DELETE]
Middleware[Auth · Rate Limit<br/>Validation · CORS]
end
subgraph "Business Logic (src/lib/)"
Auth[auth.ts]
Pricing[pricing-engine.ts]
Recs[recommendations.ts]
Stripe_Lib[stripe.ts]
Recipes[recipe-engine.ts]
Analytics_Lib[analytics.ts]
Community_Lib[community.ts]
Operations[operations.ts]
GDPR[gdpr.ts]
I18N[i18n.ts]
end
subgraph "Data Access"
Prisma[Prisma Client<br/>78 models]
InMem[In-Memory Store<br/>dev/seed data]
end
subgraph "Infrastructure"
Logger[Structured Logger]
Config[Config Manager]
Security[Security Headers<br/>CSRF · Sanitization]
RateLimit[Rate Limiter<br/>Redis + Memory]
Email[Email Service]
end
Pages --> Routes
Components --> Pages
Routes --> Middleware
Middleware --> Auth
Middleware --> Pricing
Middleware --> Recs
Middleware --> Stripe_Lib
Middleware --> Recipes
Middleware --> Analytics_Lib
Auth --> Prisma
Pricing --> Prisma
Recs --> Prisma
Operations --> Prisma
Middleware --> Logger
Middleware --> RateLimit
RateLimit --> Config
The 78 Prisma models are organized into these bounded contexts:
graph TB
subgraph "👤 Identity & Auth"
User
Session
OAuthAccount
UserAddress
UserMfaSecret
PasswordResetToken
ConsentRecord
DataExportRequest
AccountDeletionRequest
NotificationPreference
UserDietaryProfile
end
subgraph "🏭 Producers & Supply"
Producer
ProducerApplication
CertificationRecord
Cooperative
CooperativeMembership
end
subgraph "📦 Products & Catalog"
Product
SeasonalAvailability
SurplusListing
Image
end
subgraph "🛒 Commerce"
CartItem
Order
OrderItem
OrderGroup
SplitOrder
SplitOrderItem
Payment
Coupon
CouponRedemption
LoyaltyAccount
LoyaltyTransaction
ShipHomeOrder
end
subgraph "📊 Pricing & Analytics"
PricingRule
PriceAlert
ProductCategoryBenchmark
ProductSimilarity
ProductLifecycle
ProducerDailySales
CustomerCohort
ProducerReport
ProducerPayout
PlatformMetric
end
subgraph "🎁 Engagement"
SubscriptionPlan
Subscription
BoxComposition
BoxExperiment
GiftBox
GiftBoxItem
GiftOrder
Event
EventBooking
AdoptionProgram
Adoption
Recipe
WeeklyMenu
end
subgraph "💬 Community"
Review
ReviewResponse
ForumPost
ForumComment
Notification
StockWatch
SupportTicket
SupportMessage
end
subgraph "🚚 Logistics"
DeliveryZone
DeliveryTimeSlot
DeliveryDriver
DeliveryAssignment
end
subgraph "🔗 External"
SupplyChainEvent
WhatsAppMessage
NewsletterSubscriber
BlogPost
TourismItinerary
HospitalityPartner
end
subgraph "⚙️ System"
HealthCheck
BackgroundJob
JobSchedule
end
User --> Producer
User --> Session
User --> Order
User --> CartItem
User --> Subscription
Producer --> Product
Product --> OrderItem
Product --> SurplusListing
Order --> OrderItem
Order --> Payment
SubscriptionPlan --> Subscription
Subscription --> BoxComposition
GiftBox --> GiftOrder
Event --> EventBooking
AdoptionProgram --> Adoption
DeliveryZone --> DeliveryTimeSlot
Review --> ReviewResponse
ForumPost --> ForumComment
sequenceDiagram
participant C as Client
participant R as Route Handler
participant RL as Rate Limiter
participant A as Auth
participant V as Validation
participant BL as Business Logic
participant DB as Prisma / DB
participant S as Stripe / Email
C->>R: HTTP Request
R->>RL: Check rate limit (IP/user)
alt Rate limited
RL-->>C: 429 Too Many Requests
end
R->>A: getCurrentUser()
alt Not authenticated
A-->>C: 401 Unauthorized
end
R->>V: Validate & sanitize input
alt Validation error
V-->>C: 400 Bad Request
end
R->>BL: Execute business logic
BL->>DB: Query / Mutate
DB-->>BL: Result
opt External service needed
BL->>S: Stripe / Resend / etc.
S-->>BL: Response
end
BL-->>R: Result
R-->>C: JSON Response + Security Headers
Tavola Romagna uses Stripe Connect with Standard accounts to split payments between the platform and producers.
sequenceDiagram
participant Customer
participant App as Tavola Romagna
participant Stripe
participant Producer as Producer's Stripe
Note over App: Producer Onboarding
App->>Stripe: createConnectAccount(email)
Stripe-->>App: Account ID + Onboarding URL
App->>Producer: Redirect to Stripe onboarding
Note over Customer: Checkout Flow
Customer->>App: POST /api/checkout
App->>App: Calculate cart total
App->>App: Apply coupons/loyalty
App->>Stripe: createCheckoutSession({<br/> amount, producerAccountId,<br/> applicationFee: 15%<br/>})
Stripe-->>App: Session ID + Checkout URL
App-->>Customer: Redirect to Stripe Checkout
Customer->>Stripe: Complete payment
Stripe->>Stripe: Split: 85% → Producer, 15% → Platform
Stripe->>App: Webhook: checkout.session.completed
App->>App: Update order status
App->>Customer: Order confirmation email
Note over App: Payouts
App->>Stripe: createProducerTransfer(amount, accountId)
Stripe->>Producer: Transfer funds
Key details:
- Platform fee: 15% on every transaction
- Currency: EUR only
- Mock fallback: When
STRIPE_SECRET_KEYissk_test_mockor missing, all Stripe calls return mock data — enabling full local development without a Stripe account - Webhook verification: HMAC-SHA256 signature validation in production
graph TB
subgraph "Input Signals"
History[Purchase History<br/>with recency decay]
Context[Context<br/>time · season · locale]
Product[Current Product<br/>category · price · origin]
end
subgraph "Recommendation Strategies"
CF[Collaborative Filtering<br/>"Also Bought"]
CB[Content-Based<br/>Similar Products]
CTX[Contextual<br/>Time + Season]
PER[Personalized<br/>Category Preferences]
TR[Trending<br/>Last 7 days]
REC[Recipe-Based<br/>Ingredient Matching]
end
subgraph "Output"
Results[Ranked Results<br/>score + reason + reasonText]
end
History --> PER
History --> CF
Context --> CTX
Product --> CF
Product --> CB
Product --> REC
CF --> Results
CB --> Results
CTX --> Results
PER --> Results
TR --> Results
REC --> Results
Scoring components:
- Collaborative filtering — co-purchase frequency among shared buyers
- Content-based — category match (0.4), organic match (0.15), origin match (0.15), price proximity (0.15), same producer (0.1)
- Personalized — category preference (0.4) + rating (0.3) + popularity (0.2) + featured (0.1), with exponential recency decay (λ=0.95)
- Cold start — falls back to trending products (7-day window)
graph LR
subgraph "Pricing Context"
Q[Quantity]
E[Days to Expiry]
T[Time of Day]
D[Delivery Slot<br/>Demand Ratio]
R[Region]
end
subgraph "Rule Matching"
Rules[(PricingRule table<br/>5 rule types)]
Match{Match conditions<br/>priority-ordered}
end
subgraph "Rule Types"
EM[Expiry Markdown<br/>10-40% off]
BD[Bulk Discount<br/>quantity thresholds]
SU[Surge Pricing<br/>demand ratio]
TOD[Time-of-Day<br/>hour windows]
DS[Delivery Slot<br/>capacity-based]
end
subgraph "Actions"
PCT[Percentage<br/>discount/surcharge]
FIX[Fixed Amount<br/>subtract value]
MUL[Multiplier<br/>scale factor]
SET[Set Price<br/>override]
end
subgraph "Output"
Result[PricingEvaluationResult<br/>original · dynamic · savings<br/>appliedRules array]
end
Q --> Match
E --> Match
T --> Match
D --> Match
R --> Match
Rules --> Match
Match --> EM
Match --> BD
Match --> SU
Match --> TOD
Match --> DS
EM --> PCT
BD --> PCT
SU --> MUL
TOD --> PCT
DS --> SET
PCT --> Result
FIX --> Result
MUL --> Result
SET --> Result
Rule evaluation order:
- Expiry markdown (automatic fallback: ≤1 day=40%, ≤2=20%, ≤3=10%)
- Time-of-day adjustments
- Bulk discounts (highest matching threshold)
- Delivery slot / surge pricing (demand ratio thresholds: ≥0.9→1.2×, ≥0.75→1.12×, ≥0.5→1.05×)
Rules cascade — each rule's output becomes the next rule's input price.
graph TB
subgraph "Login Methods"
Email[Email + Password<br/>SHA-256 + salt]
Google[Google OAuth]
Apple[Apple OAuth]
end
subgraph "Session Layer"
Cookie[HttpOnly Cookie<br/>session_token<br/>30-day TTL]
DB_Session[(Session table<br/>userId · token · expiresAt)]
end
subgraph "Authorization"
GetUser[getCurrentUser()<br/>Cookie → DB lookup]
Roles{Role Check}
Consumer[consumer]
ProducerRole[producer]
Admin[admin]
end
subgraph "Security"
MFA[MFA / TOTP]
RateLimit[Rate Limit<br/>5 attempts / 15 min]
CSRF[CSRF Token]
Headers[Security Headers<br/>CSP · HSTS · X-Frame]
end
Email --> Cookie
Google --> Cookie
Apple --> Cookie
Cookie --> DB_Session
DB_Session --> GetUser
GetUser --> Roles
Roles --> Consumer
Roles --> ProducerRole
Roles --> Admin
Email --> RateLimit
GetUser --> MFA
GetUser --> CSRF
GetUser --> Headers
graph LR
subgraph "Triggers"
OrderEvent[Order Events]
StockEvent[Stock Changes]
PromoEvent[Promotions]
SocialEvent[Community Activity]
end
subgraph "Channels"
InApp[In-App<br/>Notification model]
EmailCh[Email<br/>Resend API]
PushCh[Web Push<br/>VAPID / Service Worker]
WhatsAppCh[WhatsApp<br/>Business API]
SSE[SSE Stream<br/>/api/notifications/stream]
end
subgraph "User Preferences"
Prefs[(NotificationPreference<br/>per-type per-channel)]
end
OrderEvent --> Prefs
StockEvent --> Prefs
PromoEvent --> Prefs
SocialEvent --> Prefs
Prefs --> InApp
Prefs --> EmailCh
Prefs --> PushCh
Prefs --> WhatsAppCh
InApp --> SSE
graph TB
subgraph "Vercel Platform"
Edge[Edge Network<br/>CDN + Static Assets]
Serverless[Serverless Functions<br/>Frankfurt fra1]
CronRunner[Cron Runner<br/>4 scheduled jobs]
BlobStore[Vercel Blob<br/>Image Storage]
end
subgraph "Database"
Turso[Turso / libSQL<br/>Edge-ready SQLite]
end
subgraph "Third-Party"
StripeAPI[Stripe API]
ResendAPI[Resend API]
UpstashAPI[Upstash Redis]
SentryAPI[Sentry]
end
Edge --> Serverless
Serverless --> Turso
Serverless --> BlobStore
Serverless --> StripeAPI
Serverless --> ResendAPI
Serverless --> UpstashAPI
Serverless --> SentryAPI
CronRunner --> Serverless
Cron schedule:
| Job | Schedule | Endpoint |
|---|---|---|
| Analytics computation | 0 2 * * * (daily 2am) |
/api/cron/analytics |
| Pricing/expiry scan | 0 */6 * * * (every 6h) |
/api/cron/pricing-scan |
| Surplus discovery | 0 8 * * * (daily 8am) |
/api/cron/surplus-scan |
| Notification digest | 0 9 * * 1 (Monday 9am) |
/api/cron/notification-digest |
78 models across 10 bounded contexts:
| Context | Models | Key Entities |
|---|---|---|
| Identity & Auth | 11 | User, Session, OAuthAccount, MFA, GDPR consent/export/deletion |
| Producers | 5 | Producer, Application, Certification, Cooperative, Membership |
| Products & Catalog | 4 | Product, SeasonalAvailability, SurplusListing, Image |
| Commerce | 12 | Cart, Order, OrderGroup, SplitOrder, Payment, Coupon, Loyalty, ShipHome |
| Pricing & Analytics | 10 | PricingRule, PriceAlert, Benchmarks, DailySales, Cohorts, Payouts |
| Engagement | 12 | Subscription, BoxComposition, Experiment, GiftBox, Event, Adoption, Recipe, Menu |
| Community | 8 | Review, Forum, Notification, StockWatch, Support |
| Logistics | 4 | DeliveryZone, TimeSlot, Driver, Assignment |
| External Integrations | 6 | SupplyChainEvent, WhatsApp, Newsletter, Blog, Tourism, Hospitality |
| System | 3 | HealthCheck, BackgroundJob, JobSchedule |
-
Prisma + libSQL adapter — Uses Turso/libSQL in production for edge-compatible SQLite. Allows zero-config local dev with a simple file-based SQLite database.
-
Stripe mock fallback — Every Stripe function checks
config.stripe.connectEnabled. When keys aren't set, returns deterministic mock data. This enables full-stack local development without any external accounts. -
In-memory rate limiter with Redis upgrade path — The rate limiter works out-of-the-box with an in-memory store. When
UPSTASH_REDIS_URLis configured, it transparently upgrades to a Redis sliding-window implementation. -
Cascading pricing rules — Pricing rules are evaluated in sequence (expiry → time-of-day → bulk → delivery slot). Each rule receives the output of the previous rule as its input, allowing rules to compound.
-
Monolithic lib/ structure — All business logic lives in
src/lib/as flat modules rather than a layered service architecture. This keeps the codebase navigable for a small team while each module remains independently testable. -
Italian-first with i18n — Error messages, UI copy, and validation messages default to Italian. The i18n system provides full translations for EN, DE, FR with accept-language detection.