@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,11 +1,34 @@
1
- import { BadRequestException, Body, Controller, Delete, ForbiddenException, Get, Inject, Ip, 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
+ Ip,
10
+ Param,
11
+ Patch,
12
+ Post,
13
+ Query,
14
+ Req,
15
+ UseGuards,
16
+ } from "@nestjs/common";
17
+ import {
18
+ ApiBearerAuth,
19
+ ApiBody,
20
+ ApiOperation,
21
+ ApiParam,
22
+ ApiQuery,
23
+ ApiResponse,
24
+ ApiTags,
25
+ } from "@nestjs/swagger";
3
26
  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";
27
+ import { AbilityGuard } from "../guards/ability.guard.js";
28
+ import { AuthGuard } from "../guards/auth.guard.js";
29
+ import { AuthService } from "../services/auth.service.js";
30
+ import { AuthzGuard } from "../guards/authz.guard.js";
31
+ import { CheckAbility } from "../route-tiers.js";
9
32
  import {
10
33
  AssignRoleDto,
11
34
  AttachPermissionDto,
@@ -21,20 +44,42 @@ import {
21
44
  RoleSummaryDto,
22
45
  UpdateRoleDto,
23
46
  UpdateUserDto,
24
- } from "./dto/auth.dto.js";
25
- import { AuditLogListResponseDto, UserListResponseDto, UserSummaryDto } from "./dto/admin.dto.js";
26
- import { CountryListResponseDto, CountrySummaryDto, CreateCountryDto, UpdateCountryDto } from "./dto/country.dto.js";
27
- import { CreateCustomerDto, CustomerListResponseDto, CustomerSummaryDto, UpdateCustomerDto } from "./dto/customer.dto.js";
28
- import { CreateLanguageDto, LanguageListResponseDto, LanguageSummaryDto, UpdateLanguageDto } from "./dto/language.dto.js";
47
+ } from "../dto/auth.dto.js";
48
+ import {
49
+ AuditLogListResponseDto,
50
+ UserListResponseDto,
51
+ UserSummaryDto,
52
+ } from "../dto/admin.dto.js";
53
+ import {
54
+ CountryListResponseDto,
55
+ CountrySummaryDto,
56
+ CreateCountryDto,
57
+ UpdateCountryDto,
58
+ } from "../dto/country.dto.js";
59
+ import {
60
+ CreateCustomerDto,
61
+ CustomerListResponseDto,
62
+ CustomerSummaryDto,
63
+ UpdateCustomerDto,
64
+ } from "../dto/customer.dto.js";
65
+ import {
66
+ CreateLanguageDto,
67
+ LanguageListResponseDto,
68
+ LanguageSummaryDto,
69
+ UpdateLanguageDto,
70
+ } from "../dto/language.dto.js";
29
71
 
30
72
  function requireString(value: unknown, field: string): string {
31
- if (typeof value !== "string" || value.length === 0) throw new BadRequestException(`${field} is required`);
73
+ if (typeof value !== "string" || value.length === 0)
74
+ throw new BadRequestException(`${field} is required`);
32
75
  return value;
33
76
  }
34
77
 
35
- const optionalString = (value: unknown): string | undefined => (typeof value === "string" ? value : undefined);
78
+ const optionalString = (value: unknown): string | undefined =>
79
+ typeof value === "string" ? value : undefined;
36
80
 
37
- const optionalBoolean = (value: unknown): boolean | undefined => (typeof value === "boolean" ? value : undefined);
81
+ const optionalBoolean = (value: unknown): boolean | undefined =>
82
+ typeof value === "boolean" ? value : undefined;
38
83
 
39
84
  // Administration of the whole deployment. Authority is a permission, never a role name — a role
40
85
  // called "admin" that carries no permissions gets the same 403 as holding no role at all. Run
