@simple-auth-kit/cli 1.4.3 → 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 +4 -4
  116. package/simple-auth-kit.ts +29 -2
  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,12 +1,36 @@
1
- import { BadRequestException, Body, Controller, Delete, ForbiddenException, Get, Inject, Ip, Param, Patch, Post, Query, Req, UseGuards } from "@nestjs/common";
2
- import { ApiBearerAuth, ApiBody, ApiHeader, 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
+ ApiHeader,
21
+ ApiOperation,
22
+ ApiParam,
23
+ ApiQuery,
24
+ ApiResponse,
25
+ ApiTags,
26
+ } from "@nestjs/swagger";
3
27
  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 { WORKSPACE_HEADER, WorkspaceGuard } from "./authz.guard.js";
8
- import { CheckAbility } from "./route-tiers.js";
9
- import { WorkspaceRepository } from "./workspace.repository.js";
28
+ import { AbilityGuard } from "../guards/ability.guard.js";
29
+ import { AuthGuard } from "../guards/auth.guard.js";
30
+ import { AuthService } from "../services/auth.service.js";
31
+ import { WORKSPACE_HEADER, WorkspaceGuard } from "../guards/authz.guard.js";
32
+ import { CheckAbility } from "../route-tiers.js";
33
+ import { WorkspaceRepository } from "../repositories/workspace.repository.js";
10
34
  import { hashPassword } from "@/lib/auth/core/crypto.js";
11
35
  import {
12
36
  AssignRoleDto,
@@ -23,23 +47,45 @@ import {
23
47
  RoleSummaryDto,
24
48
  UpdateRoleDto,
25
49
  UpdateUserDto,
26
- } from "./dto/auth.dto.js";
27
- import { AuditLogListResponseDto, UserListResponseDto, UserSummaryDto } from "./dto/admin.dto.js";
28
- import { CountryListResponseDto, CountrySummaryDto, CreateCountryDto, UpdateCountryDto } from "./dto/country.dto.js";
29
- import { CreateCustomerDto, CustomerListResponseDto, CustomerSummaryDto, UpdateCustomerDto } from "./dto/customer.dto.js";
30
- import { CreateLanguageDto, LanguageListResponseDto, LanguageSummaryDto, UpdateLanguageDto } from "./dto/language.dto.js";
31
- import { CountryRepository } from "./country.repository.js";
32
- import { CustomerRepository } from "./customer.repository.js";
33
- import { LanguageRepository } from "./language.repository.js";
50
+ } from "../dto/auth.dto.js";
51
+ import {
52
+ AuditLogListResponseDto,
53
+ UserListResponseDto,
54
+ UserSummaryDto,
55
+ } from "../dto/admin.dto.js";
56
+ import {
57
+ CountryListResponseDto,
58
+ CountrySummaryDto,
59
+ CreateCountryDto,
60
+ UpdateCountryDto,
61
+ } from "../dto/country.dto.js";
62
+ import {
63
+ CreateCustomerDto,
64
+ CustomerListResponseDto,
65
+ CustomerSummaryDto,
66
+ UpdateCustomerDto,
67
+ } from "../dto/customer.dto.js";
68
+ import {
69
+ CreateLanguageDto,
70
+ LanguageListResponseDto,
71
+ LanguageSummaryDto,
72
+ UpdateLanguageDto,
73
+ } from "../dto/language.dto.js";
74
+ import { CountryRepository } from "../repositories/country.repository.js";
75
+ import { CustomerRepository } from "../repositories/customer.repository.js";
76
+ import { LanguageRepository } from "../repositories/language.repository.js";
34
77
 
35
78
  function requireString(value: unknown, field: string): string {
36
- if (typeof value !== "string" || value.length === 0) throw new BadRequestException(`${field} is required`);
79
+ if (typeof value !== "string" || value.length === 0)
80
+ throw new BadRequestException(`${field} is required`);
37
81
  return value;
38
82
  }
39
83
 
40
- const optionalString = (value: unknown): string | undefined => (typeof value === "string" ? value : undefined);
84
+ const optionalString = (value: unknown): string | undefined =>
85
+ typeof value === "string" ? value : undefined;
41
86
 
42
- const optionalBoolean = (value: unknown): boolean | undefined => (typeof value === "boolean" ? value : undefined);
87
+ const optionalBoolean = (value: unknown): boolean | undefined =>
88
+ typeof value === "boolean" ? value : undefined;
43
89
 
44
90
  // Administration *of one workspace*, and nothing else. `WorkspaceGuard` resolves the caller's
45
91
  // membership of the workspace named by the `X-Workspace-Id` header into the roles/permissions
