Add BMAD, Claude, Cursor, and OpenCode configuration directories along with AGENTS.md documentation. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
13 KiB
Critial Instructions!
- When you need to search docs, use
context7tools. Always search docs before writing any code. - When working with UI components, use
shadcnormagicuitools. Always prefer premade components over coming up with them from scratch. - When working on UI or making large changes, use the
playwrighttools to navigate in the browser. - Whenever needing broader domain research, use the
brave-searchtool to search the web.
Project Overview
United Tattoo is a Next.js-based website for a tattoo studio in Fountain, CO. The application includes artist portfolios, booking systems, appointment management with CalDAV integration, and admin dashboards.
Stack:
- Next.js 14 (App Router) with TypeScript
- Cloudflare D1 (SQLite) for database
- Cloudflare R2 for file storage
- NextAuth.js for authentication
- Deployed via OpenNext on Cloudflare Workers
- ShadCN UI components + Tailwind CSS
- Vitest for testing
Common Commands
Development
npm run dev # Start Next.js dev server (port 3000)
npm run dev:wrangler # Build and preview with OpenNext/Cloudflare
Testing
npm run test # Run Vitest in watch mode
npm run test:ui # Run Vitest with UI
npm run test:run # Run tests once
npm run test:coverage # Run tests with coverage report
Build & Deployment
npm run pages:build # Build with OpenNext for Cloudflare
npm run build # Standard Next.js build (standalone)
npm run preview # Preview OpenNext build locally
npm run deploy # Deploy to Cloudflare Pages
CI Commands
npm run ci:lint # ESLint
npm run ci:typecheck # TypeScript type checking (noEmit)
npm run ci:test # Run tests with coverage
npm run ci:build # Build for production
npm run ci:budgets # Check bundle size budgets
Database Management
# Local database
npm run db:migrate:local # Apply schema to local D1
npm run db:studio:local # Show tables in local D1
# Preview (default) environment
npm run db:migrate # Apply schema to preview D1
npm run db:migrate:latest:preview # Apply all migrations from sql/migrations/
npm run db:studio # Show tables in preview D1
npm run db:backup # Backup preview database
# Production environment
npm run db:migrate:up:prod # Apply specific migration to production
npm run db:migrate:latest:prod # Apply all migrations to production
npm run db:backup:local # Backup local database
# Direct Wrangler commands
wrangler d1 execute united-tattoo --local --command="SELECT * FROM artists"
wrangler d1 execute united-tattoo --file=./sql/schema.sql
Code Quality
npm run lint # Run ESLint
npm run format # Format code with Prettier
npm run format:check # Check formatting without changing files
npm run security:audit # Run npm audit
Architecture
Database Layer (lib/db.ts)
The database layer provides type-safe functions for interacting with Cloudflare D1. Key patterns:
- Binding access:
getDB(env)retrieves D1 from Cloudflare bindings via OpenNext's global symbol - R2 access:
getR2Bucket(env)retrieves R2 bucket binding for file uploads - Namespace-style exports: Use
db.artists.findMany(),db.portfolioImages.create(), etc. - JSON fields:
specialtiesandtagsare stored as JSON strings and parsed/stringified automatically
Main tables:
users- Authentication and user profiles with roles (SUPER_ADMIN, SHOP_ADMIN, ARTIST, CLIENT)artists- Artist profiles linked to users, includes slug for URLsportfolio_images- Artist portfolio work with tags and orderingappointments- Booking appointments with CalDAV sync supportflash_items- Flash tattoo designs available for bookingsite_settings- Global site configurationartist_calendars- Nextcloud CalDAV calendar configuration per artist
Authentication (lib/auth.ts)
NextAuth.js setup with role-based access control and Nextcloud OAuth integration:
-
Primary Provider: Nextcloud OAuth (recommended for all users)
- Artists and admins sign in via their Nextcloud credentials
- Auto-provisioning: Users in 'artists' or 'shop_admins' Nextcloud groups are automatically created
- Group-based role assignment:
adminoradminsgroup → SUPER_ADMINshop_adminsgroup (configurable) → SHOP_ADMINartistsgroup (configurable) → ARTIST (with auto-created artist profile)- Users not in authorized groups are denied access
- Requires:
NEXTCLOUD_OAUTH_CLIENT_ID,NEXTCLOUD_OAUTH_CLIENT_SECRET,NEXTCLOUD_BASE_URL
-
Fallback Provider: Credentials (email/password)
- Available only via
/auth/signin?admin=truequery parameter - Admin emergency access only
- Dev mode: Any email/password combo creates a SUPER_ADMIN user for testing
- Seed admin:
nicholai@biohazardvfx.comis hardcoded as admin
- Available only via
-
Deprecated Providers: Google/GitHub OAuth (still configured but not actively used)
-
Session strategy: JWT (no database adapter currently)
-
Nextcloud Integration (
lib/nextcloud-client.ts):getNextcloudUserProfile(userId)- Fetch user details from Nextcloud OCS APIgetNextcloudUserGroups(userId)- Get user's group membershipsdetermineUserRole(userId)- Auto-assign role based on Nextcloud groups- Uses service account credentials (NEXTCLOUD_USERNAME/PASSWORD) for API access
-
Helper functions:
requireAuth(role?)- Protect routes, throws if unauthorizedgetArtistSession()- Get artist profile for logged-in artist userscanEditArtist(userId, artistId)- Check edit permissionshasRole(userRole, requiredRole)- Check role hierarchy
CalDAV Integration (lib/calendar-sync.ts, lib/caldav-client.ts)
Bidirectional sync between database appointments and Nextcloud calendars:
- Push to calendar:
syncAppointmentToCalendar()- Called when creating/updating appointments - Pull from calendar:
pullCalendarEventsToDatabase()- Background sync to import calendar events - Availability checking:
checkArtistAvailability()- Real-time conflict detection using CalDAV - Per-artist calendars: Each artist can have their own Nextcloud calendar configured in
artist_calendarstable
Environment variables required:
NEXTCLOUD_BASE_URLNEXTCLOUD_USERNAMENEXTCLOUD_PASSWORDNEXTCLOUD_CALENDAR_BASE_PATH(defaults to/remote.php/dav/calendars)
File Uploads (lib/r2-upload.ts, lib/upload.ts)
- R2 storage: Files uploaded to Cloudflare R2 bucket
- Image processing: HEIC to JPEG conversion, resizing, AVIF format support
- Public URLs: Files served from R2 public URL
- Upload API:
/api/uploadhandles multipart form data
Environment Configuration (lib/env.ts)
Validates required environment variables at boot using Zod. Critical vars:
- Database:
DATABASE_URL,DIRECT_URL(Supabase URLs, though using D1) - Auth:
NEXTAUTH_URL,NEXTAUTH_SECRET - Storage: AWS/R2 credentials (
AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY,AWS_BUCKET_NAME,AWS_ENDPOINT_URL) - Nextcloud OAuth (required for artist authentication):
NEXTCLOUD_BASE_URL- Nextcloud instance URL (e.g., https://portal.united-tattoos.com)NEXTCLOUD_OAUTH_CLIENT_ID- OAuth app client ID from NextcloudNEXTCLOUD_OAUTH_CLIENT_SECRET- OAuth app client secret from NextcloudNEXTCLOUD_ARTISTS_GROUP- Group name for artists (default: "artists")NEXTCLOUD_ADMINS_GROUP- Group name for admins (default: "shop_admins")
- Nextcloud CalDAV (optional, for calendar sync):
NEXTCLOUD_USERNAME- Service account usernameNEXTCLOUD_PASSWORD- Service account password or app-specific passwordNEXTCLOUD_CALENDAR_BASE_PATH- CalDAV path (default: "/remote.php/dav/calendars")
Note: The env validation expects Supabase URLs but actual runtime uses Cloudflare D1 via bindings.
API Routes
All API routes follow Next.js App Router conventions (app/api/*/route.ts):
Public APIs:
/api/artists- List public artists with portfolio images/api/artists/[id]- Get single artist by ID or slug/api/public/migrate- Public migration endpoint (token-protected)
Protected APIs (require authentication):
/api/artists/me- Current artist's profile (ARTIST role)/api/portfolio- CRUD for portfolio images/api/flash/[artistId]- Manage flash tattoo items/api/appointments- Appointment management/api/upload- File upload to R2/api/admin/*- Admin-only endpoints (stats, migrations, calendars)/api/caldav/sync- Trigger CalDAV sync/api/caldav/availability- Check artist availability
Frontend Structure
Pages:
/- Homepage (hero, artists, services, contact)/artists- Artist listing/artists/[id]- Individual artist portfolio (supports slug or ID)/artists/[id]/book- Book with specific artist/book- General booking page/admin/*- Admin dashboard (analytics, portfolio, calendar, artist management, uploads)/artist-dashboard/*- Artist-specific dashboard (profile, portfolio editing)/auth/signin- Login page
Data Sources:
data/artists.ts- Static artist data (may be legacy, check if still used vs database)
Routing Notes
- Middleware (
middleware.ts): Handles permanent redirects (e.g.,/artists/amari-rodriguez→/artists/amari-kyss) - Dynamic routes: Artist pages work with both database IDs and slugs
- Authentication: Admin and artist dashboard routes require appropriate roles
Testing
- Framework: Vitest with React Testing Library
- Config:
vitest.config.ts(check for any custom setup) - Tests located alongside components or in
__tests__/directories
Bundle Size Budgets
Defined in package.json under budgets key:
TOTAL_STATIC_MAX_BYTES: 3,000,000 (3MB)MAX_ASSET_BYTES: 1,500,000 (1.5MB)
Checked by scripts/budgets.mjs during CI.
Development Workflow
Working with Migrations
- Create new migration file in
sql/migrations/with formatYYYYMMDD_NNNN_description.sql - For local testing:
npm run db:migrate:local - For preview:
npm run db:migrate:latest:preview - For production:
npm run db:migrate:latest:prod
Migrations are also tracked in sql/migrations_up/ for Wrangler's built-in migration system.
Working with Artists
Artists have both a user account and an artist profile:
- User created in
userstable with roleARTIST - Artist profile in
artiststable linked viauser_id - Slug auto-generated from name, handles duplicates with numeric suffix
- Portfolio images in
portfolio_imagestable - Flash items in
flash_itemstable (optional, new feature)
Adding New Features
When adding database tables:
- Add to
sql/schema.sql - Create migration file in
sql/migrations/ - Update TypeScript types in
types/database.ts - Add CRUD functions to
lib/db.ts - Create API routes if needed
- Update this CLAUDE.md if it's a major architectural change
CI/CD Pipeline
Located in .gitea/workflows/:
ci.yaml- Main CI pipeline (lint, typecheck, test, build, budgets)security.yaml- Security auditsperformance.yaml- Performance checksdeploy.yaml- Deployment to Cloudflare
The CI enforces:
- ESLint passing
- TypeScript compilation (with
ignoreBuildErrors: truefor builds but strict for typecheck) - Test coverage
- Bundle size budgets
- Migration dry-run (best-effort with local D1)
Known Configuration Quirks
- TypeScript errors ignored during build (
next.config.mjs):typescript.ignoreBuildErrors: trueallows builds to succeed even with type errors. CI separately runstsc --noEmitto catch them. - Images unoptimized: Next.js Image optimization disabled for Cloudflare Workers compatibility
- Standalone output: Docker builds use
output: "standalone"mode - Node.js compatibility: Cloudflare Workers use
nodejs_compatflag inwrangler.toml
Deployment
Production URL: https://united-tattoos.com
Deploy command: npm run pages:build && wrangler deploy
The deployment process:
- Build with OpenNext:
npm run pages:build→ outputs to.vercel/output/static - Deploy to Cloudflare:
wrangler pages deploy .vercel/output/static
Docker Support
Dockerfile included for self-hosting:
docker build -t united-tattoo:latest .
docker run --rm -p 3000:3000 -e PORT=3000 united-tattoo:latest
Uses Next.js standalone mode for minimal image size.
Important Notes
- Always test database changes locally first with
--localflag - The migration token (
MIGRATE_TOKEN) protects public migration endpoints - CalDAV integration is optional; appointments work without it
- Role hierarchy: CLIENT < ARTIST < SHOP_ADMIN < SUPER_ADMIN
- Flash items feature may not exist in older database schemas (lib/db.ts tolerates missing table)