@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
@@ -1,15 +1,25 @@
1
1
  import { Inject } from "@nestjs/common";
2
- import { OnGatewayConnection, WebSocketGateway, WebSocketServer } from "@nestjs/websockets";
2
+ import {
3
+ OnGatewayConnection,
4
+ WebSocketGateway,
5
+ WebSocketServer,
6
+ } from "@nestjs/websockets";
3
7
  import type { Server, Socket } from "socket.io";
4
8
  import { verifyAccessToken } from "@/lib/auth/core/token-service.js";
5
- import { AuditLogEntry, AuditLogRepository } from "./audit-log.repository.js";
6
- import { KeyProviderService } from "./key-provider.js";
7
- import { SessionRepository } from "./session.repository.js";
9
+ import {
10
+ AuditLogEntry,
11
+ AuditLogRepository,
12
+ } from "../repositories/audit-log.repository.js";
13
+ import { KeyProviderService } from "../key-provider.js";
14
+ import { SessionRepository } from "../repositories/session.repository.js";
8
15
 
9
16
  // socket.io clients disagree on where a bearer token travels: `auth.token` (the socket.io-native
10
17
  // slot), an Authorization header, or a `token` query param. Accept all three, first present wins.
11
18
  function extractToken(client: Socket): string | undefined {
12
- const raw = client.handshake.auth?.["token"] ?? client.handshake.headers.authorization ?? client.handshake.query?.["token"];
19
+ const raw =
20
+ client.handshake.auth?.["token"] ??
21
+ client.handshake.headers.authorization ??
22
+ client.handshake.query?.["token"];
13
23
  const value = Array.isArray(raw) ? raw[0] : raw;
14
24
  if (typeof value !== "string" || value.length === 0) return undefined;
15
25
  return value.startsWith("Bearer ") ? value.slice("Bearer ".length) : value;
@@ -38,7 +48,13 @@ export class AuditLogGateway implements OnGatewayConnection {
38
48
  return;
39
49
  }
40
50
  try {
41
- await verifyAccessToken({ secret: this.keys.secret, isDenylisted: (jti) => this.sessions.isDenylisted(jti) }, token);
51
+ await verifyAccessToken(
52
+ {
53
+ secret: this.keys.secret,
54
+ isDenylisted: (jti) => this.sessions.isDenylisted(jti),
55
+ },
56
+ token,
57
+ );
42
58
  } catch {
43
59
  client.disconnect(true);
44
60
  }
@@ -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
  // Roles and permissions are global to this deployment — one set per user, resolved from the
7
12
  // database on the request that uses them. The access token carries identity only.
@@ -26,7 +31,9 @@ export class AuthzGuard implements CanActivate {
26
31
  const req = context.switchToHttp().getRequest();
27
32
  if (req.auth) {
28
33
  const userId = req.auth.sub as string;
29
- req.authz = (await this.cache.resolve<AuthzContext>(userId, () => this.rbac.resolveAuthzContext(userId))) ?? { roles: [], permissions: [] };
34
+ req.authz = (await this.cache.resolve<AuthzContext>(userId, () =>
35
+ this.rbac.resolveAuthzContext(userId),
36
+ )) ?? { roles: [], permissions: [] };
30
37
  req.ability = defineAbilitiesFor(req.authz.permissions);
31
38
  }
32
39
  return true;
@@ -4,7 +4,7 @@
4
4
  // `provisionDefaultRoles` writes these rows once at seed time; from then on the database is
5
5
  // authoritative and nothing in the request path reads this file. Callers: `src/seed.ts` and
6
6
  // `variants/base/test/variant-hooks.ts`.
7
- import type { PrismaClient } from "../generated/prisma/client.js";
7
+ import type { PrismaClient } from "./../generated/prisma/client.js";
8
8
 
9
9
  export interface PermissionSeed {
10
10
  displayName: string;
@@ -17,8 +17,18 @@ export interface PermissionSeed {
17
17
  // same noun are separate slugs; block and unblock share one since they're a single capability
18
18
  // used in two directions.
19
19
  export const PERMISSION_CATALOG = {
20
- "users:read": { displayName: "List users", description: "GET /auth/admin/users, GET /auth/admin/users/:userId", group: "Users", order: 1 },
21
- "users:block": { displayName: "Block and unblock users", description: "POST /auth/admin/users/:userId/block, .../unblock", group: "Users", order: 2 },
20
+ "users:read": {
21
+ displayName: "List users",
22
+ description: "GET /auth/admin/users, GET /auth/admin/users/:userId",
23
+ group: "Users",
24
+ order: 1,
25
+ },
26
+ "users:block": {
27
+ displayName: "Block and unblock users",
28
+ description: "POST /auth/admin/users/:userId/block, .../unblock",
29
+ group: "Users",
30
+ order: 2,
31
+ },
22
32
  "users:manage": {
23
33
  displayName: "Create, edit, and delete users",
24
34
  description:
@@ -28,69 +38,107 @@ export const PERMISSION_CATALOG = {
28
38
  },
29
39
  "roles:manage": {
30
40
  displayName: "Define roles",
31
- description: "Create, edit, or delete roles and say what they carry — POST/PATCH/DELETE /auth/admin/roles(/:roleId), POST /auth/admin/roles/:roleId/permissions",
41
+ description:
42
+ "Create, edit, or delete roles and say what they carry — POST/PATCH/DELETE /auth/admin/roles(/:roleId), POST /auth/admin/roles/:roleId/permissions",
32
43
  group: "Roles",
33
44
  order: 1,
34
45
  },
35
46
  "roles:assign": {
36
47
  displayName: "Assign roles",
37
- description: "Assign and revoke a user's roles — POST /auth/admin/users/:userId/roles, .../roles/:roleSlug/revoke",
48
+ description:
49
+ "Assign and revoke a user's roles — POST /auth/admin/users/:userId/roles, .../roles/:roleSlug/revoke",
38
50
  group: "Roles",
39
51
  order: 2,
40
52
  },
41
- "permissions:read": { displayName: "Read the permission catalog", description: "GET /auth/admin/permissions", group: "Permissions", order: 1 },
53
+ "permissions:read": {
54
+ displayName: "Read the permission catalog",
55
+ description: "GET /auth/admin/permissions",
56
+ group: "Permissions",
57
+ order: 1,
58
+ },
42
59
  "permissions:define": {
43
60
  displayName: "Define permissions",
44
- description: "Create a permission, rename it, or deactivate it — POST /auth/admin/permissions. The authority that defines all the others.",
61
+ description:
62
+ "Create a permission, rename it, or deactivate it — POST /auth/admin/permissions. The authority that defines all the others.",
45
63
  group: "Permissions",
46
64
  order: 2,
47
65
  },
48
66
  "permissions:grant": {
49
67
  displayName: "Grant permissions directly",
50
- description: "Grant and revoke a permission straight to a user, bypassing roles — POST /auth/admin/users/:userId/permissions, .../permissions/:slug/revoke",
68
+ description:
69
+ "Grant and revoke a permission straight to a user, bypassing roles — POST /auth/admin/users/:userId/permissions, .../permissions/:slug/revoke",
51
70
  group: "Permissions",
52
71
  order: 3,
53
72
  },
54
- "audit-log:read": { displayName: "Read the audit log", description: "GET /auth/admin/audit-log", group: "Audit", order: 1 },
73
+ "audit-log:read": {
74
+ displayName: "Read the audit log",
75
+ description: "GET /auth/admin/audit-log",
76
+ group: "Audit",
77
+ order: 1,
78
+ },
55
79
 
56
- "countries:read": { displayName: "List countries", description: "GET /auth/admin/countries, GET /auth/admin/countries/:countryId", group: "Countries", order: 1 },
80
+ "countries:read": {
81
+ displayName: "List countries",
82
+ description:
83
+ "GET /auth/admin/countries, GET /auth/admin/countries/:countryId",
84
+ group: "Countries",
85
+ order: 1,
86
+ },
57
87
  "countries:manage": {
58
88
  displayName: "Create, edit, and delete countries",
59
- description: "POST /auth/admin/countries, PATCH /auth/admin/countries/:countryId, DELETE /auth/admin/countries/:countryId",
89
+ description:
90
+ "POST /auth/admin/countries, PATCH /auth/admin/countries/:countryId, DELETE /auth/admin/countries/:countryId",
60
91
  group: "Countries",
61
92
  order: 2,
62
93
  },
63
94
  "countries:status": {
64
95
  displayName: "Activate and deactivate countries",
65
- description: "POST /auth/admin/countries/:countryId/activate, .../deactivate",
96
+ description:
97
+ "POST /auth/admin/countries/:countryId/activate, .../deactivate",
66
98
  group: "Countries",
67
99
  order: 3,
68
100
  },
69
101
 
70
- "languages:read": { displayName: "List languages", description: "GET /auth/admin/languages, GET /auth/admin/languages/:languageId", group: "Languages", order: 1 },
102
+ "languages:read": {
103
+ displayName: "List languages",
104
+ description:
105
+ "GET /auth/admin/languages, GET /auth/admin/languages/:languageId",
106
+ group: "Languages",
107
+ order: 1,
108
+ },
71
109
  "languages:manage": {
72
110
  displayName: "Create, edit, and delete languages",
73
- description: "POST /auth/admin/languages, PATCH /auth/admin/languages/:languageId, DELETE /auth/admin/languages/:languageId",
111
+ description:
112
+ "POST /auth/admin/languages, PATCH /auth/admin/languages/:languageId, DELETE /auth/admin/languages/:languageId",
74
113
  group: "Languages",
75
114
  order: 2,
76
115
  },
77
116
  "languages:status": {
78
117
  displayName: "Activate and deactivate languages",
79
- description: "POST /auth/admin/languages/:languageId/activate, .../deactivate",
118
+ description:
119
+ "POST /auth/admin/languages/:languageId/activate, .../deactivate",
80
120
  group: "Languages",
81
121
  order: 3,
82
122
  },
83
123
 
84
- "customers:read": { displayName: "List customers", description: "GET /auth/admin/customers, GET /auth/admin/customers/:customerId", group: "Customers", order: 1 },
124
+ "customers:read": {
125
+ displayName: "List customers",
126
+ description:
127
+ "GET /auth/admin/customers, GET /auth/admin/customers/:customerId",
128
+ group: "Customers",
129
+ order: 1,
130
+ },
85
131
  "customers:manage": {
86
132
  displayName: "Create, edit, and delete customers",
87
- description: "POST /auth/admin/customers, PATCH /auth/admin/customers/:customerId, DELETE /auth/admin/customers/:customerId",
133
+ description:
134
+ "POST /auth/admin/customers, PATCH /auth/admin/customers/:customerId, DELETE /auth/admin/customers/:customerId",
88
135
  group: "Customers",
89
136
  order: 2,
90
137
  },
91
138
  "customers:status": {
92
139
  displayName: "Activate and deactivate customers",
93
- description: "POST /auth/admin/customers/:customerId/activate, .../deactivate",
140
+ description:
141
+ "POST /auth/admin/customers/:customerId/activate, .../deactivate",
94
142
  group: "Customers",
95
143
  order: 3,
96
144
  },
@@ -101,10 +149,14 @@ export const PERMISSION_CATALOG = {
101
149
  // type at compile time.
102
150
  export type PermissionSlug = keyof typeof PERMISSION_CATALOG;
103
151
 
104
- export const PERMISSION_SLUGS = Object.keys(PERMISSION_CATALOG) as PermissionSlug[];
152
+ export const PERMISSION_SLUGS = Object.keys(
153
+ PERMISSION_CATALOG,
154
+ ) as PermissionSlug[];
105
155
 
106
156
  /** Groups, in render order for an admin console. Derived from the catalog so it can't drift. */
107
- export const PERMISSION_GROUP_ORDER: string[] = [...new Set(PERMISSION_SLUGS.map((slug) => PERMISSION_CATALOG[slug].group))];
157
+ export const PERMISSION_GROUP_ORDER: string[] = [
158
+ ...new Set(PERMISSION_SLUGS.map((slug) => PERMISSION_CATALOG[slug].group)),
159
+ ];
108
160
 
109
161
  export interface RoleSeed {
110
162
  slug: string;
@@ -131,7 +183,8 @@ export const DEFAULT_ROLES: readonly RoleSeed[] = [
131
183
  {
132
184
  slug: "superadmin",
133
185
  displayName: "Super Admin",
134
- description: "Carries every permission in the catalog — same authority as admin, held by the seeded super_admin account (see SEED_SUPERADMIN_* in seed.ts).",
186
+ description:
187
+ "Carries every permission in the catalog — same authority as admin, held by the seeded super_admin account (see SEED_SUPERADMIN_* in seed.ts).",
135
188
  isDefault: false,
136
189
  order: 1,
137
190
  permissions: PERMISSION_SLUGS,
@@ -152,7 +205,10 @@ export const SEED_ADMIN_ROLES: string[] = ["admin", "member"];
152
205
  /** Role slugs given to the seeded super_admin. `member` is included so they're also an ordinary user. */
153
206
  export const SEED_SUPERADMIN_ROLES: string[] = ["superadmin", "member"];
154
207
 
155
- export type RbacWriter = Pick<PrismaClient, "permission" | "role" | "permissionRole">;
208
+ export type RbacWriter = Pick<
209
+ PrismaClient,
210
+ "permission" | "role" | "permissionRole"
211
+ >;
156
212
 
157
213
  // Idempotent and safe to run concurrently: every insert is `skipDuplicates` on the natural
158
214
  // unique key, and nothing is ever deleted or updated.
@@ -164,12 +220,17 @@ export async function provisionDefaultRoles(db: RbacWriter): Promise<void> {
164
220
  displayName: PERMISSION_CATALOG[slug].displayName,
165
221
  description: PERMISSION_CATALOG[slug].description,
166
222
  group: PERMISSION_CATALOG[slug].group,
167
- groupOrder: PERMISSION_GROUP_ORDER.indexOf(PERMISSION_CATALOG[slug].group),
223
+ groupOrder: PERMISSION_GROUP_ORDER.indexOf(
224
+ PERMISSION_CATALOG[slug].group,
225
+ ),
168
226
  order: PERMISSION_CATALOG[slug].order,
169
227
  })),
170
228
  skipDuplicates: true,
171
229
  });
172
- const permissions = await db.permission.findMany({ where: { slug: { in: PERMISSION_SLUGS } }, select: { id: true, slug: true } });
230
+ const permissions = await db.permission.findMany({
231
+ where: { slug: { in: PERMISSION_SLUGS } },
232
+ select: { id: true, slug: true },
233
+ });
173
234
  const permissionId = new Map(permissions.map((p) => [p.slug, p.id]));
174
235
 
175
236
  await db.role.createMany({
@@ -183,11 +244,21 @@ export async function provisionDefaultRoles(db: RbacWriter): Promise<void> {
183
244
  })),
184
245
  skipDuplicates: true,
185
246
  });
186
- const roles = await db.role.findMany({ where: { slug: { in: DEFAULT_ROLES.map((r) => r.slug) } }, select: { id: true, slug: true } });
247
+ const roles = await db.role.findMany({
248
+ where: { slug: { in: DEFAULT_ROLES.map((r) => r.slug) } },
249
+ select: { id: true, slug: true },
250
+ });
187
251
  const seedBySlug = new Map(DEFAULT_ROLES.map((role) => [role.slug, role]));
188
252
 
189
253
  const rolePermissions = roles.flatMap((role) =>
190
- (seedBySlug.get(role.slug)?.permissions ?? []).map((slug) => ({ roleId: role.id, permissionId: permissionId.get(slug)! })),
254
+ (seedBySlug.get(role.slug)?.permissions ?? []).map((slug) => ({
255
+ roleId: role.id,
256
+ permissionId: permissionId.get(slug)!,
257
+ })),
191
258
  );
192
- if (rolePermissions.length) await db.permissionRole.createMany({ data: rolePermissions, skipDuplicates: true });
259
+ if (rolePermissions.length)
260
+ await db.permissionRole.createMany({
261
+ data: rolePermissions,
262
+ skipDuplicates: true,
263
+ });
193
264
  }
@@ -1,8 +1,13 @@
1
1
  import { Inject, Injectable } from "@nestjs/common";
2
2
  import type { AuditEvent } from "@/lib/auth/core/types.js";
3
- import { Prisma, PrismaClient } from "../generated/prisma/client.js";
4
- import { toIdOrNull, toIdOrUndefined } from "./id.helper.js";
5
- import { buildPageMeta, normalizeLimit, normalizePage, type Paginated } from "./pagination.js";
3
+ import { Prisma, PrismaClient } from "../../generated/prisma/client.js";
4
+ import { toIdOrNull, toIdOrUndefined } from "../id.helper.js";
5
+ import {
6
+ buildPageMeta,
7
+ normalizeLimit,
8
+ normalizePage,
9
+ type Paginated,
10
+ } from "../pagination.js";
6
11
 
7
12
  export interface AuditLogEntry {
8
13
  id: string;
@@ -27,7 +32,10 @@ export interface AuditLogListFilter {
27
32
  // "role_assigned" -> "Role assigned". Derived rather than passed in, so core never carries display text.
28
33
  export function humanizeAction(action: string): string {
29
34
  const words = action.split("_");
30
- return [words[0].charAt(0).toUpperCase() + words[0].slice(1), ...words.slice(1)].join(" ");
35
+ return [
36
+ words[0].charAt(0).toUpperCase() + words[0].slice(1),
37
+ ...words.slice(1),
38
+ ].join(" ");
31
39
  }
32
40
 
33
41
  /** An `AuditLog` row as `findMany` returns it. */
@@ -63,7 +71,10 @@ export class AuditLogRepository {
63
71
  this.listeners.add(listener);
64
72
  }
65
73
 
66
- async append(event: AuditEvent, opts: { remarks?: string } = {}): Promise<void> {
74
+ async append(
75
+ event: AuditEvent,
76
+ opts: { remarks?: string } = {},
77
+ ): Promise<void> {
67
78
  const row = await this.prisma.auditLog.create({
68
79
  data: {
69
80
  action: event.type,
@@ -1,7 +1,17 @@
1
- import { ConflictException, Inject, Injectable, NotFoundException } from "@nestjs/common";
2
- import { Prisma, PrismaClient } from "../generated/prisma/client.js";
3
- import { toId, toIdOrNull } from "./id.helper.js";
4
- import { buildPageMeta, normalizeLimit, normalizePage, type Paginated } from "./pagination.js";
1
+ import {
2
+ ConflictException,
3
+ Inject,
4
+ Injectable,
5
+ NotFoundException,
6
+ } from "@nestjs/common";
7
+ import { Prisma, PrismaClient } from "../../generated/prisma/client.js";
8
+ import { toId, toIdOrNull } from "../id.helper.js";
9
+ import {
10
+ buildPageMeta,
11
+ normalizeLimit,
12
+ normalizePage,
13
+ type Paginated,
14
+ } from "../pagination.js";
5
15
 
6
16
  /** A `Country` row as `findUnique`/`findMany` return it. */
7
17
  export type CountryRow = Prisma.CountryGetPayload<object>;
@@ -90,60 +100,123 @@ export class CountryRepository {
90
100
  };
91
101
 
92
102
  const [rows, total] = await this.prisma.$transaction([
93
- this.prisma.country.findMany({ where, orderBy: [{ createdAt: "desc" }, { id: "desc" }], take: limit, skip: (page - 1) * limit }),
103
+ this.prisma.country.findMany({
104
+ where,
105
+ orderBy: [{ createdAt: "desc" }, { id: "desc" }],
106
+ take: limit,
107
+ skip: (page - 1) * limit,
108
+ }),
94
109
  this.prisma.country.count({ where }),
95
110
  ]);
96
111
 
97
- return { items: rows.map(toCountrySummary), meta: buildPageMeta(page, limit, total) };
112
+ return {
113
+ items: rows.map(toCountrySummary),
114
+ meta: buildPageMeta(page, limit, total),
115
+ };
98
116
  }
99
117
 
100
118
  async get(countryId: string): Promise<CountrySummary> {
101
- const row = await this.prisma.country.findUnique({ where: { id: toId(countryId), isDeleted: false } });
119
+ const row = await this.prisma.country.findUnique({
120
+ where: { id: toId(countryId), isDeleted: false },
121
+ });
102
122
  if (!row) throw new NotFoundException(`country "${countryId}" not found`);
103
123
  return toCountrySummary(row);
104
124
  }
105
125
 
106
- async create(input: CountryInput, actorUserId: string | null): Promise<CountrySummary> {
107
- const existing = await this.prisma.country.findFirst({ where: { OR: [{ code: input.code }, { isoCode: input.isoCode }] } });
108
- if (existing) throw new ConflictException("a country with this code or ISO code already exists");
126
+ async create(
127
+ input: CountryInput,
128
+ actorUserId: string | null,
129
+ ): Promise<CountrySummary> {
130
+ const existing = await this.prisma.country.findFirst({
131
+ where: { OR: [{ code: input.code }, { isoCode: input.isoCode }] },
132
+ });
133
+ if (existing)
134
+ throw new ConflictException(
135
+ "a country with this code or ISO code already exists",
136
+ );
109
137
 
110
138
  const row = await this.prisma.country.create({
111
- data: { ...input, createdBy: toIdOrNull(actorUserId), updatedBy: toIdOrNull(actorUserId) },
139
+ data: {
140
+ ...input,
141
+ createdBy: toIdOrNull(actorUserId),
142
+ updatedBy: toIdOrNull(actorUserId),
143
+ },
112
144
  });
113
145
  return toCountrySummary(row);
114
146
  }
115
147
 
116
- async update(countryId: string, input: Partial<CountryInput>, actorUserId: string | null): Promise<CountrySummary> {
148
+ async update(
149
+ countryId: string,
150
+ input: Partial<CountryInput>,
151
+ actorUserId: string | null,
152
+ ): Promise<CountrySummary> {
117
153
  const countryIdBig = toId(countryId);
118
- const existing = await this.prisma.country.findUnique({ where: { id: countryIdBig, isDeleted: false }, select: { id: true } });
119
- if (!existing) throw new NotFoundException(`country "${countryId}" not found`);
154
+ const existing = await this.prisma.country.findUnique({
155
+ where: { id: countryIdBig, isDeleted: false },
156
+ select: { id: true },
157
+ });
158
+ if (!existing)
159
+ throw new NotFoundException(`country "${countryId}" not found`);
120
160
 
121
161
  if (input.code !== undefined || input.isoCode !== undefined) {
122
162
  const clash = await this.prisma.country.findFirst({
123
- where: { NOT: { id: countryIdBig }, OR: [{ code: input.code }, { isoCode: input.isoCode }] },
163
+ where: {
164
+ NOT: { id: countryIdBig },
165
+ OR: [{ code: input.code }, { isoCode: input.isoCode }],
166
+ },
124
167
  });
125
- if (clash) throw new ConflictException("a country with this code or ISO code already exists");
168
+ if (clash)
169
+ throw new ConflictException(
170
+ "a country with this code or ISO code already exists",
171
+ );
126
172
  }
127
173
 
128
- const row = await this.prisma.country.update({ where: { id: countryIdBig }, data: { ...input, updatedBy: toIdOrNull(actorUserId) } });
174
+ const row = await this.prisma.country.update({
175
+ where: { id: countryIdBig },
176
+ data: { ...input, updatedBy: toIdOrNull(actorUserId) },
177
+ });
129
178
  return toCountrySummary(row);
130
179
  }
131
180
 
132
181
  // Soft-delete, matching every other table's isDeleted/deletedAt/deletedBy/deletedReason pattern.
133
- async delete(countryId: string, actorUserId: string | null, reason?: string): Promise<void> {
182
+ async delete(
183
+ countryId: string,
184
+ actorUserId: string | null,
185
+ reason?: string,
186
+ ): Promise<void> {
134
187
  const countryIdBig = toId(countryId);
135
- const existing = await this.prisma.country.findUnique({ where: { id: countryIdBig, isDeleted: false }, select: { id: true } });
136
- if (!existing) throw new NotFoundException(`country "${countryId}" not found`);
188
+ const existing = await this.prisma.country.findUnique({
189
+ where: { id: countryIdBig, isDeleted: false },
190
+ select: { id: true },
191
+ });
192
+ if (!existing)
193
+ throw new NotFoundException(`country "${countryId}" not found`);
137
194
  await this.prisma.country.update({
138
195
  where: { id: countryIdBig },
139
- data: { isDeleted: true, deletedAt: new Date(), deletedBy: toIdOrNull(actorUserId), deletedReason: reason ?? null },
196
+ data: {
197
+ isDeleted: true,
198
+ deletedAt: new Date(),
199
+ deletedBy: toIdOrNull(actorUserId),
200
+ deletedReason: reason ?? null,
201
+ },
140
202
  });
141
203
  }
142
204
 
143
- async setActive(countryId: string, isActive: boolean, actorUserId: string | null): Promise<void> {
205
+ async setActive(
206
+ countryId: string,
207
+ isActive: boolean,
208
+ actorUserId: string | null,
209
+ ): Promise<void> {
144
210
  const countryIdBig = toId(countryId);
145
- const existing = await this.prisma.country.findUnique({ where: { id: countryIdBig, isDeleted: false }, select: { id: true } });
146
- if (!existing) throw new NotFoundException(`country "${countryId}" not found`);
147
- await this.prisma.country.update({ where: { id: countryIdBig }, data: { isActive, updatedBy: toIdOrNull(actorUserId) } });
211
+ const existing = await this.prisma.country.findUnique({
212
+ where: { id: countryIdBig, isDeleted: false },
213
+ select: { id: true },
214
+ });
215
+ if (!existing)
216
+ throw new NotFoundException(`country "${countryId}" not found`);
217
+ await this.prisma.country.update({
218
+ where: { id: countryIdBig },
219
+ data: { isActive, updatedBy: toIdOrNull(actorUserId) },
220
+ });
148
221
  }
149
222
  }