* chore: add CLAUDE.md * chore: pr feedback * chore: contributing guideline rules * chore: pr feedback (cherry picked from commit fc30140e6ec313c94405e7ca4fbab8096dc1c6c7)
4.8 KiB
4.8 KiB
server/AGENTS.md
NestJS + Kysely + PostgreSQL, layered controllers → services → repositories. The process model and job system are described in docs/docs/developer/architecture.mdx.
Rules
- ESM with no relative imports (lint-enforced). Import as
src/...jsandtest/...js. - Every route has
@Authenticated(...)and@Endpoint({ summary, description, history }); the globalAuthGuardthrows otherwise. Controllers only call one service method with(auth, dto). - Services
extends BaseServiceand declare no constructor; repositories are available asthis.xRepository. Callawait this.requireAccess({ auth, permission, ids })before touching a resource. - Repositories are the only I/O layer and return rows. Services interpret results and throw
BadRequestExceptionetc.; repositories never throw HTTP exceptions. - DTOs are Zod schemas via
nestjs-zod(createZodDto,.describe(),.meta({ id })) insrc/dtos/, withmapX()mappers beside them. - Enums (
JobName,QueueName,Permission,BootstrapEventPriority) live insrc/enum.ts; job payload unions insrc/types.ts. Admin-editable config and itsdefaultsare insrc/dtos/config.dto.ts; env vars insrc/dtos/env.dto.ts. - Codegen tasks (
migrations,sql,sync-open-api) run fromdist/, somise //server:buildfirst.
Skills
- Add an endpoint: define the request/response DTOs in
src/dtos/with a mapper, add the service method (callrequireAccessfirst), add the controller method with@Authenticatedand@EndpointwhosehistoryusesHistoryBuilder(.added('vN'),.beta,.stable,.deprecated), then add a*.controller.spec.tscase and regenerate the API clients (root skill). - Add a repository: update all of
BaseServiceconstructor andBaseService.create()insrc/services/base.service.ts,BASE_SERVICE_DEPENDENCIES,src/repositories/index.ts, andnewTestService/getMocksintest/utils.ts. Kysely repos inject@InjectKysely() db: Kysely<DB>. - Change the schema: edit
src/schema/tables/*.table.ts(@immich/sql-toolsdecorators plusUpdatedAtTrigger,UpdateIdColumn,PrimaryGeneratedUuidV7Columnfromsrc/decorators.ts). A new table is registered in bothImmichDatabaseand theDBinterface insrc/schema/index.ts. Then, with Postgres reachable atDB_URL(defaultpostgres://postgres:postgres@localhost:5432/immich):mise //server:build,mise //server:migrations generate <Name>, review the file, move it intosrc/schema/migrations/, andmise //server:migrations sync-order.ORDERis append-only. CI jobs "SQL Schema Checks" and "Verify migration ORDER" fail otherwise.mise //server:migrations revertmoves the file out ofsrc/; restore it and rebuild afterwards. - Regenerate SQL snapshots: repository methods decorated with
@GenerateSql({ params: [DummyValue.UUID] })have their SQL written tosrc/queries/*.sql. After changing one, runmise //server:buildthenmise //:sqlwith Postgres reachable. Use@ChunkedArray/@ChunkedSeton methods that take ID lists. CI job "SQL Schema Checks" fails if the snapshots are stale. - Add a background job: add the
JobNametosrc/enum.tsand its payload to theJobItemunion insrc/types.ts, then exactly one@OnJob({ name, queue })handler on a service (startup fails on zero or two). Chaining to follow-up jobs lives inJobService.onDone. In-process events use@OnEventwithEventMapinsrc/repositories/event.repository.ts. - Unit tests:
src/**/*.spec.ts.const { sut, mocks } = newTestService(XService)fromtest/utils.tsauto-mocks every repository; strict mocks fail when called without an implementation. Fixtures intest/fixtures/*.stub.ts, builders intest/small.factory.ts(factory.auth(), ...) andtest/factories/. Run one file withmise //server:test --run src/services/album.service.spec.ts. - Controller tests:
src/controllers/*.controller.spec.tsusecontrollerSetup(Controller, [{ provide: Service, useValue: mockBaseService(Service) }]), which boots Nest with the real validation pipe andAuthGuard, and assert with supertest. They cover validation and auth wiring, not business logic. - Medium tests:
test/medium/specs/{services,repositories,sync,exif,workflow}/. They need Docker:globalSetupstarts a Postgres testcontainer, migrates a template database, and each spec file gets a clone. BuildnewMediumService(Service, { database, real: [AssetRepository, ...], mock: [JobRepository, ...] })fromtest/medium.factory.ts; it returns{ sut, ctx }wherectx.newUser(),ctx.newAsset(),ctx.get(Repo)create data and reach repositories. Exif specs read frome2e/test-assets; workflow specs needmise //:pluginsfirst. Run withmise //server:test-medium --run test/medium/specs/services/album.service.spec.ts.