# United Tattoo — Brownfield Architecture Document (Focused: Epic A — Admin Dashboard & Artist Management) This document captures the CURRENT STATE of the United Tattoo codebase relevant to Admin Dashboard & Artist Management (Epic A). It reflects actual patterns, technical debt, and constraints to enable AI agents to work effectively on enhancements in this area. ## Document Scope Focused on areas relevant to: Admin invitations & onboarding, RBAC, artist profiles, portfolio and asset management, settings, and admin-only API routes. ### Change Log | Date | Version | Description | Author | | ---------- | ------- | ------------------------------------------- | ---------------- | | 2025-09-18 | 1.0 | Initial brownfield analysis (Admin focus) | Architect Agent | --- ## Quick Reference — Key Files and Entry Points ### Critical Files for Understanding the System - App entry and layouts - app/layout.tsx, app/ClientLayout.tsx, app/page.tsx - app/admin/layout.tsx, app/admin/page.tsx, plus nested admin pages - Routing and security - middleware.ts (route protection and public-route policy) - lib/auth.ts (NextAuth config, JWT callbacks, role assignment) - Cloudflare/OpenNext deployment - wrangler.toml (D1/R2 bindings, compatibility flags) - next.config.mjs (output: standalone, images.unoptimized) - open-next.config.ts (present; not analyzed in depth here) - Data layer and storage - sql/schema.sql (Cloudflare D1 schema) - lib/db.ts (D1 helpers and CRUD for artists, portfolio, appointments, settings; R2 bucket getter) - lib/r2-upload.ts (R2 upload manager, portfolio/profile helpers) - Validation and types - lib/validations.ts (Zod schemas for users, artists, portfolio images, appointments, site settings, forms) - types/database.ts (domain models and Cloudflare D1/R2 ambient types) - Public docs (reference) - docs/PRD.md (feature scope; this doc is scoped to Epic A) - docs/Architecture.md, docs/architecture.md (legacy/other architecture docs) ### Admin UI Pages (App Router) - app/admin/analytics/page.tsx - app/admin/artists/page.tsx - app/admin/artists/[id]/page.tsx - app/admin/artists/new/page.tsx - app/admin/calendar/page.tsx - app/admin/portfolio/page.tsx - app/admin/settings/page.tsx - app/admin/uploads/page.tsx ### Admin/Related API Routes (Route Handlers) - app/api/admin/migrate/route.ts - app/api/admin/stats/route.ts - app/api/artists/route.ts - app/api/portfolio/route.ts - app/api/portfolio/[id]/route.ts - app/api/portfolio/bulk-delete/route.ts - app/api/portfolio/stats/route.ts - app/api/files/route.ts - app/api/files/bulk-delete/route.ts - app/api/files/folder/route.ts - app/api/files/stats/route.ts - app/api/settings/route.ts - app/api/users/route.ts - app/api/appointments/route.ts (admin-usable but spans Booking epic as well) - app/api/auth/[...nextauth]/ (NextAuth core) --- ## High-Level Architecture ### Technical Summary (Actual) - Next.js 14.2.16 (App Router), React 18, Tailwind 4.x, shadcn/ui patterns - Cloudflare Pages + Workers via OpenNext adapter - Cloudflare D1 for relational data (env.DB); Cloudflare R2 for object storage (env.R2_BUCKET) - Auth via next-auth (JWT session strategy, Credentials + optional Google/GitHub) - Validation via Zod across routes and forms - Client state: TanStack Query; forms via react-hook-form ### Actual Tech Stack (from package.json and code) | Category | Technology | Version | Notes | | -------------- | -------------------------------- | ----------- | ----- | | Runtime | Cloudflare Pages/Workers | Wrangler 4 | OpenNext adapter, nodejs_compat enabled | | Framework | Next.js (App Router) | 14.2.16 | output: standalone; images.unoptimized | | UI | shadcn/ui + Radix primitives | mixed | shadcn patterns across pages/components | | State | @tanstack/react-query | ^5.89.0 | Devtools present | | Forms | react-hook-form + zod resolver | ^7.60.0 | Zod schemas in lib/validations.ts | | Auth | next-auth (JWT) | ^4.24.11 | Credentials; optional Google/GitHub | | DB | Cloudflare D1 | — | Access via global env bindings | | Storage | Cloudflare R2 | — | via env.R2_BUCKET; custom public URL expected | | Dev/Test | Vitest + RTL | ^3.2.4 | tests under __tests__/ | | Deploy | OpenNext Cloudflare | ^1.8.2 | pages:build → .vercel/output/static | ### Repository Structure Reality Check - Polyrepo (single app) - Package manager: npm (scripts define build/preview/deploy and D1 ops) - Notable: - next.config.mjs ignores TS and ESLint errors during build (risk: hidden issues) - images.unoptimized: Cloudflare Images or custom loader recommended for prod - Zod env validator requires many variables not used by D1 codepaths (see debt) --- ## Source Tree and Module Organization ### Project Structure (Actual, abridged) ``` project-root/ ├── app/ │ ├── admin/ # Admin UI pages │ ├── api/ # Route handlers (REST-ish) │ ├── (public sections) # /artists, /aftercare, etc. │ └── auth/ # auth/signin pages ├── components/ # UI components (public/admin) ├── lib/ # auth, db (D1/R2), uploads, validations, utils ├── sql/schema.sql # D1 schema ├── types/database.ts # domain types and Cloudflare ambient types ├── docs/ # PRD and architecture docs ├── wrangler.toml # Cloudflare bindings/config ├── next.config.mjs # Next build config └── open-next.config.ts # OpenNext adapter config ``` ### Key Admin Modules and Their Purpose - RBAC and route protection - middleware.ts: protects /admin and API subsets; maintains public routes list. - lib/auth.ts: next-auth config. Credentials provider returns SUPER_ADMIN for dev users; JWT carries role. - Data layer and storage - lib/db.ts: D1 CRUD for artists, portfolio images, appointments, site settings; getDB/getR2Bucket read bindings from Cloudflare context or globals (OpenNext). - lib/r2-upload.ts: Upload manager wrapping R2; bulk uploads; portfolio/profile helpers; expects R2_PUBLIC_URL for public reads. - Validation and types - lib/validations.ts: Comprehensive Zod schemas for admin entities (artists, portfolio images, settings) and form payloads. - types/database.ts: Roles, entities, appointment status; Cloudflare D1/R2 ambient types to ease dev. - Admin APIs (examples) - app/api/artists/route.ts: - GET: lists artists with filters and pagination (in-memory filtering after fetch) - POST: requires SHOP_ADMIN (or higher); validates body; creates artist tied to session user --- ## Data Models and APIs ### Data Models (from sql/schema.sql and types) - users (id TEXT PK, email UNIQUE, name, role enum, avatar, timestamps) - artists (id TEXT PK, user_id FK users, name, bio, specialties JSON string, social, is_active, hourly_rate, timestamps) - portfolio_images (id TEXT PK, artist_id FK, url, caption, tags JSON string, order_index, is_public, created_at) - appointments (id TEXT PK, artist_id FK, client_id FK users, title, description, times, status enum, amounts, notes, timestamps) - availability (id TEXT PK, artist_id FK, day_of_week int, start_time/end_time HH:mm, is_active) - site_settings (id TEXT PK, fields for studio and branding; id is 'default' row by convention) - file_uploads (id TEXT PK, metadata, url, uploaded_by FK users) Notes: - IDs are TEXT and often UUIDs; site_settings uses a constant id 'default'. - JSON stored as TEXT (specialties, tags, social_media, business_hours). ### Admin-Relevant Route Handlers (observed) - /api/admin/migrate, /api/admin/stats - /api/artists (GET, POST) - /api/portfolio, /api/portfolio/[id], /api/portfolio/bulk-delete, /api/portfolio/stats - /api/files, /api/files/bulk-delete, /api/files/folder, /api/files/stats - /api/settings - /api/users - /api/appointments (shared with booking) Patterns: - Validations with Zod schemas from lib/validations.ts - D1 access through lib/db.ts helpers using Cloudflare env context (context?.env in handlers) - Role checks via middleware + requireAuth(UserRole.*) on sensitive operations --- ## Technical Debt and Known Issues (REALITY) 1. Env validation vs Cloudflare bindings - lib/env.ts requires DATABASE_URL, DIRECT_URL, and multiple AWS_* variables. - Actual DB access in lib/db.ts uses Cloudflare D1 binding (env.DB); no DATABASE_URL is used. - R2 uploads build public URLs using process.env.R2_PUBLIC_URL, but env.ts does not validate R2_PUBLIC_URL, and wrangler.toml does not set it. Missing/invalid public URL will break returned URLs for uploaded assets. 2. SiteSettings id handling - DB uses id='default' singleton row. - lib/validations.ts siteSettingsSchema expects id to be a UUID; mismatch with actual data causes invalidation/confusion and could break validation workflows. 3. Portfolio image ordering field name mismatch - DB column: order_index - lib/validations.ts schemas use 'order' as the property name; not aligned with DB and lib/db.ts update semantics. Risk of incorrect mapping when integrating UI forms → API → DB. 4. Auth and security (development shortcuts) - Credentials provider in lib/auth.ts accepts any credentials and assigns SUPER_ADMIN by default for non-whitelisted users (dev convenience). - No DB adapter (JWT-only). RBAC is token-based; no persistent user store beyond D1 writes that may occur when creating artists (which auto-creates ARTIST users). - This is acceptable for local/dev but must be hardened before production. 5. Middleware routing inconsistencies - middleware.ts checks pathname.startsWith("/artist") (singular) for “Artist-specific routes” role gating. - Public pages are under /artists/... (plural). There is also an allow rule for /^\/artists\/[^\/]+$/ as public. Mixed naming increases cognitive load; the singular check may be a stale/unused path. 6. Build config hides issues - next.config.mjs ignores TypeScript and ESLint errors during build. This can allow broken types or lint issues to ship; not suitable for CI/CD production gates. 7. Schema/tooling inconsistencies - sql/schema.sql header suggests executing “wrangler d1 execute united-tattoo-db …” while package.json uses database name “united-tattoo”. Mismatch in naming in comments could confuse operators. 8. R2 public access patterns - r2-upload.ts assumes a simple base URL concatenation for public reads. Cloudflare R2 often requires either a custom public domain or R2 public buckets; the base URL must be configured and documented. No presigned upload flow yet (stubbed). --- ## Integration Points and External Dependencies ### External Services | Service | Purpose | Integration Type | Key Files | | ------------- | ---------------- | ---------------- | -------------------- | | Cloudflare D1 | Relational DB | Worker binding | wrangler.toml, lib/db.ts | | Cloudflare R2 | Object storage | Worker binding | wrangler.toml, lib/db.ts, lib/r2-upload.ts | | NextAuth | Authentication | Providers/JWT | lib/auth.ts, app/api/auth/[...nextauth]/ | | OpenNext | Next→Workers | Build adapter | package.json scripts, open-next.config.ts, wrangler.toml | ### Internal Integration Points - Admin UI → API routes: Admin pages consume /api/* endpoints for CRUD on artists, portfolio images, settings, and files. - Validation: UI forms align to Zod schemas (lib/validations.ts); ensure property names match DB contract (see debt on order/order_index). - Role enforcement: middleware.ts + requireAuth(UserRole.*) enforce admin-only access. --- ## Development and Deployment ### Local Development Setup (Actual) - Install deps: npm install - D1 DB create/migrate: - npm run db:create (creates DB) - npm run db:migrate or npm run db:migrate:local (executes sql/schema.sql) - Preview Workers runtime locally: - npm run dev:wrangler (build via OpenNext then preview) - or npm run preview (OpenNext preview) - App dev server: - npm run dev (Next dev server; note some Cloudflare bindings are only available via OpenNext preview) Required environment (observed/assumed): - Wrangler configured/login - Cloudflare bindings as per wrangler.toml - NEXTAUTH_SECRET and NEXTAUTH_URL set appropriately - R2_PUBLIC_URL should be set for correct public asset URLs (not currently validated by env.ts) ### Build and Deployment Process - Build (OpenNext): npm run pages:build - Preview: npm run preview - Deploy to Cloudflare Pages: npm run deploy (wrangler pages deploy .vercel/output/static) - wrangler.toml: - compatibility_date >= 2024-09-23 - compatibility_flags ["nodejs_compat"] - D1 and R2 bindings configured --- ## Testing Reality - Unit/component tests: Vitest with RTL (see __tests__/) - E2E: Not observed in repo - Coverage: Test scripts available; actual coverage not measured here - QA: Manual likely; shadcn components + form/zod patterns amenable to RTL coverage --- ## If Enhancement PRD Provided — Impact Analysis (Admin Focus) Based on PRD Epic A, the following files/modules are most likely to be affected: ### Files/Modules Likely to Need Modification - UI - app/admin/artists/* (listing, detail, new) - app/admin/uploads/page.tsx (batch upload flows, progress) - app/admin/settings/page.tsx (site settings form) - components/admin/* (admin-specific components) - APIs - app/api/artists/route.ts (filters, pagination, create/linking behaviors) - app/api/portfolio/route.ts and /[id]/route.ts (CRUD, ordering, tags) - app/api/files/* (upload metadata, deletion, folder organization) - app/api/settings/route.ts (singleton settings updates) - app/api/users/route.ts (invite flows and role assignment) - Lib - lib/r2-upload.ts (public URL handling, presigned URL path, folder conventions) - lib/db.ts (query optimizations, joining, business rules, activity logs) - lib/validations.ts (resolve property mismatches; align with DB) - lib/auth.ts (tighten dev-only flows, enforce role creation pathways) - middleware.ts (route gate consistency for admin vs artists) ### New Files/Modules Potentially Needed - Activity Logs: D1 table and APIs for admin auditing per PRD (FR-A5.x) - Invite & Onboarding APIs: Route handlers and email delivery for A1.x - Moderation Queue: Table and APIs for uploads moderation hook (FR-A5.3) - Image Processing: Server-side transformations (could leverage Cloudflare Images; not present now) ### Integration Considerations - Enforce consistent role model end-to-end (Invite → Signup → Role assignment) - Align Zod schemas with DB column names and types (e.g., order_index) - R2 public URL and folder conventions should be codified and validated - Consider introducing DB migrations governance and seed paths for admin roles --- ## Appendix — Useful Commands and Scripts From package.json: ```bash # Dev & Build npm run dev npm run pages:build npm run preview npm run deploy # D1 Management npm run db:create npm run db:migrate npm run db:migrate:local npm run db:studio npm run db:studio:local # Tests npm run test npm run test:ui npm run test:run npm run test:coverage ``` --- ## Gotchas and Practical Notes (Must Read) - To use D1/R2 in preview, prefer OpenNext preview (npm run preview) where bindings are available via global Cloudflare context. Running plain next dev may not expose env.DB/env.R2_BUCKET without additional shims. - Set NEXTAUTH_SECRET and NEXTAUTH_URL for auth to function correctly; in preview/production, wrangler.toml provides NEXTAUTH_URL for env scopes. - Configure R2_PUBLIC_URL; otherwise URLs returned by upload endpoints may be unusable externally. - next.config.mjs ignoring errors is risky; fix warnings and enable strict CI gates for production readiness. - Site settings expect a singleton row with id 'default'; update APIs rely on this assumption. --- ## Recommended Fixes (Non-Blocking, Advisory) - Update env validation to match Cloudflare bindings reality - Either remove DATABASE_URL from required env, or separate “Worker runtime” env from “local dev” .env with appropriate fallbacks. - Add R2_PUBLIC_URL to Zod-validated schema. - Align schemas and properties - Rename portfolio image 'order' → 'orderIndex' in Zod schemas and UI forms to match DB. - SiteSettings schema: do not require UUID id; or adapt DB to UUID if desired (and update code). - Security hardening - Replace dev-only SUPER_ADMIN behavior with an invite/token-based onboarding flow. - Implement NextAuth adapter (e.g., D1 via Drizzle/Kysely or Supabase per .clinerules) if persistence is required for sessions and users. - Middleware consistency - Remove/rename singular '/artist' gating or align with actual '/artists' conventions; centralize route constants. - CI/CD quality - Re-enable TypeScript and ESLint checks to catch regressions early. - Add component and route handler tests for admin flows (artists, portfolio, settings). --- This document reflects the actual state of the system for Admin Dashboard & Artist Management, including technical debt and real-world constraints. It references concrete files and paths to accelerate development work by AI agents and maintainers.