@simple-auth-kit/cli 1.4.2 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (133) hide show
  1. package/package.json +1 -1
  2. package/registry/combos/express-drizzle/shared/src/{auth-core-error.middleware.ts → middleware/auth-core-error.middleware.ts} +17 -4
  3. package/registry/combos/express-drizzle/shared/src/{auth.middleware.ts → middleware/auth.middleware.ts} +23 -7
  4. package/registry/combos/{express-prisma/shared/src → express-drizzle/shared/src/middleware}/response-envelope.middleware.ts +15 -3
  5. package/registry/combos/express-drizzle/shared/src/{oauth.repository.ts → repositories/oauth.repository.ts} +32 -9
  6. package/registry/combos/express-drizzle/shared/src/repositories/password-reset.repository.ts +59 -0
  7. package/registry/combos/express-drizzle/shared/src/{session.repository.ts → repositories/session.repository.ts} +52 -14
  8. package/registry/combos/express-drizzle/shared/src/{two-factor.repository.ts → repositories/two-factor.repository.ts} +20 -6
  9. package/registry/combos/express-drizzle/shared/src/request-context.ts +1 -1
  10. package/registry/combos/express-drizzle/shared/src/routers/auth.router.ts +395 -0
  11. package/registry/combos/express-drizzle/variants/base/src/create-auth-app.ts +52 -19
  12. package/registry/combos/express-drizzle/variants/base/src/{authz.middleware.ts → middleware/authz.middleware.ts} +15 -7
  13. package/registry/combos/express-drizzle/variants/base/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +28 -10
  14. package/registry/combos/express-drizzle/variants/base/src/{rbac.repository.ts → repositories/rbac.repository.ts} +239 -48
  15. package/registry/combos/express-drizzle/variants/base/src/routers/admin.router.ts +497 -0
  16. package/registry/combos/express-drizzle/variants/base/src/{auth.service.ts → services/auth.service.ts} +480 -109
  17. package/registry/combos/express-drizzle/variants/workspaces/src/create-auth-app.ts +69 -23
  18. package/registry/combos/express-drizzle/variants/workspaces/src/{authz.middleware.ts → middleware/authz.middleware.ts} +32 -12
  19. package/registry/combos/express-drizzle/variants/workspaces/src/openapi-workspace.ts +147 -28
  20. package/registry/combos/express-drizzle/variants/workspaces/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +30 -11
  21. package/registry/combos/express-drizzle/variants/workspaces/src/{rbac.repository.ts → repositories/rbac.repository.ts} +319 -70
  22. package/registry/combos/express-drizzle/variants/workspaces/src/{workspace.repository.ts → repositories/workspace.repository.ts} +127 -30
  23. package/registry/combos/express-drizzle/variants/workspaces/src/routers/admin.router.ts +534 -0
  24. package/registry/combos/express-drizzle/variants/workspaces/src/routers/workspace.router.ts +194 -0
  25. package/registry/combos/express-drizzle/variants/workspaces/src/{auth.service.ts → services/auth.service.ts} +544 -122
  26. package/registry/combos/express-prisma/shared/src/{auth-core-error.middleware.ts → middleware/auth-core-error.middleware.ts} +17 -4
  27. package/registry/combos/express-prisma/shared/src/{auth.middleware.ts → middleware/auth.middleware.ts} +19 -6
  28. package/registry/combos/{express-drizzle/shared/src → express-prisma/shared/src/middleware}/response-envelope.middleware.ts +15 -3
  29. package/registry/combos/express-prisma/shared/src/{oauth.repository.ts → repositories/oauth.repository.ts} +27 -9
  30. package/registry/combos/express-prisma/shared/src/repositories/password-reset.repository.ts +55 -0
  31. package/registry/combos/express-prisma/shared/src/{session.repository.ts → repositories/session.repository.ts} +30 -9
  32. package/registry/combos/express-prisma/shared/src/{two-factor.repository.ts → repositories/two-factor.repository.ts} +11 -4
  33. package/registry/combos/express-prisma/shared/src/request-context.ts +1 -1
  34. package/registry/combos/express-prisma/shared/src/routers/auth.router.ts +398 -0
  35. package/registry/combos/express-prisma/variants/base/src/create-auth-app.ts +57 -22
  36. package/registry/combos/express-prisma/variants/base/src/{authz.middleware.ts → middleware/authz.middleware.ts} +15 -7
  37. package/registry/combos/express-prisma/variants/base/src/rbac.defaults.ts +67 -19
  38. package/registry/combos/express-prisma/variants/base/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +16 -5
  39. package/registry/combos/express-prisma/variants/base/src/{rbac.repository.ts → repositories/rbac.repository.ts} +199 -48
  40. package/registry/combos/express-prisma/variants/base/src/routers/admin.router.ts +508 -0
  41. package/registry/combos/express-prisma/variants/base/src/seed.ts +78 -20
  42. package/registry/combos/express-prisma/variants/base/src/{auth.service.ts → services/auth.service.ts} +454 -105
  43. package/registry/combos/express-prisma/variants/workspaces/src/create-auth-app.ts +81 -26
  44. package/registry/combos/express-prisma/variants/workspaces/src/{authz.middleware.ts → middleware/authz.middleware.ts} +32 -12
  45. package/registry/combos/express-prisma/variants/workspaces/src/openapi-workspace.ts +147 -28
  46. package/registry/combos/express-prisma/variants/workspaces/src/rbac.defaults.ts +73 -21
  47. package/registry/combos/express-prisma/variants/workspaces/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +16 -5
  48. package/registry/combos/express-prisma/variants/workspaces/src/{rbac.repository.ts → repositories/rbac.repository.ts} +272 -64
  49. package/registry/combos/express-prisma/variants/workspaces/src/{workspace.repository.ts → repositories/workspace.repository.ts} +86 -23
  50. package/registry/combos/express-prisma/variants/workspaces/src/routers/admin.router.ts +545 -0
  51. package/registry/combos/express-prisma/variants/workspaces/src/routers/workspace.router.ts +194 -0
  52. package/registry/combos/express-prisma/variants/workspaces/src/seed.ts +109 -29
  53. package/registry/combos/express-prisma/variants/workspaces/src/{auth.service.ts → services/auth.service.ts} +518 -119
  54. package/registry/combos/nestjs-drizzle/shared/src/{auth.controller.ts → controllers/auth.controller.ts} +6 -6
  55. package/registry/combos/nestjs-drizzle/shared/src/{auth-core-error.filter.ts → filters/auth-core-error.filter.ts} +29 -5
  56. package/registry/combos/nestjs-drizzle/shared/src/{ability.guard.ts → guards/ability.guard.ts} +20 -7
  57. package/registry/combos/nestjs-drizzle/shared/src/{auth.guard.ts → guards/auth.guard.ts} +19 -7
  58. package/registry/combos/{nestjs-prisma/shared/src → nestjs-drizzle/shared/src/interceptors}/response.interceptor.ts +10 -2
  59. package/registry/combos/nestjs-drizzle/shared/src/repositories/oauth.repository.ts +63 -0
  60. package/registry/combos/nestjs-drizzle/shared/src/repositories/password-reset.repository.ts +59 -0
  61. package/registry/combos/nestjs-drizzle/shared/src/{session.repository.ts → repositories/session.repository.ts} +52 -14
  62. package/registry/combos/nestjs-drizzle/shared/src/{two-factor.repository.ts → repositories/two-factor.repository.ts} +21 -7
  63. package/registry/combos/nestjs-drizzle/shared/src/request-context.ts +1 -1
  64. package/registry/combos/nestjs-drizzle/test/prove-cycle.ts +1097 -307
  65. package/registry/combos/nestjs-drizzle/variants/base/src/auth.module.ts +44 -20
  66. package/registry/combos/nestjs-drizzle/variants/base/src/{admin.controller.ts → controllers/admin.controller.ts} +228 -62
  67. package/registry/combos/nestjs-drizzle/variants/base/src/{authz.guard.ts → guards/authz.guard.ts} +12 -5
  68. package/registry/combos/nestjs-drizzle/variants/base/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +27 -10
  69. package/registry/combos/nestjs-drizzle/variants/base/src/{rbac.repository.ts → repositories/rbac.repository.ts} +249 -51
  70. package/registry/combos/nestjs-drizzle/variants/base/src/{auth.service.ts → services/auth.service.ts} +499 -116
  71. package/registry/combos/nestjs-drizzle/variants/workspaces/src/auth.module.ts +48 -22
  72. package/registry/combos/nestjs-drizzle/variants/workspaces/src/{admin.controller.ts → controllers/admin.controller.ts} +269 -70
  73. package/registry/combos/nestjs-drizzle/variants/workspaces/src/{workspace.controller.ts → controllers/workspace.controller.ts} +93 -26
  74. package/registry/combos/nestjs-drizzle/variants/workspaces/src/{authz.guard.ts → guards/authz.guard.ts} +26 -8
  75. package/registry/combos/nestjs-drizzle/variants/workspaces/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +29 -11
  76. package/registry/combos/nestjs-drizzle/variants/workspaces/src/{rbac.repository.ts → repositories/rbac.repository.ts} +323 -71
  77. package/registry/combos/nestjs-drizzle/variants/workspaces/src/{workspace.repository.ts → repositories/workspace.repository.ts} +131 -30
  78. package/registry/combos/nestjs-drizzle/variants/workspaces/src/services/auth.service.ts +988 -0
  79. package/registry/combos/nestjs-prisma/shared/src/{auth.controller.ts → controllers/auth.controller.ts} +6 -6
  80. package/registry/combos/nestjs-prisma/shared/src/{auth-core-error.filter.ts → filters/auth-core-error.filter.ts} +29 -5
  81. package/registry/combos/nestjs-prisma/shared/src/{ability.guard.ts → guards/ability.guard.ts} +20 -7
  82. package/registry/combos/nestjs-prisma/shared/src/{auth.guard.ts → guards/auth.guard.ts} +19 -7
  83. package/registry/combos/{nestjs-drizzle/shared/src → nestjs-prisma/shared/src/interceptors}/response.interceptor.ts +10 -2
  84. package/registry/combos/nestjs-prisma/shared/src/{oauth.repository.ts → repositories/oauth.repository.ts} +25 -8
  85. package/registry/combos/nestjs-prisma/shared/src/repositories/password-reset.repository.ts +55 -0
  86. package/registry/combos/nestjs-prisma/shared/src/{session.repository.ts → repositories/session.repository.ts} +30 -9
  87. package/registry/combos/nestjs-prisma/shared/src/{two-factor.repository.ts → repositories/two-factor.repository.ts} +11 -4
  88. package/registry/combos/nestjs-prisma/shared/src/request-context.ts +1 -1
  89. package/registry/combos/nestjs-prisma/test/prove-cycle.ts +1097 -307
  90. package/registry/combos/nestjs-prisma/variants/base/src/auth.module.ts +59 -28
  91. package/registry/combos/nestjs-prisma/variants/base/src/{admin.controller.ts → controllers/admin.controller.ts} +469 -110
  92. package/registry/combos/nestjs-prisma/variants/base/src/{audit-log.gateway.ts → gateways/audit-log.gateway.ts} +22 -6
  93. package/registry/combos/nestjs-prisma/variants/base/src/{authz.guard.ts → guards/authz.guard.ts} +12 -5
  94. package/registry/combos/nestjs-prisma/variants/base/src/rbac.defaults.ts +98 -27
  95. package/registry/combos/nestjs-prisma/variants/base/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +16 -5
  96. package/registry/combos/nestjs-prisma/variants/base/src/{country.repository.ts → repositories/country.repository.ts} +98 -25
  97. package/registry/combos/nestjs-prisma/variants/base/src/{customer.repository.ts → repositories/customer.repository.ts} +106 -27
  98. package/registry/combos/nestjs-prisma/variants/base/src/{language.repository.ts → repositories/language.repository.ts} +93 -25
  99. package/registry/combos/nestjs-prisma/variants/base/src/{rbac.repository.ts → repositories/rbac.repository.ts} +229 -55
  100. package/registry/combos/nestjs-prisma/variants/base/src/seed.ts +78 -20
  101. package/registry/combos/nestjs-prisma/variants/base/src/services/auth.service.ts +1064 -0
  102. package/registry/combos/nestjs-prisma/variants/workspaces/src/auth.module.ts +55 -27
  103. package/registry/combos/nestjs-prisma/variants/workspaces/src/{admin.controller.ts → controllers/admin.controller.ts} +544 -128
  104. package/registry/combos/nestjs-prisma/variants/workspaces/src/{workspace.controller.ts → controllers/workspace.controller.ts} +93 -26
  105. package/registry/combos/nestjs-prisma/variants/workspaces/src/{authz.guard.ts → guards/authz.guard.ts} +26 -8
  106. package/registry/combos/nestjs-prisma/variants/workspaces/src/rbac.defaults.ts +104 -29
  107. package/registry/combos/nestjs-prisma/variants/workspaces/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +16 -5
  108. package/registry/combos/nestjs-prisma/variants/workspaces/src/{country.repository.ts → repositories/country.repository.ts} +127 -26
  109. package/registry/combos/nestjs-prisma/variants/workspaces/src/{customer.repository.ts → repositories/customer.repository.ts} +137 -28
  110. package/registry/combos/nestjs-prisma/variants/workspaces/src/repositories/language.repository.ts +243 -0
  111. package/registry/combos/nestjs-prisma/variants/workspaces/src/{rbac.repository.ts → repositories/rbac.repository.ts} +314 -74
  112. package/registry/combos/nestjs-prisma/variants/workspaces/src/{workspace.repository.ts → repositories/workspace.repository.ts} +94 -24
  113. package/registry/combos/nestjs-prisma/variants/workspaces/src/seed.ts +109 -29
  114. package/registry/combos/nestjs-prisma/variants/workspaces/src/services/auth.service.ts +961 -0
  115. package/registry.json +6 -4
  116. package/simple-auth-kit.ts +582 -124
  117. package/registry/combos/express-drizzle/shared/src/auth.router.ts +0 -266
  118. package/registry/combos/express-drizzle/shared/src/password-reset.repository.ts +0 -35
  119. package/registry/combos/express-drizzle/variants/base/src/admin.router.ts +0 -307
  120. package/registry/combos/express-drizzle/variants/workspaces/src/admin.router.ts +0 -323
  121. package/registry/combos/express-drizzle/variants/workspaces/src/workspace.router.ts +0 -123
  122. package/registry/combos/express-prisma/shared/src/auth.router.ts +0 -269
  123. package/registry/combos/express-prisma/shared/src/password-reset.repository.ts +0 -29
  124. package/registry/combos/express-prisma/variants/base/src/admin.router.ts +0 -314
  125. package/registry/combos/express-prisma/variants/workspaces/src/admin.router.ts +0 -330
  126. package/registry/combos/express-prisma/variants/workspaces/src/workspace.router.ts +0 -123
  127. package/registry/combos/nestjs-drizzle/shared/src/oauth.repository.ts +0 -36
  128. package/registry/combos/nestjs-drizzle/shared/src/password-reset.repository.ts +0 -29
  129. package/registry/combos/nestjs-drizzle/variants/workspaces/src/auth.service.ts +0 -553
  130. package/registry/combos/nestjs-prisma/shared/src/password-reset.repository.ts +0 -29
  131. package/registry/combos/nestjs-prisma/variants/base/src/auth.service.ts +0 -618
  132. package/registry/combos/nestjs-prisma/variants/workspaces/src/auth.service.ts +0 -509
  133. package/registry/combos/nestjs-prisma/variants/workspaces/src/language.repository.ts +0 -143
