@ftisindia/create-app 0.1.3 → 0.1.4
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/package.json +1 -1
- package/template/README.md +1 -1
- package/template/_package.json +0 -2
- package/template/docs/API_REFERENCE.md +13 -0
- package/template/docs/OAUTH.md +7 -3
- package/template/scripts/gen-module.mjs +2 -0
- package/template/src/app.module.ts +16 -22
- package/template/src/common/dto/error-response.dto.ts +3 -3
- package/template/src/common/dto/membership-response.dto.ts +26 -14
- package/template/src/common/dto/mutation-response.dto.ts +1 -1
- package/template/src/common/dto/pagination-query.dto.ts +37 -0
- package/template/src/common/dto/role-summary.dto.ts +5 -5
- package/template/src/common/dto/user-summary.dto.ts +6 -6
- package/template/src/common/filters/http-exception.filter.ts +9 -19
- package/template/src/common/swagger/api-error-responses.ts +12 -12
- package/template/src/config/app.config.ts +3 -3
- package/template/src/config/auth.config.ts +3 -3
- package/template/src/config/database.config.ts +3 -3
- package/template/src/config/env.validation.ts +58 -40
- package/template/src/config/index.ts +5 -5
- package/template/src/config/rbac.config.ts +3 -3
- package/template/src/database/prisma/prisma-transaction.ts +1 -1
- package/template/src/database/prisma/prisma.module.ts +2 -2
- package/template/src/database/prisma/prisma.service.ts +3 -6
- package/template/src/main.ts +11 -11
- package/template/src/modules/access-control/access-control.module.ts +9 -9
- package/template/src/modules/access-control/application/role-permission-policy.ts +71 -0
- package/template/src/modules/access-control/application/route-registry.validator.ts +34 -63
- package/template/src/modules/access-control/application/services/ability.factory.ts +5 -9
- package/template/src/modules/access-control/application/services/access-control.service.ts +78 -85
- package/template/src/modules/access-control/application/services/permission.guard.ts +16 -21
- package/template/src/modules/access-control/application/services/rbac-cache.service.ts +7 -9
- package/template/src/modules/access-control/dto/access-control-response.dto.ts +32 -20
- package/template/src/modules/access-control/dto/create-role.dto.ts +6 -6
- package/template/src/modules/access-control/dto/update-role-permissions.dto.ts +3 -10
- package/template/src/modules/access-control/dto/update-role.dto.ts +6 -6
- package/template/src/modules/access-control/presentation/access-control.controller.ts +69 -74
- package/template/src/modules/access-control/presentation/permissions.decorator.ts +3 -3
- package/template/src/modules/access-control/presentation/public.decorator.ts +2 -2
- package/template/src/modules/access-control/types/permission-key.ts +19 -19
- package/template/src/modules/access-control/types/route-permission-registry.ts +76 -76
- package/template/src/modules/audit/application/services/audit.service.ts +7 -7
- package/template/src/modules/audit/audit.module.ts +4 -4
- package/template/src/modules/audit/dto/audit-response.dto.ts +18 -18
- package/template/src/modules/audit/dto/list-audit-logs-query.dto.ts +14 -14
- package/template/src/modules/audit/presentation/audit.controller.ts +17 -23
- package/template/src/modules/auth/application/services/auth.service.ts +147 -110
- package/template/src/modules/auth/application/services/password.service.ts +2 -2
- package/template/src/modules/auth/application/services/token.service.ts +20 -21
- package/template/src/modules/auth/auth.module.ts +20 -47
- package/template/src/modules/auth/dto/auth-response.dto.ts +9 -10
- package/template/src/modules/auth/dto/login.dto.ts +4 -4
- package/template/src/modules/auth/dto/logout.dto.ts +1 -1
- package/template/src/modules/auth/dto/oauth-exchange.dto.ts +4 -5
- package/template/src/modules/auth/dto/refresh-token.dto.ts +4 -5
- package/template/src/modules/auth/dto/signup.dto.ts +5 -11
- package/template/src/modules/auth/infrastructure/passport/google-auth.guard.ts +6 -14
- package/template/src/modules/auth/infrastructure/passport/google-oauth-state.store.ts +98 -0
- package/template/src/modules/auth/infrastructure/passport/google.strategy.ts +21 -30
- package/template/src/modules/auth/infrastructure/passport/jwt-auth.guard.ts +3 -3
- package/template/src/modules/auth/infrastructure/passport/jwt.strategy.ts +11 -11
- package/template/src/modules/auth/presentation/auth.controller.ts +45 -45
- package/template/src/modules/auth/presentation/current-user.decorator.ts +3 -5
- package/template/src/modules/auth/presentation/google-oauth-exception.filter.ts +5 -10
- package/template/src/modules/health/dto/health-response.dto.ts +5 -5
- package/template/src/modules/health/health.module.ts +2 -2
- package/template/src/modules/health/presentation/health.controller.ts +13 -13
- package/template/src/modules/invitations/application/services/invitations.service.ts +127 -176
- package/template/src/modules/invitations/dto/accept-invitation.dto.ts +6 -7
- package/template/src/modules/invitations/dto/create-invitation.dto.ts +14 -15
- package/template/src/modules/invitations/dto/invitation-response.dto.ts +37 -29
- package/template/src/modules/invitations/dto/invitation-token.dto.ts +4 -4
- package/template/src/modules/invitations/invitations.module.ts +5 -5
- package/template/src/modules/invitations/presentation/invitations.controller.ts +61 -63
- package/template/src/modules/memberships/application/services/memberships.service.ts +70 -84
- package/template/src/modules/memberships/dto/transfer-owner.dto.ts +4 -4
- package/template/src/modules/memberships/dto/update-billing-contact.dto.ts +2 -2
- package/template/src/modules/memberships/dto/update-membership-owner.dto.ts +2 -2
- package/template/src/modules/memberships/dto/update-membership-role.dto.ts +4 -4
- package/template/src/modules/memberships/dto/update-membership-status.dto.ts +3 -3
- package/template/src/modules/memberships/memberships.module.ts +4 -4
- package/template/src/modules/memberships/presentation/memberships.controller.ts +83 -99
- package/template/src/modules/organisations/application/services/organisations.service.ts +21 -23
- package/template/src/modules/organisations/dto/create-organisation.dto.ts +6 -13
- package/template/src/modules/organisations/dto/organisation-response.dto.ts +14 -14
- package/template/src/modules/organisations/infrastructure/repositories/organisations.repository.ts +4 -7
- package/template/src/modules/organisations/organisations.module.ts +5 -5
- package/template/src/modules/organisations/presentation/organisations.controller.ts +14 -23
- package/template/src/modules/organisations/types/default-organisation-data.ts +3 -9
- package/template/src/modules/request-context/application/services/request-context.service.ts +15 -7
- package/template/src/modules/request-context/presentation/org-scope.guard.ts +4 -9
- package/template/src/modules/request-context/presentation/request-context.interceptor.ts +4 -9
- package/template/src/modules/request-context/presentation/request-context.middleware.ts +7 -8
- package/template/src/modules/request-context/request-context.module.ts +7 -7
- package/template/src/modules/request-context/types/request-context.ts +2 -2
- package/template/src/modules/sample/application/services/sample.service.ts +10 -8
- package/template/src/modules/sample/dto/sample-echo.dto.ts +3 -3
- package/template/src/modules/sample/dto/sample-response.dto.ts +12 -12
- package/template/src/modules/sample/presentation/sample.controller.ts +25 -42
- package/template/src/modules/sample/sample.module.ts +4 -4
- package/template/src/modules/settings/application/services/settings.service.ts +15 -27
- package/template/src/modules/settings/dto/setting-response.dto.ts +9 -9
- package/template/src/modules/settings/dto/update-setting.dto.ts +5 -5
- package/template/src/modules/settings/presentation/settings.controller.ts +29 -35
- package/template/src/modules/settings/settings.module.ts +5 -5
- package/template/src/modules/settings/types/setting-definitions.ts +49 -33
- package/template/test/auth-refresh.spec.ts +90 -0
- package/template/test/role-permission-policy.spec.ts +94 -0
|
@@ -7,8 +7,9 @@ import {
|
|
|
7
7
|
Patch,
|
|
8
8
|
Post,
|
|
9
9
|
Put,
|
|
10
|
+
Query,
|
|
10
11
|
UseGuards,
|
|
11
|
-
} from
|
|
12
|
+
} from '@nestjs/common';
|
|
12
13
|
import {
|
|
13
14
|
ApiBearerAuth,
|
|
14
15
|
ApiCreatedResponse,
|
|
@@ -16,142 +17,136 @@ import {
|
|
|
16
17
|
ApiOperation,
|
|
17
18
|
ApiParam,
|
|
18
19
|
ApiTags,
|
|
19
|
-
} from
|
|
20
|
-
import { DeletedResponseDto } from
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
27
|
-
import {
|
|
20
|
+
} from '@nestjs/swagger';
|
|
21
|
+
import { DeletedResponseDto } from '../../../common/dto/mutation-response.dto';
|
|
22
|
+
import { PaginationQueryDto } from '../../../common/dto/pagination-query.dto';
|
|
23
|
+
import { ApiProtectedErrorResponses } from '../../../common/swagger/api-error-responses';
|
|
24
|
+
import { JwtAuthGuard } from '../../auth/infrastructure/passport/jwt-auth.guard';
|
|
25
|
+
import { CurrentUser } from '../../auth/presentation/current-user.decorator';
|
|
26
|
+
import { AuthenticatedUser } from '../../auth/types/authenticated-user';
|
|
27
|
+
import { OrgScopeGuard } from '../../request-context/presentation/org-scope.guard';
|
|
28
|
+
import { AccessControlService } from '../application/services/access-control.service';
|
|
29
|
+
import { PermissionGuard } from '../application/services/permission.guard';
|
|
28
30
|
import {
|
|
29
31
|
PermissionResponseDto,
|
|
32
|
+
RoleListResponseDto,
|
|
30
33
|
RoleResponseDto,
|
|
31
34
|
RoutePermissionResponseDto,
|
|
32
|
-
} from
|
|
33
|
-
import { CreateRoleDto } from
|
|
34
|
-
import { UpdateRolePermissionsDto } from
|
|
35
|
-
import { UpdateRoleDto } from
|
|
36
|
-
import { RequirePermissions } from
|
|
35
|
+
} from '../dto/access-control-response.dto';
|
|
36
|
+
import { CreateRoleDto } from '../dto/create-role.dto';
|
|
37
|
+
import { UpdateRolePermissionsDto } from '../dto/update-role-permissions.dto';
|
|
38
|
+
import { UpdateRoleDto } from '../dto/update-role.dto';
|
|
39
|
+
import { RequirePermissions } from './permissions.decorator';
|
|
37
40
|
|
|
38
|
-
@ApiTags(
|
|
41
|
+
@ApiTags('Access control')
|
|
39
42
|
@ApiBearerAuth()
|
|
40
|
-
@ApiParam({ name:
|
|
43
|
+
@ApiParam({ name: 'orgId', description: 'Organisation ID.', format: 'uuid' })
|
|
41
44
|
@ApiProtectedErrorResponses(404, 409)
|
|
42
|
-
@Controller(
|
|
45
|
+
@Controller('organisations/:orgId/access-control')
|
|
43
46
|
@UseGuards(JwtAuthGuard, OrgScopeGuard, PermissionGuard)
|
|
44
47
|
export class AccessControlController {
|
|
45
48
|
constructor(private readonly accessControlService: AccessControlService) {}
|
|
46
49
|
|
|
47
|
-
@Get(
|
|
48
|
-
@RequirePermissions(
|
|
49
|
-
@ApiOperation({ summary:
|
|
50
|
+
@Get('permissions')
|
|
51
|
+
@RequirePermissions('roles.read')
|
|
52
|
+
@ApiOperation({ summary: 'List the seeded permission catalogue.' })
|
|
50
53
|
@ApiOkResponse({
|
|
51
|
-
description:
|
|
54
|
+
description: 'Available permissions.',
|
|
52
55
|
type: [PermissionResponseDto],
|
|
53
56
|
})
|
|
54
57
|
listPermissions() {
|
|
55
58
|
return this.accessControlService.listPermissions();
|
|
56
59
|
}
|
|
57
60
|
|
|
58
|
-
@Get(
|
|
59
|
-
@RequirePermissions(
|
|
61
|
+
@Get('route-permissions')
|
|
62
|
+
@RequirePermissions('roles.read')
|
|
60
63
|
@ApiOperation({
|
|
61
|
-
summary:
|
|
64
|
+
summary: 'List permission requirements for protected routes.',
|
|
62
65
|
})
|
|
63
66
|
@ApiOkResponse({
|
|
64
|
-
description:
|
|
67
|
+
description: 'Protected route permission registry.',
|
|
65
68
|
type: [RoutePermissionResponseDto],
|
|
66
69
|
})
|
|
67
70
|
listRoutePermissions() {
|
|
68
71
|
return this.accessControlService.listRoutePermissions();
|
|
69
72
|
}
|
|
70
73
|
|
|
71
|
-
@Get(
|
|
72
|
-
@RequirePermissions(
|
|
73
|
-
@ApiOperation({ summary:
|
|
74
|
+
@Get('roles')
|
|
75
|
+
@RequirePermissions('roles.read')
|
|
76
|
+
@ApiOperation({ summary: 'List organisation roles.' })
|
|
74
77
|
@ApiOkResponse({
|
|
75
|
-
description:
|
|
76
|
-
type:
|
|
78
|
+
description: 'Organisation roles.',
|
|
79
|
+
type: RoleListResponseDto,
|
|
77
80
|
})
|
|
78
|
-
listRoles(@Param(
|
|
79
|
-
return this.accessControlService.listRoles(orgId);
|
|
81
|
+
listRoles(@Param('orgId') orgId: string, @Query() query: PaginationQueryDto) {
|
|
82
|
+
return this.accessControlService.listRoles(orgId, query);
|
|
80
83
|
}
|
|
81
84
|
|
|
82
|
-
@Post(
|
|
83
|
-
@RequirePermissions(
|
|
84
|
-
@ApiOperation({ summary:
|
|
85
|
-
@ApiCreatedResponse({ description:
|
|
85
|
+
@Post('roles')
|
|
86
|
+
@RequirePermissions('roles.manage')
|
|
87
|
+
@ApiOperation({ summary: 'Create an organisation role.' })
|
|
88
|
+
@ApiCreatedResponse({ description: 'Role created.', type: RoleResponseDto })
|
|
86
89
|
createRole(
|
|
87
90
|
@CurrentUser() user: AuthenticatedUser,
|
|
88
|
-
@Param(
|
|
91
|
+
@Param('orgId') orgId: string,
|
|
89
92
|
@Body() dto: CreateRoleDto,
|
|
90
93
|
) {
|
|
91
94
|
return this.accessControlService.createRole(user, orgId, dto);
|
|
92
95
|
}
|
|
93
96
|
|
|
94
|
-
@Patch(
|
|
95
|
-
@RequirePermissions(
|
|
96
|
-
@ApiParam({ name:
|
|
97
|
-
@ApiOperation({ summary:
|
|
98
|
-
@ApiOkResponse({ description:
|
|
97
|
+
@Patch('roles/:roleId')
|
|
98
|
+
@RequirePermissions('roles.manage')
|
|
99
|
+
@ApiParam({ name: 'roleId', description: 'Role ID.', format: 'uuid' })
|
|
100
|
+
@ApiOperation({ summary: 'Update role name or description.' })
|
|
101
|
+
@ApiOkResponse({ description: 'Role updated.', type: RoleResponseDto })
|
|
99
102
|
updateRole(
|
|
100
103
|
@CurrentUser() user: AuthenticatedUser,
|
|
101
|
-
@Param(
|
|
102
|
-
@Param(
|
|
104
|
+
@Param('orgId') orgId: string,
|
|
105
|
+
@Param('roleId') roleId: string,
|
|
103
106
|
@Body() dto: UpdateRoleDto,
|
|
104
107
|
) {
|
|
105
108
|
return this.accessControlService.updateRole(user, orgId, roleId, dto);
|
|
106
109
|
}
|
|
107
110
|
|
|
108
|
-
@Delete(
|
|
109
|
-
@RequirePermissions(
|
|
110
|
-
@ApiParam({ name:
|
|
111
|
-
@ApiOperation({ summary:
|
|
112
|
-
@ApiOkResponse({ description:
|
|
111
|
+
@Delete('roles/:roleId')
|
|
112
|
+
@RequirePermissions('roles.manage')
|
|
113
|
+
@ApiParam({ name: 'roleId', description: 'Role ID.', format: 'uuid' })
|
|
114
|
+
@ApiOperation({ summary: 'Delete an unused non-system role.' })
|
|
115
|
+
@ApiOkResponse({ description: 'Role deleted.', type: DeletedResponseDto })
|
|
113
116
|
deleteRole(
|
|
114
117
|
@CurrentUser() user: AuthenticatedUser,
|
|
115
|
-
@Param(
|
|
116
|
-
@Param(
|
|
118
|
+
@Param('orgId') orgId: string,
|
|
119
|
+
@Param('roleId') roleId: string,
|
|
117
120
|
) {
|
|
118
121
|
return this.accessControlService.deleteRole(user, orgId, roleId);
|
|
119
122
|
}
|
|
120
123
|
|
|
121
|
-
@Get(
|
|
122
|
-
@RequirePermissions(
|
|
123
|
-
@ApiParam({ name:
|
|
124
|
-
@ApiOperation({ summary:
|
|
124
|
+
@Get('roles/:roleId/permissions')
|
|
125
|
+
@RequirePermissions('roles.read')
|
|
126
|
+
@ApiParam({ name: 'roleId', description: 'Role ID.', format: 'uuid' })
|
|
127
|
+
@ApiOperation({ summary: 'Return role permissions.' })
|
|
125
128
|
@ApiOkResponse({
|
|
126
|
-
description:
|
|
129
|
+
description: 'Role with assigned permissions.',
|
|
127
130
|
type: RoleResponseDto,
|
|
128
131
|
})
|
|
129
|
-
listRolePermissions(
|
|
130
|
-
@Param("orgId") orgId: string,
|
|
131
|
-
@Param("roleId") roleId: string,
|
|
132
|
-
) {
|
|
132
|
+
listRolePermissions(@Param('orgId') orgId: string, @Param('roleId') roleId: string) {
|
|
133
133
|
return this.accessControlService.listRolePermissions(orgId, roleId);
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
-
@Put(
|
|
137
|
-
@RequirePermissions(
|
|
138
|
-
@ApiParam({ name:
|
|
139
|
-
@ApiOperation({ summary:
|
|
136
|
+
@Put('roles/:roleId/permissions')
|
|
137
|
+
@RequirePermissions('permissions.assign')
|
|
138
|
+
@ApiParam({ name: 'roleId', description: 'Role ID.', format: 'uuid' })
|
|
139
|
+
@ApiOperation({ summary: 'Replace all permissions assigned to a role.' })
|
|
140
140
|
@ApiOkResponse({
|
|
141
|
-
description:
|
|
141
|
+
description: 'Role with replacement permissions.',
|
|
142
142
|
type: RoleResponseDto,
|
|
143
143
|
})
|
|
144
144
|
replaceRolePermissions(
|
|
145
145
|
@CurrentUser() user: AuthenticatedUser,
|
|
146
|
-
@Param(
|
|
147
|
-
@Param(
|
|
146
|
+
@Param('orgId') orgId: string,
|
|
147
|
+
@Param('roleId') roleId: string,
|
|
148
148
|
@Body() dto: UpdateRolePermissionsDto,
|
|
149
149
|
) {
|
|
150
|
-
return this.accessControlService.replaceRolePermissions(
|
|
151
|
-
user,
|
|
152
|
-
orgId,
|
|
153
|
-
roleId,
|
|
154
|
-
dto,
|
|
155
|
-
);
|
|
150
|
+
return this.accessControlService.replaceRolePermissions(user, orgId, roleId, dto);
|
|
156
151
|
}
|
|
157
152
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { SetMetadata } from
|
|
2
|
-
import { PermissionKey } from
|
|
1
|
+
import { SetMetadata } from '@nestjs/common';
|
|
2
|
+
import { PermissionKey } from '../types/permission-key';
|
|
3
3
|
|
|
4
|
-
export const REQUIRED_PERMISSIONS_KEY =
|
|
4
|
+
export const REQUIRED_PERMISSIONS_KEY = 'requiredPermissions';
|
|
5
5
|
|
|
6
6
|
export function RequirePermissions(...permissions: PermissionKey[]) {
|
|
7
7
|
return SetMetadata(REQUIRED_PERMISSIONS_KEY, permissions);
|
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
export const permissionKeys = [
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
2
|
+
'users.read',
|
|
3
|
+
'users.create',
|
|
4
|
+
'users.update',
|
|
5
|
+
'users.suspend',
|
|
6
|
+
'organisations.read',
|
|
7
|
+
'organisations.update',
|
|
8
|
+
'memberships.read',
|
|
9
|
+
'memberships.invite',
|
|
10
|
+
'memberships.revoke',
|
|
11
|
+
'roles.read',
|
|
12
|
+
'roles.manage',
|
|
13
|
+
'permissions.assign',
|
|
14
|
+
'billing.read',
|
|
15
|
+
'billing.manage',
|
|
16
|
+
'settings.read',
|
|
17
|
+
'settings.update',
|
|
18
|
+
'audit.read',
|
|
19
|
+
'platform.admin',
|
|
20
20
|
] as const;
|
|
21
21
|
|
|
22
22
|
export type PermissionKey = (typeof permissionKeys)[number];
|
|
@@ -28,7 +28,7 @@ export type RoutePermissionEntry = {
|
|
|
28
28
|
};
|
|
29
29
|
|
|
30
30
|
export function permissionKeyToRule(key: string) {
|
|
31
|
-
const [subject, action] = key.split(
|
|
31
|
+
const [subject, action] = key.split('.', 2);
|
|
32
32
|
if (!subject || !action) {
|
|
33
33
|
throw new Error(`Invalid permission key: ${key}`);
|
|
34
34
|
}
|
|
@@ -1,129 +1,129 @@
|
|
|
1
|
-
import { RoutePermissionEntry } from
|
|
1
|
+
import { RoutePermissionEntry } from './permission-key';
|
|
2
2
|
|
|
3
3
|
export const routePermissionRegistry: RoutePermissionEntry[] = [
|
|
4
4
|
{
|
|
5
|
-
method:
|
|
6
|
-
path:
|
|
7
|
-
permissions: [
|
|
5
|
+
method: 'GET',
|
|
6
|
+
path: '/organisations/:orgId/memberships/me',
|
|
7
|
+
permissions: ['memberships.read'],
|
|
8
8
|
},
|
|
9
9
|
{
|
|
10
|
-
method:
|
|
11
|
-
path:
|
|
12
|
-
permissions: [
|
|
10
|
+
method: 'GET',
|
|
11
|
+
path: '/organisations/:orgId/memberships',
|
|
12
|
+
permissions: ['memberships.read'],
|
|
13
13
|
},
|
|
14
14
|
{
|
|
15
|
-
method:
|
|
16
|
-
path:
|
|
17
|
-
permissions: [
|
|
15
|
+
method: 'PATCH',
|
|
16
|
+
path: '/organisations/:orgId/memberships/:membershipId/status',
|
|
17
|
+
permissions: ['memberships.revoke'],
|
|
18
18
|
},
|
|
19
19
|
{
|
|
20
|
-
method:
|
|
21
|
-
path:
|
|
22
|
-
permissions: [
|
|
20
|
+
method: 'PATCH',
|
|
21
|
+
path: '/organisations/:orgId/memberships/:membershipId/role',
|
|
22
|
+
permissions: ['roles.manage'],
|
|
23
23
|
},
|
|
24
24
|
{
|
|
25
|
-
method:
|
|
26
|
-
path:
|
|
27
|
-
permissions: [
|
|
25
|
+
method: 'PATCH',
|
|
26
|
+
path: '/organisations/:orgId/memberships/:membershipId/owner',
|
|
27
|
+
permissions: ['roles.manage'],
|
|
28
28
|
},
|
|
29
29
|
{
|
|
30
|
-
method:
|
|
31
|
-
path:
|
|
32
|
-
permissions: [
|
|
30
|
+
method: 'PATCH',
|
|
31
|
+
path: '/organisations/:orgId/memberships/:membershipId/billing-contact',
|
|
32
|
+
permissions: ['billing.manage'],
|
|
33
33
|
},
|
|
34
34
|
{
|
|
35
|
-
method:
|
|
36
|
-
path:
|
|
37
|
-
permissions: [
|
|
35
|
+
method: 'POST',
|
|
36
|
+
path: '/organisations/:orgId/memberships/transfer-owner',
|
|
37
|
+
permissions: ['roles.manage'],
|
|
38
38
|
},
|
|
39
39
|
{
|
|
40
|
-
method:
|
|
41
|
-
path:
|
|
42
|
-
permissions: [
|
|
40
|
+
method: 'POST',
|
|
41
|
+
path: '/organisations/:orgId/invitations',
|
|
42
|
+
permissions: ['memberships.invite'],
|
|
43
43
|
},
|
|
44
44
|
{
|
|
45
|
-
method:
|
|
46
|
-
path:
|
|
47
|
-
permissions: [
|
|
45
|
+
method: 'GET',
|
|
46
|
+
path: '/organisations/:orgId/invitations',
|
|
47
|
+
permissions: ['memberships.read'],
|
|
48
48
|
},
|
|
49
49
|
{
|
|
50
|
-
method:
|
|
51
|
-
path:
|
|
52
|
-
permissions: [
|
|
50
|
+
method: 'POST',
|
|
51
|
+
path: '/organisations/:orgId/invitations/:invitationId/revoke',
|
|
52
|
+
permissions: ['memberships.revoke'],
|
|
53
53
|
},
|
|
54
54
|
{
|
|
55
|
-
method:
|
|
56
|
-
path:
|
|
57
|
-
permissions: [
|
|
55
|
+
method: 'POST',
|
|
56
|
+
path: '/organisations/:orgId/invitations/:invitationId/resend',
|
|
57
|
+
permissions: ['memberships.invite'],
|
|
58
58
|
},
|
|
59
59
|
{
|
|
60
|
-
method:
|
|
61
|
-
path:
|
|
62
|
-
permissions: [
|
|
60
|
+
method: 'GET',
|
|
61
|
+
path: '/organisations/:orgId/access-control/permissions',
|
|
62
|
+
permissions: ['roles.read'],
|
|
63
63
|
},
|
|
64
64
|
{
|
|
65
|
-
method:
|
|
66
|
-
path:
|
|
67
|
-
permissions: [
|
|
65
|
+
method: 'GET',
|
|
66
|
+
path: '/organisations/:orgId/access-control/roles',
|
|
67
|
+
permissions: ['roles.read'],
|
|
68
68
|
},
|
|
69
69
|
{
|
|
70
|
-
method:
|
|
71
|
-
path:
|
|
72
|
-
permissions: [
|
|
70
|
+
method: 'POST',
|
|
71
|
+
path: '/organisations/:orgId/access-control/roles',
|
|
72
|
+
permissions: ['roles.manage'],
|
|
73
73
|
},
|
|
74
74
|
{
|
|
75
|
-
method:
|
|
76
|
-
path:
|
|
77
|
-
permissions: [
|
|
75
|
+
method: 'PATCH',
|
|
76
|
+
path: '/organisations/:orgId/access-control/roles/:roleId',
|
|
77
|
+
permissions: ['roles.manage'],
|
|
78
78
|
},
|
|
79
79
|
{
|
|
80
|
-
method:
|
|
81
|
-
path:
|
|
82
|
-
permissions: [
|
|
80
|
+
method: 'DELETE',
|
|
81
|
+
path: '/organisations/:orgId/access-control/roles/:roleId',
|
|
82
|
+
permissions: ['roles.manage'],
|
|
83
83
|
},
|
|
84
84
|
{
|
|
85
|
-
method:
|
|
86
|
-
path:
|
|
87
|
-
permissions: [
|
|
85
|
+
method: 'GET',
|
|
86
|
+
path: '/organisations/:orgId/access-control/roles/:roleId/permissions',
|
|
87
|
+
permissions: ['roles.read'],
|
|
88
88
|
},
|
|
89
89
|
{
|
|
90
|
-
method:
|
|
91
|
-
path:
|
|
92
|
-
permissions: [
|
|
90
|
+
method: 'PUT',
|
|
91
|
+
path: '/organisations/:orgId/access-control/roles/:roleId/permissions',
|
|
92
|
+
permissions: ['permissions.assign'],
|
|
93
93
|
},
|
|
94
94
|
{
|
|
95
|
-
method:
|
|
96
|
-
path:
|
|
97
|
-
permissions: [
|
|
95
|
+
method: 'GET',
|
|
96
|
+
path: '/organisations/:orgId/access-control/route-permissions',
|
|
97
|
+
permissions: ['roles.read'],
|
|
98
98
|
},
|
|
99
99
|
{
|
|
100
|
-
method:
|
|
101
|
-
path:
|
|
102
|
-
permissions: [
|
|
100
|
+
method: 'GET',
|
|
101
|
+
path: '/organisations/:orgId/audit',
|
|
102
|
+
permissions: ['audit.read'],
|
|
103
103
|
},
|
|
104
104
|
{
|
|
105
|
-
method:
|
|
106
|
-
path:
|
|
107
|
-
permissions: [
|
|
105
|
+
method: 'GET',
|
|
106
|
+
path: '/organisations/:orgId/settings',
|
|
107
|
+
permissions: ['settings.read'],
|
|
108
108
|
},
|
|
109
109
|
{
|
|
110
|
-
method:
|
|
111
|
-
path:
|
|
112
|
-
permissions: [
|
|
110
|
+
method: 'GET',
|
|
111
|
+
path: '/organisations/:orgId/settings/:key',
|
|
112
|
+
permissions: ['settings.read'],
|
|
113
113
|
},
|
|
114
114
|
{
|
|
115
|
-
method:
|
|
116
|
-
path:
|
|
117
|
-
permissions: [
|
|
115
|
+
method: 'PATCH',
|
|
116
|
+
path: '/organisations/:orgId/settings',
|
|
117
|
+
permissions: ['settings.update'],
|
|
118
118
|
},
|
|
119
119
|
{
|
|
120
|
-
method:
|
|
121
|
-
path:
|
|
122
|
-
permissions: [
|
|
120
|
+
method: 'GET',
|
|
121
|
+
path: '/organisations/:orgId/sample/status',
|
|
122
|
+
permissions: ['organisations.read'],
|
|
123
123
|
},
|
|
124
124
|
{
|
|
125
|
-
method:
|
|
126
|
-
path:
|
|
127
|
-
permissions: [
|
|
125
|
+
method: 'POST',
|
|
126
|
+
path: '/organisations/:orgId/sample/echo',
|
|
127
|
+
permissions: ['organisations.update'],
|
|
128
128
|
},
|
|
129
129
|
];
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { BadRequestException, Injectable } from
|
|
2
|
-
import { Prisma } from
|
|
3
|
-
import { PrismaService } from
|
|
4
|
-
import { RequestContextService } from
|
|
5
|
-
import { ListAuditLogsQueryDto } from
|
|
1
|
+
import { BadRequestException, Injectable } from '@nestjs/common';
|
|
2
|
+
import { Prisma } from '@prisma/client';
|
|
3
|
+
import { PrismaService } from '../../../../database/prisma/prisma.service';
|
|
4
|
+
import { RequestContextService } from '../../../request-context/application/services/request-context.service';
|
|
5
|
+
import { ListAuditLogsQueryDto } from '../../dto/list-audit-logs-query.dto';
|
|
6
6
|
|
|
7
7
|
type PrismaClient = Prisma.TransactionClient | PrismaService;
|
|
8
8
|
|
|
@@ -47,7 +47,7 @@ export class AuditService {
|
|
|
47
47
|
|
|
48
48
|
const limit = Math.min(query.limit ?? DEFAULT_LIMIT, MAX_LIMIT);
|
|
49
49
|
if (query.from && query.to && new Date(query.from) > new Date(query.to)) {
|
|
50
|
-
throw new BadRequestException(
|
|
50
|
+
throw new BadRequestException('from must be before or equal to to.');
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
const createdAt: Prisma.DateTimeFilter =
|
|
@@ -76,7 +76,7 @@ export class AuditService {
|
|
|
76
76
|
},
|
|
77
77
|
},
|
|
78
78
|
},
|
|
79
|
-
orderBy: [{ createdAt:
|
|
79
|
+
orderBy: [{ createdAt: 'desc' }, { id: 'desc' }],
|
|
80
80
|
take: limit + 1,
|
|
81
81
|
...(query.cursor
|
|
82
82
|
? {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { Global, Module } from
|
|
2
|
-
import { AuthModule } from
|
|
3
|
-
import { AuditService } from
|
|
4
|
-
import { AuditController } from
|
|
1
|
+
import { Global, Module } from '@nestjs/common';
|
|
2
|
+
import { AuthModule } from '../auth/auth.module';
|
|
3
|
+
import { AuditService } from './application/services/audit.service';
|
|
4
|
+
import { AuditController } from './presentation/audit.controller';
|
|
5
5
|
|
|
6
6
|
@Global()
|
|
7
7
|
@Module({
|
|
@@ -1,62 +1,62 @@
|
|
|
1
|
-
import { ApiProperty, ApiPropertyOptional } from
|
|
1
|
+
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
|
2
2
|
|
|
3
3
|
class AuditActorResponseDto {
|
|
4
4
|
@ApiProperty({
|
|
5
|
-
example:
|
|
6
|
-
format:
|
|
5
|
+
example: '4a4f0d8a-4bd2-469f-a6a9-3e1cb6a2b456',
|
|
6
|
+
format: 'uuid',
|
|
7
7
|
})
|
|
8
8
|
id!: string;
|
|
9
9
|
|
|
10
|
-
@ApiPropertyOptional({ example:
|
|
10
|
+
@ApiPropertyOptional({ example: 'Starter Owner', nullable: true })
|
|
11
11
|
displayName?: string | null;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
export class AuditLogResponseDto {
|
|
15
15
|
@ApiProperty({
|
|
16
|
-
example:
|
|
17
|
-
format:
|
|
16
|
+
example: 'df6537c4-f58b-452e-a67e-18ec528d0f0f',
|
|
17
|
+
format: 'uuid',
|
|
18
18
|
})
|
|
19
19
|
id!: string;
|
|
20
20
|
|
|
21
21
|
@ApiPropertyOptional({
|
|
22
|
-
example:
|
|
23
|
-
format:
|
|
22
|
+
example: '2c67399d-670c-4025-a5fd-1ea9a211891e',
|
|
23
|
+
format: 'uuid',
|
|
24
24
|
nullable: true,
|
|
25
25
|
})
|
|
26
26
|
orgId?: string | null;
|
|
27
27
|
|
|
28
28
|
@ApiPropertyOptional({
|
|
29
|
-
example:
|
|
30
|
-
format:
|
|
29
|
+
example: '4a4f0d8a-4bd2-469f-a6a9-3e1cb6a2b456',
|
|
30
|
+
format: 'uuid',
|
|
31
31
|
nullable: true,
|
|
32
32
|
})
|
|
33
33
|
actorUserId?: string | null;
|
|
34
34
|
|
|
35
|
-
@ApiProperty({ example:
|
|
35
|
+
@ApiProperty({ example: 'membership.status.update' })
|
|
36
36
|
action!: string;
|
|
37
37
|
|
|
38
|
-
@ApiProperty({ example:
|
|
38
|
+
@ApiProperty({ example: 'Membership' })
|
|
39
39
|
targetType!: string;
|
|
40
40
|
|
|
41
41
|
@ApiPropertyOptional({
|
|
42
|
-
example:
|
|
42
|
+
example: '0a57fb4a-95c6-4f7e-bd5a-f96dbe0599e3',
|
|
43
43
|
nullable: true,
|
|
44
44
|
})
|
|
45
45
|
targetId?: string | null;
|
|
46
46
|
|
|
47
47
|
@ApiPropertyOptional({
|
|
48
|
-
example: { previousStatus:
|
|
48
|
+
example: { previousStatus: 'ACTIVE', nextStatus: 'SUSPENDED' },
|
|
49
49
|
nullable: true,
|
|
50
50
|
})
|
|
51
51
|
metadata?: Record<string, unknown> | null;
|
|
52
52
|
|
|
53
|
-
@ApiPropertyOptional({ example:
|
|
53
|
+
@ApiPropertyOptional({ example: '127.0.0.1', nullable: true })
|
|
54
54
|
ipAddress?: string | null;
|
|
55
55
|
|
|
56
|
-
@ApiPropertyOptional({ example:
|
|
56
|
+
@ApiPropertyOptional({ example: 'Mozilla/5.0', nullable: true })
|
|
57
57
|
userAgent?: string | null;
|
|
58
58
|
|
|
59
|
-
@ApiProperty({ example:
|
|
59
|
+
@ApiProperty({ example: '2026-06-01T10:30:00.000Z', format: 'date-time' })
|
|
60
60
|
createdAt!: string;
|
|
61
61
|
|
|
62
62
|
@ApiPropertyOptional({ type: AuditActorResponseDto, nullable: true })
|
|
@@ -68,7 +68,7 @@ export class AuditLogListResponseDto {
|
|
|
68
68
|
items!: AuditLogResponseDto[];
|
|
69
69
|
|
|
70
70
|
@ApiPropertyOptional({
|
|
71
|
-
example:
|
|
71
|
+
example: 'df6537c4-f58b-452e-a67e-18ec528d0f0f',
|
|
72
72
|
nullable: true,
|
|
73
73
|
})
|
|
74
74
|
nextCursor?: string | null;
|