Skip to content

Commit 5059530

Browse files
committed
feat(json-api-nestjs): add hooks support and enhance guards handling with afterCreateController hook and guards logic
- prepare for acl libs
1 parent 20f39ab commit 5059530

File tree

10 files changed

+98
-11
lines changed

10 files changed

+98
-11
lines changed

libs/json-api/json-api-nestjs/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export {
1818
export {
1919
JsonApiTransformerService,
2020
ErrorFormatService,
21+
EntityParamMapService
2122
} from './lib/modules/mixin/service';
2223
export {
2324
MODULE_OPTIONS_TOKEN,

libs/json-api/json-api-nestjs/src/lib/modules/atomic-operation/service/execute.service.spec.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ describe('ExecuteService', () => {
111111
{
112112
controller: { name: 'TestController' },
113113
methodName: 'someMethod',
114+
module: {}
114115
},
115116
] as unknown as ParamsForExecute[];
116117
const callback = vi.fn().mockReturnValue({ value: 'test' });
@@ -147,6 +148,7 @@ describe('ExecuteService', () => {
147148
{
148149
controller: { name: 'TestController' },
149150
methodName: 'someMethod',
151+
module: {}
150152
},
151153
] as unknown as ParamsForExecute[];
152154

@@ -185,6 +187,7 @@ describe('ExecuteService', () => {
185187
{
186188
controller: { name: 'TestController' },
187189
methodName: 'someMethod',
190+
module: {}
188191
},
189192
] as unknown as ParamsForExecute[];
190193

libs/json-api/json-api-nestjs/src/lib/modules/atomic-operation/service/execute.service.ts

Lines changed: 63 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
PipeTransform,
77
Type,
88
InternalServerErrorException,
9+
ForbiddenException,
910
} from '@nestjs/common';
1011
import {
1112
INTERCEPTORS_METADATA,
@@ -24,6 +25,11 @@ import {
2425
InterceptorsConsumer,
2526
InterceptorsContextCreator,
2627
} from '@nestjs/core/interceptors';
28+
import {
29+
FORBIDDEN_MESSAGE,
30+
GuardsConsumer,
31+
GuardsContextCreator,
32+
} from '@nestjs/core/guards';
2733
import { Controller } from '@nestjs/common/interfaces';
2834
import { lastValueFrom } from 'rxjs';
2935
import { AsyncLocalStorage } from 'async_hooks';
@@ -75,6 +81,7 @@ export class ExecuteService {
7581
@Inject(ErrorFormatService) private errorFormatService!: ErrorFormatService;
7682

7783
private _interceptorsContextCreator!: InterceptorsContextCreator;
84+
private _guardsContextCreator!: GuardsContextCreator;
7885

7986
get interceptorsContextCreator() {
8087
if (!this._interceptorsContextCreator) {
@@ -87,7 +94,19 @@ export class ExecuteService {
8794
return this._interceptorsContextCreator;
8895
}
8996

97+
get guardsContextCreator() {
98+
if (!this._guardsContextCreator) {
99+
this._guardsContextCreator = new GuardsContextCreator(
100+
this.moduleRef.container,
101+
this.moduleRef.applicationConfig
102+
);
103+
}
104+
105+
return this._guardsContextCreator;
106+
}
107+
90108
private interceptorsConsumer = new InterceptorsConsumer();
109+
private guardsConsumer = new GuardsConsumer();
91110

92111
async run(params: ParamsForExecute[], tmpIds: (string | number)[]) {
93112
return this.runInTransaction(() => this.executeOperations(params, tmpIds));
@@ -127,6 +146,17 @@ export class ExecuteService {
127146
itemReplace[itemReplace.length - 1];
128147
}
129148
}
149+
const guarFunction = this.getCanActivateFn(
150+
controller,
151+
controller[methodName],
152+
currentParams.module
153+
);
154+
155+
if (guarFunction) {
156+
await guarFunction(
157+
Object.values(this.asyncLocalStorage.getStore() || {})
158+
);
159+
}
130160

131161
const interceptors = this.getInterceptorsArray(
132162
controller,
@@ -167,6 +197,32 @@ export class ExecuteService {
167197
return resultArray;
168198
}
169199

200+
private getCanActivateFn(
201+
controller: Controller,
202+
callback: (...arg: any) => any,
203+
module: ParamsForExecute['module']
204+
) {
205+
const guards = this.guardsContextCreator.create(
206+
controller,
207+
callback,
208+
module.token
209+
);
210+
211+
const canActivateFn = async (args: any[]) => {
212+
const canActivate = await this.guardsConsumer.tryActivate(
213+
guards,
214+
args,
215+
controller,
216+
callback,
217+
'http'
218+
);
219+
if (!canActivate) {
220+
throw new ForbiddenException(FORBIDDEN_MESSAGE);
221+
}
222+
};
223+
return guards.length ? canActivateFn : null;
224+
}
225+
170226
private getInterceptorsArray(
171227
controller: Controller,
172228
callback: (...arg: any) => any,
@@ -299,10 +355,14 @@ export class ExecuteService {
299355
const formatError = this.errorFormatService.formatError(e);
300356

301357
if (formatError instanceof InternalServerErrorException) {
302-
throw formatError
358+
throw formatError;
303359
}
304-
const response = formatError.getResponse()
305-
if (typeof response === 'object' && 'message' in response && Array.isArray(response['message'])) {
360+
const response = formatError.getResponse();
361+
if (
362+
typeof response === 'object' &&
363+
'message' in response &&
364+
Array.isArray(response['message'])
365+
) {
306366
response['message'] = response['message'].map((m: any) => {
307367
m['path'] = [KEY_MAIN_INPUT_SCHEMA, `${i}`, ...m['path']];
308368
return m;

libs/json-api/json-api-nestjs/src/lib/modules/mixin/mixin.module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ export class MixinModule {
3636

3737
bindController(controllerClass, entity, moduleConfig);
3838

39+
mixinOptions.config.hooks.afterCreateController(controllerClass)
40+
3941
const optionProvider = {
4042
provide: CONTROLLER_OPTIONS_TOKEN,
4143
useValue: moduleConfig,

libs/json-api/json-api-nestjs/src/lib/modules/mixin/swagger/method/index.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@ import { deleteRelationship } from './delete-relationship';
88
import { postRelationship } from './post-relationship';
99
import { patchRelationship } from './patch-relationship';
1010

11-
import { OrmService } from '../../types';
12-
1311
export const swaggerMethod = {
1412
getAll,
1513
getOne,
@@ -22,6 +20,3 @@ export const swaggerMethod = {
2220
patchRelationship,
2321
} as const;
2422

25-
export type SwaggerMethod<E extends object> = {
26-
[Key in keyof OrmService<E>]?: (typeof swaggerMethod)[Key];
27-
};

libs/json-api/json-api-nestjs/src/lib/modules/mixin/types/orm-service.type.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,23 @@ import {
1515

1616
export interface OrmService<E extends object, IdKey extends string = 'id'> {
1717
getAll(
18-
query: Query<E, IdKey>
18+
query: Query<E, IdKey>,
1919
): Promise<ResourceObject<E, 'array', null, IdKey>>;
20+
getAll(
21+
query: Query<E, IdKey>,
22+
transformData?: boolean,
23+
additionalQueryParams?: Record<string, unknown>
24+
): Promise<ResourceObject<E, 'array', null, IdKey> | { totalItems: number; items: E[] }>;
2025
getOne(
2126
id: number | string,
2227
query: QueryOne<E, IdKey>
2328
): Promise<ResourceObject<E, 'object', null, IdKey>>;
29+
getOne(
30+
id: number | string,
31+
query: QueryOne<E, IdKey>,
32+
transformData?: boolean,
33+
additionalQueryParams?: Record<string, unknown>
34+
): Promise<ResourceObject<E, 'object', null, IdKey> | E>;
2435
deleteOne(id: number | string): Promise<void>;
2536
patchOne(
2637
id: number | string,
@@ -51,4 +62,10 @@ export interface OrmService<E extends object, IdKey extends string = 'id'> {
5162
rel: Rel,
5263
input: PatchRelationshipData
5364
): Promise<ResourceObjectRelationships<E, IdKey, Rel>>;
65+
66+
loadRelations(
67+
relationships: PatchData<E, IdKey>['relationships'] | PostData<E, IdKey>['relationships'],
68+
): Promise<{
69+
[K in RelationKeys<E>]: E[K];
70+
}>;
5471
}

libs/json-api/json-api-nestjs/src/lib/modules/mixin/zod/zod-query-schema/filter.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ function getZodRulesForField(type: TypeField = TypeField.string) {
8989
...acum,
9090
[val]: zodRuleStringArray
9191
.refine(elementOfArrayMustBe(type), {
92-
error: `String should be as ${type}`,
92+
error: `String element of array should be as ${type}`,
9393
})
9494
.optional(),
9595
}),

libs/json-api/json-api-nestjs/src/lib/types/module-options.types.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { DynamicModule } from '@nestjs/common';
1+
import { DynamicModule, Type } from '@nestjs/common';
22
import { NonEmptyArray } from 'zod-validation-error';
33
import { AnyEntity, EntityClass } from '@klerick/json-api-nestjs-shared';
44
import {
@@ -15,6 +15,9 @@ type ModuleCommonParams = {
1515
controllers?: NestController;
1616
providers?: NestProvider;
1717
imports?: NestImport;
18+
hooks?: {
19+
afterCreateController: (controller: Type<any>) => void;
20+
}
1821
};
1922

2023
type ModuleCommonOptions = {

libs/json-api/json-api-nestjs/src/lib/utils/module-helper.spec.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,9 @@ describe('module-helper', () => {
7979
providers: [],
8080
controllers: [],
8181
entities: [],
82+
hooks: {
83+
afterCreateController: result.hooks.afterCreateController,
84+
},
8285
options: {
8386
operationUrl: undefined,
8487
requiredSelectField: false,

libs/json-api/json-api-nestjs/src/lib/utils/module-helper.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ export function prepareConfig<OrmParams>(
3030
debug: !!options['debug'],
3131
pipeForId: options['pipeForId'] || ParseIntPipe,
3232
allowSetId: !!options['allowSetId']
33+
},
34+
hooks: {
35+
afterCreateController: moduleParams['hooks'] && moduleParams['hooks']['afterCreateController'] || (() => void 0)
3336
}
3437
};
3538
}

0 commit comments

Comments
 (0)