Files
adastra_api/CLAUDE.md
T

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 with APP_ENV=local, listens on port 3201 (override via nodemon.json). The default port elsewhere is 3200.
  • npm run devAPP_ENV=development, no nodemon
  • npm startAPP_ENV=production
  • npm stop — kills whatever is on port 3200
  • npm run setup:api — full bootstrap: migrations → seed roles → interactive admin user creation → seed articles
  • npm test — Postman/newman collection (no Jest/unit tests). The tests/ collection JSONs are the source of truth.
  • npm run create:user — interactive admin user creation script
  • npm run mongo:start / mongo:stop — local MongoDB via Docker container hu-mongo on 27017

Sequelize CLI:

  • npx sequelize-cli db:migrate / db:migrate:status / db:migrate:undo:all
  • npx sequelize-cli db:seed:all or --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:

  1. Express app is created, CORS, morgan (color-coded by status), JSON/urlencoded body parsers, method-override, static /public, session.
  2. errorhandler middleware is attached only when APP_ENV !== 'production'.
  3. Mongoose models are registered (require('./src/database/models/mongo')) and passport-local strategy is initialized.
  4. Swagger UI is mounted at /api-docs (specs assembled from YAML in src/swagger/).
  5. connectAllDb() runs first — connects MongoDB then MySQL. The MySQL step calls showAllTables() and compares against the model registry; if any expected table is missing the process exits with an error pointing to db:migrate. There is no sequelize.sync() — schema changes must go through migrations.
  6. 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:

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.

Auth

src/middlewares/auth.js exposes:

  • auth.requiredJwt / auth.optionalexpress-jwt (HS256) reading Authorization: <Bearer|Token|Basic|Api-Key> <value>. Decoded payload lands on req.payload.
  • auth.required — dual-mode. If the Authorization header prefix matches process.env.AUTHORIZATION_PREFIX (i.e. Api-Key) it goes through passport-headerapikey and sets req.application + req.payload.id = application.author.id. Otherwise it falls back to JWT. Use auth.required on 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.

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.