Each startSync request takes the Postgres advisory lock on its own
connection (its own session), so it already refuses concurrent calls on
the same instance: the in-process syncInProgress flag and its
isSyncInProgress getter were redundant and are removed. syncAll becomes
private: syncs start only through startSync.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
- Validate the environment at startup (src/config): required variables
must be present, HIBOUTIK_BASE_URL must be HTTPS.
- Global ValidationPipe (whitelist, forbidNonWhitelisted, transform).
- Helmet, CORS disabled unless CORS_ORIGINS is set, TRUST_PROXY support,
HTTP header/request timeouts, shutdown hooks.
- Rate limiting: 60 req/min/IP globally, 5 req/min on POST /sync.
- AllExceptionsFilter: unexpected errors are logged (message + stack,
never the raw error object) and answered with a generic 500.
- Postgres advisory lock (dedicated connection) so only one
synchronization runs at a time across all instances; released by
Postgres itself if the process dies.
- Fix the Jest moduleNameMapper (unescaped dot also matched .cjs files).
- Document the input validation and security rules in CLAUDE.md.
- Add class-validator, class-transformer, helmet and @nestjs/throttler;
npm audit fix refreshed package-lock.json.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A successful (2xx) answer from Hiboutik is now the truth, even an empty
list: records missing from it are deleted. The "empty list means API
issue" safety nets are removed.
Deletion stays safe because a failed call deletes nothing: HTTP errors
throw, and the new HiboutikClient.getList (used by getAllPages and the
non-paginated lists) also throws on a 2xx body that is not a list, so a
malformed answer is never mistaken for an empty list. A 2xx without body
is an empty list.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>