@simple-auth-kit/cli 1.4.3 → 1.5.1
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.
- package/lib/copy.ts +1 -1
- package/package.json +1 -1
- package/registry/combos/express-drizzle/shared/src/{auth-core-error.middleware.ts → middleware/auth-core-error.middleware.ts} +17 -4
- package/registry/combos/express-drizzle/shared/src/{auth.middleware.ts → middleware/auth.middleware.ts} +23 -7
- package/registry/combos/{express-prisma/shared/src → express-drizzle/shared/src/middleware}/response-envelope.middleware.ts +15 -3
- package/registry/combos/express-drizzle/shared/src/{oauth.repository.ts → repositories/oauth.repository.ts} +32 -9
- package/registry/combos/express-drizzle/shared/src/repositories/password-reset.repository.ts +59 -0
- package/registry/combos/express-drizzle/shared/src/{session.repository.ts → repositories/session.repository.ts} +52 -14
- package/registry/combos/express-drizzle/shared/src/{two-factor.repository.ts → repositories/two-factor.repository.ts} +20 -6
- package/registry/combos/express-drizzle/shared/src/request-context.ts +1 -1
- package/registry/combos/express-drizzle/shared/src/routers/auth.router.ts +395 -0
- package/registry/combos/express-drizzle/variants/base/src/create-auth-app.ts +52 -19
- package/registry/combos/express-drizzle/variants/base/src/{authz.middleware.ts → middleware/authz.middleware.ts} +15 -7
- package/registry/combos/express-drizzle/variants/base/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +28 -10
- package/registry/combos/express-drizzle/variants/base/src/{rbac.repository.ts → repositories/rbac.repository.ts} +239 -48
- package/registry/combos/express-drizzle/variants/base/src/routers/admin.router.ts +497 -0
- package/registry/combos/express-drizzle/variants/base/src/{auth.service.ts → services/auth.service.ts} +480 -109
- package/registry/combos/express-drizzle/variants/workspaces/src/create-auth-app.ts +69 -23
- package/registry/combos/express-drizzle/variants/workspaces/src/{authz.middleware.ts → middleware/authz.middleware.ts} +32 -12
- package/registry/combos/express-drizzle/variants/workspaces/src/openapi-workspace.ts +147 -28
- package/registry/combos/express-drizzle/variants/workspaces/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +30 -11
- package/registry/combos/express-drizzle/variants/workspaces/src/{rbac.repository.ts → repositories/rbac.repository.ts} +319 -70
- package/registry/combos/express-drizzle/variants/workspaces/src/{workspace.repository.ts → repositories/workspace.repository.ts} +127 -30
- package/registry/combos/express-drizzle/variants/workspaces/src/routers/admin.router.ts +534 -0
- package/registry/combos/express-drizzle/variants/workspaces/src/routers/workspace.router.ts +194 -0
- package/registry/combos/express-drizzle/variants/workspaces/src/{auth.service.ts → services/auth.service.ts} +544 -122
- package/registry/combos/express-prisma/shared/src/{auth-core-error.middleware.ts → middleware/auth-core-error.middleware.ts} +17 -4
- package/registry/combos/express-prisma/shared/src/{auth.middleware.ts → middleware/auth.middleware.ts} +19 -6
- package/registry/combos/{express-drizzle/shared/src → express-prisma/shared/src/middleware}/response-envelope.middleware.ts +15 -3
- package/registry/combos/express-prisma/shared/src/{oauth.repository.ts → repositories/oauth.repository.ts} +27 -9
- package/registry/combos/express-prisma/shared/src/repositories/password-reset.repository.ts +55 -0
- package/registry/combos/express-prisma/shared/src/{session.repository.ts → repositories/session.repository.ts} +30 -9
- package/registry/combos/express-prisma/shared/src/{two-factor.repository.ts → repositories/two-factor.repository.ts} +11 -4
- package/registry/combos/express-prisma/shared/src/request-context.ts +1 -1
- package/registry/combos/express-prisma/shared/src/routers/auth.router.ts +398 -0
- package/registry/combos/express-prisma/variants/base/src/create-auth-app.ts +56 -21
- package/registry/combos/express-prisma/variants/base/src/{authz.middleware.ts → middleware/authz.middleware.ts} +15 -7
- package/registry/combos/express-prisma/variants/base/src/rbac.defaults.ts +66 -18
- package/registry/combos/express-prisma/variants/base/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +16 -5
- package/registry/combos/express-prisma/variants/base/src/{rbac.repository.ts → repositories/rbac.repository.ts} +199 -48
- package/registry/combos/express-prisma/variants/base/src/routers/admin.router.ts +508 -0
- package/registry/combos/express-prisma/variants/base/src/seed.ts +77 -19
- package/registry/combos/express-prisma/variants/base/src/{auth.service.ts → services/auth.service.ts} +454 -105
- package/registry/combos/express-prisma/variants/workspaces/src/create-auth-app.ts +80 -25
- package/registry/combos/express-prisma/variants/workspaces/src/{authz.middleware.ts → middleware/authz.middleware.ts} +32 -12
- package/registry/combos/express-prisma/variants/workspaces/src/openapi-workspace.ts +147 -28
- package/registry/combos/express-prisma/variants/workspaces/src/rbac.defaults.ts +72 -20
- package/registry/combos/express-prisma/variants/workspaces/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +16 -5
- package/registry/combos/express-prisma/variants/workspaces/src/{rbac.repository.ts → repositories/rbac.repository.ts} +272 -64
- package/registry/combos/express-prisma/variants/workspaces/src/{workspace.repository.ts → repositories/workspace.repository.ts} +86 -23
- package/registry/combos/express-prisma/variants/workspaces/src/routers/admin.router.ts +545 -0
- package/registry/combos/express-prisma/variants/workspaces/src/routers/workspace.router.ts +194 -0
- package/registry/combos/express-prisma/variants/workspaces/src/seed.ts +108 -28
- package/registry/combos/express-prisma/variants/workspaces/src/{auth.service.ts → services/auth.service.ts} +518 -119
- package/registry/combos/nestjs-drizzle/shared/src/{auth.controller.ts → controllers/auth.controller.ts} +6 -6
- package/registry/combos/nestjs-drizzle/shared/src/{auth-core-error.filter.ts → filters/auth-core-error.filter.ts} +29 -5
- package/registry/combos/nestjs-drizzle/shared/src/{ability.guard.ts → guards/ability.guard.ts} +20 -7
- package/registry/combos/nestjs-drizzle/shared/src/{auth.guard.ts → guards/auth.guard.ts} +19 -7
- package/registry/combos/{nestjs-prisma/shared/src → nestjs-drizzle/shared/src/interceptors}/response.interceptor.ts +10 -2
- package/registry/combos/nestjs-drizzle/shared/src/repositories/oauth.repository.ts +63 -0
- package/registry/combos/nestjs-drizzle/shared/src/repositories/password-reset.repository.ts +59 -0
- package/registry/combos/nestjs-drizzle/shared/src/{session.repository.ts → repositories/session.repository.ts} +52 -14
- package/registry/combos/nestjs-drizzle/shared/src/{two-factor.repository.ts → repositories/two-factor.repository.ts} +21 -7
- package/registry/combos/nestjs-drizzle/shared/src/request-context.ts +1 -1
- package/registry/combos/nestjs-drizzle/test/prove-cycle.ts +1097 -307
- package/registry/combos/nestjs-drizzle/variants/base/src/auth.module.ts +44 -20
- package/registry/combos/nestjs-drizzle/variants/base/src/{admin.controller.ts → controllers/admin.controller.ts} +228 -62
- package/registry/combos/nestjs-drizzle/variants/base/src/{authz.guard.ts → guards/authz.guard.ts} +12 -5
- package/registry/combos/nestjs-drizzle/variants/base/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +27 -10
- package/registry/combos/nestjs-drizzle/variants/base/src/{rbac.repository.ts → repositories/rbac.repository.ts} +249 -51
- package/registry/combos/nestjs-drizzle/variants/base/src/{auth.service.ts → services/auth.service.ts} +499 -116
- package/registry/combos/nestjs-drizzle/variants/workspaces/src/auth.module.ts +48 -22
- package/registry/combos/nestjs-drizzle/variants/workspaces/src/{admin.controller.ts → controllers/admin.controller.ts} +269 -70
- package/registry/combos/nestjs-drizzle/variants/workspaces/src/{workspace.controller.ts → controllers/workspace.controller.ts} +93 -26
- package/registry/combos/nestjs-drizzle/variants/workspaces/src/{authz.guard.ts → guards/authz.guard.ts} +26 -8
- package/registry/combos/nestjs-drizzle/variants/workspaces/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +29 -11
- package/registry/combos/nestjs-drizzle/variants/workspaces/src/{rbac.repository.ts → repositories/rbac.repository.ts} +323 -71
- package/registry/combos/nestjs-drizzle/variants/workspaces/src/{workspace.repository.ts → repositories/workspace.repository.ts} +131 -30
- package/registry/combos/nestjs-drizzle/variants/workspaces/src/services/auth.service.ts +988 -0
- package/registry/combos/nestjs-prisma/shared/src/{auth.controller.ts → controllers/auth.controller.ts} +6 -6
- package/registry/combos/nestjs-prisma/shared/src/{auth-core-error.filter.ts → filters/auth-core-error.filter.ts} +29 -5
- package/registry/combos/nestjs-prisma/shared/src/{ability.guard.ts → guards/ability.guard.ts} +20 -7
- package/registry/combos/nestjs-prisma/shared/src/{auth.guard.ts → guards/auth.guard.ts} +19 -7
- package/registry/combos/{nestjs-drizzle/shared/src → nestjs-prisma/shared/src/interceptors}/response.interceptor.ts +10 -2
- package/registry/combos/nestjs-prisma/shared/src/{oauth.repository.ts → repositories/oauth.repository.ts} +25 -8
- package/registry/combos/nestjs-prisma/shared/src/repositories/password-reset.repository.ts +55 -0
- package/registry/combos/nestjs-prisma/shared/src/{session.repository.ts → repositories/session.repository.ts} +30 -9
- package/registry/combos/nestjs-prisma/shared/src/{two-factor.repository.ts → repositories/two-factor.repository.ts} +11 -4
- package/registry/combos/nestjs-prisma/shared/src/request-context.ts +1 -1
- package/registry/combos/nestjs-prisma/test/prove-cycle.ts +1097 -307
- package/registry/combos/nestjs-prisma/variants/base/src/auth.module.ts +58 -27
- package/registry/combos/nestjs-prisma/variants/base/src/{admin.controller.ts → controllers/admin.controller.ts} +469 -110
- package/registry/combos/nestjs-prisma/variants/base/src/{audit-log.gateway.ts → gateways/audit-log.gateway.ts} +22 -6
- package/registry/combos/nestjs-prisma/variants/base/src/{authz.guard.ts → guards/authz.guard.ts} +12 -5
- package/registry/combos/nestjs-prisma/variants/base/src/rbac.defaults.ts +97 -26
- package/registry/combos/nestjs-prisma/variants/base/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +16 -5
- package/registry/combos/nestjs-prisma/variants/base/src/{country.repository.ts → repositories/country.repository.ts} +98 -25
- package/registry/combos/nestjs-prisma/variants/base/src/{customer.repository.ts → repositories/customer.repository.ts} +106 -27
- package/registry/combos/nestjs-prisma/variants/base/src/{language.repository.ts → repositories/language.repository.ts} +93 -25
- package/registry/combos/nestjs-prisma/variants/base/src/{rbac.repository.ts → repositories/rbac.repository.ts} +229 -55
- package/registry/combos/nestjs-prisma/variants/base/src/seed.ts +77 -19
- package/registry/combos/nestjs-prisma/variants/base/src/services/auth.service.ts +1064 -0
- package/registry/combos/nestjs-prisma/variants/workspaces/src/auth.module.ts +54 -26
- package/registry/combos/nestjs-prisma/variants/workspaces/src/{admin.controller.ts → controllers/admin.controller.ts} +544 -128
- package/registry/combos/nestjs-prisma/variants/workspaces/src/{workspace.controller.ts → controllers/workspace.controller.ts} +93 -26
- package/registry/combos/nestjs-prisma/variants/workspaces/src/{authz.guard.ts → guards/authz.guard.ts} +26 -8
- package/registry/combos/nestjs-prisma/variants/workspaces/src/rbac.defaults.ts +103 -28
- package/registry/combos/nestjs-prisma/variants/workspaces/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +16 -5
- package/registry/combos/nestjs-prisma/variants/workspaces/src/{country.repository.ts → repositories/country.repository.ts} +127 -26
- package/registry/combos/nestjs-prisma/variants/workspaces/src/{customer.repository.ts → repositories/customer.repository.ts} +137 -28
- package/registry/combos/nestjs-prisma/variants/workspaces/src/repositories/language.repository.ts +243 -0
- package/registry/combos/nestjs-prisma/variants/workspaces/src/{rbac.repository.ts → repositories/rbac.repository.ts} +314 -74
- package/registry/combos/nestjs-prisma/variants/workspaces/src/{workspace.repository.ts → repositories/workspace.repository.ts} +94 -24
- package/registry/combos/nestjs-prisma/variants/workspaces/src/seed.ts +108 -28
- package/registry/combos/nestjs-prisma/variants/workspaces/src/services/auth.service.ts +961 -0
- package/registry.json +4 -4
- package/simple-auth-kit.ts +42 -11
- package/registry/combos/express-drizzle/shared/src/auth.router.ts +0 -266
- package/registry/combos/express-drizzle/shared/src/password-reset.repository.ts +0 -35
- package/registry/combos/express-drizzle/variants/base/src/admin.router.ts +0 -307
- package/registry/combos/express-drizzle/variants/workspaces/src/admin.router.ts +0 -323
- package/registry/combos/express-drizzle/variants/workspaces/src/workspace.router.ts +0 -123
- package/registry/combos/express-prisma/shared/src/auth.router.ts +0 -269
- package/registry/combos/express-prisma/shared/src/password-reset.repository.ts +0 -29
- package/registry/combos/express-prisma/variants/base/src/admin.router.ts +0 -314
- package/registry/combos/express-prisma/variants/workspaces/src/admin.router.ts +0 -330
- package/registry/combos/express-prisma/variants/workspaces/src/workspace.router.ts +0 -123
- package/registry/combos/nestjs-drizzle/shared/src/oauth.repository.ts +0 -36
- package/registry/combos/nestjs-drizzle/shared/src/password-reset.repository.ts +0 -29
- package/registry/combos/nestjs-drizzle/variants/workspaces/src/auth.service.ts +0 -553
- package/registry/combos/nestjs-prisma/shared/src/password-reset.repository.ts +0 -29
- package/registry/combos/nestjs-prisma/variants/base/src/auth.service.ts +0 -618
- package/registry/combos/nestjs-prisma/variants/workspaces/src/auth.service.ts +0 -509
- package/registry/combos/nestjs-prisma/variants/workspaces/src/language.repository.ts +0 -143
|
@@ -1,11 +1,34 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
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 "
|
|
5
|
-
import { AuthGuard } from "
|
|
6
|
-
import { AuthService } from "
|
|
7
|
-
import { AuthzGuard } from "
|
|
8
|
-
import { CheckAbility } from "
|
|
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 "
|
|
25
|
-
import {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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)
|
|
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 =>
|
|
78
|
+
const optionalString = (value: unknown): string | undefined =>
|
|
79
|
+
typeof value === "string" ? value : undefined;
|
|
36
80
|
|
|
37
|
-
const optionalBoolean = (value: unknown): boolean | 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({
|
|
53
|
-
|
|
54
|
-
|
|
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(
|
|
57
|
-
|
|
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({
|
|
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:
|
|
80
|
-
|
|
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({
|
|
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(
|
|
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:
|
|
187
|
+
firstName:
|
|
188
|
+
body.firstName === null ? null : optionalString(body.firstName),
|
|
106
189
|
lastName: body.lastName === null ? null : optionalString(body.lastName),
|
|
107
|
-
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:
|
|
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(
|
|
129
|
-
|
|
130
|
-
|
|
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({
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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({
|
|
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:
|
|
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(
|
|
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:
|
|
314
|
+
description:
|
|
315
|
+
body.description === null ? null : optionalString(body.description),
|
|
184
316
|
group: optionalString(body.group),
|
|
185
|
-
groupOrder:
|
|
317
|
+
groupOrder:
|
|
318
|
+
typeof body.groupOrder === "number" ? body.groupOrder : undefined,
|
|
186
319
|
order: typeof body.order === "number" ? body.order : undefined,
|
|
187
|
-
isActive:
|
|
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({
|
|
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:
|
|
215
|
-
|
|
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(
|
|
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:
|
|
234
|
-
|
|
235
|
-
|
|
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:
|
|
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(
|
|
251
|
-
|
|
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(
|
|
262
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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)
|
|
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({
|
|
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(
|
|
309
|
-
|
|
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({
|
|
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(
|
|
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({
|
|
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(
|
|
330
|
-
|
|
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(
|
|
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:
|
|
349
|
-
|
|
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(
|
|
354
|
-
|
|
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(
|
|
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({
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
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({
|
|
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(
|
|
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(
|
|
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({
|
|
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(
|
|
450
|
-
|
|
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(
|
|
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(
|
|
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({
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
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({
|
|
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(
|
|
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(
|
|
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({
|
|
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(
|
|
549
|
-
|
|
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(
|
|
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(
|
|
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({
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
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({
|
|
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(
|
|
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(
|
|
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:
|
|
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({
|
|
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(
|
|
661
|
-
|
|
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(
|
|
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(
|
|
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
|
}
|