This document lists code and features that were added during development/debugging that might be candidates for cleanup or removal in the future. ## Debug/Development Code ### 1. Verbose Logging in Production **Location:** Multiple files **Status:** Consider reducing in production - `lib/auth.ts` - Session callback logging (lines 78-103, 105-113) - Logs full session details on every session creation - Could be reduced to warnings only or removed in production - `app/photos/page.tsx` - Page render logging (lines 12-33) - Logs auth() calls and session details - Useful for debugging but verbose for production - `app/api/debug/session/route.ts` - Entire debug endpoint - Created for debugging session issues - Consider removing or protecting with admin-only access - Or move to development-only route ### 2. Activity Logging **Location:** `lib/activity-log.ts`, `proxy.ts`, API routes **Status:** Keep but consider optimization - Activity logging is useful for monitoring - Consider: - Moving to structured logging (JSON format) - Adding log rotation/retention policies - Option to disable in production if not needed - Rate limiting logs to prevent spam ### 3. Upload Verification Logging **Location:** `app/api/photos/upload/route.ts` **Status:** Keep but reduce verbosity - Lines 89-91: Directory creation/existence logging - Lines 101: File save verification logging - Useful for debugging but could be reduced to errors only ### 4. Middleware Debug Logging **Location:** `proxy.ts` **Status:** Keep but consider reducing - Lines 22-37: Activity logging for all requests - Useful for monitoring but generates many logs - Consider: log only important events or add log level filtering ## Unused/Redundant Code ### 5. Legacy Upload Route **Location:** `app/api/photos/route.ts` **Status:** Consider deprecating - Legacy URL-based upload endpoint - New uploads use `/api/photos/upload` - Consider: - Marking as deprecated - Removing if not used - Or consolidating with upload route ### 6. Multiple Upload Routes **Location:** `app/api/photos/upload/route.ts` and `app/api/photos/upload-multiple/route.ts` **Status:** Keep but document usage - Two separate upload endpoints - Consider if both are needed or can be consolidated ### 7. Proxy.ts Cookie Name Variable **Location:** `proxy.ts` line 15 **Status:** Minor cleanup - `cookieName` variable defined but could use constant - Consider moving to shared constant or env var ## Configuration Cleanup ### 8. Next.js Config **Location:** `next.config.ts` **Status:** Review - Image optimization settings (line 19: `unoptimized: false`) - Consider if all remote patterns are needed - Review Turbopack configuration if not using ## Documentation Cleanup ### 10. ARCHITECTURE.md References **Location:** `ARCHITECTURE.md` line 156 **Status:** Update - Still references `middleware.ts` in some places - Should reference `proxy.ts` instead - Update all middleware references ## Testing/Debugging Utilities ### 11. Watch Activity Script **Location:** `watch-activity.sh` (if created) **Status:** Keep or document - Useful utility for monitoring - Consider adding to README or removing if not needed ## Recommendations ### High Priority (Consider Removing) 1. `app/api/debug/session/route.ts` - Debug endpoint (protect or remove) 2. Verbose logging in `app/photos/page.tsx` - Reduce to errors only 3. Update ARCHITECTURE.md middleware references ### Medium Priority (Optimize) 1. Activity logging - Add log levels or filtering 2. Upload logging - Reduce verbosity 3. Session callback logging - Reduce in production ### Low Priority (Keep) 1. Activity logging utility - Useful for monitoring 2. Multiple upload routes - Document usage 3. Watch activity script - Useful utility ## Notes - **Consider** adding environment-based log levels (DEBUG, INFO, WARN, ERROR) - **Consider** moving debug endpoints behind admin authentication - **Consider** adding log rotation/retention for production --- Do all these in stages. create new tests and test and docuemtn as u go. add DEBUG, INFO, WARN, ERROR flags and only show when asked for. create new branch.
13 KiB
MirrorMatch Architecture
Overview
MirrorMatch is a photo guessing game built with Next.js App Router, PostgreSQL, and NextAuth. Users upload photos with answer names, and other users guess to earn points.
System Architecture
Application Structure
mirrormatch/
├── app/ # Next.js App Router
│ ├── api/ # API route handlers (Next.js route handlers)
│ │ ├── admin/ # Admin-only API endpoints
│ │ ├── auth/ # NextAuth routes
│ │ ├── photos/ # Photo-related APIs
│ │ └── profile/ # User profile APIs
│ ├── admin/ # Admin panel (server component)
│ ├── leaderboard/ # Leaderboard page (server component)
│ ├── login/ # Login page (client component)
│ ├── photos/ # Photo listing and detail pages
│ ├── profile/ # User profile page (server component)
│ └── upload/ # Photo upload page (client component)
├── components/ # Reusable React components
│ ├── Navigation.tsx # Navigation bar (client component)
│ └── [others] # Form components, UI components
├── lib/ # Utility libraries and helpers
│ ├── prisma.ts # Prisma client singleton (lazy initialization)
│ ├── auth.ts # NextAuth configuration
│ ├── email.ts # Email sending utilities
│ ├── utils.ts # Helper functions (hashing, etc.)
│ └── activity-log.ts # Activity logging utility
├── prisma/ # Database schema and migrations
│ ├── schema.prisma # Prisma schema definition
│ └── seed.ts # Database seeding script
├── types/ # TypeScript type definitions
│ └── next-auth.d.ts # NextAuth type extensions
├── proxy.ts # Next.js proxy/middleware for route protection (Next.js 16)
└── lib/
└── activity-log.ts # Activity logging utility
Data Model
Database Schema
User Model
model User {
id String @id @default(cuid())
name String
email String @unique
passwordHash String
role Role @default(USER)
points Int @default(0)
createdAt DateTime @default(now())
uploadedPhotos Photo[] @relation("PhotoUploader")
guesses Guess[]
}
Fields:
id: Unique identifier (CUID)name: User's display nameemail: Unique email address (used for login)passwordHash: Bcrypt-hashed password (never exposed)role: Either "ADMIN" or "USER"points: Accumulated points from correct guessescreatedAt: Account creation timestamp
Relations:
uploadedPhotos: All photos uploaded by this userguesses: All guesses made by this user
Photo Model
model Photo {
id String @id @default(cuid())
uploaderId String
uploader User @relation("PhotoUploader", fields: [uploaderId], references: [id])
url String
answerName String
createdAt DateTime @default(now())
guesses Guess[]
}
Fields:
id: Unique identifier (CUID)uploaderId: Foreign key to User who uploadedurl: URL to the photo imageanswerName: The correct answer users should guesscreatedAt: Upload timestamp
Relations:
uploader: The User who uploaded this photoguesses: All guesses made for this photo
Guess Model
model Guess {
id String @id @default(cuid())
userId String
user User @relation(fields: [userId], references: [id])
photoId String
photo Photo @relation(fields: [photoId], references: [id])
guessText String
correct Boolean @default(false)
createdAt DateTime @default(now())
@@index([userId])
@@index([photoId])
}
Fields:
id: Unique identifier (CUID)userId: Foreign key to User who made the guessphotoId: Foreign key to Photo being guessedguessText: The user's guess textcorrect: Whether the guess matches the answer (case-insensitive)createdAt: Guess timestamp
Relations:
user: The User who made this guessphoto: The Photo being guessed
Indexes:
- Indexed on
userIdfor fast user guess queries - Indexed on
photoIdfor fast photo guess queries
Authentication Flow
NextAuth Configuration
Location: lib/auth.ts
Provider: Credentials Provider (email + password)
Flow:
- User submits email and password on
/login - NextAuth calls
authorizefunction - System looks up user by email in database
- Compares provided password with stored
passwordHashusing bcrypt - If valid, creates JWT session with user data (id, email, name, role)
- Session stored in JWT (no database session table)
Session Data:
id: User IDemail: User emailname: User namerole: User role (ADMIN | USER)
Route Protection
Location: proxy.ts (Next.js 16 uses proxy.ts instead of middleware.ts)
Public Routes:
/login/api/auth/*(NextAuth endpoints)/uploads/*(uploaded files - legacy, now served via API)/api/uploads/*(uploaded files served via API route)
Protected Routes:
- All other routes require authentication
/admin/*routes additionally requirerole === "ADMIN"
Implementation:
- Uses NextAuth
getTokenfromnext-auth/jwtin Edge runtime - Checks JWT token on each request via
proxy.ts - Explicitly specifies cookie name:
__Secure-authjs.session-token - Redirects unauthenticated users to
/loginwithcallbackUrl - Redirects non-admin users trying to access admin routes to home
- Logs all user activity (page visits, API calls)
Activity Logging:
- All authenticated requests are logged with user info, IP, path, method
- Unauthenticated access attempts are also logged
- Photo uploads and guess submissions have dedicated activity logs
- Logs format:
[ACTIVITY] timestamp | method path | User: email (role) | IP: ip
Application Flows
1. Admin Creates User
Flow:
- Admin navigates to
/admin - Fills out user creation form (name, email, password, role)
- Form submits to
POST /api/admin/users - API route:
- Verifies admin session
- Checks if email already exists
- Hashes password with bcrypt
- Creates User record in database
- Admin sees new user in user list
API Route: app/api/admin/users/route.ts
Component: components/CreateUserForm.tsx
2. User Login
Flow:
- User navigates to
/login - Enters email and password
- Client calls NextAuth
signIn("credentials", ...) - NextAuth validates credentials via
lib/auth.ts - On success, redirects to
/photos - Session stored in JWT cookie
Page: app/login/page.tsx (client component)
3. User Changes Password
Flow:
- User navigates to
/profile - Enters current password and new password
- Form submits to
POST /api/profile/change-password - API route:
- Verifies session
- Validates current password against stored hash
- Hashes new password
- Updates User record
- User sees success message
API Route: app/api/profile/change-password/route.ts
Component: components/ChangePasswordForm.tsx
4. Photo Upload
Flow:
- User navigates to
/upload - Uploads photo file or enters photo URL and answer name
- Form submits to
POST /api/photos/upload(supports both file and URL uploads) - API route:
- Verifies session
- For file uploads:
- Validates file type and size (max 10MB)
- Calculates SHA256 hash for duplicate detection
- Generates unique filename:
timestamp-randomstring.extension - Saves file to
public/uploads/directory - Sets photo URL to
/api/uploads/[filename]
- For URL uploads:
- Validates URL format
- Checks for duplicate URLs
- Uses provided URL directly
- Creates Photo record with
uploaderId,url,answerName,fileHash - Queries all other users (excluding uploader)
- Sends email notifications to all other users (async, non-blocking)
- Logs activity:
[PHOTO_UPLOAD]
- User redirected to photo detail page
API Routes:
app/api/photos/upload/route.ts- Single photo upload endpoint (supports both file and URL uploads)app/api/photos/upload-multiple/route.ts- Multiple photo upload endpointapp/api/uploads/[filename]/route.ts- Serves uploaded files
File Storage:
- Files stored in
public/uploads/directory - Served via
/api/uploads/[filename]API route - Files verified after write to ensure successful save
- Duplicate detection via SHA256 hash
Email: lib/email.ts - sendNewPhotoEmail()
Page: app/upload/page.tsx (client component)
5. Email Notifications
Implementation: lib/email.ts
Development Mode:
- Uses Ethereal Email (test SMTP service)
- Provides preview URLs in console
- Falls back to console transport if Ethereal unavailable
Production Mode:
- Uses SMTP server (configured via env vars)
- Sends HTML and plaintext emails
- Includes link to photo guess page
Email Content:
- Subject: "New Photo Ready to Guess!"
- Body: Includes uploader name, link to
/photos/[id] - Sent asynchronously (doesn't block photo creation)
6. Guess Submission
Flow:
- User views photo at
/photos/[id] - User enters guess text
- Form submits to
POST /api/photos/[photoId]/guess - API route:
- Verifies session
- Checks if user already has correct guess (prevent duplicate points)
- Prevents users from guessing their own photos
- Normalizes guess text and answer (trim, lowercase)
- Compares normalized strings
- Creates Guess record with
correctboolean - If correct: increments user's points by photo's
pointsvalue - If wrong and penalty enabled: deducts penalty points
- Logs activity:
[GUESS_SUBMIT]with result
- Page refreshes to show feedback
API Route: app/api/photos/[photoId]/guess/route.ts
Component: components/GuessForm.tsx
Page: app/photos/[id]/page.tsx (server component)
Guess Matching:
- Case-insensitive comparison
- Trims whitespace
- Exact match required (no fuzzy matching)
- Points awarded based on photo's
pointsfield (default: 1)
7. Leaderboard
Flow:
- User navigates to
/leaderboard - Server component queries all users
- Orders by
points DESC - Renders table with rank, name, email, points
- Highlights current user's row
Page: app/leaderboard/page.tsx (server component)
Query: prisma.user.findMany({ orderBy: { points: "desc" } })
Code Organization Guidelines
Where to Put Code
Server Actions:
- Use Next.js route handlers (
app/api/*/route.ts) for API endpoints - Consider server actions (
app/actions.ts) for form submissions if preferred pattern
Route Handlers:
- All API endpoints in
app/api/*/route.ts - Use
GET,POST,PUT,DELETEexports as needed - Always verify session and authorization
- Return JSON responses
Shared Utilities:
- Database access:
lib/prisma.ts(Prisma client singleton) - Auth config:
lib/auth.ts - Email:
lib/email.ts - General helpers:
lib/utils.ts(hashing, string normalization, etc.)
Components:
- Reusable UI components:
components/ - Page-specific components can live in
app/[page]/if not reused - Prefer server components, use
"use client"only when needed
Type Definitions:
- NextAuth types:
types/next-auth.d.ts - Shared types:
types/directory - Component prop types: inline or in component file
Database Access Pattern
Always use Prisma:
import { prisma } from "@/lib/prisma"
// Example query
const user = await prisma.user.findUnique({
where: { email: "user@example.com" }
})
Never:
- Use raw SQL queries
- Use other ORMs
- Access database directly without Prisma
Security Considerations
Password Security
- All passwords hashed with bcrypt (10 rounds)
- Password hashes never exposed in API responses
- Password changes require current password verification
Authorization
- All mutations check user session
- Admin routes verify
role === "ADMIN" - Users can only modify their own data (except admins)
- Photo uploads require authentication
- Guess submissions require authentication
Input Validation
- Validate all user inputs
- Sanitize before database operations
- Use Prisma's type safety to prevent SQL injection
- Normalize guess text before comparison
Important Notes
- Always read this document and README.md before making architectural changes
- Update this document when adding new features or changing data flows
- Keep documentation in sync with code changes
- Follow established patterns for consistency
Last Updated: When architecture changes, update this file and notify the team.