@@ -49,17 +94,41 @@ export class AdminController {
49
94
  @Get("users")
50
95
  @CheckAbility("users:read")
51
96
  @ApiOperation({ summary: "[admin] List users" })
52
- @ApiQuery({ name: "search", required: false, description: "Email substring match" })
53
- @ApiQuery({ name: "page", required: false, description: "1-indexed. Defaults to 1." })
54
- @ApiQuery({ name: "limit", required: false, description: "Defaults to 25, capped at 100." })
97
+ @ApiQuery({
98
+ name: "search",
99
+ required: false,
100
+ description: "Email substring match",
101
+ })
102
+ @ApiQuery({
103
+ name: "page",
104
+ required: false,
105
+ description: "1-indexed. Defaults to 1.",
106
+ })
107
+ @ApiQuery({
108
+ name: "limit",
109
+ required: false,
110
+ description: "Defaults to 25, capped at 100.",
111
+ })
55
112
  @ApiResponse({ status: 200, type: UserListResponseDto })
56
- async listUsers(@Query("search") search?: string, @Query("page") page?: string, @Query("limit") limit?: string) {
57
- return this.auth.listUsers({ search, page: page ? Number(page) : undefined, limit: limit ? Number(limit) : undefined });
113
+ async listUsers(
114
+ @Query("search") search?: string,
115
+ @Query("page") page?: string,
116
+ @Query("limit") limit?: string,
117
+ ) {
118
+ return this.auth.listUsers({
119
+ search,
120
+ page: page ? Number(page) : undefined,
121
+ limit: limit ? Number(limit) : undefined,
122
+ });
58
123
  }
59
124
 
60
125
  @Post("users")
61
126
  @CheckAbility("users:manage")
62
- @ApiOperation({ summary: "[admin] Create a user directly", description: "No invitation email — the account is usable immediately with the password given here." })
127
+ @ApiOperation({
128
+ summary: "[admin] Create a user directly",
129
+ description:
130
+ "No invitation email — the account is usable immediately with the password given here.",
131
+ })
63
132
  @ApiBody({ type: CreateUserDto })
64
133
  @ApiResponse({ status: 201, type: UserSummaryDto })
65
134
  async createUser(@Body() body: Record<string, unknown>, @Req() req: Request) {
@@ -76,8 +145,13 @@ export class AdminController {
76
145
  dob: optionalString(body.dob),
77
146
  gender: optionalString(body.gender),
78
147
  joinedDate: optionalString(body.joinedDate),
79
- isActive: typeof body.isActive === "boolean" ? body.isActive : undefined,
80
- roles: Array.isArray(body.roles) ? body.roles.filter((role): role is string => typeof role === "string") : undefined,
148
+ isActive:
149
+ typeof body.isActive === "boolean" ? body.isActive : undefined,
150
+ roles: Array.isArray(body.roles)
151
+ ? body.roles.filter(
152
+ (role): role is string => typeof role === "string",
153
+ )
154
+ : undefined,
81
155
  },
82
156
  req.auth!.sub,
83
157
  );
@@ -94,17 +168,27 @@ export class AdminController {
94
168
 
95
169
  @Patch("users/:userId")
96
170
  @CheckAbility("users:manage")
97
- @ApiOperation({ summary: "[admin] Edit a user's profile", description: "Profile fields only — email is the login identifier and is not editable here." })
171
+ @ApiOperation({
172
+ summary: "[admin] Edit a user's profile",
173
+ description:
174
+ "Profile fields only — email is the login identifier and is not editable here.",
175
+ })
98
176
  @ApiParam({ name: "userId" })
99
177
  @ApiBody({ type: UpdateUserDto })
100
178
  @ApiResponse({ status: 200, type: UserSummaryDto })
101
- async updateUser(@Param("userId") userId: string, @Body() body: Record<string, unknown>, @Req() req: Request) {
179
+ async updateUser(
180
+ @Param("userId") userId: string,
181
+ @Body() body: Record<string, unknown>,
182
+ @Req() req: Request,
183
+ ) {
102
184
  return this.auth.updateUser(
103
185
  userId,
104
186
  {
105
- firstName: body.firstName === null ? null : optionalString(body.firstName),
187
+ firstName:
188
+ body.firstName === null ? null : optionalString(body.firstName),
106
189
  lastName: body.lastName === null ? null : optionalString(body.lastName),
107
- displayName: body.displayName === null ? null : optionalString(body.displayName),
190
+ displayName:
191
+ body.displayName === null ? null : optionalString(body.displayName),
108
192
  phone: body.phone === null ? null : optionalString(body.phone),
109
193
  username: body.username === null ? null : optionalString(body.username),
110
194
  photo: body.photo === null ? null : optionalString(body.photo),
@@ -120,14 +204,24 @@ export class AdminController {
120
204
  @CheckAbility("users:manage")
121
205
  @ApiOperation({
122
206
  summary: "[admin] Delete a user",
123
- description: "Soft-delete: the row survives for audit purposes, stops appearing in listings, and can no longer authenticate.",
207
+ description:
208
+ "Soft-delete: the row survives for audit purposes, stops appearing in listings, and can no longer authenticate.",
124
209
  })
125
210
  @ApiParam({ name: "userId" })
126
211
  @ApiBody({ type: DeleteReasonDto, required: false })
127
212
  @ApiResponse({ status: 200, type: OkResponseDto })
128
- async deleteUser(@Param("userId") userId: string, @Body() body: Record<string, unknown>, @Req() req: Request) {
129
- if (userId === req.auth!.sub) throw new ForbiddenException("cannot delete your own account");
130
- await this.auth.deleteUser(userId, req.auth!.sub, optionalString(body?.reason));
213
+ async deleteUser(
214
+ @Param("userId") userId: string,
215
+ @Body() body: Record<string, unknown>,
216
+ @Req() req: Request,
217
+ ) {
218
+ if (userId === req.auth!.sub)
219
+ throw new ForbiddenException("cannot delete your own account");
220
+ await this.auth.deleteUser(
221
+ userId,
222
+ req.auth!.sub,
223
+ optionalString(body?.reason),
224
+ );
131
225
  return { ok: true };
132
226
  }
133
227
 
@@ -135,11 +229,31 @@ export class AdminController {
135
229
  @CheckAbility("audit-log:read")
136
230
  @ApiOperation({ summary: "[admin] List audit log entries, newest first" })
137
231
  @ApiQuery({ name: "userId", required: false })
138
- @ApiQuery({ name: "action", required: false, description: "AuditEvent discriminant, e.g. 'role_assigned'" })
139
- @ApiQuery({ name: "since", required: false, description: "ISO 8601 timestamp" })
140
- @ApiQuery({ name: "until", required: false, description: "ISO 8601 timestamp" })
141
- @ApiQuery({ name: "page", required: false, description: "1-indexed. Defaults to 1." })
142
- @ApiQuery({ name: "limit", required: false, description: "Defaults to 25, capped at 100." })
232
+ @ApiQuery({
233
+ name: "action",
234
+ required: false,
235
+ description: "AuditEvent discriminant, e.g. 'role_assigned'",
236
+ })
237
+ @ApiQuery({
238
+ name: "since",
239
+ required: false,
240
+ description: "ISO 8601 timestamp",
241
+ })
242
+ @ApiQuery({
243
+ name: "until",
244
+ required: false,
245
+ description: "ISO 8601 timestamp",
246
+ })
247
+ @ApiQuery({
248
+ name: "page",
249
+ required: false,
250
+ description: "1-indexed. Defaults to 1.",
251
+ })
252
+ @ApiQuery({
253
+ name: "limit",
254
+ required: false,
255
+ description: "Defaults to 25, capped at 100.",
256
+ })
143
257
  @ApiResponse({ status: 200, type: AuditLogListResponseDto })
144
258
  async listAuditLog(
145
259
  @Query("userId") userId?: string,
@@ -149,16 +263,30 @@ export class AdminController {
149
263
  @Query("page") page?: string,
150
264
  @Query("limit") limit?: string,
151
265
  ) {
152
- return this.auth.listAuditLog({ userId, action, since, until, page: page ? Number(page) : undefined, limit: limit ? Number(limit) : undefined });
266
+ return this.auth.listAuditLog({
267
+ userId,
268
+ action,
269
+ since,
270
+ until,
271
+ page: page ? Number(page) : undefined,
272
+ limit: limit ? Number(limit) : undefined,
273
+ });
153
274
  }
154
275
 
155
276
  @Get("permissions")
156
277
  @CheckAbility("permissions:read")
157
278
  @ApiOperation({
158
279
  summary: "[admin] List the permission catalog",
159
- description: "Grouped and ordered for a permission matrix. This is the whole vocabulary of the deployment — there is nothing about authorization outside these rows.",
280
+ description:
281
+ "Grouped and ordered for a permission matrix. This is the whole vocabulary of the deployment — there is nothing about authorization outside these rows.",
282
+ })
283
+ @ApiQuery({
284
+ name: "activeOnly",
285
+ required: false,
286
+ type: Boolean,
287
+ description:
288
+ "Pass true for a picker/dropdown — false or omitted returns everything, active or not.",
160
289
  })
161
- @ApiQuery({ name: "activeOnly", required: false, type: Boolean, description: "Pass true for a picker/dropdown — false or omitted returns everything, active or not." })
162
290
  @ApiResponse({ status: 200, type: PermissionListResponseDto })
163
291
  async listPermissions(@Query("activeOnly") activeOnly?: string) {
164
292
  return this.auth.listPermissions(activeOnly === "true");
@@ -174,17 +302,23 @@ export class AdminController {
174
302
  })
175
303
  @ApiBody({ type: DefinePermissionDto })
176
304
  @ApiResponse({ status: 201, type: PermissionSummaryDto })
177
- async definePermission(@Body() body: Record<string, unknown>, @Req() req: Request) {
305
+ async definePermission(
306
+ @Body() body: Record<string, unknown>,
307
+ @Req() req: Request,
308
+ ) {
178
309
  return this.auth.definePermission(
179
310
  {
180
311
  slug: requireString(body.slug, "slug"),
181
312
  name: optionalString(body.name),
182
313
  displayName: optionalString(body.displayName),
183
- description: body.description === null ? null : optionalString(body.description),
314
+ description:
315
+ body.description === null ? null : optionalString(body.description),
184
316
  group: optionalString(body.group),
185
- groupOrder: typeof body.groupOrder === "number" ? body.groupOrder : undefined,
317
+ groupOrder:
318
+ typeof body.groupOrder === "number" ? body.groupOrder : undefined,
186
319
  order: typeof body.order === "number" ? body.order : undefined,
187
- isActive: typeof body.isActive === "boolean" ? body.isActive : undefined,
320
+ isActive:
321
+ typeof body.isActive === "boolean" ? body.isActive : undefined,
188
322
  },
189
323
  req.auth!.sub,
190
324
  );
@@ -193,7 +327,13 @@ export class AdminController {
193
327
  @Get("roles")
194
328
  @CheckAbility("roles:manage")
195
329
  @ApiOperation({ summary: "[admin] List the roles this deployment defines" })
196
- @ApiQuery({ name: "activeOnly", required: false, type: Boolean, description: "Pass true for a picker/dropdown — false or omitted returns everything, active or not." })
330
+ @ApiQuery({
331
+ name: "activeOnly",
332
+ required: false,
333
+ type: Boolean,
334
+ description:
335
+ "Pass true for a picker/dropdown — false or omitted returns everything, active or not.",
336
+ })
197
337
  @ApiResponse({ status: 200, type: RoleListResponseDto })
198
338
  async listRoles(@Query("activeOnly") activeOnly?: string) {
199
339
  return this.auth.listRoles(activeOnly === "true");
@@ -211,8 +351,10 @@ export class AdminController {
211
351
  name: optionalString(body.name),
212
352
  displayName: optionalString(body.displayName),
213
353
  description: optionalString(body.description) ?? null,
214
- isDefault: typeof body.isDefault === "boolean" ? body.isDefault : undefined,
215
- isActive: typeof body.isActive === "boolean" ? body.isActive : undefined,
354
+ isDefault:
355
+ typeof body.isDefault === "boolean" ? body.isDefault : undefined,
356
+ isActive:
357
+ typeof body.isActive === "boolean" ? body.isActive : undefined,
216
358
  },
217
359
  req.auth!.sub,
218
360
  );
@@ -224,15 +366,22 @@ export class AdminController {
224
366
  @ApiParam({ name: "roleId" })
225
367
  @ApiBody({ type: UpdateRoleDto })
226
368
  @ApiResponse({ status: 200, type: RoleSummaryDto })
227
- async updateRole(@Param("roleId") roleId: string, @Body() body: Record<string, unknown>, @Req() req: Request) {
369
+ async updateRole(
370
+ @Param("roleId") roleId: string,
371
+ @Body() body: Record<string, unknown>,
372
+ @Req() req: Request,
373
+ ) {
228
374
  return this.auth.updateRole(
229
375
  roleId,
230
376
  {
231
377
  name: optionalString(body.name),
232
378
  displayName: optionalString(body.displayName),
233
- description: body.description === null ? null : optionalString(body.description),
234
- isDefault: typeof body.isDefault === "boolean" ? body.isDefault : undefined,
235
- isActive: typeof body.isActive === "boolean" ? body.isActive : undefined,
379
+ description:
380
+ body.description === null ? null : optionalString(body.description),
381
+ isDefault:
382
+ typeof body.isDefault === "boolean" ? body.isDefault : undefined,
383
+ isActive:
384
+ typeof body.isActive === "boolean" ? body.isActive : undefined,
236
385
  },
237
386
  req.auth!.sub,
238
387
  );
@@ -242,13 +391,22 @@ export class AdminController {
242
391
  @CheckAbility("roles:manage")
243
392
  @ApiOperation({
244
393
  summary: "[admin] Delete a role",
245
- description: "Soft-delete: existing assignments are left in place rather than cascade-deleted, and the role simply stops being resolved.",
394
+ description:
395
+ "Soft-delete: existing assignments are left in place rather than cascade-deleted, and the role simply stops being resolved.",
246
396
  })
247
397
  @ApiParam({ name: "roleId" })
248
398
  @ApiBody({ type: DeleteReasonDto, required: false })
249
399
  @ApiResponse({ status: 200, type: OkResponseDto })
250
- async deleteRole(@Param("roleId") roleId: string, @Body() body: Record<string, unknown>, @Req() req: Request) {
251
- await this.auth.deleteRole(roleId, req.auth!.sub, optionalString(body?.reason));
400
+ async deleteRole(
401
+ @Param("roleId") roleId: string,
402
+ @Body() body: Record<string, unknown>,
403
+ @Req() req: Request,
404
+ ) {
405
+ await this.auth.deleteRole(
406
+ roleId,
407
+ req.auth!.sub,
408
+ optionalString(body?.reason),
409
+ );
252
410
  return { ok: true };
253
411
  }
254
412
 
@@ -258,8 +416,16 @@ export class AdminController {
258
416
  @ApiParam({ name: "roleId" })
259
417
  @ApiBody({ type: AttachPermissionDto })
260
418
  @ApiResponse({ status: 201, type: OkResponseDto })
261
- async attachPermissionToRole(@Param("roleId") roleId: string, @Body() body: Record<string, unknown>, @Req() req: Request) {
262
- await this.auth.attachPermissionToRole(roleId, requireString(body.permission, "permission"), req.auth!.sub);
419
+ async attachPermissionToRole(
420
+ @Param("roleId") roleId: string,
421
+ @Body() body: Record<string, unknown>,
422
+ @Req() req: Request,
423
+ ) {
424
+ await this.auth.attachPermissionToRole(
425
+ roleId,
426
+ requireString(body.permission, "permission"),
427
+ req.auth!.sub,
428
+ );
263
429
  return { ok: true };
264
430
  }
265
431
 
@@ -269,7 +435,10 @@ export class AdminController {
269
435
  @ApiParam({ name: "roleId" })
270
436
  @ApiParam({ name: "permissionSlug" })
271
437
  @ApiResponse({ status: 201, type: OkResponseDto })
272
- async detachPermissionFromRole(@Param("roleId") roleId: string, @Param("permissionSlug") permissionSlug: string) {
438
+ async detachPermissionFromRole(
439
+ @Param("roleId") roleId: string,
440
+ @Param("permissionSlug") permissionSlug: string,
441
+ ) {
273
442
  await this.auth.detachPermissionFromRole(roleId, permissionSlug);
274
443
  return { ok: true };
275
444
  }
@@ -280,7 +449,10 @@ export class AdminController {
280
449
  @ApiParam({ name: "userId" })
281
450
  @ApiBody({ type: AssignRoleDto })
282
451
  @ApiResponse({ status: 201, type: OkResponseDto })
283
- async assignRole(@Param("userId") userId: string, @Body() body: Record<string, unknown>) {
452
+ async assignRole(
453
+ @Param("userId") userId: string,
454
+ @Body() body: Record<string, unknown>,
455
+ ) {
284
456
  await this.auth.assignRole(userId, requireString(body.role, "role"));
285
457
  return { ok: true };
286
458
  }
@@ -291,43 +463,70 @@ export class AdminController {
291
463
  @ApiParam({ name: "userId" })
292
464
  @ApiParam({ name: "roleSlug" })
293
465
  @ApiResponse({ status: 201, type: OkResponseDto })
294
- async revokeRole(@Param("userId") userId: string, @Param("roleSlug") roleSlug: string, @Req() req: Request) {
466
+ async revokeRole(
467
+ @Param("userId") userId: string,
468
+ @Param("roleSlug") roleSlug: string,
469
+ @Req() req: Request,
470
+ ) {
295
471
  // Revoking your own `admin` would strip the very permission that let you call this,
296
472
  // with no route back in. Assigning to yourself is fine — it can't lock anyone out.
297
- if (userId === req.auth!.sub) throw new ForbiddenException("cannot change your own roles");
473
+ if (userId === req.auth!.sub)
474
+ throw new ForbiddenException("cannot change your own roles");
298
475
  await this.auth.revokeRole(userId, roleSlug);
299
476
  return { ok: true };
300
477
  }
301
478
 
302
479
  @Post("users/:userId/permissions")
303
480
  @CheckAbility("permissions:grant")
304
- @ApiOperation({ summary: "[admin] Grant a permission directly to a user, bypassing roles" })
481
+ @ApiOperation({
482
+ summary: "[admin] Grant a permission directly to a user, bypassing roles",
483
+ })
305
484
  @ApiParam({ name: "userId" })
306
485
  @ApiBody({ type: GrantPermissionDto })
307
486
  @ApiResponse({ status: 201, type: OkResponseDto })
308
- async grantPermission(@Param("userId") userId: string, @Body() body: Record<string, unknown>, @Req() req: Request) {
309
- await this.auth.grantPermission(userId, requireString(body.permission, "permission"), req.auth!.sub);
487
+ async grantPermission(
488
+ @Param("userId") userId: string,
489
+ @Body() body: Record<string, unknown>,
490
+ @Req() req: Request,
491
+ ) {
492
+ await this.auth.grantPermission(
493
+ userId,
494
+ requireString(body.permission, "permission"),
495
+ req.auth!.sub,
496
+ );
310
497
  return { ok: true };
311
498
  }
312
499
 
313
500
  @Post("users/:userId/permissions/:permissionSlug/revoke")
314
501
  @CheckAbility("permissions:grant")
315
- @ApiOperation({ summary: "[admin] Revoke a direct permission grant from a user" })
502
+ @ApiOperation({
503
+ summary: "[admin] Revoke a direct permission grant from a user",
504
+ })
316
505
  @ApiParam({ name: "userId" })
317
506
  @ApiParam({ name: "permissionSlug" })
318
507
  @ApiResponse({ status: 201, type: OkResponseDto })
319
- async revokePermission(@Param("userId") userId: string, @Param("permissionSlug") permissionSlug: string) {
508
+ async revokePermission(
509
+ @Param("userId") userId: string,
510
+ @Param("permissionSlug") permissionSlug: string,
511
+ ) {
320
512
  await this.auth.revokePermission(userId, permissionSlug);
321
513
  return { ok: true };
322
514
  }
323
515
 
324
516
  @Post("users/:userId/block")
325
517
  @CheckAbility("users:block")
326
- @ApiOperation({ summary: "[admin] Block a user, revoking all their sessions immediately" })
518
+ @ApiOperation({
519
+ summary: "[admin] Block a user, revoking all their sessions immediately",
520
+ })
327
521
  @ApiParam({ name: "userId" })
328
522
  @ApiResponse({ status: 201, type: OkResponseDto })
329
- async block(@Param("userId") userId: string, @Req() req: Request, @Ip() ip: string) {
330
- if (userId === req.auth!.sub) throw new ForbiddenException("cannot block your own account");
523
+ async block(
524
+ @Param("userId") userId: string,
525
+ @Req() req: Request,
526
+ @Ip() ip: string,
527
+ ) {
528
+ if (userId === req.auth!.sub)
529
+ throw new ForbiddenException("cannot block your own account");
331
530
  await this.auth.block(userId, { userId: req.auth!.sub, ip });
332
531
  return { ok: true };
333
532
  }
@@ -337,7 +536,11 @@ export class AdminController {
337
536
  @ApiOperation({ summary: "[admin] Unblock a user" })
338
537
  @ApiParam({ name: "userId" })
339
538
  @ApiResponse({ status: 201, type: OkResponseDto })
340
- async unblock(@Param("userId") userId: string, @Req() req: Request, @Ip() ip: string) {
539
+ async unblock(
540
+ @Param("userId") userId: string,
541
+ @Req() req: Request,
542
+ @Ip() ip: string,
543
+ ) {
341
544
  await this.auth.unblock(userId, { userId: req.auth!.sub, ip });
342
545
  return { ok: true };
343
546
  }
@@ -345,13 +548,20 @@ export class AdminController {
345
548
  @Post("users/:userId/deactivate")
346
549
  @CheckAbility("users:block")
347
550
  @ApiOperation({
348
- summary: "[admin] Deactivate a user, revoking all their sessions immediately",
349
- description: "Distinct from block/unblock — a routine administrative toggle, not a security action. Both independently deny login.",
551
+ summary:
552
+ "[admin] Deactivate a user, revoking all their sessions immediately",
553
+ description:
554
+ "Distinct from block/unblock — a routine administrative toggle, not a security action. Both independently deny login.",
350
555
  })
351
556
  @ApiParam({ name: "userId" })
352
557
  @ApiResponse({ status: 201, type: OkResponseDto })
353
- async deactivate(@Param("userId") userId: string, @Req() req: Request, @Ip() ip: string) {
354
- if (userId === req.auth!.sub) throw new ForbiddenException("cannot deactivate your own account");
558
+ async deactivate(
559
+ @Param("userId") userId: string,
560
+ @Req() req: Request,
561
+ @Ip() ip: string,
562
+ ) {
563
+ if (userId === req.auth!.sub)
564
+ throw new ForbiddenException("cannot deactivate your own account");
355
565
  await this.auth.deactivate(userId, { userId: req.auth!.sub, ip });
356
566
  return { ok: true };
357
567
  }
@@ -361,7 +571,11 @@ export class AdminController {
361
571
  @ApiOperation({ summary: "[admin] Reactivate a user" })
362
572
  @ApiParam({ name: "userId" })
363
573
  @ApiResponse({ status: 201, type: OkResponseDto })
364
- async activate(@Param("userId") userId: string, @Req() req: Request, @Ip() ip: string) {
574
+ async activate(
575
+ @Param("userId") userId: string,
576
+ @Req() req: Request,
577
+ @Ip() ip: string,
578
+ ) {
365
579
  await this.auth.activate(userId, { userId: req.auth!.sub, ip });
366
580
  return { ok: true };
367
581
  }
@@ -371,10 +585,28 @@ export class AdminController {
371
585
  @Get("countries")
372
586
  @CheckAbility("countries:read")
373
587
  @ApiOperation({ summary: "[admin] List countries" })
374
- @ApiQuery({ name: "search", required: false, description: "Matches name/code/isoCode" })
375
- @ApiQuery({ name: "page", required: false, description: "1-indexed. Defaults to 1." })
376
- @ApiQuery({ name: "limit", required: false, description: "Defaults to 25, capped at 100." })
377
- @ApiQuery({ name: "activeOnly", required: false, type: Boolean, description: "Pass true for a picker/dropdown — false or omitted returns everything, active or not." })
588
+ @ApiQuery({
589
+ name: "search",
590
+ required: false,
591
+ description: "Matches name/code/isoCode",
592
+ })
593
+ @ApiQuery({
594
+ name: "page",
595
+ required: false,
596
+ description: "1-indexed. Defaults to 1.",
597
+ })
598
+ @ApiQuery({
599
+ name: "limit",
600
+ required: false,
601
+ description: "Defaults to 25, capped at 100.",
602
+ })
603
+ @ApiQuery({
604
+ name: "activeOnly",
605
+ required: false,
606
+ type: Boolean,
607
+ description:
608
+ "Pass true for a picker/dropdown — false or omitted returns everything, active or not.",
609
+ })
378
610
  @ApiResponse({ status: 200, type: CountryListResponseDto })
379
611
  async listCountries(
380
612
  @Query("search") search?: string,
@@ -382,7 +614,12 @@ export class AdminController {
382
614
  @Query("limit") limit?: string,
383
615
  @Query("activeOnly") activeOnly?: string,
384
616
  ) {
385
- return this.auth.listCountries({ search, page: page ? Number(page) : undefined, limit: limit ? Number(limit) : undefined, activeOnly: activeOnly === "true" });
617
+ return this.auth.listCountries({
618
+ search,
619
+ page: page ? Number(page) : undefined,
620
+ limit: limit ? Number(limit) : undefined,
621
+ activeOnly: activeOnly === "true",
622
+ });
386
623
  }
387
624
 
388
625
  @Post("countries")
@@ -390,7 +627,10 @@ export class AdminController {
390
627
  @ApiOperation({ summary: "[admin] Create a country" })
391
628
  @ApiBody({ type: CreateCountryDto })
392
629
  @ApiResponse({ status: 201, type: CountrySummaryDto })
393
- async createCountry(@Body() body: Record<string, unknown>, @Req() req: Request) {
630
+ async createCountry(
631
+ @Body() body: Record<string, unknown>,
632
+ @Req() req: Request,
633
+ ) {
394
634
  return this.auth.createCountry(
395
635
  {
396
636
  code: requireString(body.code, "code"),
@@ -422,7 +662,11 @@ export class AdminController {
422
662
  @ApiParam({ name: "countryId" })
423
663
  @ApiBody({ type: UpdateCountryDto })
424
664
  @ApiResponse({ status: 200, type: CountrySummaryDto })
425
- async updateCountry(@Param("countryId") countryId: string, @Body() body: Record<string, unknown>, @Req() req: Request) {
665
+ async updateCountry(
666
+ @Param("countryId") countryId: string,
667
+ @Body() body: Record<string, unknown>,
668
+ @Req() req: Request,
669
+ ) {
426
670
  return this.auth.updateCountry(
427
671
  countryId,
428
672
  {
@@ -442,12 +686,24 @@ export class AdminController {
442
686
 
443
687
  @Delete("countries/:countryId")
444
688
  @CheckAbility("countries:manage")
445
- @ApiOperation({ summary: "[admin] Delete a country", description: "Soft-delete: the row survives for audit purposes and stops appearing in listings." })
689
+ @ApiOperation({
690
+ summary: "[admin] Delete a country",
691
+ description:
692
+ "Soft-delete: the row survives for audit purposes and stops appearing in listings.",
693
+ })
446
694
  @ApiParam({ name: "countryId" })
447
695
  @ApiBody({ type: DeleteReasonDto, required: false })
448
696
  @ApiResponse({ status: 200, type: OkResponseDto })
449
- async deleteCountry(@Param("countryId") countryId: string, @Body() body: Record<string, unknown>, @Req() req: Request) {
450
- await this.auth.deleteCountry(countryId, req.auth!.sub, optionalString(body?.reason));
697
+ async deleteCountry(
698
+ @Param("countryId") countryId: string,
699
+ @Body() body: Record<string, unknown>,
700
+ @Req() req: Request,
701
+ ) {
702
+ await this.auth.deleteCountry(
703
+ countryId,
704
+ req.auth!.sub,
705
+ optionalString(body?.reason),
706
+ );
451
707
  return { ok: true };
452
708
  }
453
709
 
@@ -456,7 +712,10 @@ export class AdminController {
456
712
  @ApiOperation({ summary: "[admin] Reactivate a country" })
457
713
  @ApiParam({ name: "countryId" })
458
714
  @ApiResponse({ status: 201, type: OkResponseDto })
459
- async activateCountry(@Param("countryId") countryId: string, @Req() req: Request) {
715
+ async activateCountry(
716
+ @Param("countryId") countryId: string,
717
+ @Req() req: Request,
718
+ ) {
460
719
  await this.auth.activateCountry(countryId, req.auth!.sub);
461
720
  return { ok: true };
462
721
  }
@@ -466,7 +725,10 @@ export class AdminController {
466
725
  @ApiOperation({ summary: "[admin] Deactivate a country" })
467
726
  @ApiParam({ name: "countryId" })
468
727
  @ApiResponse({ status: 201, type: OkResponseDto })
469
- async deactivateCountry(@Param("countryId") countryId: string, @Req() req: Request) {
728
+ async deactivateCountry(
729
+ @Param("countryId") countryId: string,
730
+ @Req() req: Request,
731
+ ) {
470
732
  await this.auth.deactivateCountry(countryId, req.auth!.sub);
471
733
  return { ok: true };
472
734
  }
@@ -476,10 +738,28 @@ export class AdminController {
476
738
  @Get("languages")
477
739
  @CheckAbility("languages:read")
478
740
  @ApiOperation({ summary: "[admin] List languages" })
479
- @ApiQuery({ name: "search", required: false, description: "Matches name/code/nativeName" })
480
- @ApiQuery({ name: "page", required: false, description: "1-indexed. Defaults to 1." })
481
- @ApiQuery({ name: "limit", required: false, description: "Defaults to 25, capped at 100." })
482
- @ApiQuery({ name: "activeOnly", required: false, type: Boolean, description: "Pass true for a picker/dropdown — false or omitted returns everything, active or not." })
741
+ @ApiQuery({
742
+ name: "search",
743
+ required: false,
744
+ description: "Matches name/code/nativeName",
745
+ })
746
+ @ApiQuery({
747
+ name: "page",
748
+ required: false,
749
+ description: "1-indexed. Defaults to 1.",
750
+ })
751
+ @ApiQuery({
752
+ name: "limit",
753
+ required: false,
754
+ description: "Defaults to 25, capped at 100.",
755
+ })
756
+ @ApiQuery({
757
+ name: "activeOnly",
758
+ required: false,
759
+ type: Boolean,
760
+ description:
761
+ "Pass true for a picker/dropdown — false or omitted returns everything, active or not.",
762
+ })
483
763
  @ApiResponse({ status: 200, type: LanguageListResponseDto })
484
764
  async listLanguages(
485
765
  @Query("search") search?: string,
@@ -487,7 +767,12 @@ export class AdminController {
487
767
  @Query("limit") limit?: string,
488
768
  @Query("activeOnly") activeOnly?: string,
489
769
  ) {
490
- return this.auth.listLanguages({ search, page: page ? Number(page) : undefined, limit: limit ? Number(limit) : undefined, activeOnly: activeOnly === "true" });
770
+ return this.auth.listLanguages({
771
+ search,
772
+ page: page ? Number(page) : undefined,
773
+ limit: limit ? Number(limit) : undefined,
774
+ activeOnly: activeOnly === "true",
775
+ });
491
776
  }
492
777
 
493
778
  @Post("languages")
@@ -495,7 +780,10 @@ export class AdminController {
495
780
  @ApiOperation({ summary: "[admin] Create a language" })
496
781
  @ApiBody({ type: CreateLanguageDto })
497
782
  @ApiResponse({ status: 201, type: LanguageSummaryDto })
498
- async createLanguage(@Body() body: Record<string, unknown>, @Req() req: Request) {
783
+ async createLanguage(
784
+ @Body() body: Record<string, unknown>,
785
+ @Req() req: Request,
786
+ ) {
499
787
  return this.auth.createLanguage(
500
788
  {
501
789
  code: requireString(body.code, "code"),
@@ -524,7 +812,11 @@ export class AdminController {
524
812
  @ApiParam({ name: "languageId" })
525
813
  @ApiBody({ type: UpdateLanguageDto })
526
814
  @ApiResponse({ status: 200, type: LanguageSummaryDto })
527
- async updateLanguage(@Param("languageId") languageId: string, @Body() body: Record<string, unknown>, @Req() req: Request) {
815
+ async updateLanguage(
816
+ @Param("languageId") languageId: string,
817
+ @Body() body: Record<string, unknown>,
818
+ @Req() req: Request,
819
+ ) {
528
820
  return this.auth.updateLanguage(
529
821
  languageId,
530
822
  {
@@ -541,12 +833,24 @@ export class AdminController {
541
833
 
542
834
  @Delete("languages/:languageId")
543
835
  @CheckAbility("languages:manage")
544
- @ApiOperation({ summary: "[admin] Delete a language", description: "Soft-delete: the row survives for audit purposes and stops appearing in listings." })
836
+ @ApiOperation({
837
+ summary: "[admin] Delete a language",
838
+ description:
839
+ "Soft-delete: the row survives for audit purposes and stops appearing in listings.",
840
+ })
545
841
  @ApiParam({ name: "languageId" })
546
842
  @ApiBody({ type: DeleteReasonDto, required: false })
547
843
  @ApiResponse({ status: 200, type: OkResponseDto })
548
- async deleteLanguage(@Param("languageId") languageId: string, @Body() body: Record<string, unknown>, @Req() req: Request) {
549
- await this.auth.deleteLanguage(languageId, req.auth!.sub, optionalString(body?.reason));
844
+ async deleteLanguage(
845
+ @Param("languageId") languageId: string,
846
+ @Body() body: Record<string, unknown>,
847
+ @Req() req: Request,
848
+ ) {
849
+ await this.auth.deleteLanguage(
850
+ languageId,
851
+ req.auth!.sub,
852
+ optionalString(body?.reason),
853
+ );
550
854
  return { ok: true };
551
855
  }
552
856
 
@@ -555,7 +859,10 @@ export class AdminController {
555
859
  @ApiOperation({ summary: "[admin] Reactivate a language" })
556
860
  @ApiParam({ name: "languageId" })
557
861
  @ApiResponse({ status: 201, type: OkResponseDto })
558
- async activateLanguage(@Param("languageId") languageId: string, @Req() req: Request) {
862
+ async activateLanguage(
863
+ @Param("languageId") languageId: string,
864
+ @Req() req: Request,
865
+ ) {
559
866
  await this.auth.activateLanguage(languageId, req.auth!.sub);
560
867
  return { ok: true };
561
868
  }
@@ -565,7 +872,10 @@ export class AdminController {
565
872
  @ApiOperation({ summary: "[admin] Deactivate a language" })
566
873
  @ApiParam({ name: "languageId" })
567
874
  @ApiResponse({ status: 201, type: OkResponseDto })
568
- async deactivateLanguage(@Param("languageId") languageId: string, @Req() req: Request) {
875
+ async deactivateLanguage(
876
+ @Param("languageId") languageId: string,
877
+ @Req() req: Request,
878
+ ) {
569
879
  await this.auth.deactivateLanguage(languageId, req.auth!.sub);
570
880
  return { ok: true };
571
881
  }
@@ -576,10 +886,28 @@ export class AdminController {
576
886
  @Get("customers")
577
887
  @CheckAbility("customers:read")
578
888
  @ApiOperation({ summary: "[admin] List customers" })
579
- @ApiQuery({ name: "search", required: false, description: "Matches name/email/username/phone" })
580
- @ApiQuery({ name: "page", required: false, description: "1-indexed. Defaults to 1." })
581
- @ApiQuery({ name: "limit", required: false, description: "Defaults to 25, capped at 100." })
582
- @ApiQuery({ name: "activeOnly", required: false, type: Boolean, description: "Pass true for a picker/dropdown — false or omitted returns everything, active or not." })
889
+ @ApiQuery({
890
+ name: "search",
891
+ required: false,
892
+ description: "Matches name/email/username/phone",
893
+ })
894
+ @ApiQuery({
895
+ name: "page",
896
+ required: false,
897
+ description: "1-indexed. Defaults to 1.",
898
+ })
899
+ @ApiQuery({
900
+ name: "limit",
901
+ required: false,
902
+ description: "Defaults to 25, capped at 100.",
903
+ })
904
+ @ApiQuery({
905
+ name: "activeOnly",
906
+ required: false,
907
+ type: Boolean,
908
+ description:
909
+ "Pass true for a picker/dropdown — false or omitted returns everything, active or not.",
910
+ })
583
911
  @ApiResponse({ status: 200, type: CustomerListResponseDto })
584
912
  async listCustomers(
585
913
  @Query("search") search?: string,
@@ -587,7 +915,12 @@ export class AdminController {
587
915
  @Query("limit") limit?: string,
588
916
  @Query("activeOnly") activeOnly?: string,
589
917
  ) {
590
- return this.auth.listCustomers({ search, page: page ? Number(page) : undefined, limit: limit ? Number(limit) : undefined, activeOnly: activeOnly === "true" });
918
+ return this.auth.listCustomers({
919
+ search,
920
+ page: page ? Number(page) : undefined,
921
+ limit: limit ? Number(limit) : undefined,
922
+ activeOnly: activeOnly === "true",
923
+ });
591
924
  }
592
925
 
593
926
  @Post("customers")
@@ -595,7 +928,10 @@ export class AdminController {
595
928
  @ApiOperation({ summary: "[admin] Create a customer" })
596
929
  @ApiBody({ type: CreateCustomerDto })
597
930
  @ApiResponse({ status: 201, type: CustomerSummaryDto })
598
- async createCustomer(@Body() body: Record<string, unknown>, @Req() req: Request) {
931
+ async createCustomer(
932
+ @Body() body: Record<string, unknown>,
933
+ @Req() req: Request,
934
+ ) {
599
935
  return this.auth.createCustomer(
600
936
  {
601
937
  email: requireString(body.email, "email"),
@@ -630,12 +966,17 @@ export class AdminController {
630
966
  @ApiParam({ name: "customerId" })
631
967
  @ApiBody({ type: UpdateCustomerDto })
632
968
  @ApiResponse({ status: 200, type: CustomerSummaryDto })
633
- async updateCustomer(@Param("customerId") customerId: string, @Body() body: Record<string, unknown>, @Req() req: Request) {
969
+ async updateCustomer(
970
+ @Param("customerId") customerId: string,
971
+ @Body() body: Record<string, unknown>,
972
+ @Req() req: Request,
973
+ ) {
634
974
  return this.auth.updateCustomer(
635
975
  customerId,
636
976
  {
637
977
  email: optionalString(body.email),
638
- firstName: body.firstName === null ? null : optionalString(body.firstName),
978
+ firstName:
979
+ body.firstName === null ? null : optionalString(body.firstName),
639
980
  lastName: body.lastName === null ? null : optionalString(body.lastName),
640
981
  username: body.username === null ? null : optionalString(body.username),
641
982
  phone: body.phone === null ? null : optionalString(body.phone),
@@ -653,12 +994,24 @@ export class AdminController {
653
994
 
654
995
  @Delete("customers/:customerId")
655
996
  @CheckAbility("customers:manage")
656
- @ApiOperation({ summary: "[admin] Delete a customer", description: "Soft-delete: the row survives for audit purposes and stops appearing in listings." })
997
+ @ApiOperation({
998
+ summary: "[admin] Delete a customer",
999
+ description:
1000
+ "Soft-delete: the row survives for audit purposes and stops appearing in listings.",
1001
+ })
657
1002
  @ApiParam({ name: "customerId" })
658
1003
  @ApiBody({ type: DeleteReasonDto, required: false })
659
1004
  @ApiResponse({ status: 200, type: OkResponseDto })
660
- async deleteCustomer(@Param("customerId") customerId: string, @Body() body: Record<string, unknown>, @Req() req: Request) {
661
- await this.auth.deleteCustomer(customerId, req.auth!.sub, optionalString(body?.reason));
1005
+ async deleteCustomer(
1006
+ @Param("customerId") customerId: string,
1007
+ @Body() body: Record<string, unknown>,
1008
+ @Req() req: Request,
1009
+ ) {
1010
+ await this.auth.deleteCustomer(
1011
+ customerId,
1012
+ req.auth!.sub,
1013
+ optionalString(body?.reason),
1014
+ );
662
1015
  return { ok: true };
663
1016
  }
664
1017
 
@@ -667,7 +1020,10 @@ export class AdminController {
667
1020
  @ApiOperation({ summary: "[admin] Reactivate a customer" })
668
1021
  @ApiParam({ name: "customerId" })
669
1022
  @ApiResponse({ status: 201, type: OkResponseDto })
670
- async activateCustomer(@Param("customerId") customerId: string, @Req() req: Request) {
1023
+ async activateCustomer(
1024
+ @Param("customerId") customerId: string,
1025
+ @Req() req: Request,
1026
+ ) {
671
1027
  await this.auth.activateCustomer(customerId, req.auth!.sub);
672
1028
  return { ok: true };
673
1029
  }
@@ -677,7 +1033,10 @@ export class AdminController {
677
1033
  @ApiOperation({ summary: "[admin] Deactivate a customer" })
678
1034
  @ApiParam({ name: "customerId" })
679
1035
  @ApiResponse({ status: 201, type: OkResponseDto })
680
- async deactivateCustomer(@Param("customerId") customerId: string, @Req() req: Request) {
1036
+ async deactivateCustomer(
1037
+ @Param("customerId") customerId: string,
1038
+ @Req() req: Request,
1039
+ ) {
681
1040
  await this.auth.deactivateCustomer(customerId, req.auth!.sub);
682
1041
  return { ok: true };
683
1042
  }