Files
library/CLAUDE.md
T
hikari 983b78b0e9
Security Scan and Upload / Security & DefectDojo Upload (push) Successful in 1m20s
Node.js CI / CI (push) Successful in 1m24s
feat: base64 uploads, reusable forms, Discord roles, and UX improvements (#66)
## Summary

This PR includes multiple feature additions and fixes to improve the library application:

### 🎨 Base64 Image Upload Support
- Fixed Fastify body limit (1MB → 10MB) to accommodate base64-encoded images
- Corrected base64 size calculation and validation logic
- Improved error handling with proper 400 status codes and helpful messages
- Removed duplicate validation that was blocking uploads
- Users can now upload cover images up to 5MB (decoded size)

### 📝 Reusable Form Components
- Created 6 form components: `GameForm`, `BookForm`, `MusicForm`, `ShowForm`, `MangaForm`, `ArtForm`
- All forms support both 'add' and 'edit' modes with pre-population
- Integrated inline editing into all detail views (edit/delete buttons)
- Enhanced admin suggestions workflow with full forms instead of basic modals
- Added scroll-to-top when clicking edit in list views for better UX

### 🖼️ Default Cover Image
- Added beautiful library reading image as default cover for all media types
- Fixed static asset serving to use correct MIME types
- Updated all 12 components (6 list views + 6 detail views) to always show images

### 🔒 Tiered Rate Limiting
- Unauthenticated users: 100 requests/minute
- Authenticated users: 500 requests/minute (5x more lenient)
- Admin users: No rate limits (complete bypass via allowList)

### 🎮 Discord Integration
- Auto-assign library member role to users in NHCarrigan Discord server
- Checks server membership on every login
- Only assigns role if user is in server and doesn't have it yet
- Graceful error handling without blocking login
- Similar pattern to badge refresh flow

### 📚 Documentation
- Added comprehensive CLAUDE.md with:
  - Project structure and tech stack
  - Development workflow and commands
  - Database schema documentation
  - Authentication flow details
  - Security features
  - Code style conventions
  - Common gotchas and solutions

## Test Plan

- [x] Base64 image uploads work for cover images up to 5MB
- [x] Helpful error messages appear for validation failures
- [x] Edit/delete buttons appear on all detail views for admin users
- [x] Inline edit forms display and save correctly
- [x] Admin suggestions workflow uses full forms for all media types
- [x] Scroll-to-top works when editing from list views
- [x] Default cover image displays when no cover is provided
- [x] Static assets serve with correct MIME types
- [x] Rate limiting works correctly for different user types
- [x] Discord role assignment works on login
- [x] All builds pass without errors
- [x] No TypeScript errors

## Related Issues

Closes #65 - Base64 image upload issue

 This pull request was created with help from Hikari~ 🌸

Reviewed-on: #66
Co-authored-by: Hikari <hikari@nhcarrigan.com>
Co-committed-by: Hikari <hikari@nhcarrigan.com>
2026-02-20 20:32:52 -08:00

11 KiB

Library Project - Claude Instructions

This is a personal media library tracking application built with an Nx monorepo structure. It tracks games, books, music, art, shows, and manga with user profiles, comments, achievements, and social features.

Git Commit Guidelines

CRITICAL: When working on this project:

  • Always commit as Hikari using: --author="Hikari <hikari@nhcarrigan.com>" --no-gpg-sign
  • Always ask permission before creating commits - Never commit without explicit user approval
  • Never modify git config - Always use CLI flags for author information

User Permissions

This is Naomi's personal library:

  • Admin (Naomi only) - Can create, update, and delete all media items (games, books, music, art, shows, manga)
  • Regular users - Can only:
    • Like items
    • Comment on items (with markdown support)
    • Submit suggestions for new items (requires admin approval)
    • Customise their own profile

All CRUD operations on media items are admin-only. Regular users interact through the social features (likes, comments, suggestions).

Project Structure

This is an Nx monorepo containing:

  • api/ - Fastify backend API with Prisma ORM
  • apps/frontend/ - Angular frontend application
  • shared-types/ - Shared TypeScript types between frontend and backend
  • libs/ - Shared libraries

Technology Stack

Backend (api/)

  • Runtime: Node.js v24
  • Framework: Fastify 5.x with plugins:
    • @fastify/autoload - Auto-loads routes and plugins
    • @fastify/jwt - JWT authentication
    • @fastify/oauth2 - Discord OAuth integration
    • @fastify/helmet - Security headers
    • @fastify/cors - CORS configuration
    • @fastify/csrf-protection - CSRF protection
    • @fastify/rate-limit - Rate limiting
    • @fastify/cookie - Cookie handling
    • @fastify/static - Static file serving
  • Database: MongoDB with Prisma ORM
  • Logging: @nhcarrigan/logger (custom logger package)
  • Markdown: marked for parsing, dompurify + jsdom for sanitisation

Frontend (apps/frontend/)

  • Framework: Angular 21.x
  • Icons: FontAwesome (solid + brands)
  • Styling: Angular's built-in styling system

Development Tools

  • Package Manager: pnpm v10
  • Monorepo: Nx 22.x
  • Linting: ESLint 9 (flat config) with @nhcarrigan/eslint-config
  • Testing: Jest 30.x
  • Build: esbuild (backend), Angular CLI (frontend)
  • Secrets: 1Password CLI (op run)

Database Schema

The Prisma schema (api/prisma/schema.prisma) defines these models:

  • Game - Video game tracking with platform, status, rating, cover image, tags, series
  • Book - Book tracking with author, ISBN, status, rating, cover image, tags, series
  • Music - Music tracking (album/single/EP) with artist, status, rating, cover art, tags
  • Art - Art collection with artist, description, image, tags
  • Show - TV/anime/film tracking with type, status, rating, cover image, tags
  • Manga - Manga tracking with author, status, rating, cover image, tags
  • User - User profiles with Discord auth, slug, bio, badges, achievements, social links
  • Comment - Comments on any media type with markdown support
  • AuditLog - Security and action logging
  • Suggestion - User-submitted content suggestions
  • Like - User likes on media items
  • RefreshToken - JWT refresh token storage
  • ProfileReport - User profile reporting system
  • CommentReport - Comment reporting system
  • UserAchievement - Achievement tracking with progress

All models use MongoDB ObjectIds and include timestamps (createdAt, updatedAt).

Authentication Flow

The application uses Discord OAuth for authentication:

  1. User clicks "Login with Discord"
  2. OAuth flow redirects to Discord for authorisation
  3. Discord redirects back with authorisation code
  4. Backend exchanges code for Discord user info
  5. Backend creates/updates User record
  6. Backend issues JWT access token (15m expiry) and refresh token (7d expiry)
  7. Frontend stores tokens in cookies
  8. Access token in Authorization header for API requests
  9. Refresh token used to obtain new access tokens

See api/AUTH_FLOW.md for detailed authentication implementation.

Image Upload Handling

The application supports two methods for cover images:

  1. Regular URLs - Standard image URLs (max 2048 characters)
  2. Base64 Data URLs - Inline base64-encoded images (max 5MB decoded size)

Base64 Upload Constraints

  • Fastify body limit: 10MB (accommodates base64 overhead)
  • Validation: Data URLs must match format data:image/(jpeg|png|gif|webp|svg+xml);base64,[base64data]
  • Size calculation: Base64 data is extracted and decoded size calculated as base64Data.length * 0.75
  • Size limit: Decoded image must be under 5MB

Implementation Notes

  • Base64 size check happens AFTER format validation
  • Regular URL length check (2048 chars) does NOT apply to data URLs
  • Validation logic is in api/src/app/services/*.service.ts files
  • Base64 validation helpers in api/src/app/utils/validation.ts

Development Workflow

Local Development

pnpm dev  # Builds all projects and starts API with dev.env secrets

The dev script:

  1. Runs nx run-many --target=build --all to build all projects
  2. Uses op run --env-file=dev.env to inject secrets from 1Password
  3. Starts the API with NODE_ENV=production node dist/api/main.js
  4. API serves the frontend as static files from dist/apps/frontend/browser

Separate Frontend/Backend Development

pnpm start:frontend:dev  # nx serve frontend (port 4200)
pnpm start:api:dev       # nx serve api (port 3000, watch mode)

Building for Production

pnpm build  # Generates Prisma client, then builds all projects

Database Management

pnpm db:gen   # Generate Prisma client
pnpm db:push  # Push schema changes to MongoDB (uses prod.env secrets)

Linting & Testing

pnpm lint  # Lints all projects with ESLint
pnpm test  # Runs all tests with Jest

CI/CD Pipeline

The project uses Gitea Actions (.gitea/workflows/ci.yml):

  1. Dependency Pin Check - Ensures all dependencies are pinned (no ^ or ~)
  2. Install Dependencies - pnpm install
  3. Lint - pnpm run lint
  4. Build - pnpm run build
  5. Test - pnpm run test

CI runs on:

  • Pushes to main branch
  • Pull requests to main branch

Configuration Standards

TypeScript

  • Uses @nhcarrigan/typescript-config as base
  • Separate configs for app code (tsconfig.app.json) and tests (tsconfig.spec.json)
  • Output directory: dist/ for builds

ESLint

  • Uses @nhcarrigan/eslint-config (ESLint 9 flat config)
  • No Prettier - all style rules handled by ESLint
  • Configured in eslint.config.mjs
  • Angular-specific rules enabled for frontend

Testing

  • Jest 30.x with ts-jest for TypeScript support
  • Separate jest configs per project (e.g., api/jest.config.cts)
  • Cypress for e2e testing (frontend-e2e project)

Secrets Management

Environment Files

  • dev.env - Development secrets (1Password vault references)
  • prod.env - Production secrets (1Password vault references)

These files contain ONLY 1Password references (e.g., op://vault/item/field), NOT actual secrets. They are safe to commit.

Non-Secret Configuration

Configuration that isn't sensitive (URLs, intervals, feature flags) should be in code (e.g., constants files), NOT in .env files.

Running with Secrets

Always use 1Password CLI:

op run --env-file=dev.env -- <command>
op run --env-file=prod.env -- <command>

Security Features

The application implements multiple security layers:

  • Helmet - Security headers (CSP, HSTS, etc.)
  • CORS - Configured origin restrictions
  • CSRF Protection - Token-based CSRF validation
  • Rate Limiting - Request rate limits per IP
  • JWT - Short-lived access tokens with refresh token rotation
  • Audit Logging - All security events logged to AuditLog model
  • Content Sanitisation - Markdown comments sanitised with DOMPurify
  • Input Validation - All inputs validated before database operations

API Routes Structure

Routes are auto-loaded via @fastify/autoload from api/src/app/routes/:

  • Each route file exports Fastify route handlers
  • Routes follow REST conventions (GET, POST, PUT, DELETE)
  • All routes require JWT authentication (except auth endpoints)
  • Route handlers call service functions for business logic

Example structure:

  • api/src/app/routes/games/index.ts - Game CRUD endpoints
  • api/src/app/services/game.service.ts - Game business logic

Code Style Conventions

General

  • Use clear, descriptive variable/function names
  • Prefer self-documenting code over comments
  • Only use comments to explain reasoning when intent isn't clear
  • Use British English spelling (colour, organise, whilst)

TypeScript

  • Prefer interface over type for object shapes
  • Use const assertions where appropriate
  • Enable strict mode (inherited from @nhcarrigan/typescript-config)

Error Handling

  • Service functions throw Error instances with descriptive messages
  • Route handlers wrap service calls in try-catch blocks
  • Return 400 for validation errors with error message
  • Return 500 for unexpected errors (message hidden in production)
  • All errors logged to AuditLog for security events

Validation

  • Validation utilities in api/src/app/utils/validation.ts
  • Constants for max lengths in shared-types/src/lib/constants.ts
  • Validate early in service functions before database operations

Common Gotchas

Base64 Image Uploads

  • Body limit must be 10MB (api/src/main.ts) to accommodate base64 overhead
  • Validate data URL format BEFORE size check
  • Extract base64 portion (after comma) for size calculation
  • Don't apply regular URL length validation to data URLs

Prisma Client Generation

  • Must run pnpm db:gen before building if schema changed
  • The build script automatically runs this
  • Prisma client is generated to api/generated/

Nx Cache

  • Nx caches build outputs for faster rebuilds
  • Clear cache with nx reset if experiencing issues
  • Cache stored in .nx/cache/

MongoDB ObjectIds

  • All IDs are MongoDB ObjectIds (not UUIDs or auto-increment integers)
  • Use @db.ObjectId in Prisma schema
  • Use String type in TypeScript for ObjectIds

Deployment

The application is deployed in production mode:

pnpm build              # Build all projects
pnpm start              # Start with prod.env secrets

Production start command:

NODE_ENV=production op run --env-file=prod.env -- node dist/api/main.js

The API serves the built frontend as static files, so only the API process needs to run in production.

Important Notes

  • This is a personal project for Naomi's media tracking
  • Uses Discord OAuth for authentication (no other auth methods)
  • MongoDB is the only supported database (Prisma schema is MongoDB-specific)
  • All timestamps are stored as UTC in the database
  • Achievements system tracks user activity and unlocks (see UserAchievement model)
  • Comments support markdown with sanitisation
  • User profiles support custom slugs, bios, and social links
  • Reporting system for profiles and comments with moderation workflow