22 KiB
United Tattoo – Backend Architecture Document
Version: 1.0
Date: 2025-09-17
Output Template: .bmad-core/templates/architecture-tmpl.yaml (architecture-template-v2)
Basis: docs/PRD.md, repo config (wrangler.toml, open-next.config.ts, next.config.mjs, package.json), lib/*, sql/schema.sql
Introduction
This document outlines the backend architecture for United Tattoo, including platform/runtime, data, integrations, security, operations, and non‑UI concerns. It is the blueprint for AI-driven and human development to implement the PRD.
Relationship to Frontend Architecture: A separate Frontend Architecture document should cover UI state, routing, component patterns, and UX specifics. Core technology selections herein (Cloudflare, Next.js App Router, D1/R2, Auth.js, Zod) apply project-wide.
Starter Template or Existing Project
Decision: Existing project (this repository) on Next.js App Router with OpenNext for Cloudflare.
- Pre-configured stack: Next 14.2.16, Tailwind, ShadCN, OpenNext Cloudflare adapter, Wrangler, Vitest.
- Structure: app/ routes, components/, lib/, sql/, etc.
- Built-in patterns: SSR with App Router, route handlers under app/api, middleware guards, Zod validations, D1/R2 bindings.
- Constraints: Cloudflare Workers runtime; D1 (SQLite semantics), R2 for media and ISR cache. Images unoptimized (Next images disabled by config). Change Log
| Date | Version | Description | Author |
|---|---|---|---|
| 2025-09-17 | 1.0 | Initial backend architecture document | Architect |
High Level Architecture
Technical Summary
United Tattoo runs as a serverless, modular monolith on Cloudflare Pages + Workers using the OpenNext adapter. Next.js App Router handles SSR/ISR and routing; Cloudflare D1 stores structured data (users, artists, appointments, settings) and R2 stores media plus incremental cache. Back-end concerns (auth, RBAC, validations, uploads, booking, payments, notifications, calendar sync) are implemented via Next.js route handlers and server actions with strict Zod validation and middleware-based RBAC. The architecture prioritizes image-forward delivery performance, reliability, and maintainability aligned to the PRD.
High Level Overview
- Style: Serverless modular monolith (single app, bounded modules by domain).
- Repo: Single repository (this project).
- Services: Single deployable (OpenNext worker) with internal modules (auth, artists, portfolio, booking, payments, notifications, calendar).
- Primary flows:
- Public SSR pages, search/discovery (later), booking/consultation routing, client portal (later).
- Admin onboarding, artist/portfolio CRUD, uploads to R2, moderation queue (later), activity logs (later).
- Payments via Stripe for deposits; later add PayPal.
- Two-way Google Calendar sync for artists (planned now).
- Key decisions:
- Cloudflare-first stack (D1, R2, Pages/Workers) with OpenNext.
- REST via route handlers + Server Actions for same-origin mutations.
- Zod across edges; strict RBAC via middleware.
- Sentry + OpenTelemetry for observability; Upstash Redis for rate limiting.
High Level Project Diagram
graph TD
subgraph Users
V[Visitor]
C[Client]
A[Admin/Artist]
end
V -->|HTTP(S)| CDN[Cloudflare CDN]
C -->|HTTP(S)| CDN
A -->|HTTP(S)| CDN
CDN --> W[OpenNext Worker (Next.js App)]
W --> D1[(Cloudflare D1)]
W --> R2[(Cloudflare R2 - Media)]
W --> R2INC[(R2 - ISR/Incremental Cache)]
W --> STRIPE[Stripe API]
W --> RESEND[Resend Email]
W --> TWILIO[Twilio SMS]
W --> GCAL[Google Calendar API]
style W fill:#222,stroke:#999,color:#fff
style D1 fill:#114,stroke:#99f,color:#fff
style R2 fill:#141,stroke:#9f9,color:#fff
style R2INC fill:#141,stroke:#9f9,color:#fff,stroke-dasharray: 5 5
Architectural and Design Patterns
- Serverless Modular Monolith (chosen): Single worker with domain modules. Rationale: Simpler ops, Cloudflare-native, low latency, satisfies PRD scope; avoids premature microservices.
- Communication: RESTful route handlers + Server Actions. Alternative: GraphQL (deferred). Rationale: Simplicity, aligns with Next App Router, easy cache/policy control.
- Data Access: Thin repository-like helpers (lib/db.ts) with prepared SQL on D1. Alternative: Kysely/Prisma (N/A on Workers) → stay with direct SQL for D1. Rationale: Control, performance, D1 fit.
- Validation: Zod at boundaries (API/server actions/forms). Rationale: Single schema source; consistent erroring.
- AuthZ: Middleware-based RBAC on path prefixes + helper functions. Rationale: Centralized enforcement.
- Caching: R2-based ISR via OpenNext override; CDN caching for assets/API where applicable. Rationale: Cost/perf balance.
- Rate limiting: Redis (Upstash). Alternative: KV tokens. Rationale: Simplicity and global availability.
Tech Stack
Cloud Infrastructure
- Provider: Cloudflare
- Key Services: Pages + Workers (OpenNext), D1 (SQL), R2 (object storage + ISR cache)
- Regions: Global edge (Cloudflare network), D1 region as configured by account
Technology Stack Table
| Category | Technology | Version | Purpose | Rationale |
|---|---|---|---|---|
| Language | TypeScript | 5.x (lockfile-resolved) | Primary backend language | Strong typing, tooling, Next.js alignment |
| Framework | Next.js (App Router) | 14.2.16 | SSR/ISR, routing, APIs, server actions | Matches UI, supports OpenNext |
| Platform | OpenNext (Cloudflare) | ^1.8.2 (lockfile pin) | Next->Workers build, ISR in R2 | Official adapter, ISR override support |
| Runtime | Cloudflare Workers | nodejs_compat enabled | Serverless execution | Edge-native, low latency |
| Config/Deploy | Wrangler | ^4.37.1 (pin in lock) | Build/preview/deploy worker + bindings | Cloudflare-native tooling |
| DB | Cloudflare D1 | Managed | Relational store for core data | Simple, sufficient for scope, low-ops |
| Storage | Cloudflare R2 | Managed | Media assets; ISR cache bucket | Cost-effective, global access |
| Auth | NextAuth (Auth.js) | ^4.24.11 (pin in lock) | JWT sessions + providers | Standard pattern with Next |
| Validation | Zod | 3.25.67 | Input/env validation | Ubiquitous + types inference |
| Testing | Vitest + RTL | ^3.2.4 + latest RTL | Unit/component testing | Fast, TS-native |
| Observability | Sentry + OpenTelemetry | Latest stable | Errors, traces, metrics | Full visibility across Workers + Next |
| Rate Limiting | Upstash Redis | Managed | Rate limit auth/forms/APIs | Simplicity, global |
| Payments | Stripe (primary); PayPal later | Latest SDK | Deposits, refunds | Start with Stripe per customization |
| Resend | Latest SDK | Transactional emails | Simple, modern API | |
| SMS | Twilio | Latest SDK | Notifications (reminders, etc.) | Reliable, well-documented |
| Calendar | Google Calendar API | v3 | Two-way artist sync | Per customization → implement now |
Note: Exact semver pins should be recorded from the lockfile in CI. Next 14.2.16 and zod 3.25.67 are exact; others are resolved via lock.
Data Models
Core entities mapped from PRD and schema:
- User: id, email, name, role (CLIENT|ARTIST|SHOP_ADMIN|SUPER_ADMIN), avatar.
- Artist: id, userId, name, bio, specialties[], instagramHandle, isActive, hourlyRate.
- PortfolioImage: id, artistId, url, caption, tags[], orderIndex, isPublic, createdAt.
- Appointment: id, artistId, clientId, title, description, startTime, endTime, status, depositAmount, totalAmount, notes.
- Availability: id, artistId, dayOfWeek, startTime, endTime, isActive.
- SiteSettings: id, studioName, description, address, phone, email, socialMedia{}, businessHours[], heroImage, logoUrl.
- FileUpload: id, filename, originalName, mimeType, size, url, uploadedBy.
Relationships:
- User 1‑to‑1/0‑1 Artist (user_id).
- Artist 1‑to‑many PortfolioImage.
- User (client) 1‑to‑many Appointment; Artist 1‑to‑many Appointment.
- Artist 1‑to‑many Availability.
Components
-
Auth & RBAC
- Responsibility: JWT sessions; role enforcement.
- Interfaces: /auth routes (NextAuth), middleware guards, helpers isAdmin/hasRole.
- Dependencies: NextAuth, middleware.ts.
- Tech: NextAuth with Credentials (dev), Google/GitHub optional.
-
Artist & Portfolio Service
- Responsibility: CRUD artists, portfolio images, ordering, visibility.
- Interfaces: app/api/artists/, app/api/portfolio/, server actions for admin UI.
- Dependencies: D1 tables, R2 uploads.
- Tech: lib/db.ts helpers; lib/r2-upload.ts.
-
Upload Service
- Responsibility: R2 uploads (single/bulk), file validation, delete.
- Interfaces: app/api/upload, admin UI dropzones.
- Tech: R2 bucket binding; R2_PUBLIC_URL used to compose public URLs.
-
Booking & Scheduling
- Responsibility: Booking/consultation routing, appointment CRUD, availability.
- Interfaces: app/api/appointments/*; server actions for stepper.
- Dependencies: D1, Notifications, Stripe.
- Tech: Zod forms; quote estimation (to be implemented).
-
Payments
- Responsibility: Deposit intents (Stripe), receipts, refunds; PayPal later.
- Interfaces: app/api/payments/* webhooks; server actions for checkout init.
- Dependencies: Stripe SDK, D1 to persist intents/receipts.
- Tech: Stripe Checkout/PaymentIntents; signed webhooks; idempotency keys.
-
Notifications
- Responsibility: Email (Resend) + SMS (Twilio), templates, preferences.
- Interfaces: internal functions invoked from flows; background execution pattern if needed.
- Dependencies: D1 preferences, external providers.
-
Calendar Integration
- Responsibility: Google Calendar two-way sync for artists (now).
- Interfaces: OAuth connect, webhook/push, polling fallback, reconciliation.
- Dependencies: Google API, D1 for tokens, Appointments.
Component Diagram
graph LR
AUTH[Auth & RBAC] --> API[Route Handlers / Server Actions]
ART[Artists/Portfolio] --> API
UP[Upload Service] --> API
BK[Booking/Scheduling] --> API
PAY[Payments (Stripe)] --> API
NOTIF[Notifications] --> API
CAL[Calendar Sync] --> API
API --> D1[(D1)]
API --> R2[(R2)]
PAY --> STRIPE[Stripe]
NOTIF --> RESEND[Resend]
NOTIF --> TWILIO[Twilio]
CAL --> GCAL[Google Calendar]
External APIs
-
Stripe API
- Purpose: Deposits, refunds; store payment intents + receipts.
- Auth: API keys (Wrangler secret); webhook signing secret.
- Rate limits: Stripe-managed; implement retries with backoff; idempotency keys.
- Endpoints: PaymentIntents, Checkout, Refunds, Webhooks.
-
Resend
- Purpose: Transactional emails (booking confirmations, receipts).
- Auth: API key (secret).
- Integration: templated emails, error handling.
-
Twilio
- Purpose: SMS notifications (confirmations/reminders).
- Auth: Account SID/Token (secret).
- Rate limits: provider-specific; implement basic backoff.
-
Google Calendar v3
- Purpose: Two-way artist sync (now).
- Auth: OAuth 2.0 per artist; token storage in D1; refresh handling.
- Endpoints: Events, Watch (push), Channels (stop), CalendarList.
Core Workflows
Booking with Deposit (sequence)
sequenceDiagram
participant U as User
participant W as Worker(App)
participant D as D1
participant S as Stripe
U->>W: Submit booking form (zod-validated)
W->>D: Create pending Appointment (PENDING)
W->>S: Create PaymentIntent (deposit) with idempotency
S-->>W: Client secret
W-->>U: Return client secret
U->>S: Confirm payment
S-->>W: Webhook event (payment_succeeded)
W->>D: Update Appointment status + store receipt reference
W-->>U: Confirmation (email/SMS)
Portfolio Upload (sequence)
sequenceDiagram
participant A as Admin/Artist
participant W as Worker(App)
participant R as R2
participant D as D1
A->>W: Upload image(s) (validated)
W->>R: Put object(s)
W->>D: Insert portfolio_images rows
W-->>A: URLs + metadata
REST API Spec (sketch)
openapi: 3.0.0 info: title: United Tattoo Backend API version: 1.0.0 description: REST endpoints backing booking, admin, and integrations servers:
- url: https://your-preview-domain.pages.dev description: Preview
- url: https://your-domain.com description: Production paths: /api/appointments: get: summary: List appointments post: summary: Create appointment (server action preferred) /api/portfolio: post: summary: Upload portfolio image (server action preferred) /api/payments/deposit-intent: post: summary: Create Stripe PaymentIntent for deposit /api/webhooks/stripe: post: summary: Stripe webhook receiver
Database Schema
- Source of truth: sql/schema.sql (D1). Entities and indexes already implemented.
- Note: Fix comment header mismatch (“united-tattoo-db” vs scripts using “united-tattoo”).
- Migrations: wrangler d1 execute … --file=./sql/schema.sql (local/prod variants).
Source Tree
project-root/
├─ app/ # App Router (public pages, admin area, api/)
│ ├─ api/ # Route handlers
│ ├─ admin/ # Admin UI sections
│ └─ ... # Site pages
├─ components/ # UI + composed components
├─ lib/
│ ├─ db.ts # D1 & R2 binding access + helpers
│ ├─ r2-upload.ts # R2 upload manager
│ ├─ auth.ts # NextAuth config + RBAC helpers
│ ├─ env.ts # Zod env validation (see alignment note)
│ └─ validations.ts # Zod schemas for domain + forms
├─ sql/
│ └─ schema.sql # D1 schema SSoT
├─ wrangler.toml # Worker config + bindings
├─ open-next.config.ts # ISR cache override to R2
├─ next.config.mjs # Next config (standalone, images unoptimized)
└─ package.json # Scripts (build/preview/deploy/db/test)
Infrastructure and Deployment
Infrastructure as Code
- Tool: Wrangler 4.x
- Location: wrangler.toml
- Approach: Wrangler-only “config of record” now; Terraform later if needed.
Deployment Strategy
- Strategy: OpenNext build → Cloudflare Pages (Worker + assets).
- CI/CD Platform: Gitea (planned) with required pipeline gates.
- Pipeline Config: .gitea/workflows/* (to be added) or equivalent CI config.
Environments
- dev: Local dev server (next dev) and/or Workers preview (OpenNext preview).
- preview: Cloudflare Pages preview (pull requests).
- production: Cloudflare Pages live.
Environment Promotion Flow
feature → PR → CF Pages preview → E2E/quality gates → merge → production deploy
Rollback Strategy
- Primary Method: Re-deploy previous artifact via CF Pages rollback / previous commit.
- Triggers: Elevated errors, failed SLOs, payment or booking failures.
- RTO: < 30 minutes for deploy rollback; data roll-forward with compensating ops.
Error Handling Strategy
General Approach
- Error Model: Domain errors (validation, auth, business) vs operational (transient, provider).
- Propagation: Translate to typed JSON errors at API boundary; avoid leaking internals.
Logging Standards
- Library: Console structured JSON (Workers) + Sentry/Otel exporters.
- Format: JSON with fields: timestamp, level, correlationId, route, userId (if any).
- Levels: debug/info/warn/error; No PII in logs.
Error Handling Patterns
- External API Errors:
- Retry: Exponential backoff (bounded), idempotency keys (Stripe).
- Circuit Breaker: Soft disable non-critical providers on repeated failures.
- Timeouts: Tight timeouts; fail fast, surface friendly errors.
- Business Logic Errors:
- Custom error types; map to 4xx with helpful messages; do not expose details.
- Data Consistency:
- D1 is transactional per statement; use idempotency on effects; write-ahead logic for webhooks.
Coding Standards (Backend)
Core Standards
- Languages & Runtimes: TypeScript (Workers-compatible), Next.js App Router.
- Style & Linting: ESLint + Prettier (enable in CI, even if build ignores failures).
- Tests: Vitest for unit; RTL for component; Playwright for e2e (planned).
Critical Rules
- Validate all external inputs with Zod at API boundary.
- Use lib/db.ts helpers; do not inline raw env binding plucking in routes.
- Payments: Always use idempotency keys for Stripe and verify signed webhooks.
- No secrets in logs; use Wrangler secrets for all provider keys.
- RBAC: All /admin and /api/admin routes must enforce SHOP_ADMIN or SUPER_ADMIN.
- Rate limiting must wrap auth/forms/API entrypoints once Upstash is configured.
Test Strategy and Standards
Testing Philosophy
- Approach: Pragmatic test-after with critical flows prioritized; expand coverage over time.
- Coverage Goals: Unit 60–70% rising; critical domain paths higher.
Test Types and Organization
- Unit: Vitest; files *.test.ts under tests/ or next to modules.
- Integration: D1 local (wrangler d1 execute) for repository tests; mock providers.
- E2E: Playwright on CF Pages preview (booking flow, uploads, admin critical).
Test Data Management
- Fixtures: tests/fixtures; factories for domain objects.
- Cleanup: Explicit clean after each suite (D1 truncate helpers for local env).
Continuous Testing
- CI Integration: Lint/typecheck → unit → build → migration dry-run → e2e on preview.
Security
Input Validation
- Library: Zod
- Location: Route handlers/server actions before processing
- Required Rules:
- Validate all inputs (query/body/params).
- Whitelist approach; reject extras.
Authentication & Authorization
- Auth Method: NextAuth JWT (Credentials for dev; OAuth optional).
- Session: JWT; role included in token; maxAge defaults; future 2FA.
- Required:
- Enforce RBAC in middleware + route-level checks for admin endpoints.
- Implement invites and 2FA per PRD in admin flows.
Secrets Management
- Development: Wrangler secrets; .env only for local dev of non-secrets.
- Production: Wrangler secrets; never commit secrets.
- Code Rules: Never log secrets; never embed keys.
API Security
- Rate Limiting: Upstash Redis (global) on auth/forms/APIs.
- CORS: Strict same-origin for server actions; minimal cross-origin where necessary.
- Security Headers: Enforce via centralized headers helper (CSP, X-Frame-Options DENY, Referrer-Policy strict-origin-when-cross-origin, Permissions-Policy).
Data Protection
- At Rest: Provider-managed (D1/R2). Encrypt sensitive app-level data if needed before storage.
- In Transit: HTTPS only.
- PII: Store minimal; redact logs.
Dependency Security
- Scanner: Enable dep audit in CI; weekly review cadence.
- New Deps: Require review; follow Context7 MCP check (per repo rules).
Security Testing
- SAST: ESLint/security rules; consider additional scanners.
- DAST: E2E includes basic auth + booking hardening; expand later.
Backend Customization Choices (Confirmed)
- Payments: Stripe first (PayPal later).
- Email: Resend.
- SMS: Twilio.
- Rate Limiting: Upstash Redis.
- Observability: Sentry + OpenTelemetry.
- Calendar Integration: Google two‑way sync now.
- API Style: Next.js Route Handlers (REST) + Server Actions.
- IaC/Config: Wrangler-only config of record (Terraform later).
- Env/Secrets: Align env.ts to Cloudflare bindings (remove unused AWS_* + DATABASE_URL now). Note: the current names in env.ts are labeled AWS_ but map to Cloudflare R2 usage—document and align.
- Security Headers: Centralized middleware/edge headers helper.
Next Steps
- Frontend Architecture Mode: Generate a separate frontend architecture doc leveraging this backend document and PRD UI requirements (ShadCN, search/filters, parallax/split).
- Product Owner Review: Validate scope and sequencing (Phases 1–4).
- Dev Agent: Implement stories for Phase 1 (Admin invites, onboarding wizard stub, artist CRUD/portfolio upload MVP, D1/R2 confirmations).
- DevOps: Add CI pipeline, lock exact versions from lockfile into doc, add .env.example listing required secrets (NEXTAUTH_URL, NEXTAUTH_SECRET, R2_PUBLIC_URL, STRIPE_KEYS, RESEND_API_KEY, TWILIO_KEYS, GOOGLE_OAUTH, UPSTASH).
Appendix
Bindings (wrangler.toml)
- DB (D1), R2_BUCKET (media), NEXT_INC_CACHE_R2_BUCKET (ISR cache), WORKER_SELF_REFERENCE (service binding).
Runbooks
- Preview: npm run pages:build && npm run preview
- Deploy: npm run pages:build && npm run deploy
- DB: npm run db:create; npm run db:migrate[:local]
References
- PRD: docs/PRD.md
- Schema: sql/schema.sql
- Config: wrangler.toml, open-next.config.ts, next.config.mjs