Skip to main content

The prompt

title: "Scaffold the log module and its resources"
context: |
  Build a `log` domain: the append-only audit trail of every request and
  exception the app handles — one flat row per request/response cycle (level,
  message, acting user identity, HTTP context, exception + stack). The
  `@ooneex/logger` package already defines the storage layer — `LogsEntity`
  (table `app_logs`), `LogsRepository`, `LogsDatabase`, a migration, the
  `LevelType`/`ELogLevel` levels, and `FindByCriteriaType`/`FindByResultType`
  query contracts — align to those; this module's real work is the admin-only
  read surface, not a new schema. Logs are written by the framework (logging
  middleware) on each request, never created/edited/deleted via a public
  endpoint, and are the most sensitive data in the system, so every read/list is
  strictly admin-only with no owner-based access. If `modules/log/` already
  exists, this work is void — do not run.
goal: |
  Create the `log` module + needed resources, admin-only paginated/filterable read.

  ## Notes
  - If `modules/log/` exists, STOP and report. Else `/module:create` log, then
    build each resource via its `*:create` skill (`--module=log`), respecting
    controllers → services → repositories → entities, registering all.
  - Model `Log` against `@ooneex/logger`: mirror `LogsEntity` (table `app_logs`),
    import every type/class from the package rather than re-declaring, and prefer
    consuming its `LogsRepository`/`LogsDatabase` over hand-rolled persistence:

        import {
          type FindByCriteriaType,
          type FindByResultType,
          type LevelType,
          ELogLevel,
          LogsDatabase,
          LogsEntity,
          LogsRepository,
          type RequestFileLogType,
        } from "@ooneex/logger";
  - Mind the level type split: the entity `level` column is `LevelType` (the
    `${ELogLevel}` string union); the `FindByCriteriaType.level` filter is the
    `ELogLevel` enum. Keep both as the package declares them; do not unify.
  - Append-only — diverge from the standard shape: NO `updatedAt`, NO `deletedAt`,
    NO update/delete endpoints. Retention/purge is an out-of-band admin job.
  - Acting-user identity (`userId`, `email`, `firstName`, `lastName`) is a
    denormalized snapshot of plain columns, NOT a `@ManyToOne(() => User)`, so the
    record survives the user's rename/deletion and lives in a separate database.
  - Own datasource: `LogsDatabase` reads `LOGS_DATABASE_URL` with
    `synchronize: false`; do not register `Log` on the default datasource.
  - Judge each resource; create the justified, skip the rest with a reason.
    Defaults: entity + repository always (mirror `LogsEntity`/`LogsRepository`:
    `create`, `find(id)`, paginated `findBy(criteria)` ordered `date DESC`,
    honoring package indexes); service + controller for read-one and list/search
    via `FindByCriteriaType` + pagination, both admin-only (no public
    create/update/delete); permission always (admin-only on every read/list, no
    owner check, reuse the permission service); migration only if applicable
    (mirror the package migration on the logs datasource); queue a strong fit (log
    write must not block the response — push onto a queue if one exists, else
    write inline but never let logging break the request); event/translation/
    storage/seed/workflow skip.
  - Redact known credential fields (passwords, tokens, auth headers) before
    persisting; keep sensitive fields admin-only.
  - Throw typed exceptions (e.g. `LogNotFoundException`), never return null.

  ### Data Model
  - `Log` is relation-free and flat — one row per request/response cycle, no
    foreign keys; user identity is denormalized plain columns, never a `User`
    relation, on its own database.
dod: |
  - [ ] Aborts with a report if `modules/log/` exists
  - [ ] `log` module created and registered into the app and `SharedModule`
  - [ ] `Log` entity aligned to `LogsEntity` (table `app_logs`) with fields:
    `level` (stored as `LevelType`; the `findBy` filter takes the `ELogLevel`
    enum), `message` (nullable), `date`, `userId`/`email`/`firstName`/`lastName`
    (nullable, denormalized), `status` (nullable), `exceptionName` (nullable),
    `stackTrace` (jsonb, nullable), `ip` (nullable), `method` (`HttpMethodType`,
    nullable), `path` (nullable), `version` (nullable), `userAgent`/`referer`
    (nullable), `params`/`queries` (jsonb, nullable), `payload` (jsonb, nullable),
    `host`/`lang` (nullable), `requestHeaders`/`cookies`/`form` (jsonb, nullable),
    `files` (`RequestFileLogType[]`, jsonb, nullable), `responseHeaders`/
    `responseData` (jsonb, nullable); NO `updatedAt`, NO `deletedAt`
  - [ ] Logs created by the framework, read/searched by admins; `findBy` filters
    by level, userId, email, names, status, exceptionName, method, path,
    paginated and ordered `date DESC`
  - [ ] Reading and listing both rejected for non-admins; no public
    create/update/delete endpoint; identity denormalized, not a `User` relation
  - [ ] Logs on their own `LOGS_DATABASE_URL` datasource, separate from the app DB
  - [ ] Sensitive fields admin-only; known secrets redacted before persisting
  - [ ] If queues are used, log writes run through a queue and never break the
    request on failure
  - [ ] Unneeded resources skipped and reported with a reason
  - [ ] `bun run fmt`, `bun run lint`, `bun run test` pass from the root