@@ -2,27 +2,34 @@ import { Pool } from "pg";
2
2
  import { drizzle } from "drizzle-orm/node-postgres";
3
3
  import { DynamicModule, Module } from "@nestjs/common";
4
4
  import { APP_FILTER, APP_INTERCEPTOR } from "@nestjs/core";
5
- import { AbilityGuard } from "./ability.guard.js";
6
- import { AdminController } from "./admin.controller.js";
7
- import { AuditLogRepository } from "./audit-log.repository.js";
8
- import { AuthCoreErrorFilter } from "./auth-core-error.filter.js";
5
+ import { AbilityGuard } from "./guards/ability.guard.js";
6
+ import { AdminController } from "./controllers/admin.controller.js";
7
+ import { AuditLogRepository } from "./repositories/audit-log.repository.js";
8
+ import { AuthCoreErrorFilter } from "./filters/auth-core-error.filter.js";
9
9
  import { AUTH_CONFIG, AuthConfig, defaultAuthConfig } from "./auth.config.js";
10
- import { AuthController } from "./auth.controller.js";
11
- import { AuthGuard } from "./auth.guard.js";
12
- import { AuthService } from "./auth.service.js";
13
- import { AuthzGuard } from "./authz.guard.js";
10
+ import { AuthController } from "./controllers/auth.controller.js";
11
+ import { AuthGuard } from "./guards/auth.guard.js";
12
+ import { AuthService } from "./services/auth.service.js";
13
+ import { AuthzGuard } from "./guards/authz.guard.js";
14
14
  import { DRIZZLE_DB } from "./db.js";
