|
| 1 | +# CLAUDE.md |
| 2 | + |
| 3 | +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. |
| 4 | + |
| 5 | +## Project Overview |
| 6 | + |
| 7 | +TPEN Services is the backend API for TPEN3 (Transcription for Paleographical and Editorial Notation), a web-based tool for transcribing and annotating digital manuscripts. This Node.js/Express API provides: |
| 8 | + |
| 9 | +- **IIIF-compliant** annotation services following W3C Web Annotation standards |
| 10 | +- **Multi-user collaboration** with role-based access control |
| 11 | +- **Persistent storage** of transcriptions via RERUM/TinyThings |
| 12 | +- **Project management** for organizing manuscript transcription work |
| 13 | +- **RESTful API** endpoints for frontend applications |
| 14 | + |
| 15 | +The service acts as a middleware layer between TPEN3 frontends and multiple data storage backends (MongoDB for metadata, RERUM for annotations). |
| 16 | + |
| 17 | +## Tech Stack |
| 18 | + |
| 19 | +### Core Technologies |
| 20 | +- **Node.js**: >= 22.20.0 (REQUIRED - uses modern JS features) |
| 21 | +- **Express**: 5.1.0 - Web framework |
| 22 | +- **MongoDB**: 6.20.0 - Primary database for projects, users, groups |
| 23 | +- **RERUM/TinyThings**: External service for persistent annotation storage |
| 24 | + |
| 25 | +### Key Dependencies |
| 26 | +- **Auth0**: JWT-based authentication via express-oauth2-jwt-bearer |
| 27 | +- **IIIF**: @iiif/helpers for Presentation API support |
| 28 | +- **DOMPurify**: XSS protection for user input |
| 29 | +- **Jest**: Testing framework with Supertest for API testing |
| 30 | + |
| 31 | +## Project Structure |
| 32 | + |
| 33 | +``` |
| 34 | +/ |
| 35 | +├── auth/ # Authentication middleware (Auth0 JWT validation) |
| 36 | +├── bin/ # Server startup script (tpen3_services.js) |
| 37 | +├── classes/ # Core business logic classes |
| 38 | +│ ├── Project.js # Project CRUD and validation |
| 39 | +│ ├── User.js # User profile and membership |
| 40 | +│ ├── Group.js # Collaborator groups and permissions |
| 41 | +│ ├── Layer.js # Annotation layers (RERUM-backed) |
| 42 | +│ ├── Page.js # Annotation pages (RERUM-backed) |
| 43 | +│ └── Line.js # Individual annotations (RERUM-backed) |
| 44 | +├── database/ # Database abstraction layer |
| 45 | +│ ├── dbDriver.js # Unified interface for all DBs |
| 46 | +│ ├── mongo/ # MongoDB implementation |
| 47 | +│ └── tiny/ # RERUM/TinyThings implementation |
| 48 | +├── project/ # Project API routes |
| 49 | +├── userProfile/ # User profile routes |
| 50 | +├── utilities/ # Shared utilities and helpers |
| 51 | +└── __tests__/ # Test suites |
| 52 | +``` |
| 53 | + |
| 54 | +## Core Concepts |
| 55 | + |
| 56 | +### Projects |
| 57 | +- Container for manuscript transcription work |
| 58 | +- Contains layers, manifests, metadata, and tools |
| 59 | +- Linked to a Group for access control |
| 60 | +- Identified by MongoDB ObjectId |
| 61 | + |
| 62 | +### Layers |
| 63 | +- Logical groupings of annotations (e.g., "Transcription", "Translation") |
| 64 | +- Stored in RERUM as AnnotationCollections |
| 65 | +- Contains Pages which contain Lines |
| 66 | + |
| 67 | +### Pages |
| 68 | +- Represents annotations for a single manuscript page/canvas |
| 69 | +- Stored as AnnotationPages in RERUM |
| 70 | +- Linked to IIIF canvas URIs |
| 71 | + |
| 72 | +### Lines |
| 73 | +- Individual annotations/transcriptions |
| 74 | +- Stored as Web Annotations in RERUM |
| 75 | +- Target specific regions on manuscript images |
| 76 | + |
| 77 | +### Groups |
| 78 | +- Manage project membership and permissions |
| 79 | +- Support custom roles beyond OWNER/CONTRIBUTOR/VIEWER |
| 80 | +- Control who can edit, view, or manage projects |
| 81 | + |
| 82 | +## API Architecture |
| 83 | + |
| 84 | +### RESTful Conventions |
| 85 | +```javascript |
| 86 | +GET /project/:id // Retrieve resource |
| 87 | +POST /project/create // Create new resource |
| 88 | +PUT /project/:id // Update resource |
| 89 | +DELETE /project/:id // Delete resource |
| 90 | +``` |
| 91 | + |
| 92 | +### Response Patterns |
| 93 | +```javascript |
| 94 | +// Success |
| 95 | +res.status(200).json(data) |
| 96 | + |
| 97 | +// Error |
| 98 | +res.status(400).json({ error: "message" }) |
| 99 | + |
| 100 | +// No content |
| 101 | +res.status(204).send() |
| 102 | +``` |
| 103 | + |
| 104 | +### Common Status Codes |
| 105 | +- 200: Success |
| 106 | +- 201: Created |
| 107 | +- 401: Unauthorized (invalid/missing token) |
| 108 | +- 403: Forbidden (insufficient permissions) |
| 109 | +- 404: Not Found |
| 110 | +- 500: Server Error |
| 111 | + |
| 112 | +## Database Strategy |
| 113 | + |
| 114 | +### MongoDB (Primary Storage) |
| 115 | +- Projects, Users, Groups, HotKeys collections |
| 116 | +- Handles metadata, relationships, and configuration |
| 117 | +- ObjectId-based identification |
| 118 | + |
| 119 | +### RERUM/TinyThings (Annotation Storage) |
| 120 | +- Persistent, versioned annotation storage |
| 121 | +- Returns stable URIs for annotations |
| 122 | +- Handles Layers, Pages, Lines as JSON-LD |
| 123 | +- Accessed via HTTP API |
| 124 | + |
| 125 | +### Database Abstraction |
| 126 | +```javascript |
| 127 | +// Always use the abstraction layer |
| 128 | +const database = new dbDriver("mongo") |
| 129 | +await database.save(data, "projects") |
| 130 | +await database.findOne({ _id: id }, "projects") |
| 131 | +``` |
| 132 | + |
| 133 | +## Authentication & Authorization |
| 134 | + |
| 135 | +### Authentication Flow |
| 136 | +1. Client sends JWT in `Authorization: Bearer <token>` |
| 137 | +2. `auth0Middleware()` validates token with Auth0 |
| 138 | +3. User agent extracted from token claims |
| 139 | +4. User record loaded/created from database |
| 140 | + |
| 141 | +### Authorization Model |
| 142 | +```javascript |
| 143 | +// Default roles |
| 144 | +OWNER // Full control, can transfer ownership |
| 145 | +CONTRIBUTOR // Can edit content |
| 146 | +VIEWER // Read-only access |
| 147 | + |
| 148 | +// Permission checking |
| 149 | +Project.checkUserAccess(userId, "edit_page", "layer", layerId) |
| 150 | +``` |
| 151 | + |
| 152 | +### Protected Routes |
| 153 | +```javascript |
| 154 | +router.post("/path", auth0Middleware(), async (req, res) => { |
| 155 | + const userId = req.user._id |
| 156 | + // Route handler |
| 157 | +}) |
| 158 | +``` |
| 159 | + |
| 160 | +## Development Workflow |
| 161 | + |
| 162 | +### Getting Started |
| 163 | +```bash |
| 164 | +# Install dependencies |
| 165 | +npm install |
| 166 | + |
| 167 | +# Set up environment |
| 168 | +cp sample.env .env |
| 169 | +# Edit .env with your configuration |
| 170 | + |
| 171 | +# Start development server |
| 172 | +npm run dev |
| 173 | + |
| 174 | +# Run tests |
| 175 | +npm test |
| 176 | +``` |
| 177 | + |
| 178 | +### Environment Variables |
| 179 | +``` |
| 180 | +# Essential for development |
| 181 | +PORT=3001 |
| 182 | +MONGODB=mongodb://localhost:27017 |
| 183 | +MONGODBNAME=tpen3 |
| 184 | +RERUMURL=https://store.rerum.io/v1 |
| 185 | +AUDIENCE=https://yourapp.auth0.com/api |
| 186 | +DOMAIN=yourapp.auth0.com |
| 187 | +``` |
| 188 | + |
| 189 | +### Making Changes |
| 190 | + |
| 191 | +1. **Follow existing patterns** - Look at similar code for guidance |
| 192 | +2. **Use the abstraction layers** - Don't access databases directly |
| 193 | +3. **Validate input** - Use utility validators in `/utilities` |
| 194 | +4. **Handle errors gracefully** - Use respondWithError() helper |
| 195 | +5. **Write tests** - Add tests for new functionality |
| 196 | + |
| 197 | +## Testing Guidelines |
| 198 | + |
| 199 | +### Running Tests |
| 200 | +```bash |
| 201 | +npm run allTests # All tests |
| 202 | +npm run unitTests # Unit tests only |
| 203 | +npm run E2Etests # End-to-end tests |
| 204 | +npm run dbTests # Database tests |
| 205 | +``` |
| 206 | + |
| 207 | +### Test Organization |
| 208 | +- Tag tests with descriptive labels (e.g., `user_class`, `auth_test`) |
| 209 | +- Use Supertest for API endpoint testing |
| 210 | +- Mock external services when appropriate |
| 211 | +- Tests should be independent and idempotent |
| 212 | + |
| 213 | +## Code Style & Conventions |
| 214 | + |
| 215 | +### JavaScript Style |
| 216 | +```javascript |
| 217 | +// ES6+ modules |
| 218 | +import { something } from './module.js' |
| 219 | + |
| 220 | +// Async/await for all async operations |
| 221 | +async function processData() { |
| 222 | + const result = await database.find({}, "collection") |
| 223 | + return result |
| 224 | +} |
| 225 | + |
| 226 | +// Early returns with guard clauses |
| 227 | +if (!isValid) { |
| 228 | + return respondWithError(res, 400, "Invalid input") |
| 229 | +} |
| 230 | + |
| 231 | +// Nullish coalescing and optional chaining |
| 232 | +const value = data?.property ?? defaultValue |
| 233 | +``` |
| 234 | +
|
| 235 | +### Naming Conventions |
| 236 | +- Files: camelCase.js |
| 237 | +- Classes: PascalCase |
| 238 | +- Functions/variables: camelCase |
| 239 | +- Routes: kebab-case |
| 240 | +- Database collections: UPPERCASE |
| 241 | +
|
| 242 | +### Commit Messages |
| 243 | +``` |
| 244 | +feat: Add new feature |
| 245 | +fix: Fix bug |
| 246 | +docs: Update documentation |
| 247 | +test: Add tests |
| 248 | +refactor: Refactor code |
| 249 | +``` |
| 250 | + |
| 251 | +## Security Considerations |
| 252 | + |
| 253 | +### Input Validation |
| 254 | +```javascript |
| 255 | +// Always sanitize user input |
| 256 | +import DOMPurify from 'isomorphic-dompurify' |
| 257 | +const clean = DOMPurify.sanitize(userInput) |
| 258 | + |
| 259 | +// Check for suspicious content |
| 260 | +if (checkIfSuspicious(content)) { |
| 261 | + throw new Error("Suspicious content detected") |
| 262 | +} |
| 263 | +``` |
| 264 | + |
| 265 | +### Authentication Required |
| 266 | +- All write operations require valid JWT |
| 267 | +- User permissions checked before data modifications |
| 268 | +- Ownership verified for sensitive operations |
| 269 | + |
| 270 | +### CORS Configuration |
| 271 | +- Configured per endpoint as needed |
| 272 | +- Default CORS settings in common_cors.json |
| 273 | +- Restrictive by default |
| 274 | + |
| 275 | +## Common Tasks |
| 276 | + |
| 277 | +### Adding a New API Endpoint |
| 278 | +1. Create route file in appropriate directory |
| 279 | +2. Define route with authentication middleware |
| 280 | +3. Implement business logic in classes/ |
| 281 | +4. Add validation and error handling |
| 282 | +5. Write tests for the endpoint |
| 283 | + |
| 284 | +### Working with Projects |
| 285 | +```javascript |
| 286 | +// Load a project |
| 287 | +const project = new Project(projectId) |
| 288 | +await project.load() |
| 289 | + |
| 290 | +// Check user access |
| 291 | +const canEdit = await project.checkUserAccess(userId, "edit", "project") |
| 292 | + |
| 293 | +// Update project |
| 294 | +project.title = "New Title" |
| 295 | +await project.save() |
| 296 | +``` |
| 297 | + |
| 298 | +### Working with Annotations |
| 299 | +```javascript |
| 300 | +// Create a new annotation page |
| 301 | +const page = new Page({ |
| 302 | + label: "Page 1", |
| 303 | + target: canvasUri |
| 304 | +}) |
| 305 | +await page.save() |
| 306 | + |
| 307 | +// Add to layer |
| 308 | +layer.pages.push(page.id) |
| 309 | +await layer.save() |
| 310 | +``` |
| 311 | + |
| 312 | +### Database Operations |
| 313 | +```javascript |
| 314 | +// Always use the driver abstraction |
| 315 | +const db = new dbDriver("mongo") |
| 316 | + |
| 317 | +// Find documents |
| 318 | +const projects = await db.find({ creator: userId }, "projects") |
| 319 | + |
| 320 | +// Update document |
| 321 | +await db.update({ _id: id, ...updates }, "projects") |
| 322 | +``` |
| 323 | + |
| 324 | +### Testing Your Changes |
| 325 | +```javascript |
| 326 | +// Write a test for your feature |
| 327 | +describe('My Feature', () => { |
| 328 | + test('should do something', async () => { |
| 329 | + const response = await request(app) |
| 330 | + .post('/api/endpoint') |
| 331 | + .send({ data: 'test' }) |
| 332 | + |
| 333 | + expect(response.status).toBe(200) |
| 334 | + expect(response.body).toHaveProperty('result') |
| 335 | + }) |
| 336 | +}) |
| 337 | +``` |
| 338 | + |
| 339 | +## Important Notes |
| 340 | + |
| 341 | +1. **RERUM Integration**: Annotation data is stored externally in RERUM. Always handle RERUM API failures gracefully. |
| 342 | + |
| 343 | +2. **MongoDB Required**: While MariaDB support exists, MongoDB is the primary database and required for full functionality. |
| 344 | + |
| 345 | +3. **IIIF Compliance**: All annotation responses follow IIIF Presentation API 3.0 and W3C Web Annotation standards. |
| 346 | + |
| 347 | +4. **Async Everything**: All database operations and external API calls must use async/await. |
| 348 | + |
| 349 | +5. **Token Expiry**: Always check JWT expiration before processing requests. |
| 350 | + |
| 351 | +6. **Maintenance Mode**: Check `DOWN` environment variable for maintenance mode handling. |
| 352 | + |
| 353 | +## Need Help? |
| 354 | + |
| 355 | +- Check existing implementations in similar files |
| 356 | +- Review test files for usage examples |
| 357 | +- Look at utility functions in `/utilities` for common operations |
| 358 | +- Follow patterns established in the `/classes` directory |
0 commit comments