generated from nhcarrigan/template
feat: base64 uploads, reusable forms, Discord roles, and UX improvements #66
@@ -0,0 +1,299 @@
|
||||
# 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
|
||||
```bash
|
||||
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
|
||||
```bash
|
||||
pnpm start:frontend:dev # nx serve frontend (port 4200)
|
||||
pnpm start:api:dev # nx serve api (port 3000, watch mode)
|
||||
```
|
||||
|
||||
### Building for Production
|
||||
```bash
|
||||
pnpm build # Generates Prisma client, then builds all projects
|
||||
```
|
||||
|
||||
### Database Management
|
||||
```bash
|
||||
pnpm db:gen # Generate Prisma client
|
||||
pnpm db:push # Push schema changes to MongoDB (uses prod.env secrets)
|
||||
```
|
||||
|
||||
### Linting & Testing
|
||||
```bash
|
||||
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:
|
||||
```bash
|
||||
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:
|
||||
```bash
|
||||
pnpm build # Build all projects
|
||||
pnpm start # Start with prod.env secrets
|
||||
```
|
||||
|
||||
Production start command:
|
||||
```bash
|
||||
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
|
||||
Reference in New Issue
Block a user