15
15
  import { KeyProviderService } from "./key-provider.js";
16
- import { OAuthRepository } from "./oauth.repository.js";
17
- import { PasswordResetRepository } from "./password-reset.repository.js";
18
- import { InMemoryPermissionCacheStore, PERMISSION_CACHE_STORE, PermissionCache } from "./permission-cache.js";
19
- import { InMemoryRateLimitStore, RATE_LIMIT_STORE } from "./rate-limit.store.js";
20
- import { RbacRepository } from "./rbac.repository.js";
21
- import { ResponseInterceptor } from "./response.interceptor.js";
16
+ import { OAuthRepository } from "./repositories/oauth.repository.js";
17
+ import { PasswordResetRepository } from "./repositories/password-reset.repository.js";
18
+ import {
19
+ InMemoryPermissionCacheStore,
20
+ PERMISSION_CACHE_STORE,
21
+ PermissionCache,
22
+ } from "./permission-cache.js";
23
+ import {
24
+ InMemoryRateLimitStore,
25
+ RATE_LIMIT_STORE,
26
+ } from "./rate-limit.store.js";
27
+ import { RbacRepository } from "./repositories/rbac.repository.js";
28
+ import { ResponseInterceptor } from "./interceptors/response.interceptor.js";
22
29
  import { assertEveryRouteDeclaresATier } from "./route-tiers.js";
23
30
  import * as schema from "./schema.js";
24
- import { SessionRepository } from "./session.repository.js";
25
- import { TwoFactorRepository } from "./two-factor.repository.js";
31
+ import { SessionRepository } from "./repositories/session.repository.js";
32
+ import { TwoFactorRepository } from "./repositories/two-factor.repository.js";
26
33
 
