Skip to content

Latest commit

 

History

History
617 lines (520 loc) · 16.3 KB

File metadata and controls

617 lines (520 loc) · 16.3 KB

Architecture Overview

System design, data model, and data flow for Tavola Romagna.


Table of Contents


High-Level Architecture

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
Loading

Application Layers

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
Loading

Domain Model

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
Loading

Request Lifecycle

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
Loading

Payment Flow (Stripe Connect)

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
Loading

Key details:

  • Platform fee: 15% on every transaction
  • Currency: EUR only
  • Mock fallback: When STRIPE_SECRET_KEY is sk_test_mock or missing, all Stripe calls return mock data — enabling full local development without a Stripe account
  • Webhook verification: HMAC-SHA256 signature validation in production

Recommendation Engine

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
Loading

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)

Dynamic Pricing Pipeline

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
Loading

Rule evaluation order:

  1. Expiry markdown (automatic fallback: ≤1 day=40%, ≤2=20%, ≤3=10%)
  2. Time-of-day adjustments
  3. Bulk discounts (highest matching threshold)
  4. 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.


Authentication & Session Management

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
Loading

Notification Architecture

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
Loading

Deployment Architecture

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
Loading

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

Data Model Summary

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

Key Design Decisions

  1. Prisma + libSQL adapter — Uses Turso/libSQL in production for edge-compatible SQLite. Allows zero-config local dev with a simple file-based SQLite database.

  2. 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.

  3. In-memory rate limiter with Redis upgrade path — The rate limiter works out-of-the-box with an in-memory store. When UPSTASH_REDIS_URL is configured, it transparently upgrades to a Redis sliding-window implementation.

  4. 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.

  5. 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.

  6. 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.