93 lines
8.1 KiB
Markdown
93 lines
8.1 KiB
Markdown
# 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 dev` — `APP_ENV=development`, no nodemon
|
|
- `npm start` — `APP_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](.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](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](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](src/routes/index.js) mounts everything under `/api`. [src/routes/api/index.js](src/routes/api/index.js) splits that into four domain routers:
|
|
|
|
- `/api/skydive/*` → [src/routes/api/skydive/](src/routes/api/skydive/) — aeronefs, applications, canopies, dropzones, jumps, qcm (MongoDB-backed)
|
|
- `/api/cms/*` → [src/routes/api/cms/](src/routes/api/cms/) — articles, comments, profiles, tags, user (MySQL-backed)
|
|
- `/api/ecommerce/*` → [src/routes/api/ecommerce/](src/routes/api/ecommerce/) — product, products (MySQL-backed). Recently moved out of `cms/`.
|
|
- `/api/herowars/*` → [src/routes/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/](src/routes/api/)) — Express routers, only wire URL + middleware + controller.
|
|
- **Controllers** ([src/controllers/](src/controllers/)) — wrapped in `asyncHandler` ([src/middlewares/asyncHandler.js](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/](src/services/)) — split into `mysql/` (Sequelize) and `mongo/` (Mongoose). Exported through [src/services/index.js](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/](src/database/models/)) — `mysql/` are Sequelize model factories (`(sequelize, DataTypes) => sequelize.define(...)`) registered through [src/database/mysql.js](src/database/mysql.js); `mongo/` are Mongoose schemas registered through [src/database/models/mongo/index.js](src/database/models/mongo/index.js). Sequelize associations live in **one place only**: [src/database/relationships/index.js](src/database/relationships/index.js), called once after the table check passes.
|
|
|
|
## Auth
|
|
|
|
[src/middlewares/auth.js](src/middlewares/auth.js) exposes:
|
|
- `auth.requiredJwt` / `auth.optional` — `express-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](src/config/swagger.js) by reading every `*.yaml` under [src/swagger/](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/](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.
|