# United Tattoo — Brownfield Architecture Document (Focused: Epic D — Technical Architecture & Delivery) This document captures the CURRENT STATE of technical architecture, build/deploy flows, runtime environment (Cloudflare/OpenNext), authentication/middleware, testing, and configuration. It reflects real behavior, gaps, and constraints to guide engineering delivery. ## Document Scope Focused on: Cloudflare/OpenNext deployment model, D1/R2 bindings and access patterns, NextAuth/middleware security, build/test/CI setup, configuration (Tailwind/PostCSS/TS), Docker alternative runtime, and operational considerations. ### Change Log | Date | Version | Description | Author | | ---------- | ------- | ----------------------------------------------------- | ---------------- | | 2025-09-18 | 1.0 | Initial brownfield analysis (Tech Architecture) | Architect Agent | --- ## Runtime and Deployment ### OpenNext + Cloudflare Pages/Workers - Adapter: @opennextjs/cloudflare with R2 incremental cache - open-next.config.ts: - incrementalCache: r2IncrementalCache - wrangler.toml: - compatibility_date: "2024-09-23" - compatibility_flags: ["nodejs_compat"] - main: ".open-next/worker.js" - [assets] directory bound as ASSETS - D1 and R2 bindings: - [[d1_databases]] binding = "DB" (database_name = "united-tattoo", database_id provided) - [[r2_buckets]] binding = "R2_BUCKET" bucket_name = "united-tattoo" - [[r2_buckets]] binding = "NEXT_INC_CACHE_R2_BUCKET" bucket_name = "united-tattoo-inc-cache" - [[services]] self-reference (WORKER_SELF_REFERENCE) - Env vars: - [env.production.vars] NEXTAUTH_URL, NODE_ENV - [env.preview.vars] NEXTAUTH_URL, NODE_ENV Build/Preview/Deploy scripts (package.json): - pages:build → npx @opennextjs/cloudflare build - preview → npx @opennextjs/cloudflare preview - deploy → wrangler pages deploy .vercel/output/static - dev:wrangler → pages:build then OpenNext preview Notes: - next.config.mjs: output: "standalone", images: { unoptimized: true }, ignores TS/ESLint errors during build. - The OpenNext output directory used by deploy: .vercel/output/static (per script). - R2-based ISR/incremental cache configured via NEXT_INC_CACHE_R2_BUCKET. ### Docker (Node runtime alternative) - Dockerfile builds a Next.js standalone server (node:20-alpine): - Build stage runs `npm run build` - Runtime uses `.next/standalone` server.js with `.next/static` and `public/` - This path runs a Node server on port 3000, not Cloudflare Workers. - Considered an alternative for self-hosting; not used for Cloudflare Pages deployment. --- ## Data & Storage ### Cloudflare D1 Access - Access pattern encapsulated in lib/db.ts: - getDB(env?): Prefers env.DB, otherwise reads from OpenNext global symbol (Symbol.for("__cloudflare-context__")). Throws if unavailable. - CRUD helpers: - Artists: get/create/update/delete - Portfolio images: get/create/update/delete - Appointments: get/create/update/delete with filters - Site settings: get/update (singleton id 'default') - Uses TEXT/JSON stored as string columns (specialties, tags, social_media, business_hours). ### Cloudflare R2 Access - getR2Bucket(env?) in lib/db.ts: same global symbol pattern; returns R2 bucket binding. - lib/r2-upload.ts: - FileUploadManager wrapper (put/get/delete/list none directly exposed; uses put/get/delete). - Public URL base constructed from process.env.R2_PUBLIC_URL (not validated in env.ts or configured in wrangler.toml). - Portfolio uploads helper and profile image upload functions. - Presigned URL generation: not implemented (returns null). Schema (sql/schema.sql): - Tables: users, artists, portfolio_images, appointments, availability, site_settings, file_uploads (plus indices). - Site settings row is singleton with id='default'. D1 setup (D1_SETUP.md): - Guides wrangler db creation, migration, and local vs prod usage. - Mentions DATABASE_URL for local SQLite in .env.local; prod uses env.DB binding. --- ## Authentication & Middleware ### NextAuth (lib/auth.ts) - Session strategy: "jwt" - Providers: - Credentials: accepts any email/password for dev; returns SUPER_ADMIN for non-whitelisted users (development convenience). - Optional Google/GitHub via env variables if provided. - JWT callback attaches role (UserRole) and userId; session callback mirrors into session.user. - Redirects: root-relative paths allowed; default redirect to /admin otherwise. - pages: signIn (/auth/signin), error (/auth/error) - events: logs sign-in/out. Security implications: - Dev-friendly Credentials provider grants SUPER_ADMIN by default for non-whitelisted users. Not production-safe. - No database adapter for NextAuth; roles are token-only unless persisted via app logic elsewhere. ### Middleware (middleware.ts) - Protects: - /admin: requires token and role SHOP_ADMIN or SUPER_ADMIN; else redirect to /auth/signin or /unauthorized. - /artist (singular) routes: requires ARTIST/SHOP_ADMIN/SUPER_ADMIN — note: public site uses /artists (plural). Potential stale path. - /api/admin: same admin role check; returns JSON errors with status 401/403. - authorized() callback: - Allows specific public routes and /artists/[slug] as public - Allows /api/auth and /api/public - Requires auth for all else - config.matcher excludes _next static/image assets and common image extensions; favicon, public served directly. Observations: - Mixed singular/plural gating ("artist" vs "artists") - Public route list is static; ensure anchors and subpages are accounted for. --- ## Build/Test/Config Tooling ### TypeScript - tsconfig.json: - strict: true; moduleResolution: "bundler"; jsx: "preserve" - paths alias: "@/*" → "./*" - allowJs: true; skipLibCheck: true; noEmit: true - next.config.mjs sets: - typescript.ignoreBuildErrors = true - eslint.ignoreDuringBuilds = true - images.unoptimized = true - output = "standalone" Risk: - Ignoring TS/ESLint hides defects and reduces CI quality. ### Tailwind / PostCSS - tailwind.config.ts: - darkMode: "class" - content globs: ./pages, ./components, ./app - theme extends shadcn palette via CSS variables - plugins: tailwindcss-animate - postcss.config.mjs: - plugin: @tailwindcss/postcss (Tailwind v4-style config) ### Testing - vitest.config.ts: - jsdom environment, setupFiles: vitest.setup.ts, alias "@" - vitest.setup.ts: - Mocks next/router, next/navigation, next-auth/react, react-query - Mocks global fetch, crypto.randomUUID, matchMedia, IntersectionObserver, ResizeObserver Existing tests: - __tests__/lib/* present (data-migration, validations) No E2E test framework present (e.g., Playwright) and no component test runner config beyond RTL usage via Vitest. --- ## CI/CD and Operational Considerations - CI config files not present in repo (no GitHub Actions/Gitea workflows included). - NPM scripts provide a conventional pipeline: - Lint, test, build, pages:build, preview, deploy, db:* commands - Secrets: - NEXTAUTH_URL configured in wrangler.toml per env - NEXTAUTH_SECRET not shown in wrangler.toml; should be stored via wrangler secret or Cloudflare dashboard - R2_PUBLIC_URL not defined/validated but required by r2-upload.ts for public URLs Caching: - OpenNext R2 incremental cache configured; ensure the bound bucket exists and permissions are correct. --- ## Technical Debt and Known Issues (REALITY) 1) Env schema misalignment with runtime - lib/env.ts requires DATABASE_URL, AWS_* for S3-style access; app uses Cloudflare D1/R2 bindings through env and global OpenNext context. - R2_PUBLIC_URL needed by r2-upload.ts is not part of env validation and not set in wrangler.toml env vars. 2) NextAuth development shortcuts - Credentials provider grants SUPER_ADMIN for arbitrary credentials (aside from a special-cased admin email). Production risk. - No adapter; no persistent user/session store beyond JWT, leading to potential drift with D1 users table. 3) Middleware route mismatch - "artist" (singular) gating vs public "artists" (plural) sections; could be dead code or misleading guard. Clean up to avoid confusion. 4) Build config suppresses quality gates - Ignore TypeScript and ESLint errors during build masks regressions. CI quality at risk. 5) Payment and sensitive headers - No global security headers or route handler-level headers exist for CSP, frame options, permissions policy, etc. - Deposit and payment flows not implemented; when added, security headers and webhook validation must be addressed. 6) Availability and appointment validation duplication - Local Zod schemas inside app/api/appointments/route.ts differ from lib/validations.ts; drift likely. 7) Docker vs Cloudflare deployment paths - Dockerfile supports standalone Node server while OpenNext targets Cloudflare. Docs/scripts support both but only one path should be primary per environment. 8) Observability absent - No Sentry, no OpenTelemetry, no structured logging strategy. 9) Incremental cache correctness - OpenNext R2 incremental cache configured; ensure tags/revalidation strategy is defined for dynamic content once public data migrates from static to DB. 10) D1 setup documentation mismatch - D1_SETUP.md references names like united-tattoo-db in examples, while wrangler.toml uses united-tattoo. Keep consistent to reduce operator confusion. --- ## Recommended Improvements (Technical Delivery) - Environment and Secrets - Extend env zod schema to include R2_PUBLIC_URL; separate “Worker runtime” bindings from local dev variables (DATABASE_URL only for local sqlite). - Store NEXTAUTH_SECRET, any gateway secrets via `wrangler secret put` and Cloudflare Dashboard; avoid wrangler.toml for secrets. - Authentication & RBAC - Replace dev SUPER_ADMIN default with an invite or email link flow. - Introduce an adapter if persistent user/session storage is required (or unify D1 users with NextAuth persistence). - Normalize middleware routes (remove singular /artist gating or align with intended private artist routes). - Build/CI Quality Gates - Re-enable TypeScript and ESLint checks in next.config.mjs for CI. - Add CI workflow: lint → test → build → pages:build → preview deploy (manual approval) → deploy. - Include bundle size budgets for main routes. - Testing - Expand unit/component tests (forms, route handlers with mocked env.DB). - Introduce Playwright for E2E: booking flow, admin flows, critical public pages render. - Contract tests for R2 upload endpoint responses. - Security Headers & Policies - Add common headers in route handlers or middleware where appropriate: - X-Frame-Options: DENY; Referrer-Policy: strict-origin-when-cross-origin - Content-Security-Policy with nonce/hash or strict static policy - Permissions-Policy scoped to required APIs - Enforce cookie flags and CSRF patterns if/when using sessions/cookies. - Data Contracts - Consolidate Zod schemas in lib/validations.ts and reuse in routes (appointments, artists, etc.) to avoid drift. - Align SiteSettings id handling (UUID vs 'default'); align portfolio order vs order_index. - Observability - Add Sentry (server/client) with release tracking and environment tags. - Consider OpenTelemetry for server actions and route handlers. - Deployment Hygiene - Clarify Docker vs Cloudflare path; recommend OpenNext Cloudflare as primary, Docker for local or self-host options. - Ensure R2 incremental cache bucket exists and is referenced by OpenNext; document cache invalidation strategy (revalidateTag). --- ## Operational Playbook - Local Dev (Next server): `npm run dev` - Cloudflare Preview (Worker runtime): `npm run preview` (after `npm run pages:build`) - Deploy: `npm run deploy` - D1: - Create: `npm run db:create` - Migrate: `npm run db:migrate[:local]` - Inspect: `npm run db:studio[:local]` --- ## Appendix — Key Files - wrangler.toml - open-next.config.ts - next.config.mjs - Dockerfile - lib/db.ts, lib/r2-upload.ts, lib/auth.ts, lib/env.ts - middleware.ts - vitest.config.ts, vitest.setup.ts - tailwind.config.ts, postcss.config.mjs, tsconfig.json - sql/schema.sql, D1_SETUP.md --- This document reflects the actual technical architecture and delivery process, calling out real gaps and steps to harden the system for production-grade delivery under Epic D.