8.1 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Commands
npm run local— nodemon withAPP_ENV=local, listens on port 3201 (override vianodemon.json). The default port elsewhere is 3200.npm run dev—APP_ENV=development, no nodemonnpm start—APP_ENV=productionnpm stop— kills whatever is on port 3200npm run setup:api— full bootstrap: migrations → seed roles → interactive admin user creation → seed articlesnpm test— Postman/newman collection (no Jest/unit tests). Thetests/collection JSONs are the source of truth.npm run create:user— interactive admin user creation scriptnpm run mongo:start/mongo:stop— local MongoDB via Docker containerhu-mongoon 27017
Sequelize CLI:
npx sequelize-cli db:migrate/db:migrate:status/db:migrate:undo:allnpx sequelize-cli db:seed:allor--seed <name>for a specific seeder
Paths are redirected by .sequelizerc: config in src/database/config/db.config.js, models in src/database/models/mysql/, migrations in src/database/migrations/, seeders in src/database/seeders/.
Node engines: ^16.20.2 || ^18.19.1 || ^20.11.1. ESM eslint config (eslint.config.mjs) but the source code is CommonJS.
Environments and secrets
app.js loads config/env/.env.${APP_ENV} at boot — every command must set APP_ENV (the npm scripts do). Defaults to development if unset.
Caution: nodemon.json currently has real credentials in its env block (MySQL host/user/password, SendGrid API key, SkydiverID token). Treat it as sensitive — don't commit further changes to it without intent, and never echo or paste those values.
Boot sequence
app.js is the single entry point. Order matters:
- Express app is created, CORS, morgan (color-coded by status), JSON/urlencoded body parsers, method-override, static
/public, session. errorhandlermiddleware is attached only whenAPP_ENV !== 'production'.- Mongoose models are registered (
require('./src/database/models/mongo')) andpassport-localstrategy is initialized. - Swagger UI is mounted at
/api-docs(specs assembled from YAML insrc/swagger/). connectAllDb()runs first — connects MongoDB then MySQL. The MySQL step callsshowAllTables()and compares against the model registry; if any expected table is missing the process exits with an error pointing todb:migrate. There is nosequelize.sync()— schema changes must go through migrations.- Only after DBs are up are routes registered (
app.use(require('./src/routes'))). Three exception handlers (notFoundErrorHandler,logErrorHandler,fianlErrorHandler[sic]) are attached after the routes.
If you add a new model, also register it in the appropriate index.js (src/database/models/mysql/index.js or mongo/index.js) and src/database/mysql.js (the latter instantiates each model with the shared sequelize instance). Otherwise the boot-time table check won't see it and routes that touch it will 500.
Routing
src/routes/index.js mounts everything under /api. src/routes/api/index.js splits that into four domain routers:
/api/skydive/*→ src/routes/api/skydive/ — aeronefs, applications, canopies, dropzones, jumps, qcm (MongoDB-backed)/api/cms/*→ src/routes/api/cms/ — articles, comments, profiles, tags, user (MySQL-backed)/api/ecommerce/*→ src/routes/api/ecommerce/ — product, products (MySQL-backed). Recently moved out ofcms/./api/herowars/*→ src/routes/api/herowars/ — clans, members, herowars (MongoDB-backed)
The v1/, v2/, v3/ folders next to these are legacy backups slated for deletion — ignore them, do not extend them, and don't reference them in routes/api/index.js (the requires are commented out).
Each domain index.js mounts its sub-routers and includes a ValidationError handler that returns 422 with field-level errors.
Layered architecture
route → controller → service → model. Don't shortcut layers.
- Routes (src/routes/api/) — Express routers, only wire URL + middleware + controller.
- Controllers (src/controllers/) — wrapped in
asyncHandler(src/middlewares/asyncHandler.js) so thrown errors propagate to the exception handlers. They orchestrate: validate input, call services, shape the response. Naming pattern:xxx.controller.js. - Services (src/services/) — split into
mysql/(Sequelize) andmongo/(Mongoose). Exported through src/services/index.js as{ArticleService, CommentService, ProductService, TagService, UserService, ClanService, MemberService}. Static-method classes; this is where DB queries live. - Models (src/database/models/) —
mysql/are Sequelize model factories ((sequelize, DataTypes) => sequelize.define(...)) registered through src/database/mysql.js;mongo/are Mongoose schemas registered through src/database/models/mongo/index.js. Sequelize associations live in one place only: src/database/relationships/index.js, called once after the table check passes.
Auth
src/middlewares/auth.js exposes:
auth.requiredJwt/auth.optional—express-jwt(HS256) readingAuthorization: <Bearer|Token|Basic|Api-Key> <value>. Decoded payload lands onreq.payload.auth.required— dual-mode. If theAuthorizationheader prefix matchesprocess.env.AUTHORIZATION_PREFIX(i.e.Api-Key) it goes throughpassport-headerapikeyand setsreq.application+req.payload.id = application.author.id. Otherwise it falls back to JWT. Useauth.requiredon endpoints that need to accept either user JWTs or third-party API keys.
JWT secret comes from process.env.SECRET (fallback 'acapisecret') — same secret used as bcrypt salt for API-key hashing.
Data stores
Two databases run side-by-side; each domain picks one:
- MySQL (Sequelize) — relational data: users/roles, articles/comments/tags/favorites/followers, products/brands/categories/packaging with their xref tables. Charset
utf8mb4,freezeTableName: true(no auto-pluralization), timezone+02:00. - MongoDB (Mongoose) — document data: skydive entities (Jump, JumpFile, Aeronef, Canopy, Dropzone, QCM tree), Hero Wars (Clan, Member, ClanRoles), plus a duplicated set of CMS-style models (Article, Comment, Tag, User, Application) that pre-dates the SQL migration.
Migrations are big-bang: a single 20251127000000-initial-schema.js builds the whole MySQL schema. Seeders (roles, articles) are dated and run individually via --seed. Don't expect a chain of incremental migrations.
Swagger
OpenAPI 3 spec assembled at boot in src/config/swagger.js by reading every *.yaml under src/swagger/ and merging their paths and components.schemas. To document a new endpoint, add or extend a YAML there — there is no JSDoc-based generation. UI is at http://localhost:<port>/api-docs.
Migration scripts and reports
scripts/ contains migrate-to-domain-structure.js, update-references-post-migration.js, finalize-migration.js, create-user.js. The first three relate to the recent restructure into domain folders (cms → ecommerce split). The MIGRATION_*.md and SCRIPTS_MIGRATION_GUIDE.md files at the repo root document that effort — read them before editing the route layout. They are reference docs, not active runbooks.
Related service
The Angular frontend lives at /Users/julien/Sites/adastra_app and consumes this API via interceptors that prepend environment.apiBaseUrl. Frontend feature domains mirror the four backend route groups (skydive, cms, ecommerce, herowars); when adding an endpoint, check whether a matching service exists there.