CodeVetter · Repo Unpack · Data flow
immich-app/immich
Data enters through Express controllers guarded by a global AuthGuard that produces an AuthDto (user, session, shared link, or API key context) via AuthService.authenticate. Uploads stream through a custom multer storage engine into StorageCore-managed folders under IMMICH_MEDIA_LOCATION, then asset rows are written through repositories over Kysely and BullMQ jobs drive metadata extraction (exiftool), thumbnail generation (sharp), transcoding (ffmpeg), and ML inference via the FastAPI service. All cross-service coordination goes through a typed EventRepository (emit/onEvent) and websocket pushes to clients; mobile/web pull changes via the sync endpoints and socket.io.
- Every HTTP request passes the global AuthGuard: @Authenticated metadata (public/setup/admin/sharedLink/permission) is read via Reflector, then AuthService.authenticate resolves credentials from headers (Authorization bearer, x-api-key), cookies (ImmichCookie), or query params (shared-link key/slug) into request.user.
- Upload path: FileUploadInterceptor runs a custom multer storage (_handleFile/_removeFile) that writes assetData/sidecarData fields to a per-user upload folder computed by AssetMediaService.getUploadFolder (StorageCore.getNestedFolder(StorageFolder.Upload, userId, uuid)) with sanitized uuid filenames.
- After a successful upload, AssetMediaService.uploadAsset creates the asset row (sha1 checksum column for dedupe), upserts sidecar/exif records, queues JobName.AssetExtractMetadata on the metadataExtraction queue, and emits the AssetCreate event; a Postgres checksum-constraint violation is converted into a DUPLICATE response.
- Job transport is BullMQ: services call jobRepository.queue/queueAll (Redis), the microservices worker's per-queue bullmq Worker re-emits each job as a 'JobRun' event, and EventRepository dispatches to the method decorated with matching @OnJob({name, queue}).
- Metadata extraction (MetadataService.handleMetadataExtraction) reads the asset, calls exiftool via media/metadata repositories, reverse-geocodes GPS through mapRepository when enabled, upserts exif/tags, and emits AssetMetadataExtracted which downstream services use to queue thumbnail/smart-search/face jobs.
- ML inference flows server→HTTP: MachineLearningRepository builds typed pipeline requests (ClipVisualRequest, FacialRecognitionRequest, OcrRequest) and POSTs multipart to the FastAPI /predict endpoint, where get_entries parses the pipeline and ModelCache loads ONNX models.
- Database access is repository-per-domain (asset.repository, user.repository, ~55 repositories in repositories/index.ts) over a shared KyselyModule connection; services never hold a Kysely handle directly and reach repos through BaseService.
- System configuration is a JSON document stored in Postgres (system_metadata/system_config), loaded through utils/config.ts getConfig/updateConfig, and broadcast via ConfigInit/ConfigValidate/ConfigUpdate events that services consume with @OnEvent (e.g., QueueService.updateConcurrency).
- Realtime output uses socket.io: WebSocketAdapter attaches a @socket.io/redis-adapter pub/sub pair so API and microservices processes can both emit; WebsocketRepository.setAuthFn authenticates socket handshakes through AuthService.authenticate.
- Storage layout is owned by StorageCore: StorageFolder-based paths (upload, library, thumbs/thumbnails, encoded-video, profile, backups) under IMMICH_MEDIA_LOCATION, with file moves tracked through move.repository so DB paths stay consistent.
- API contract flows outward: Nest swagger decorators (@Endpoint, @Authenticated, DTO zod schemas) generate immich-openapi-specs.json, which oazapfts turns into packages/sdk fetch-client consumed by web (@immich/sdk), @immich/cli, e2e, and the mobile generated openapi package.
- Mobile state sync is pull-based delta sync: sync.controller exposes stream/checkpoint endpoints backed by SyncRepository + SyncCheckpointRepository so the Flutter app's drift sqlite database can incrementally reconcile server state. (inferred)