27
34
  /**
28
35
  * Every controller this library ships, in one place. It is both what the module registers and
@@ -39,7 +46,10 @@ export class AuthModule {
39
46
  // stops the process here, by name, rather than shipping as an open endpoint nobody noticed.
40
47
  // Nothing above this line allocates a connection pool or a port, so a failed boot leaves
41
48
  // nothing behind.
42
- assertEveryRouteDeclaresATier(AUTH_CONTROLLERS, { authentication: AuthGuard, ability: AbilityGuard });
49
+ assertEveryRouteDeclaresATier(AUTH_CONTROLLERS, {
50
+ authentication: AuthGuard,
51
+ ability: AbilityGuard,
52
+ });
43
53
 
44
54
  const pool = new Pool({ connectionString: process.env["DATABASE_URL"] });
45
55
  const db = drizzle(pool, { schema });
@@ -60,14 +70,21 @@ export class AuthModule {
60
70
  { provide: DRIZZLE_DB, useValue: db },
61
71
  // The cache seam. Swap the store for a Redis-backed one by passing `permissionCacheStore`
62
72
  // to forRoot — nothing in this library's source changes. Keys are namespaced simpleauthkit:authz:*.
63
- { provide: PERMISSION_CACHE_STORE, useValue: config.permissionCacheStore ?? new InMemoryPermissionCacheStore() },
73
+ {
74
+ provide: PERMISSION_CACHE_STORE,
75
+ useValue:
76
+ config.permissionCacheStore ?? new InMemoryPermissionCacheStore(),
77
+ },
64
78
  PermissionCache,
65
79
  { provide: APP_FILTER, useClass: AuthCoreErrorFilter },
66
80
  { provide: APP_INTERCEPTOR, useClass: ResponseInterceptor },
67
81
  AuditLogRepository,
68
82
  SessionRepository,
69
83
  KeyProviderService,
70
- { provide: RATE_LIMIT_STORE, useValue: config.rateLimitStore ?? new InMemoryRateLimitStore() },
84
+ {
85
+ provide: RATE_LIMIT_STORE,
86
+ useValue: config.rateLimitStore ?? new InMemoryRateLimitStore(),
87
+ },
71
88
  RbacRepository,
72
89
  TwoFactorRepository,
73
90
  OAuthRepository,
@@ -77,7 +94,14 @@ export class AuthModule {
77
94
  AuthzGuard,
78
95
  AbilityGuard,
79
96
  ],
80
- exports: [AuthService, AuthGuard, AuthzGuard, AbilityGuard, PermissionCache, DRIZZLE_DB],
97
+ exports: [
98
+ AuthService,
99
+ AuthGuard,
100
+ AuthzGuard,
101
+ AbilityGuard,
102
+ PermissionCache,
103
+ DRIZZLE_DB,
104
+ ],
81
105
  };
82
106
  }
83
107
  }
@@ -1,11 +1,33 @@
1
- import { BadRequestException, Body, Controller, Delete, ForbiddenException, Get, Inject, Param, Patch, Post, Query, Req, UseGuards } from "@nestjs/common";
2
- import { ApiBearerAuth, ApiBody, ApiOperation, ApiParam, ApiQuery, ApiResponse, ApiTags } from "@nestjs/swagger";
1
+ import {
2
+ BadRequestException,
3
+ Body,
4
+ Controller,
5
+ Delete,
6
+ ForbiddenException,
7
+ Get,
8
+ Inject,
9
+ Param,
10
+ Patch,
11
+ Post,
12
+ Query,
13
+ Req,
14
+ UseGuards,
15
+ } from "@nestjs/common";
16
+ import {
17
+ ApiBearerAuth,
18
+ ApiBody,
19
+ ApiOperation,
20
+ ApiParam,
21
+ ApiQuery,
22
+ ApiResponse,
23
+ ApiTags,
24
+ } from "@nestjs/swagger";
3
25
  import type { Request } from "express";
4
- import { AbilityGuard } from "./ability.guard.js";
5
- import { AuthGuard } from "./auth.guard.js";
6
- import { AuthService } from "./auth.service.js";
7
- import { AuthzGuard } from "./authz.guard.js";
8
- import { CheckAbility } from "./route-tiers.js";
26
+ import { AbilityGuard } from "../guards/ability.guard.js";
27
+ import { AuthGuard } from "../guards/auth.guard.js";
28
+ import { AuthService } from "../services/auth.service.js";
29
+ import { AuthzGuard } from "../guards/authz.guard.js";
30
+ import { CheckAbility } from "../route-tiers.js";
9
31
  import {
10
32
  AssignRoleDto,
11
33
  AttachPermissionDto,
@@ -21,15 +43,21 @@ import {
21
43
  RoleSummaryDto,
22
44
  UpdateRoleDto,
23
45
  UpdateUserDto,
24
- } from "./dto/auth.dto.js";
25
- import { AuditLogListResponseDto, UserListResponseDto, UserSummaryDto } from "./dto/admin.dto.js";
46
+ } from "../dto/auth.dto.js";
47
+ import {
48
+ AuditLogListResponseDto,
49
+ UserListResponseDto,
50
+ UserSummaryDto,
51
+ } from "../dto/admin.dto.js";
26
52
 
27
53
  function requireString(value: unknown, field: string): string {
28
- if (typeof value !== "string" || value.length === 0) throw new BadRequestException(`${field} is required`);
54
+ if (typeof value !== "string" || value.length === 0)
55
+ throw new BadRequestException(`${field} is required`);
29
56
  return value;
30
57
  }
31
58
 
32
- const optionalString = (value: unknown): string | undefined => (typeof value === "string" ? value : undefined);
59
+ const optionalString = (value: unknown): string | undefined =>
60
+ typeof value === "string" ? value : undefined;
33
61
 
34
62
  /**
35
63
  * Administration of the whole deployment. Authority is a permission, never a role name:
@@ -56,17 +84,41 @@ export class AdminController {
56
84
  @Get("users")
57
85
  @CheckAbility("users:read")
58
86
  @ApiOperation({ summary: "[admin] List users" })
59
- @ApiQuery({ name: "search", required: false, description: "Email substring match" })
60
- @ApiQuery({ name: "page", required: false, description: "1-indexed. Defaults to 1." })
61
- @ApiQuery({ name: "limit", required: false, description: "Defaults to 25, capped at 100." })
87
+ @ApiQuery({
88
+ name: "search",
89
+ required: false,
90
+ description: "Email substring match",
91
+ })
92
+ @ApiQuery({
93
+ name: "page",
94
+ required: false,
95
+ description: "1-indexed. Defaults to 1.",
96
+ })
97
+ @ApiQuery({
98
+ name: "limit",
99
+ required: false,
100
+ description: "Defaults to 25, capped at 100.",
101
+ })
62
102
  @ApiResponse({ status: 200, type: UserListResponseDto })
63
- async listUsers(@Query("search") search?: string, @Query("page") page?: string, @Query("limit") limit?: string) {
64
- return this.auth.listUsers({ search, page: page ? Number(page) : undefined, limit: limit ? Number(limit) : undefined });
103
+ async listUsers(
104
+ @Query("search") search?: string,
105
+ @Query("page") page?: string,
106
+ @Query("limit") limit?: string,
107
+ ) {
108
+ return this.auth.listUsers({
109
+ search,
110
+ page: page ? Number(page) : undefined,
111
+ limit: limit ? Number(limit) : undefined,
112
+ });
65
113
  }
66
114
 
67
115
  @Post("users")
68
116
  @CheckAbility("users:manage")
69
- @ApiOperation({ summary: "[admin] Create a user directly", description: "No invitation email — the account is usable immediately with the password given here." })
117
+ @ApiOperation({
118
+ summary: "[admin] Create a user directly",
119
+ description:
120
+ "No invitation email — the account is usable immediately with the password given here.",
121
+ })
70
122
  @ApiBody({ type: CreateUserDto })
71
123
  @ApiResponse({ status: 201, type: UserSummaryDto })
72
124
  async createUser(@Body() body: Record<string, unknown>, @Req() req: Request) {
@@ -82,8 +134,13 @@ export class AdminController {
82
134
  dob: optionalString(body.dob),
83
135
  gender: optionalString(body.gender),
84
136
  joinedDate: optionalString(body.joinedDate),
85
- isActive: typeof body.isActive === "boolean" ? body.isActive : undefined,
86
- roles: Array.isArray(body.roles) ? body.roles.filter((role): role is string => typeof role === "string") : undefined,
137
+ isActive:
138
+ typeof body.isActive === "boolean" ? body.isActive : undefined,
139
+ roles: Array.isArray(body.roles)
140
+ ? body.roles.filter(
141
+ (role): role is string => typeof role === "string",
142
+ )
143
+ : undefined,
87
144
  },
88
145
  req.auth!.sub,
89
146
  );
@@ -100,17 +157,27 @@ export class AdminController {
100
157
 
101
158
  @Patch("users/:userId")
102
159
  @CheckAbility("users:manage")
103
- @ApiOperation({ summary: "[admin] Edit a user's profile", description: "Profile fields only — email is the login identifier and is not editable here." })
160
+ @ApiOperation({
161
+ summary: "[admin] Edit a user's profile",
162
+ description:
163
+ "Profile fields only — email is the login identifier and is not editable here.",
164
+ })
104
165
  @ApiParam({ name: "userId" })
105
166
  @ApiBody({ type: UpdateUserDto })
106
167
  @ApiResponse({ status: 200, type: UserSummaryDto })
107
- async updateUser(@Param("userId") userId: string, @Body() body: Record<string, unknown>, @Req() req: Request) {
168
+ async updateUser(
169
+ @Param("userId") userId: string,
170
+ @Body() body: Record<string, unknown>,
171
+ @Req() req: Request,
172
+ ) {
108
173
  return this.auth.updateUser(
109
174
  userId,
110
175
  {
111
- firstName: body.firstName === null ? null : optionalString(body.firstName),
176
+ firstName:
177
+ body.firstName === null ? null : optionalString(body.firstName),
112
178
  lastName: body.lastName === null ? null : optionalString(body.lastName),
113
- displayName: body.displayName === null ? null : optionalString(body.displayName),
179
+ displayName:
180
+ body.displayName === null ? null : optionalString(body.displayName),
114
181
  phone: body.phone === null ? null : optionalString(body.phone),
115
182
  username: body.username === null ? null : optionalString(body.username),
116
183
  photo: body.photo === null ? null : optionalString(body.photo),
@@ -126,14 +193,24 @@ export class AdminController {
126
193
  @CheckAbility("users:manage")
127
194
  @ApiOperation({
128
195
  summary: "[admin] Delete a user",
129
- description: "Soft-delete: the row survives for audit purposes, stops appearing in listings, and can no longer authenticate.",
196
+ description:
197
+ "Soft-delete: the row survives for audit purposes, stops appearing in listings, and can no longer authenticate.",
130
198
  })
131
199
  @ApiParam({ name: "userId" })
132
200
  @ApiBody({ type: DeleteReasonDto, required: false })
133
201
  @ApiResponse({ status: 200, type: OkResponseDto })
134
- async deleteUser(@Param("userId") userId: string, @Body() body: Record<string, unknown>, @Req() req: Request) {
135
- if (userId === req.auth!.sub) throw new ForbiddenException("cannot delete your own account");
136
- await this.auth.deleteUser(userId, req.auth!.sub, optionalString(body?.reason));
202
+ async deleteUser(
203
+ @Param("userId") userId: string,
204
+ @Body() body: Record<string, unknown>,
205
+ @Req() req: Request,
206
+ ) {
207
+ if (userId === req.auth!.sub)
208
+ throw new ForbiddenException("cannot delete your own account");
209
+ await this.auth.deleteUser(
210
+ userId,
211
+ req.auth!.sub,
212
+ optionalString(body?.reason),
213
+ );
137
214
  return { ok: true };
138
215
  }
139
216
 
@@ -141,11 +218,31 @@ export class AdminController {
141
218
  @CheckAbility("audit-log:read")
142
219
  @ApiOperation({ summary: "[admin] List audit log entries, newest first" })
143
220
  @ApiQuery({ name: "userId", required: false })
144
- @ApiQuery({ name: "action", required: false, description: "AuditEvent discriminant, e.g. 'role_assigned'" })
145
- @ApiQuery({ name: "since", required: false, description: "ISO 8601 timestamp" })
146
- @ApiQuery({ name: "until", required: false, description: "ISO 8601 timestamp" })
147
- @ApiQuery({ name: "page", required: false, description: "1-indexed. Defaults to 1." })
148
- @ApiQuery({ name: "limit", required: false, description: "Defaults to 25, capped at 100." })
221
+ @ApiQuery({
222
+ name: "action",
223
+ required: false,
224
+ description: "AuditEvent discriminant, e.g. 'role_assigned'",
225
+ })
226
+ @ApiQuery({
227
+ name: "since",
228
+ required: false,
229
+ description: "ISO 8601 timestamp",
230
+ })
231
+ @ApiQuery({
232
+ name: "until",
233
+ required: false,
234
+ description: "ISO 8601 timestamp",
235
+ })
236
+ @ApiQuery({
237
+ name: "page",
238
+ required: false,
239
+ description: "1-indexed. Defaults to 1.",
240
+ })
241
+ @ApiQuery({
242
+ name: "limit",
243
+ required: false,
244
+ description: "Defaults to 25, capped at 100.",
245
+ })
149
246
  @ApiResponse({ status: 200, type: AuditLogListResponseDto })
150
247
  async listAuditLog(
151
248
  @Query("userId") userId?: string,
@@ -155,14 +252,22 @@ export class AdminController {
155
252
  @Query("page") page?: string,
156
253
  @Query("limit") limit?: string,
157
254
  ) {
158
- return this.auth.listAuditLog({ userId, action, since, until, page: page ? Number(page) : undefined, limit: limit ? Number(limit) : undefined });
255
+ return this.auth.listAuditLog({
256
+ userId,
257
+ action,
258
+ since,
259
+ until,
260
+ page: page ? Number(page) : undefined,
261
+ limit: limit ? Number(limit) : undefined,
262
+ });
159
263
  }
160
264
 
161
265
  @Get("permissions")
162
266
  @CheckAbility("permissions:read")
163
267
  @ApiOperation({
164
268
  summary: "[admin] List the permission catalog",
165
- description: "Grouped and ordered for a permission matrix. This is the whole vocabulary of the deployment — there is nothing about authorization outside these rows.",
269
+ description:
270
+ "Grouped and ordered for a permission matrix. This is the whole vocabulary of the deployment — there is nothing about authorization outside these rows.",
166
271
  })
167
272
  @ApiResponse({ status: 200, type: PermissionListResponseDto })
168
273
  async listPermissions() {
@@ -179,17 +284,23 @@ export class AdminController {
179
284
  })
180
285
  @ApiBody({ type: DefinePermissionDto })
181
286
  @ApiResponse({ status: 201, type: PermissionSummaryDto })
182
- async definePermission(@Body() body: Record<string, unknown>, @Req() req: Request) {
287
+ async definePermission(
288
+ @Body() body: Record<string, unknown>,
289
+ @Req() req: Request,
290
+ ) {
183
291
  return this.auth.definePermission(
184
292
  {
185
293
  slug: requireString(body.slug, "slug"),
186
294
  name: optionalString(body.name),
187
295
  displayName: optionalString(body.displayName),
188
- description: body.description === null ? null : optionalString(body.description),
296
+ description:
297
+ body.description === null ? null : optionalString(body.description),
189
298
  group: optionalString(body.group),
190
- groupOrder: typeof body.groupOrder === "number" ? body.groupOrder : undefined,
299
+ groupOrder:
300
+ typeof body.groupOrder === "number" ? body.groupOrder : undefined,
191
301
  order: typeof body.order === "number" ? body.order : undefined,
192
- isActive: typeof body.isActive === "boolean" ? body.isActive : undefined,
302
+ isActive:
303
+ typeof body.isActive === "boolean" ? body.isActive : undefined,
193
304
  },
194
305
  req.auth!.sub,
195
306
  );
@@ -215,8 +326,10 @@ export class AdminController {
215
326
  name: optionalString(body.name),
216
327
  displayName: optionalString(body.displayName),
217
328
  description: optionalString(body.description) ?? null,
218
- isDefault: typeof body.isDefault === "boolean" ? body.isDefault : undefined,
219
- isActive: typeof body.isActive === "boolean" ? body.isActive : undefined,
329
+ isDefault:
330
+ typeof body.isDefault === "boolean" ? body.isDefault : undefined,
331
+ isActive:
332
+ typeof body.isActive === "boolean" ? body.isActive : undefined,
220
333
  },
221
334
  req.auth!.sub,
222
335
  );
@@ -228,15 +341,22 @@ export class AdminController {
228
341
  @ApiParam({ name: "roleId" })
229
342
  @ApiBody({ type: UpdateRoleDto })
230
343
  @ApiResponse({ status: 200, type: RoleSummaryDto })
231
- async updateRole(@Param("roleId") roleId: string, @Body() body: Record<string, unknown>, @Req() req: Request) {
344
+ async updateRole(
345
+ @Param("roleId") roleId: string,
346
+ @Body() body: Record<string, unknown>,
347
+ @Req() req: Request,
348
+ ) {
232
349
  return this.auth.updateRole(
233
350
  roleId,
234
351
  {
235
352
  name: optionalString(body.name),
236
353
  displayName: optionalString(body.displayName),
237
- description: body.description === null ? null : optionalString(body.description),
238
- isDefault: typeof body.isDefault === "boolean" ? body.isDefault : undefined,
239
- isActive: typeof body.isActive === "boolean" ? body.isActive : undefined,
354
+ description:
355
+ body.description === null ? null : optionalString(body.description),
356
+ isDefault:
357
+ typeof body.isDefault === "boolean" ? body.isDefault : undefined,
358
+ isActive:
359
+ typeof body.isActive === "boolean" ? body.isActive : undefined,
240
360
  },
241
361
  req.auth!.sub,
242
362
  );
@@ -246,13 +366,22 @@ export class AdminController {
246
366
  @CheckAbility("roles:manage")
247
367
  @ApiOperation({
248
368
  summary: "[admin] Delete a role",
249
- description: "Soft-delete: existing assignments are left in place rather than cascade-deleted, and the role simply stops being resolved.",
369
+ description:
370
+ "Soft-delete: existing assignments are left in place rather than cascade-deleted, and the role simply stops being resolved.",
250
371
  })
251
372
  @ApiParam({ name: "roleId" })
252
373
  @ApiBody({ type: DeleteReasonDto, required: false })
253
374
  @ApiResponse({ status: 200, type: OkResponseDto })
254
- async deleteRole(@Param("roleId") roleId: string, @Body() body: Record<string, unknown>, @Req() req: Request) {
255
- await this.auth.deleteRole(roleId, req.auth!.sub, optionalString(body?.reason));
375
+ async deleteRole(
376
+ @Param("roleId") roleId: string,
377
+ @Body() body: Record<string, unknown>,
378
+ @Req() req: Request,
379
+ ) {
380
+ await this.auth.deleteRole(
381
+ roleId,
382
+ req.auth!.sub,
383
+ optionalString(body?.reason),
384
+ );
256
385
  return { ok: true };
257
386
  }
258
387
 
@@ -262,8 +391,16 @@ export class AdminController {
262
391
  @ApiParam({ name: "roleId" })
263
392
  @ApiBody({ type: AttachPermissionDto })
264
393
  @ApiResponse({ status: 201, type: OkResponseDto })
265
- async attachPermissionToRole(@Param("roleId") roleId: string, @Body() body: Record<string, unknown>, @Req() req: Request) {
266
- await this.auth.attachPermissionToRole(roleId, requireString(body.permission, "permission"), req.auth!.sub);
394
+ async attachPermissionToRole(
395
+ @Param("roleId") roleId: string,
396
+ @Body() body: Record<string, unknown>,
397
+ @Req() req: Request,
398
+ ) {
399
+ await this.auth.attachPermissionToRole(
400
+ roleId,
401
+ requireString(body.permission, "permission"),
402
+ req.auth!.sub,
403
+ );
267
404
  return { ok: true };
268
405
  }
269
406
 
@@ -273,7 +410,10 @@ export class AdminController {
273
410
  @ApiParam({ name: "userId" })
274
411
  @ApiBody({ type: AssignRoleDto })
275
412
  @ApiResponse({ status: 201, type: OkResponseDto })
276
- async assignRole(@Param("userId") userId: string, @Body() body: Record<string, unknown>) {
413
+ async assignRole(
414
+ @Param("userId") userId: string,
415
+ @Body() body: Record<string, unknown>,
416
+ ) {
277
417
  await this.auth.assignRole(userId, requireString(body.role, "role"));
278
418
  return { ok: true };
279
419
  }
@@ -284,43 +424,66 @@ export class AdminController {
284
424
  @ApiParam({ name: "userId" })
285
425
  @ApiParam({ name: "roleSlug" })
286
426
  @ApiResponse({ status: 201, type: OkResponseDto })
287
- async revokeRole(@Param("userId") userId: string, @Param("roleSlug") roleSlug: string, @Req() req: Request) {
427
+ async revokeRole(
428
+ @Param("userId") userId: string,
429
+ @Param("roleSlug") roleSlug: string,
430
+ @Req() req: Request,
431
+ ) {
288
432
  // Revoking your own `admin` would strip the very permission that let you call this,
289
433
  // with no route back in. Assigning to yourself is fine — it can't lock anyone out.
290
- if (userId === req.auth!.sub) throw new ForbiddenException("cannot change your own roles");
434
+ if (userId === req.auth!.sub)
435
+ throw new ForbiddenException("cannot change your own roles");
291
436
  await this.auth.revokeRole(userId, roleSlug);
292
437
  return { ok: true };
293
438
  }
294
439
 
295
440
  @Post("users/:userId/permissions")
296
441
  @CheckAbility("permissions:grant")
297
- @ApiOperation({ summary: "[admin] Grant a permission directly to a user, bypassing roles" })
442
+ @ApiOperation({
443
+ summary: "[admin] Grant a permission directly to a user, bypassing roles",
444
+ })
298
445
  @ApiParam({ name: "userId" })
299
446
  @ApiBody({ type: GrantPermissionDto })
300
447
  @ApiResponse({ status: 201, type: OkResponseDto })
301
- async grantPermission(@Param("userId") userId: string, @Body() body: Record<string, unknown>, @Req() req: Request) {
302
- await this.auth.grantPermission(userId, requireString(body.permission, "permission"), req.auth!.sub);
448
+ async grantPermission(
449
+ @Param("userId") userId: string,
450
+ @Body() body: Record<string, unknown>,
451
+ @Req() req: Request,
452
+ ) {
453
+ await this.auth.grantPermission(
454
+ userId,
455
+ requireString(body.permission, "permission"),
456
+ req.auth!.sub,
457
+ );
303
458
  return { ok: true };
304
459
  }
305
460
 
306
461
  @Post("users/:userId/permissions/:permissionSlug/revoke")
307
462
  @CheckAbility("permissions:grant")
308
- @ApiOperation({ summary: "[admin] Revoke a direct permission grant from a user" })
463
+ @ApiOperation({
464
+ summary: "[admin] Revoke a direct permission grant from a user",
465
+ })
309
466
  @ApiParam({ name: "userId" })
310
467
  @ApiParam({ name: "permissionSlug" })
311
468
  @ApiResponse({ status: 201, type: OkResponseDto })
312
- async revokePermission(@Param("userId") userId: string, @Param("permissionSlug") permissionSlug: string) {
469
+ async revokePermission(
470
+ @Param("userId") userId: string,
471
+ @Param("permissionSlug") permissionSlug: string,
472
+ ) {
313
473
  await this.auth.revokePermission(userId, permissionSlug);
314
474
  return { ok: true };
315
475
  }
316
476
 
317
477
  @Post("users/:userId/block")
318
478
  @CheckAbility("users:block")
319
- @ApiOperation({ summary: "[admin] Block a user, revoking all their sessions immediately" })
479
+ @ApiOperation({
480
+ summary: "[admin] Block a user, revoking all their sessions immediately",
481
+ })
320
482
  @ApiParam({ name: "userId" })
321
483
  @ApiResponse({ status: 201, type: OkResponseDto })
322
484
  async block(@Param("userId") userId: string, @Req() req: Request) {
323
- if (userId === req.auth!.sub) throw new ForbiddenException("cannot block your own account");
485
+ if (userId === req.auth!.sub)
486
+ throw new ForbiddenException("cannot block your own account");
324
487
  await this.auth.block(userId, { userId: req.auth!.sub, ip: req.ip });
325
488
  return { ok: true };
326
489
  }
@@ -338,13 +501,16 @@ export class AdminController {
338
501
  @Post("users/:userId/deactivate")
339
502
  @CheckAbility("users:block")
340
503
  @ApiOperation({
341
- summary: "[admin] Deactivate a user, revoking all their sessions immediately",
342
- description: "Distinct from block/unblock — a routine administrative toggle, not a security action. Both independently deny login.",
504
+ summary:
505
+ "[admin] Deactivate a user, revoking all their sessions immediately",
506
+ description:
507
+ "Distinct from block/unblock — a routine administrative toggle, not a security action. Both independently deny login.",
343
508
  })
344
509
  @ApiParam({ name: "userId" })
345
510
  @ApiResponse({ status: 201, type: OkResponseDto })
346
511
  async deactivate(@Param("userId") userId: string, @Req() req: Request) {
347
- if (userId === req.auth!.sub) throw new ForbiddenException("cannot deactivate your own account");
512
+ if (userId === req.auth!.sub)
513
+ throw new ForbiddenException("cannot deactivate your own account");
348
514
  await this.auth.deactivate(userId, { userId: req.auth!.sub, ip: req.ip });
349
515
  return { ok: true };
350
516
  }
@@ -1,7 +1,12 @@
1
- import { CanActivate, ExecutionContext, Inject, Injectable } from "@nestjs/common";
2
- import { defineAbilitiesFor } from "./ability.js";
3
- import { PermissionCache } from "./permission-cache.js";
4
- import { RbacRepository } from "./rbac.repository.js";
1
+ import {
2
+ CanActivate,
3
+ ExecutionContext,
4
+ Inject,
5
+ Injectable,
6
+ } from "@nestjs/common";
7
+ import { defineAbilitiesFor } from "../ability.js";
8
+ import { PermissionCache } from "../permission-cache.js";
9
+ import { RbacRepository } from "../repositories/rbac.repository.js";
5
10
 
6
11
  /**
7
12
  * Roles and permissions are global to this deployment: a user has one set, and it applies
@@ -45,7 +50,9 @@ export class AuthzGuard implements CanActivate {
45
50
  const req = context.switchToHttp().getRequest();
46
51
  if (req.auth) {
47
52
  const userId = req.auth.sub as string;
48
- req.authz = (await this.cache.resolve<AuthzContext>(userId, () => this.rbac.resolveAuthzContext(userId))) ?? { roles: [], permissions: [] };
53
+ req.authz = (await this.cache.resolve<AuthzContext>(userId, () =>
54
+ this.rbac.resolveAuthzContext(userId),
55
+ )) ?? { roles: [], permissions: [] };
49
56
  req.ability = defineAbilitiesFor(req.authz.permissions);
50
57
  }
51
58
  return true;
@@ -1,10 +1,15 @@
1
1
  import { Inject, Injectable } from "@nestjs/common";
2
2
  import { and, count, desc, eq, gte, lte, type SQL } from "drizzle-orm";
3
3
  import type { AuditEvent } from "@/lib/auth/core/types.js";
4
- import { DRIZZLE_DB, type Database } from "./db.js";
5
- import { buildPageMeta, normalizeLimit, normalizePage, type Paginated } from "./pagination.js";
6
- import { auditLogs } from "./schema.js";
7
- import { toIdOrNull, toIdOrUndefined } from "./id.helper.js";
4
+ import { DRIZZLE_DB, type Database } from "../db.js";
5
+ import {
6
+ buildPageMeta,
7
+ normalizeLimit,
8
+ normalizePage,
9
+ type Paginated,
10
+ } from "../pagination.js";
11
+ import { auditLogs } from "../schema.js";
12
+ import { toIdOrNull, toIdOrUndefined } from "../id.helper.js";
8
13
 
9
14
  export interface AuditLogEntry {
10
15
  id: string;
@@ -46,14 +51,20 @@ export function toAuditLogEntry(row: AuditLogRow): AuditLogEntry {
46
51
  /** "role_assigned" -> "Role assigned". Derived rather than passed in, so core never has to carry display text. */
