Files
API/CLAUDE.md
T
Paddy 3ea9e630ed Migrate the API to native ESM and vitest; pin Node 26
Prep PR for the admin auth module (docs/plan-admin-auth.md step 1).
better-auth 1.7 ships ESM only, so the API moves off CommonJS:

- "type": "module", module nodenext, target ES2024, .js suffixes on all
  relative imports, require('mariadb'|'cors') replaced by imports, and
  export= packages (winston, app-root-path, bcrypt) consumed via default
  imports. The logger now uses appRoot.path explicitly.
- TypeScript 5.9, @types/node 26, tslint removed. Node 26 pinned via
  engines and .nvmrc (Plesk runs 26).
- Jest 28 + ts-jest replaced by vitest 5. Eight test files depend on
  hoisted module mocks with static imports and resetModules + require,
  which Jest's ESM mode does not support; vitest keeps them nearly
  verbatim. Coverage via @vitest/coverage-v8 (lcov), Sonar generic report
  via vitest-sonar-reporter, so sonar-project.properties is unchanged.
  vitest.config.ts sets FEEDBACK_IP_SALT so the suite passes without a
  local .env.
- dotenv 8 -> 16 and axios 0.24 -> 1.x: their old typings are not
  resolvable under nodenext.
- autoCommit: false dropped from the pool configs; it is not a mariadb
  connector option and was silently ignored.

tsc clean, 96/96 tests green, compiled app boots and serves /, /docs and
CORS under Node ESM.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-05 16:02:26 +02:00

3.5 KiB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Commands

npm run build    # Compile TypeScript → dist/
npm run start    # Build and start (tsc && node ./dist/app.js)
npm run debug    # Start with DEBUG=* environment variable
npm run test     # Run the vitest suite once with coverage (lcov + testResults/sonar-report.xml)
npm run test:watch  # vitest in watch mode

Run a single test file:

npx vitest run test/some.test.ts

Architecture

Express.js REST API in TypeScript with a service-oriented layering. Domains: Calendar (events, users) and Feedback (concert feedback forms, mounted at /feedback, backed by its own FEEDBACK_DB — see src/models/feedback/: public submission flow, admin CRUD, reporting, and a Salesforce newsletter-sync integration).

Request path:

  1. app.ts mounts Calendar.router.ts at /calendar
  2. Calendar.router.ts delegates to events.router.ts and users.router.ts
  3. Routers call services; services call the MariaDB pool in Calendar.db.ts

Key layers:

Layer Location
Router src/models/calendar/Calendar.router.ts, …/events/events.router.ts, …/users/users.router.ts
Services …/events/events.service.ts, …/users/users.service.ts, …/events/credentials.service.ts, …/events/icalgenerator.service.ts
DB pool src/models/calendar/Calendar.db.ts (MariaDB, pool size 5)
Shared src/common/ (base route class, nodemailer wrapper), src/middleware/logger.ts (Winston)

Auth model: Users must have a @nachklang.art email. After activation they receive a session token (30-day window); the token hash + IP are stored in the DB. Credentials for non-user calendar access (MEMBER_CREDENTIAL, CHOIR_CREDENTIAL, MANAGEMENT_CREDENTIAL) come from .env.

Event versioning: Events have a companion event_versions table. events.service.ts manages writes to both.

Calendar types and IDs: public (1), members (2), management (3), choir (4), birthdays (5). credentials.service.ts enforces which session/credential can read each calendar.

iCal export: icalgenerator.service.ts converts DB events to RFC 5545 format; reachable via GET /calendar/events/{calendar}/ical.

API docs: Swagger UI served at /docs, generated from JSDoc annotations in the router files.

Environment

Copy .env.example (or create .env) with:

PORT=
DB_HOST=
DB_USER=
DB_PASSWORD=
CALENDAR_DB=
FEEDBACK_DB=
FEEDBACK_IP_SALT=
FEEDBACK_RATE_LIMIT_MAX=
FEEDBACK_RATE_LIMIT_WINDOW_MIN=
SALESFORCE_ENABLED=
SALESFORCE_API_URL=
SALESFORCE_CLIENT_ID=
SALESFORCE_CLIENT_SECRET=
EMAIL_HOST=
EMAIL_USERNAME=
EMAIL_PASSWORD=
MEMBER_CREDENTIAL=
CHOIR_CREDENTIAL=
MANAGEMENT_CREDENTIAL=

TypeScript / module system

The API runs on Node 26 (engines in package.json, .nvmrc; Plesk runs 26 too) and is native ESM ("type": "module", module: nodenext, target ES2024, strict mode, compiled output in ./dist, inline source maps). Consequences:

  • Relative imports carry the .js suffix (import {x} from "./x.js") even though the source file is .ts.
  • CommonJS dependencies are consumed via default imports (import mariadb from "mariadb", import cors from "cors", import winston from "winston"), never require().
  • Tests run with vitest directly against .ts sources; import describe/it/expect/vi from vitest explicitly (no globals). Module mocks use vi.mock(...) with the same .js-suffixed paths as the imports.