diff --git a/.cursor/rules/adminjs.mdc b/.cursor/rules/adminjs.mdc index e12d316..075e843 100644 --- a/.cursor/rules/adminjs.mdc +++ b/.cursor/rules/adminjs.mdc @@ -17,10 +17,13 @@ Ops panel at `/admin` (`backend/src/admin/`). Resources are a **manual allowlist 3. **Renamed / removed model** → update or remove the matching resource (broken `getModelByName` breaks `/admin` boot). 4. **New secret fields** (hashes, tokens, share tokens) → hide via `isVisible: false` (list/filter/show/edit). 5. **Catalog-like reference data** → list/show/edit only; disable `new` / `delete` / `bulkDelete`. -6. Skip pure join/cursor tables unless ops need them (`LabCaseUserReadState`, `LabCaseUserTabReadState`, working-hours, `LabCaseAttachment`). +6. **Composite `@@id` only** (no single `@id`) → **do not register** — AdminJS list returns 500 (`Resource does not have an id property`). Examples: `LabCaseDetail`, `MembershipPermission`, read-cursor tables. +7. Skip pure join/cursor tables unless ops need them and they have a single id. Auth: `ADMINJS_EMAIL` / `ADMINJS_PASSWORD` — production login disabled if password missing or still `admin123`. +Production Docker: `ADMIN_JS_TMP_DIR=/app/adminjs-tmp` (not `.adminjs`) so `components.bundle.js` can be written/served. + ## Secrets to hide `passwordHash`, session `token`/`refreshToken`, invite/OTP `tokenHash`/`codeHash`, `LabCase.accessToken`. diff --git a/AGENTS.md b/AGENTS.md index 31e1cb6..61e54cb 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -92,7 +92,7 @@ backend/src/ Errors: `AppException` + `ErrorCode` → frontend `getUserFacingError()`. Unexpected 500s: GlitchTip (`SENTRY_DSN`). Never throw raw strings for user-facing failures. -**AdminJS:** Manual resource allowlist in `backend/src/admin/resources.ts`. **Whenever `schema.prisma` changes**, update AdminJS resources in the same task (new/renamed/removed models, hide secrets). Rule: `.cursor/rules/adminjs.mdc`. +**AdminJS:** Manual resource allowlist in `backend/src/admin/resources.ts`. **Whenever `schema.prisma` changes**, update AdminJS resources in the same task (new/renamed/removed models, hide secrets). Models with only composite `@@id` must not be registered (list 500). Rule: `.cursor/rules/adminjs.mdc`. ## Git & commits diff --git a/backend/Dockerfile b/backend/Dockerfile index 1ab9c07..1a943ee 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -53,7 +53,11 @@ COPY docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh # Windows git/build context may use CRLF; strip before chmod (fixes dumb-init "No such file or directory"). RUN sed -i 's/\r$//' /usr/local/bin/docker-entrypoint.sh && chmod +x /usr/local/bin/docker-entrypoint.sh -RUN mkdir -p /app/logs && \ +# AdminJS writes/serves the custom-components bundle here. Avoid the default +# ".adminjs" path — Express sendFile + nosniff can 500 on dot-directories. +ENV ADMIN_JS_TMP_DIR=/app/adminjs-tmp + +RUN mkdir -p /app/logs /app/adminjs-tmp && \ chown -R dyolink:nodejs /app USER dyolink diff --git a/backend/src/admin/resources.ts b/backend/src/admin/resources.ts index b5d889f..5470c71 100644 --- a/backend/src/admin/resources.ts +++ b/backend/src/admin/resources.ts @@ -76,9 +76,7 @@ export function buildAdminResources(prisma: PrismaService): AdminResource[] { resource(prisma, 'Permission', { navigation: { name: 'Orgs & access', icon: 'Settings' }, }), - resource(prisma, 'MembershipPermission', { - navigation: { name: 'Orgs & access', icon: 'Settings' }, - }), + // MembershipPermission omitted: composite @@id — AdminJS list 500s without a single id resource(prisma, 'Feature', { navigation: { name: 'Orgs & access', icon: 'Settings' }, }), @@ -119,9 +117,8 @@ export function buildAdminResources(prisma: PrismaService): AdminResource[] { resource(prisma, 'LabCaseLine', { navigation: { name: 'Lab', icon: 'Archive' }, }), - resource(prisma, 'LabCaseDetail', { - navigation: { name: 'Lab', icon: 'Archive' }, - }), + // LabCaseDetail omitted: composite @@id — AdminJS list 500s without a single id + // (inspect LabCase + TreatmentDetail instead) resource(prisma, 'LabCaseSend', { navigation: { name: 'Lab', icon: 'Archive' }, }), diff --git a/infrastructure/backend.prod.env.example b/infrastructure/backend.prod.env.example index 9da4678..e6841ec 100644 --- a/infrastructure/backend.prod.env.example +++ b/infrastructure/backend.prod.env.example @@ -18,6 +18,8 @@ FRONTEND_URL=https://nudentic.ir # Change these — the code defaults are only for local development. ADMINJS_EMAIL=admin@nudentic.ir ADMINJS_PASSWORD=CHANGE_ME_STRONG_ADMINJS_PASSWORD +# Optional override; Docker image sets /app/adminjs-tmp (do not use ".adminjs"). +# ADMIN_JS_TMP_DIR=/app/adminjs-tmp # Required for HTTPS — browsers reject Secure cookies over plain HTTP COOKIE_SECURE=true diff --git a/infrastructure/backend.staging.env.example b/infrastructure/backend.staging.env.example index 7fef5f3..6c72e9c 100644 --- a/infrastructure/backend.staging.env.example +++ b/infrastructure/backend.staging.env.example @@ -15,6 +15,8 @@ FRONTEND_URL=https://wixur.ir # AdminJS at https://wixur.ir/admin (nginx proxies /admin to the API). ADMINJS_EMAIL=admin@wixur.ir ADMINJS_PASSWORD=CHANGE_ME_STRONG_ADMINJS_PASSWORD +# Optional override; Docker image sets /app/adminjs-tmp (do not use ".adminjs"). +# ADMIN_JS_TMP_DIR=/app/adminjs-tmp # TLS is terminated on Windows nginx :443 — cookies must be Secure COOKIE_SECURE=true diff --git a/infrastructure/docker-compose.prod.yml b/infrastructure/docker-compose.prod.yml index 31748ff..0dea857 100644 --- a/infrastructure/docker-compose.prod.yml +++ b/infrastructure/docker-compose.prod.yml @@ -48,6 +48,7 @@ services: NODE_ENV: production TZ: UTC PORT: "3000" + ADMIN_JS_TMP_DIR: /app/adminjs-tmp SENTRY_ENVIRONMENT: production SENTRY_RELEASE: ${TAG:-latest} expose: diff --git a/infrastructure/docker-compose.registry.yml b/infrastructure/docker-compose.registry.yml index b420351..0170187 100644 --- a/infrastructure/docker-compose.registry.yml +++ b/infrastructure/docker-compose.registry.yml @@ -47,6 +47,7 @@ services: NODE_ENV: production TZ: UTC PORT: "3000" + ADMIN_JS_TMP_DIR: /app/adminjs-tmp SENTRY_ENVIRONMENT: staging SENTRY_RELEASE: ${IMAGE_TAG:-latest} expose: diff --git a/infrastructure/docker-compose.staging.yml b/infrastructure/docker-compose.staging.yml index cd1c9fb..b562d08 100644 --- a/infrastructure/docker-compose.staging.yml +++ b/infrastructure/docker-compose.staging.yml @@ -43,6 +43,7 @@ services: NODE_ENV: production TZ: UTC PORT: "3000" + ADMIN_JS_TMP_DIR: /app/adminjs-tmp SENTRY_ENVIRONMENT: staging expose: - "3000"