@@ -49,12 +95,18 @@ const optionalBoolean = (value: unknown): boolean | undefined => (typeof value =
49
95
  @ApiTags("auth")
50
96
  @Controller("auth/admin")
51
97
  @ApiBearerAuth()
52
- @ApiHeader({ name: WORKSPACE_HEADER, required: true, description: "The workspace this request administers. The caller must be an admin member of it." })
98
+ @ApiHeader({
99
+ name: WORKSPACE_HEADER,
100
+ required: true,
101
+ description:
102
+ "The workspace this request administers. The caller must be an admin member of it.",
103
+ })
53
104
  @UseGuards(AuthGuard, WorkspaceGuard, AbilityGuard)
54
105
  export class AdminController {
55
106
  constructor(
56
107
  @Inject(AuthService) private readonly auth: AuthService,
57
- @Inject(WorkspaceRepository) private readonly workspaces: WorkspaceRepository,
108
+ @Inject(WorkspaceRepository)
109
+ private readonly workspaces: WorkspaceRepository,
58
110
  @Inject(CountryRepository) private readonly countries: CountryRepository,
59
111
  @Inject(LanguageRepository) private readonly languages: LanguageRepository,
60
112
  @Inject(CustomerRepository) private readonly customers: CustomerRepository,
@@ -63,24 +115,48 @@ export class AdminController {
63
115
  @Get("users")
64
116
  @CheckAbility("users:read")
65
117
  @ApiOperation({ summary: "[admin] List the members of this workspace" })
66
- @ApiQuery({ name: "search", required: false, description: "Email substring match" })
67
- @ApiQuery({ name: "page", required: false, description: "1-indexed. Defaults to 1." })
68
- @ApiQuery({ name: "limit", required: false, description: "Defaults to 25, capped at 100." })
118
+ @ApiQuery({
119
+ name: "search",
120
+ required: false,
121
+ description: "Email substring match",
122
+ })
123
+ @ApiQuery({
124
+ name: "page",
125
+ required: false,
126
+ description: "1-indexed. Defaults to 1.",
127
+ })
128
+ @ApiQuery({
129
+ name: "limit",
130
+ required: false,
131
+ description: "Defaults to 25, capped at 100.",
132
+ })
69
133
  @ApiResponse({ status: 200, type: UserListResponseDto })
70
- async listUsers(@Req() req: Request, @Query("search") search?: string, @Query("page") page?: string, @Query("limit") limit?: string) {
71
- return this.auth.listUsers(req.authz!, { search, page: page ? Number(page) : undefined, limit: limit ? Number(limit) : undefined });
134
+ async listUsers(
135
+ @Req() req: Request,
136
+ @Query("search") search?: string,
137
+ @Query("page") page?: string,
138
+ @Query("limit") limit?: string,
139
+ ) {
140
+ return this.auth.listUsers(req.authz!, {
141
+ search,
142
+ page: page ? Number(page) : undefined,
143
+ limit: limit ? Number(limit) : undefined,
144
+ });
72
145
  }
73
146
 
74
147
  @Post("users")
75
148
  @CheckAbility("users:manage")
76
149
  @ApiOperation({
77
150
  summary: "[admin] Create a user and add them to this workspace",
78
- description: "No invitation email — the account is usable immediately with the password given here.",
151
+ description:
152
+ "No invitation email — the account is usable immediately with the password given here.",
79
153
  })
80
154
  @ApiBody({ type: CreateUserDto })
81
155
  @ApiResponse({ status: 201, type: UserSummaryDto })
82
156
  async createUser(@Body() body: Record<string, unknown>, @Req() req: Request) {
83
- const passwordHash = await hashPassword(requireString(body.password, "password"));
157
+ const passwordHash = await hashPassword(
158
+ requireString(body.password, "password"),
159
+ );
84
160
  const member = await this.workspaces.createMember(
85
161
  req.authz!.workspaceId,
86
162
  {
@@ -92,7 +168,11 @@ export class AdminController {
92
168
  phone: optionalString(body.phone),
93
169
  username: optionalString(body.username),
94
170
  photo: optionalString(body.photo),
95
- roles: Array.isArray(body.roles) ? body.roles.filter((role): role is string => typeof role === "string") : undefined,
171
+ roles: Array.isArray(body.roles)
172
+ ? body.roles.filter(
173
+ (role): role is string => typeof role === "string",
174
+ )
175
+ : undefined,
96
176
  },
97
177
  req.auth!.sub,
98
178
  );
@@ -110,18 +190,28 @@ export class AdminController {
110
190
 
111
191
  @Patch("users/:userId")
112
192
  @CheckAbility("users:manage")
113
- @ApiOperation({ summary: "[admin] Edit a member's profile", description: "Profile fields only — email is the login identifier and is not editable here." })
193
+ @ApiOperation({
194
+ summary: "[admin] Edit a member's profile",
195
+ description:
196
+ "Profile fields only — email is the login identifier and is not editable here.",
197
+ })
114
198
  @ApiParam({ name: "userId" })
115
199
  @ApiBody({ type: UpdateUserDto })
116
200
  @ApiResponse({ status: 200, type: UserSummaryDto })
117
- async updateUser(@Param("userId") userId: string, @Body() body: Record<string, unknown>, @Req() req: Request) {
201
+ async updateUser(
202
+ @Param("userId") userId: string,
203
+ @Body() body: Record<string, unknown>,
204
+ @Req() req: Request,
205
+ ) {
118
206
  return this.auth.updateUser(
119
207
  req.authz!,
120
208
  userId,
121
209
  {
122
- firstName: body.firstName === null ? null : optionalString(body.firstName),
210
+ firstName:
211
+ body.firstName === null ? null : optionalString(body.firstName),
123
212
  lastName: body.lastName === null ? null : optionalString(body.lastName),
124
- displayName: body.displayName === null ? null : optionalString(body.displayName),
213
+ displayName:
214
+ body.displayName === null ? null : optionalString(body.displayName),
125
215
  phone: body.phone === null ? null : optionalString(body.phone),
126
216
  username: body.username === null ? null : optionalString(body.username),
127
217
  photo: body.photo === null ? null : optionalString(body.photo),
@@ -141,21 +231,53 @@ export class AdminController {
141
231
  @ApiParam({ name: "userId" })
142
232
  @ApiBody({ type: DeleteReasonDto, required: false })
143
233
  @ApiResponse({ status: 200, type: OkResponseDto })
144
- async deleteUser(@Param("userId") userId: string, @Body() body: Record<string, unknown>, @Req() req: Request) {
145
- if (userId === req.auth!.sub) throw new ForbiddenException("cannot delete your own account");
146
- await this.auth.deleteUser(req.authz!, userId, req.auth!.sub, optionalString(body?.reason));
234
+ async deleteUser(
235
+ @Param("userId") userId: string,
236
+ @Body() body: Record<string, unknown>,
237
+ @Req() req: Request,
238
+ ) {
239
+ if (userId === req.auth!.sub)
240
+ throw new ForbiddenException("cannot delete your own account");
241
+ await this.auth.deleteUser(
242
+ req.authz!,
243
+ userId,
244
+ req.auth!.sub,
245
+ optionalString(body?.reason),
246
+ );
147
247
  return { ok: true };
148
248
  }
149
249
 
150
250
  @Get("audit-log")
151
251
  @CheckAbility("audit-log:read")
152
- @ApiOperation({ summary: "[admin] List this workspace's audit log entries, newest first" })
252
+ @ApiOperation({
253
+ summary: "[admin] List this workspace's audit log entries, newest first",
254
+ })
153
255
  @ApiQuery({ name: "userId", required: false })
154
- @ApiQuery({ name: "action", required: false, description: "AuditEvent discriminant, e.g. 'role_assigned'" })
155
- @ApiQuery({ name: "since", required: false, description: "ISO 8601 timestamp" })
156
- @ApiQuery({ name: "until", required: false, description: "ISO 8601 timestamp" })
157
- @ApiQuery({ name: "page", required: false, description: "1-indexed. Defaults to 1." })
158
- @ApiQuery({ name: "limit", required: false, description: "Defaults to 25, capped at 100." })
256
+ @ApiQuery({
257
+ name: "action",
258
+ required: false,
259
+ description: "AuditEvent discriminant, e.g. 'role_assigned'",
260
+ })
261
+ @ApiQuery({
262
+ name: "since",
263
+ required: false,
264
+ description: "ISO 8601 timestamp",
265
+ })
266
+ @ApiQuery({
267
+ name: "until",
268
+ required: false,
269
+ description: "ISO 8601 timestamp",
270
+ })
271
+ @ApiQuery({
272
+ name: "page",
273
+ required: false,
274
+ description: "1-indexed. Defaults to 1.",
275
+ })
276
+ @ApiQuery({
277
+ name: "limit",
278
+ required: false,
279
+ description: "Defaults to 25, capped at 100.",
280
+ })
159
281
  @ApiResponse({ status: 200, type: AuditLogListResponseDto })
160
282
  async listAuditLog(
161
283
  @Req() req: Request,
@@ -183,7 +305,13 @@ export class AdminController {
183
305
  description:
184
306
  "Grouped and ordered for a permission matrix. The catalog is global, like the Permission table: what is scoped to this workspace is which of its roles and memberships point at each row.",
185
307
  })
186
- @ApiQuery({ name: "activeOnly", required: false, type: Boolean, description: "Pass true for a picker/dropdown — false or omitted returns everything, active or not." })
308
+ @ApiQuery({
309
+ name: "activeOnly",
310
+ required: false,
311
+ type: Boolean,
312
+ description:
313
+ "Pass true for a picker/dropdown — false or omitted returns everything, active or not.",
314
+ })
187
315
  @ApiResponse({ status: 200, type: PermissionListResponseDto })
188
316
  async listPermissions(@Query("activeOnly") activeOnly?: string) {
189
317
  return this.auth.listPermissions(activeOnly === "true");
@@ -199,17 +327,23 @@ export class AdminController {
199
327
  })
200
328
  @ApiBody({ type: DefinePermissionDto })
201
329
  @ApiResponse({ status: 201, type: PermissionSummaryDto })
202
- async definePermission(@Body() body: Record<string, unknown>, @Req() req: Request) {
330
+ async definePermission(
331
+ @Body() body: Record<string, unknown>,
332
+ @Req() req: Request,
333
+ ) {
203
334
  return this.auth.definePermission(
204
335
  {
205
336
  slug: requireString(body.slug, "slug"),
206
337
  name: optionalString(body.name),
207
338
  displayName: optionalString(body.displayName),
208
- description: body.description === null ? null : optionalString(body.description),
339
+ description:
340
+ body.description === null ? null : optionalString(body.description),
209
341
  group: optionalString(body.group),
210
- groupOrder: typeof body.groupOrder === "number" ? body.groupOrder : undefined,
342
+ groupOrder:
343
+ typeof body.groupOrder === "number" ? body.groupOrder : undefined,
211
344
  order: typeof body.order === "number" ? body.order : undefined,
212
- isActive: typeof body.isActive === "boolean" ? body.isActive : undefined,
345
+ isActive:
346
+ typeof body.isActive === "boolean" ? body.isActive : undefined,
213
347
  },
214
348
  req.auth!.sub,
215
349
  );
@@ -218,9 +352,18 @@ export class AdminController {
218
352
  @Get("roles")
219
353
  @CheckAbility("roles:manage")
220
354
  @ApiOperation({ summary: "[admin] List the roles this workspace defines" })
221
- @ApiQuery({ name: "activeOnly", required: false, type: Boolean, description: "Pass true for a picker/dropdown — false or omitted returns everything, active or not." })
355
+ @ApiQuery({
356
+ name: "activeOnly",
357
+ required: false,
358
+ type: Boolean,
359
+ description:
360
+ "Pass true for a picker/dropdown — false or omitted returns everything, active or not.",
361
+ })
222
362
  @ApiResponse({ status: 200, type: RoleListResponseDto })
223
- async listRoles(@Req() req: Request, @Query("activeOnly") activeOnly?: string) {
363
+ async listRoles(
364
+ @Req() req: Request,
365
+ @Query("activeOnly") activeOnly?: string,
366
+ ) {
224
367
  return this.auth.listRoles(req.authz!, activeOnly === "true");
225
368
  }
226
369
 
@@ -248,15 +391,21 @@ export class AdminController {
248
391
  @ApiParam({ name: "roleId" })
249
392
  @ApiBody({ type: UpdateRoleDto })
250
393
  @ApiResponse({ status: 200, type: RoleSummaryDto })
251
- async updateRole(@Param("roleId") roleId: string, @Body() body: Record<string, unknown>, @Req() req: Request) {
394
+ async updateRole(
395
+ @Param("roleId") roleId: string,
396
+ @Body() body: Record<string, unknown>,
397
+ @Req() req: Request,
398
+ ) {
252
399
  return this.auth.updateRole(
253
400
  req.authz!,
254
401
  roleId,
255
402
  {
256
403
  name: optionalString(body.name),
257
404
  displayName: optionalString(body.displayName),
258
- description: body.description === null ? null : optionalString(body.description),
259
- isActive: typeof body.isActive === "boolean" ? body.isActive : undefined,
405
+ description:
406
+ body.description === null ? null : optionalString(body.description),
407
+ isActive:
408
+ typeof body.isActive === "boolean" ? body.isActive : undefined,
260
409
  },
261
410
  req.auth!.sub,
262
411
  );
@@ -266,93 +415,167 @@ export class AdminController {
266
415
  @CheckAbility("roles:manage")
267
416
  @ApiOperation({
268
417
  summary: "[admin] Delete one of this workspace's roles",
269
- description: "Soft-delete: existing assignments are left in place rather than cascade-deleted, and the role simply stops being resolved.",
418
+ description:
419
+ "Soft-delete: existing assignments are left in place rather than cascade-deleted, and the role simply stops being resolved.",
270
420
  })
271
421
  @ApiParam({ name: "roleId" })
272
422
  @ApiBody({ type: DeleteReasonDto, required: false })
273
423
  @ApiResponse({ status: 200, type: OkResponseDto })
274
- async deleteRole(@Param("roleId") roleId: string, @Body() body: Record<string, unknown>, @Req() req: Request) {
275
- await this.auth.deleteRole(req.authz!, roleId, req.auth!.sub, optionalString(body?.reason));
424
+ async deleteRole(
425
+ @Param("roleId") roleId: string,
426
+ @Body() body: Record<string, unknown>,
427
+ @Req() req: Request,
428
+ ) {
429
+ await this.auth.deleteRole(
430
+ req.authz!,
431
+ roleId,
432
+ req.auth!.sub,
433
+ optionalString(body?.reason),
434
+ );
276
435
  return { ok: true };
277
436
  }
278
437
 
279
438
  @Post("roles/:roleId/permissions")
280
439
  @CheckAbility("roles:manage")
281
- @ApiOperation({ summary: "[admin] Attach a permission to one of this workspace's roles" })
440
+ @ApiOperation({
441
+ summary: "[admin] Attach a permission to one of this workspace's roles",
442
+ })
282
443
  @ApiParam({ name: "roleId" })
283
444
  @ApiBody({ type: AttachPermissionDto })
284
445
  @ApiResponse({ status: 201, type: OkResponseDto })
285
- async attachPermissionToRole(@Param("roleId") roleId: string, @Body() body: Record<string, unknown>, @Req() req: Request) {
286
- await this.auth.attachPermissionToRole(req.authz!, roleId, requireString(body.permission, "permission"), req.auth!.sub);
446
+ async attachPermissionToRole(
447
+ @Param("roleId") roleId: string,
448
+ @Body() body: Record<string, unknown>,
449
+ @Req() req: Request,
450
+ ) {
451
+ await this.auth.attachPermissionToRole(
452
+ req.authz!,
453
+ roleId,
454
+ requireString(body.permission, "permission"),
455
+ req.auth!.sub,
456
+ );
287
457
  return { ok: true };
288
458
  }
289
459
 
290
460
  @Post("roles/:roleId/permissions/:permissionSlug/revoke")
291
461
  @CheckAbility("roles:manage")
292
- @ApiOperation({ summary: "[admin] Detach a permission from one of this workspace's roles" })
462
+ @ApiOperation({
463
+ summary: "[admin] Detach a permission from one of this workspace's roles",
464
+ })
293
465
  @ApiParam({ name: "roleId" })
294
466
  @ApiParam({ name: "permissionSlug" })
295
467
  @ApiResponse({ status: 201, type: OkResponseDto })
296
- async detachPermissionFromRole(@Param("roleId") roleId: string, @Param("permissionSlug") permissionSlug: string, @Req() req: Request) {
297
- await this.auth.detachPermissionFromRole(req.authz!, roleId, permissionSlug);
468
+ async detachPermissionFromRole(
469
+ @Param("roleId") roleId: string,
470
+ @Param("permissionSlug") permissionSlug: string,
471
+ @Req() req: Request,
472
+ ) {
473
+ await this.auth.detachPermissionFromRole(
474
+ req.authz!,
475
+ roleId,
476
+ permissionSlug,
477
+ );
298
478
  return { ok: true };
299
479
  }
300
480
 
301
481
  @Post("users/:userId/roles")
302
482
  @CheckAbility("roles:assign")
303
- @ApiOperation({ summary: "[admin] Assign a role to a member of this workspace" })
483
+ @ApiOperation({
484
+ summary: "[admin] Assign a role to a member of this workspace",
485
+ })
304
486
  @ApiParam({ name: "userId" })
305
487
  @ApiBody({ type: AssignRoleDto })
306
488
  @ApiResponse({ status: 201, type: OkResponseDto })
307
- async assignRole(@Param("userId") userId: string, @Body() body: Record<string, unknown>, @Req() req: Request) {
308
- await this.auth.assignRole(req.authz!, userId, requireString(body.role, "role"));
489
+ async assignRole(
490
+ @Param("userId") userId: string,
491
+ @Body() body: Record<string, unknown>,
492
+ @Req() req: Request,
493
+ ) {
494
+ await this.auth.assignRole(
495
+ req.authz!,
496
+ userId,
497
+ requireString(body.role, "role"),
498
+ );
309
499
  return { ok: true };
310
500
  }
311
501
 
312
502
  @Post("users/:userId/roles/:roleSlug/revoke")
313
503
  @CheckAbility("roles:assign")
314
- @ApiOperation({ summary: "[admin] Revoke a role from a member of this workspace" })
504
+ @ApiOperation({
505
+ summary: "[admin] Revoke a role from a member of this workspace",
506
+ })
315
507
  @ApiParam({ name: "userId" })
316
508
  @ApiParam({ name: "roleSlug" })
317
509
  @ApiResponse({ status: 201, type: OkResponseDto })
318
- async revokeRole(@Param("userId") userId: string, @Param("roleSlug") roleSlug: string, @Req() req: Request) {
510
+ async revokeRole(
511
+ @Param("userId") userId: string,
512
+ @Param("roleSlug") roleSlug: string,
513
+ @Req() req: Request,
514
+ ) {
319
515
  // Revoking your own admin role would strip the very permission that authorized the call,
320
516
  // with no way back in. Assigning to yourself is fine; it can't lock anyone out.
321
- if (userId === req.auth!.sub) throw new ForbiddenException("cannot change your own roles");
517
+ if (userId === req.auth!.sub)
518
+ throw new ForbiddenException("cannot change your own roles");
322
519
  await this.auth.revokeRole(req.authz!, userId, roleSlug);
323
520
  return { ok: true };
324
521
  }
325
522
 
326
523
  @Post("users/:userId/permissions")
327
524
  @CheckAbility("permissions:grant")
328
- @ApiOperation({ summary: "[admin] Grant a permission directly to a member, bypassing roles", description: "The grant is scoped to this workspace." })
525
+ @ApiOperation({
526
+ summary: "[admin] Grant a permission directly to a member, bypassing roles",
527
+ description: "The grant is scoped to this workspace.",
528
+ })
329
529
  @ApiParam({ name: "userId" })
330
530
  @ApiBody({ type: GrantPermissionDto })
331
531
  @ApiResponse({ status: 201, type: OkResponseDto })
332
- async grantPermission(@Param("userId") userId: string, @Body() body: Record<string, unknown>, @Req() req: Request) {
333
- await this.auth.grantPermission(req.authz!, userId, requireString(body.permission, "permission"), req.auth!.sub);
532
+ async grantPermission(
533
+ @Param("userId") userId: string,
534
+ @Body() body: Record<string, unknown>,
535
+ @Req() req: Request,
536
+ ) {
537
+ await this.auth.grantPermission(
538
+ req.authz!,
539
+ userId,
540
+ requireString(body.permission, "permission"),
541
+ req.auth!.sub,
542
+ );
334
543
  return { ok: true };
335
544
  }
336
545
 
337
546
  @Post("users/:userId/permissions/:permissionSlug/revoke")
338
547
  @CheckAbility("permissions:grant")
339
- @ApiOperation({ summary: "[admin] Revoke a direct permission grant from a member" })
548
+ @ApiOperation({
549
+ summary: "[admin] Revoke a direct permission grant from a member",
550
+ })
340
551
  @ApiParam({ name: "userId" })
341
552
  @ApiParam({ name: "permissionSlug" })
342
553
  @ApiResponse({ status: 201, type: OkResponseDto })
343
- async revokePermission(@Param("userId") userId: string, @Param("permissionSlug") permissionSlug: string, @Req() req: Request) {
554
+ async revokePermission(
555
+ @Param("userId") userId: string,
556
+ @Param("permissionSlug") permissionSlug: string,
557
+ @Req() req: Request,
558
+ ) {
344
559
  await this.auth.revokePermission(req.authz!, userId, permissionSlug);
345
560
  return { ok: true };
346
561
  }
347
562
 
348
563
  @Post("users/:userId/block")
349
564
  @CheckAbility("users:block")
350
- @ApiOperation({ summary: "[admin] Block a member, revoking all their sessions immediately",
351
- description: "Blocking disables the whole account, so it is only allowed against a member of the workspace you administer." })
565
+ @ApiOperation({
566
+ summary: "[admin] Block a member, revoking all their sessions immediately",
567
+ description:
568
+ "Blocking disables the whole account, so it is only allowed against a member of the workspace you administer.",
569
+ })
352
570
  @ApiParam({ name: "userId" })
353
571
  @ApiResponse({ status: 201, type: OkResponseDto })
354
- async block(@Param("userId") userId: string, @Req() req: Request, @Ip() ip: string) {
355
- if (userId === req.auth!.sub) throw new ForbiddenException("cannot block your own account");
572
+ async block(
573
+ @Param("userId") userId: string,
574
+ @Req() req: Request,
575
+ @Ip() ip: string,
576
+ ) {
577
+ if (userId === req.auth!.sub)
578
+ throw new ForbiddenException("cannot block your own account");
356
579
  await this.auth.block(req.authz!, userId, { userId: req.auth!.sub, ip });
357
580
  return { ok: true };
358
581
  }
@@ -362,7 +585,11 @@ export class AdminController {
362
585
  @ApiOperation({ summary: "[admin] Unblock a member" })
363
586
  @ApiParam({ name: "userId" })
364
587
  @ApiResponse({ status: 201, type: OkResponseDto })
365
- async unblock(@Param("userId") userId: string, @Req() req: Request, @Ip() ip: string) {
588
+ async unblock(
589
+ @Param("userId") userId: string,
590
+ @Req() req: Request,
591
+ @Ip() ip: string,
592
+ ) {
366
593
  await this.auth.unblock(req.authz!, userId, { userId: req.auth!.sub, ip });
367
594
  return { ok: true };
368
595
  }
@@ -370,14 +597,24 @@ export class AdminController {
370
597
  @Post("users/:userId/deactivate")
371
598
  @CheckAbility("users:block")
372
599
  @ApiOperation({
373
- summary: "[admin] Deactivate a member, revoking all their sessions immediately",
374
- description: "Distinct from block/unblock — a routine administrative toggle, not a security action. Both independently deny login.",
600
+ summary:
601
+ "[admin] Deactivate a member, revoking all their sessions immediately",
602
+ description:
603
+ "Distinct from block/unblock — a routine administrative toggle, not a security action. Both independently deny login.",
375
604
  })
376
605
  @ApiParam({ name: "userId" })
377
606
  @ApiResponse({ status: 201, type: OkResponseDto })
378
- async deactivate(@Param("userId") userId: string, @Req() req: Request, @Ip() ip: string) {
379
- if (userId === req.auth!.sub) throw new ForbiddenException("cannot deactivate your own account");
380
- await this.auth.deactivate(req.authz!, userId, { userId: req.auth!.sub, ip });
607
+ async deactivate(
608
+ @Param("userId") userId: string,
609
+ @Req() req: Request,
610
+ @Ip() ip: string,
611
+ ) {
612
+ if (userId === req.auth!.sub)
613
+ throw new ForbiddenException("cannot deactivate your own account");
614
+ await this.auth.deactivate(req.authz!, userId, {
615
+ userId: req.auth!.sub,
616
+ ip,
617
+ });
381
618
  return { ok: true };
382
619
  }
383
620
 
@@ -386,7 +623,11 @@ export class AdminController {
386
623
  @ApiOperation({ summary: "[admin] Reactivate a member" })
387
624
  @ApiParam({ name: "userId" })
388
625
  @ApiResponse({ status: 201, type: OkResponseDto })
389
- async activate(@Param("userId") userId: string, @Req() req: Request, @Ip() ip: string) {
626
+ async activate(
627
+ @Param("userId") userId: string,
628
+ @Req() req: Request,
629
+ @Ip() ip: string,
630
+ ) {
390
631
  await this.auth.activate(req.authz!, userId, { userId: req.auth!.sub, ip });
391
632
  return { ok: true };
392
633
  }
@@ -396,10 +637,28 @@ export class AdminController {
396
637
  @Get("countries")
397
638
  @CheckAbility("countries:read")
398
639
  @ApiOperation({ summary: "[admin] List this workspace's countries" })
399
- @ApiQuery({ name: "search", required: false, description: "Matches name/code/isoCode" })
400
- @ApiQuery({ name: "page", required: false, description: "1-indexed. Defaults to 1." })
401
- @ApiQuery({ name: "limit", required: false, description: "Defaults to 25, capped at 100." })
402
- @ApiQuery({ name: "activeOnly", required: false, type: Boolean, description: "Pass true for a picker/dropdown — false or omitted returns everything, active or not." })
640
+ @ApiQuery({
641
+ name: "search",
642
+ required: false,
643
+ description: "Matches name/code/isoCode",
644
+ })
645
+ @ApiQuery({
646
+ name: "page",
647
+ required: false,
648
+ description: "1-indexed. Defaults to 1.",
649
+ })
650
+ @ApiQuery({
651
+ name: "limit",
652
+ required: false,
653
+ description: "Defaults to 25, capped at 100.",
654
+ })
655
+ @ApiQuery({
656
+ name: "activeOnly",
657
+ required: false,
658
+ type: Boolean,
659
+ description:
660
+ "Pass true for a picker/dropdown — false or omitted returns everything, active or not.",
661
+ })
403
662
  @ApiResponse({ status: 200, type: CountryListResponseDto })
404
663
  async listCountries(
405
664
  @Req() req: Request,
@@ -421,7 +680,10 @@ export class AdminController {
421
680
  @ApiOperation({ summary: "[admin] Create a country in this workspace" })
422
681
  @ApiBody({ type: CreateCountryDto })
423
682
  @ApiResponse({ status: 201, type: CountrySummaryDto })
424
- async createCountry(@Body() body: Record<string, unknown>, @Req() req: Request) {
683
+ async createCountry(
684
+ @Body() body: Record<string, unknown>,
685
+ @Req() req: Request,
686
+ ) {
425
687
  return this.countries.create(
426
688
  req.authz!.workspaceId,
427
689
  {
@@ -441,7 +703,9 @@ export class AdminController {
441
703
 
442
704
  @Get("countries/:countryId")
443
705
  @CheckAbility("countries:read")
444
- @ApiOperation({ summary: "[admin] Fetch a single country from this workspace" })
706
+ @ApiOperation({
707
+ summary: "[admin] Fetch a single country from this workspace",
708
+ })
445
709
  @ApiParam({ name: "countryId" })
446
710
  @ApiResponse({ status: 200, type: CountrySummaryDto })
447
711
  async getCountry(@Param("countryId") countryId: string, @Req() req: Request) {
@@ -454,7 +718,11 @@ export class AdminController {
454
718
  @ApiParam({ name: "countryId" })
455
719
  @ApiBody({ type: UpdateCountryDto })
456
720
  @ApiResponse({ status: 200, type: CountrySummaryDto })
457
- async updateCountry(@Param("countryId") countryId: string, @Body() body: Record<string, unknown>, @Req() req: Request) {
721
+ async updateCountry(
722
+ @Param("countryId") countryId: string,
723
+ @Body() body: Record<string, unknown>,
724
+ @Req() req: Request,
725
+ ) {
458
726
  return this.countries.update(
459
727
  req.authz!.workspaceId,
460
728
  countryId,
@@ -475,12 +743,25 @@ export class AdminController {
475
743
 
476
744
  @Delete("countries/:countryId")
477
745
  @CheckAbility("countries:manage")
478
- @ApiOperation({ summary: "[admin] Delete a country from this workspace", description: "Soft-delete: the row survives for audit purposes and stops appearing in listings." })
746
+ @ApiOperation({
747
+ summary: "[admin] Delete a country from this workspace",
748
+ description:
749
+ "Soft-delete: the row survives for audit purposes and stops appearing in listings.",
750
+ })
479
751
  @ApiParam({ name: "countryId" })
480
752
  @ApiBody({ type: DeleteReasonDto, required: false })
481
753
  @ApiResponse({ status: 200, type: OkResponseDto })
482
- async deleteCountry(@Param("countryId") countryId: string, @Body() body: Record<string, unknown>, @Req() req: Request) {
483
- await this.countries.delete(req.authz!.workspaceId, countryId, req.auth!.sub, optionalString(body?.reason));
754
+ async deleteCountry(
755
+ @Param("countryId") countryId: string,
756
+ @Body() body: Record<string, unknown>,
757
+ @Req() req: Request,
758
+ ) {
759
+ await this.countries.delete(
760
+ req.authz!.workspaceId,
761
+ countryId,
762
+ req.auth!.sub,
763
+ optionalString(body?.reason),
764
+ );
484
765
  return { ok: true };
485
766
  }
486
767
 
@@ -489,8 +770,16 @@ export class AdminController {
489
770
  @ApiOperation({ summary: "[admin] Reactivate a country" })
490
771
  @ApiParam({ name: "countryId" })
491
772
  @ApiResponse({ status: 201, type: OkResponseDto })
492
- async activateCountry(@Param("countryId") countryId: string, @Req() req: Request) {
493
- await this.countries.setActive(req.authz!.workspaceId, countryId, true, req.auth!.sub);
773
+ async activateCountry(
774
+ @Param("countryId") countryId: string,
775
+ @Req() req: Request,
776
+ ) {
777
+ await this.countries.setActive(
778
+ req.authz!.workspaceId,
779
+ countryId,
780
+ true,
781
+ req.auth!.sub,
782
+ );
494
783
  return { ok: true };
495
784
  }
496
785
 
@@ -499,8 +788,16 @@ export class AdminController {
499
788
  @ApiOperation({ summary: "[admin] Deactivate a country" })
500
789
  @ApiParam({ name: "countryId" })
501
790
  @ApiResponse({ status: 201, type: OkResponseDto })
502
- async deactivateCountry(@Param("countryId") countryId: string, @Req() req: Request) {
503
- await this.countries.setActive(req.authz!.workspaceId, countryId, false, req.auth!.sub);
791
+ async deactivateCountry(
792
+ @Param("countryId") countryId: string,
793
+ @Req() req: Request,
794
+ ) {
795
+ await this.countries.setActive(
796
+ req.authz!.workspaceId,
797
+ countryId,
798
+ false,
799
+ req.auth!.sub,
800
+ );
504
801
  return { ok: true };
505
802
  }
506
803
 
@@ -509,10 +806,28 @@ export class AdminController {
509
806
  @Get("languages")
510
807
  @CheckAbility("languages:read")
511
808
  @ApiOperation({ summary: "[admin] List this workspace's languages" })
512
- @ApiQuery({ name: "search", required: false, description: "Matches name/code/nativeName" })
513
- @ApiQuery({ name: "page", required: false, description: "1-indexed. Defaults to 1." })
514
- @ApiQuery({ name: "limit", required: false, description: "Defaults to 25, capped at 100." })
515
- @ApiQuery({ name: "activeOnly", required: false, type: Boolean, description: "Pass true for a picker/dropdown — false or omitted returns everything, active or not." })
809
+ @ApiQuery({
810
+ name: "search",
811
+ required: false,
812
+ description: "Matches name/code/nativeName",
813
+ })
814
+ @ApiQuery({
815
+ name: "page",
816
+ required: false,
817
+ description: "1-indexed. Defaults to 1.",
818
+ })
819
+ @ApiQuery({
820
+ name: "limit",
821
+ required: false,
822
+ description: "Defaults to 25, capped at 100.",
823
+ })
824
+ @ApiQuery({
825
+ name: "activeOnly",
826
+ required: false,
827
+ type: Boolean,
828
+ description:
829
+ "Pass true for a picker/dropdown — false or omitted returns everything, active or not.",
830
+ })
516
831
  @ApiResponse({ status: 200, type: LanguageListResponseDto })
517
832
  async listLanguages(
518
833
  @Req() req: Request,
@@ -534,7 +849,10 @@ export class AdminController {
534
849
  @ApiOperation({ summary: "[admin] Create a language in this workspace" })
535
850
  @ApiBody({ type: CreateLanguageDto })
536
851
  @ApiResponse({ status: 201, type: LanguageSummaryDto })
537
- async createLanguage(@Body() body: Record<string, unknown>, @Req() req: Request) {
852
+ async createLanguage(
853
+ @Body() body: Record<string, unknown>,
854
+ @Req() req: Request,
855
+ ) {
538
856
  return this.languages.create(
539
857
  req.authz!.workspaceId,
540
858
  {
@@ -551,10 +869,15 @@ export class AdminController {
551
869
 
552
870
  @Get("languages/:languageId")
553
871
  @CheckAbility("languages:read")
554
- @ApiOperation({ summary: "[admin] Fetch a single language from this workspace" })
872
+ @ApiOperation({
873
+ summary: "[admin] Fetch a single language from this workspace",
874
+ })
555
875
  @ApiParam({ name: "languageId" })
556
876
  @ApiResponse({ status: 200, type: LanguageSummaryDto })
557
- async getLanguage(@Param("languageId") languageId: string, @Req() req: Request) {
877
+ async getLanguage(
878
+ @Param("languageId") languageId: string,
879
+ @Req() req: Request,
880
+ ) {
558
881
  return this.languages.get(req.authz!.workspaceId, languageId);
559
882
  }
560
883
 
@@ -564,7 +887,11 @@ export class AdminController {
564
887
  @ApiParam({ name: "languageId" })
565
888
  @ApiBody({ type: UpdateLanguageDto })
566
889
  @ApiResponse({ status: 200, type: LanguageSummaryDto })
567
- async updateLanguage(@Param("languageId") languageId: string, @Body() body: Record<string, unknown>, @Req() req: Request) {
890
+ async updateLanguage(
891
+ @Param("languageId") languageId: string,
892
+ @Body() body: Record<string, unknown>,
893
+ @Req() req: Request,
894
+ ) {
568
895
  return this.languages.update(
569
896
  req.authz!.workspaceId,
570
897
  languageId,
@@ -582,12 +909,25 @@ export class AdminController {
582
909
 
583
910
  @Delete("languages/:languageId")
584
911
  @CheckAbility("languages:manage")
585
- @ApiOperation({ summary: "[admin] Delete a language from this workspace", description: "Soft-delete: the row survives for audit purposes and stops appearing in listings." })
912
+ @ApiOperation({
913
+ summary: "[admin] Delete a language from this workspace",
914
+ description:
915
+ "Soft-delete: the row survives for audit purposes and stops appearing in listings.",
916
+ })
586
917
  @ApiParam({ name: "languageId" })
587
918
  @ApiBody({ type: DeleteReasonDto, required: false })
588
919
  @ApiResponse({ status: 200, type: OkResponseDto })
589
- async deleteLanguage(@Param("languageId") languageId: string, @Body() body: Record<string, unknown>, @Req() req: Request) {
590
- await this.languages.delete(req.authz!.workspaceId, languageId, req.auth!.sub, optionalString(body?.reason));
920
+ async deleteLanguage(
921
+ @Param("languageId") languageId: string,
922
+ @Body() body: Record<string, unknown>,
923
+ @Req() req: Request,
924
+ ) {
925
+ await this.languages.delete(
926
+ req.authz!.workspaceId,
927
+ languageId,
928
+ req.auth!.sub,
929
+ optionalString(body?.reason),
930
+ );
591
931
  return { ok: true };
592
932
  }
593
933
 
@@ -596,8 +936,16 @@ export class AdminController {
596
936
  @ApiOperation({ summary: "[admin] Reactivate a language" })
597
937
  @ApiParam({ name: "languageId" })
598
938
  @ApiResponse({ status: 201, type: OkResponseDto })
599
- async activateLanguage(@Param("languageId") languageId: string, @Req() req: Request) {
600
- await this.languages.setActive(req.authz!.workspaceId, languageId, true, req.auth!.sub);
939
+ async activateLanguage(
940
+ @Param("languageId") languageId: string,
941
+ @Req() req: Request,
942
+ ) {
943
+ await this.languages.setActive(
944
+ req.authz!.workspaceId,
945
+ languageId,
946
+ true,
947
+ req.auth!.sub,
948
+ );
601
949
  return { ok: true };
602
950
  }
603
951
 
@@ -606,8 +954,16 @@ export class AdminController {
606
954
  @ApiOperation({ summary: "[admin] Deactivate a language" })
607
955
  @ApiParam({ name: "languageId" })
608
956
  @ApiResponse({ status: 201, type: OkResponseDto })
609
- async deactivateLanguage(@Param("languageId") languageId: string, @Req() req: Request) {
610
- await this.languages.setActive(req.authz!.workspaceId, languageId, false, req.auth!.sub);
957
+ async deactivateLanguage(
958
+ @Param("languageId") languageId: string,
959
+ @Req() req: Request,
960
+ ) {
961
+ await this.languages.setActive(
962
+ req.authz!.workspaceId,
963
+ languageId,
964
+ false,
965
+ req.auth!.sub,
966
+ );
611
967
  return { ok: true };
612
968
  }
613
969
 
@@ -617,10 +973,28 @@ export class AdminController {
617
973
  @Get("customers")
618
974
  @CheckAbility("customers:read")
619
975
  @ApiOperation({ summary: "[admin] List this workspace's customers" })
620
- @ApiQuery({ name: "search", required: false, description: "Matches name/email/username/phone" })
621
- @ApiQuery({ name: "page", required: false, description: "1-indexed. Defaults to 1." })
622
- @ApiQuery({ name: "limit", required: false, description: "Defaults to 25, capped at 100." })
623
- @ApiQuery({ name: "activeOnly", required: false, type: Boolean, description: "Pass true for a picker/dropdown — false or omitted returns everything, active or not." })
976
+ @ApiQuery({
977
+ name: "search",
978
+ required: false,
979
+ description: "Matches name/email/username/phone",
980
+ })
981
+ @ApiQuery({
982
+ name: "page",
983
+ required: false,
984
+ description: "1-indexed. Defaults to 1.",
985
+ })
986
+ @ApiQuery({
987
+ name: "limit",
988
+ required: false,
989
+ description: "Defaults to 25, capped at 100.",
990
+ })
991
+ @ApiQuery({
992
+ name: "activeOnly",
993
+ required: false,
994
+ type: Boolean,
995
+ description:
996
+ "Pass true for a picker/dropdown — false or omitted returns everything, active or not.",
997
+ })
624
998
  @ApiResponse({ status: 200, type: CustomerListResponseDto })
625
999
  async listCustomers(
626
1000
  @Req() req: Request,
@@ -642,7 +1016,10 @@ export class AdminController {
642
1016
  @ApiOperation({ summary: "[admin] Create a customer in this workspace" })
643
1017
  @ApiBody({ type: CreateCustomerDto })
644
1018
  @ApiResponse({ status: 201, type: CustomerSummaryDto })
645
- async createCustomer(@Body() body: Record<string, unknown>, @Req() req: Request) {
1019
+ async createCustomer(
1020
+ @Body() body: Record<string, unknown>,
1021
+ @Req() req: Request,
1022
+ ) {
646
1023
  return this.customers.create(
647
1024
  req.authz!.workspaceId,
648
1025
  {
@@ -665,10 +1042,15 @@ export class AdminController {
665
1042
 
666
1043
  @Get("customers/:customerId")
667
1044
  @CheckAbility("customers:read")
668
- @ApiOperation({ summary: "[admin] Fetch a single customer from this workspace" })
1045
+ @ApiOperation({
1046
+ summary: "[admin] Fetch a single customer from this workspace",
1047
+ })
669
1048
  @ApiParam({ name: "customerId" })
670
1049
  @ApiResponse({ status: 200, type: CustomerSummaryDto })
671
- async getCustomer(@Param("customerId") customerId: string, @Req() req: Request) {
1050
+ async getCustomer(
1051
+ @Param("customerId") customerId: string,
1052
+ @Req() req: Request,
1053
+ ) {
672
1054
  return this.customers.get(req.authz!.workspaceId, customerId);
673
1055
  }
674
1056
 
@@ -678,13 +1060,18 @@ export class AdminController {
678
1060
  @ApiParam({ name: "customerId" })
679
1061
  @ApiBody({ type: UpdateCustomerDto })
680
1062
  @ApiResponse({ status: 200, type: CustomerSummaryDto })
681
- async updateCustomer(@Param("customerId") customerId: string, @Body() body: Record<string, unknown>, @Req() req: Request) {
1063
+ async updateCustomer(
1064
+ @Param("customerId") customerId: string,
1065
+ @Body() body: Record<string, unknown>,
1066
+ @Req() req: Request,
1067
+ ) {
682
1068
  return this.customers.update(
683
1069
  req.authz!.workspaceId,
684
1070
  customerId,
685
1071
  {
686
1072
  email: optionalString(body.email),
687
- firstName: body.firstName === null ? null : optionalString(body.firstName),
1073
+ firstName:
1074
+ body.firstName === null ? null : optionalString(body.firstName),
688
1075
  lastName: body.lastName === null ? null : optionalString(body.lastName),
689
1076
  username: body.username === null ? null : optionalString(body.username),
690
1077
  phone: body.phone === null ? null : optionalString(body.phone),
@@ -702,12 +1089,25 @@ export class AdminController {
702
1089
 
703
1090
  @Delete("customers/:customerId")
704
1091
  @CheckAbility("customers:manage")
705
- @ApiOperation({ summary: "[admin] Delete a customer from this workspace", description: "Soft-delete: the row survives for audit purposes and stops appearing in listings." })
1092
+ @ApiOperation({
1093
+ summary: "[admin] Delete a customer from this workspace",
1094
+ description:
1095
+ "Soft-delete: the row survives for audit purposes and stops appearing in listings.",
1096
+ })
706
1097
  @ApiParam({ name: "customerId" })
707
1098
  @ApiBody({ type: DeleteReasonDto, required: false })
708
1099
  @ApiResponse({ status: 200, type: OkResponseDto })
709
- async deleteCustomer(@Param("customerId") customerId: string, @Body() body: Record<string, unknown>, @Req() req: Request) {
710
- await this.customers.delete(req.authz!.workspaceId, customerId, req.auth!.sub, optionalString(body?.reason));
1100
+ async deleteCustomer(
1101
+ @Param("customerId") customerId: string,
1102
+ @Body() body: Record<string, unknown>,
1103
+ @Req() req: Request,
1104
+ ) {
1105
+ await this.customers.delete(
1106
+ req.authz!.workspaceId,
1107
+ customerId,
1108
+ req.auth!.sub,
1109
+ optionalString(body?.reason),
1110
+ );
711
1111
  return { ok: true };
712
1112
  }
713
1113
 
@@ -716,8 +1116,16 @@ export class AdminController {
716
1116
  @ApiOperation({ summary: "[admin] Reactivate a customer" })
717
1117
  @ApiParam({ name: "customerId" })
718
1118
  @ApiResponse({ status: 201, type: OkResponseDto })
719
- async activateCustomer(@Param("customerId") customerId: string, @Req() req: Request) {
720
- await this.customers.setActive(req.authz!.workspaceId, customerId, true, req.auth!.sub);
1119
+ async activateCustomer(
1120
+ @Param("customerId") customerId: string,
1121
+ @Req() req: Request,
1122
+ ) {
1123
+ await this.customers.setActive(
1124
+ req.authz!.workspaceId,
1125
+ customerId,
1126
+ true,
1127
+ req.auth!.sub,
1128
+ );
721
1129
  return { ok: true };
722
1130
  }
723
1131
 
@@ -726,8 +1134,16 @@ export class AdminController {
726
1134
  @ApiOperation({ summary: "[admin] Deactivate a customer" })
727
1135
  @ApiParam({ name: "customerId" })
728
1136
  @ApiResponse({ status: 201, type: OkResponseDto })
729
- async deactivateCustomer(@Param("customerId") customerId: string, @Req() req: Request) {
730
- await this.customers.setActive(req.authz!.workspaceId, customerId, false, req.auth!.sub);
1137
+ async deactivateCustomer(
1138
+ @Param("customerId") customerId: string,
1139
+ @Req() req: Request,
1140
+ ) {
1141
+ await this.customers.setActive(
1142
+ req.authz!.workspaceId,
1143
+ customerId,
1144
+ false,
1145
+ req.auth!.sub,
1146
+ );
731
1147
  return { ok: true };
732
1148
  }
733
1149
  }