47
52
  export function humanizeAction(action: string): string {
48
53
  const words = action.split("_");
49
- return [words[0].charAt(0).toUpperCase() + words[0].slice(1), ...words.slice(1)].join(" ");
54
+ return [
55
+ words[0].charAt(0).toUpperCase() + words[0].slice(1),
56
+ ...words.slice(1),
57
+ ].join(" ");
50
58
  }
51
59
 
52
60
  @Injectable()
53
61
  export class AuditLogRepository {
54
62
  constructor(@Inject(DRIZZLE_DB) private readonly db: Database) {}
55
63
 
56
- async append(event: AuditEvent, opts: { remarks?: string } = {}): Promise<void> {
64
+ async append(
65
+ event: AuditEvent,
66
+ opts: { remarks?: string } = {},
67
+ ): Promise<void> {
57
68
  await this.db.insert(auditLogs).values({
58
69
  action: event.type,
59
70
  name: humanizeAction(event.type),
@@ -70,10 +81,13 @@ export class AuditLogRepository {
70
81
 
71
82
  const conditions: SQL[] = [];
72
83
  const userIdBig = toIdOrUndefined(filter.userId);
73
- if (userIdBig !== undefined) conditions.push(eq(auditLogs.userId, userIdBig));
84
+ if (userIdBig !== undefined)
85
+ conditions.push(eq(auditLogs.userId, userIdBig));
74
86
  if (filter.action) conditions.push(eq(auditLogs.action, filter.action));
75
- if (filter.since) conditions.push(gte(auditLogs.createdAt, new Date(filter.since)));
76
- if (filter.until) conditions.push(lte(auditLogs.createdAt, new Date(filter.until)));
87
+ if (filter.since)
88
+ conditions.push(gte(auditLogs.createdAt, new Date(filter.since)));
89
+ if (filter.until)
90
+ conditions.push(lte(auditLogs.createdAt, new Date(filter.until)));
77
91
  const where = conditions.length ? and(...conditions) : undefined;
78
92
 
79
93
  const rows = await this.db
@@ -83,7 +97,10 @@ export class AuditLogRepository {
83
97
  .orderBy(desc(auditLogs.createdAt), desc(auditLogs.id))
84
98
  .limit(limit)
85
99
  .offset((page - 1) * limit);
86
- const [{ value: total }] = await this.db.select({ value: count() }).from(auditLogs).where(where);
100
+ const [{ value: total }] = await this.db
101
+ .select({ value: count() })
102
+ .from(auditLogs)
103
+ .where(where);
87
104
 
88
105
  return { items: rows, meta: buildPageMeta(page, limit, total) };
89
106
  }