@appxdigital/appx-core 0.1.120 → 0.1.121-beta.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/LICENSE +21 -21
- package/README.md +564 -533
- package/dist/common/interceptors/prisma.interceptor.d.ts.map +1 -1
- package/dist/common/interceptors/prisma.interceptor.js +18 -6
- package/dist/common/setup/core-config.d.ts +66 -0
- package/dist/common/setup/core-config.d.ts.map +1 -0
- package/dist/common/setup/core-config.js +67 -0
- package/dist/common/setup/setup-core-security.d.ts +62 -0
- package/dist/common/setup/setup-core-security.d.ts.map +1 -0
- package/dist/common/setup/setup-core-security.js +40 -0
- package/dist/config/generate-all.js +4 -0
- package/dist/config/generate-controllers.js +19 -2
- package/dist/config/generate-dtos.d.ts +2 -0
- package/dist/config/generate-dtos.d.ts.map +1 -0
- package/dist/config/generate-dtos.js +199 -0
- package/dist/config/utils.d.ts +14 -0
- package/dist/config/utils.d.ts.map +1 -1
- package/dist/config/utils.js +35 -2
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/modules/auth/auth.controller.d.ts.map +1 -1
- package/dist/modules/auth/auth.controller.js +6 -2
- package/dist/modules/auth/auth.service.d.ts.map +1 -1
- package/dist/modules/core/core.controller.d.ts.map +1 -1
- package/dist/modules/core/core.controller.js +12 -5
- package/dist/modules/user/user.service.d.ts.map +1 -1
- package/dist/modules/user/user.service.js +7 -3
- package/dist/prisma/prisma.service.d.ts +45 -0
- package/dist/prisma/prisma.service.d.ts.map +1 -1
- package/dist/prisma/prisma.service.js +208 -3
- package/package.json +108 -86
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prisma.interceptor.d.ts","sourceRoot":"","sources":["../../../src/common/interceptors/prisma.interceptor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,WAAW,EAAE,gBAAgB,EAAc,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAC3F,OAAO,EAAoB,aAAa,EAAC,MAAM,6BAA6B,CAAC;AAC7E,OAAO,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AACvC,OAAO,EAAC,aAAa,EAAC,MAAM,gBAAgB,CAAC;AAG7C,OAAO,EAAC,UAAU,EAAa,MAAM,MAAM,CAAC;AAK5C,qBACa,iBAAkB,YAAW,eAAe;IAIjD,OAAO,CAAC,QAAQ,CAAC,aAAa;IAC9B,OAAO,CAAC,SAAS;IACjB,OAAO,CAAC,aAAa;IALzB,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAAS;gBAG1B,aAAa,EAAE,aAAa,EACrC,SAAS,EAAE,SAAS,EACpB,aAAa,EAAE,aAAa;IAKxC,SAAS,CAAC,OAAO,EAAE,gBAAgB,EAAE,IAAI,EAAE,WAAW,GAAG,UAAU,CAAC,GAAG,CAAC;
|
|
1
|
+
{"version":3,"file":"prisma.interceptor.d.ts","sourceRoot":"","sources":["../../../src/common/interceptors/prisma.interceptor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,WAAW,EAAE,gBAAgB,EAAc,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAC3F,OAAO,EAAoB,aAAa,EAAC,MAAM,6BAA6B,CAAC;AAC7E,OAAO,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AACvC,OAAO,EAAC,aAAa,EAAC,MAAM,gBAAgB,CAAC;AAG7C,OAAO,EAAC,UAAU,EAAa,MAAM,MAAM,CAAC;AAK5C,qBACa,iBAAkB,YAAW,eAAe;IAIjD,OAAO,CAAC,QAAQ,CAAC,aAAa;IAC9B,OAAO,CAAC,SAAS;IACjB,OAAO,CAAC,aAAa;IALzB,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAAS;gBAG1B,aAAa,EAAE,aAAa,EACrC,SAAS,EAAE,SAAS,EACpB,aAAa,EAAE,aAAa;IAKxC,SAAS,CAAC,OAAO,EAAE,gBAAgB,EAAE,IAAI,EAAE,WAAW,GAAG,UAAU,CAAC,GAAG,CAAC;CA+E3E"}
|
|
@@ -39,31 +39,43 @@ let PrismaInterceptor = exports.PrismaInterceptor = class PrismaInterceptor {
|
|
|
39
39
|
}, () => {
|
|
40
40
|
const useTransaction = this.reflector.get('useTransaction', context.getHandler()) ?? this.defaultUseTransaction === 'true';
|
|
41
41
|
if (useTransaction) {
|
|
42
|
+
// Run the handler inside the transaction and CAPTURE its result,
|
|
43
|
+
// but do not emit the response yet: the client must not see a
|
|
44
|
+
// success response until the transaction has actually committed.
|
|
45
|
+
// Emitting inside the transaction (before it commits) races the
|
|
46
|
+
// commit — an immediate read-after-write can miss the row.
|
|
47
|
+
let handlerResult;
|
|
42
48
|
this.prismaService
|
|
43
49
|
.$transaction(async (transactionClient) => {
|
|
44
50
|
nestjs_request_context_1.RequestContext.currentContext.req.prisma = transactionClient;
|
|
45
|
-
await new Promise((resolve, reject) => {
|
|
51
|
+
handlerResult = await new Promise((resolve, reject) => {
|
|
52
|
+
let captured;
|
|
46
53
|
next
|
|
47
54
|
.handle()
|
|
48
|
-
.pipe((0, operators_1.
|
|
55
|
+
.pipe((0, operators_1.catchError)((err) => {
|
|
49
56
|
const handledError = (0, error_handler_1.handleError)(err);
|
|
50
57
|
reject(handledError);
|
|
51
58
|
return (0, rxjs_1.throwError)(() => handledError);
|
|
52
59
|
}))
|
|
53
60
|
.subscribe({
|
|
54
|
-
next: (result) =>
|
|
61
|
+
next: (result) => {
|
|
62
|
+
captured = result;
|
|
63
|
+
},
|
|
55
64
|
complete: () => {
|
|
56
65
|
delete nestjs_request_context_1.RequestContext.currentContext.req.prisma;
|
|
57
|
-
|
|
58
|
-
resolve(null);
|
|
66
|
+
resolve(captured);
|
|
59
67
|
},
|
|
60
68
|
error: (err) => {
|
|
61
69
|
delete nestjs_request_context_1.RequestContext.currentContext.req.prisma;
|
|
62
|
-
observer.error(err);
|
|
63
70
|
reject(err);
|
|
64
71
|
},
|
|
65
72
|
});
|
|
66
73
|
});
|
|
74
|
+
})
|
|
75
|
+
.then(() => {
|
|
76
|
+
// The transaction has committed — now it is safe to respond.
|
|
77
|
+
observer.next(handlerResult);
|
|
78
|
+
observer.complete();
|
|
67
79
|
})
|
|
68
80
|
.catch((err) => {
|
|
69
81
|
delete nestjs_request_context_1.RequestContext.currentContext.req.prisma;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Boot-time configuration helpers used by the scaffold's `main.ts` /
|
|
3
|
+
* `app.module.ts`. They fail fast on insecure or ambiguous configuration
|
|
4
|
+
* instead of silently falling back.
|
|
5
|
+
*/
|
|
6
|
+
/** Minimum length we require of a session signing secret. */
|
|
7
|
+
export declare const MIN_SESSION_SECRET_LENGTH = 32;
|
|
8
|
+
export interface CoreSessionOptionsInput {
|
|
9
|
+
/** The session signing secret. Required — no fallback. */
|
|
10
|
+
secret?: string;
|
|
11
|
+
/** The express-session store (e.g. `CorePrismaSessionStore`). */
|
|
12
|
+
store: any;
|
|
13
|
+
/** Cookie name. */
|
|
14
|
+
cookieName?: string;
|
|
15
|
+
/** Session TTL in seconds (drives cookie `maxAge`). Default 86400. */
|
|
16
|
+
ttlSeconds?: number;
|
|
17
|
+
/**
|
|
18
|
+
* Force the `Secure` cookie flag. Defaults to `true` in production
|
|
19
|
+
* (`NODE_ENV === 'production'`), `false` otherwise so local HTTP dev works.
|
|
20
|
+
*/
|
|
21
|
+
secure?: boolean;
|
|
22
|
+
/** `SameSite` policy. Default `'lax'`. */
|
|
23
|
+
sameSite?: boolean | 'lax' | 'strict' | 'none';
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Builds hardened `express-session` options.
|
|
27
|
+
*
|
|
28
|
+
* - **:** throws if `secret` is missing or shorter than
|
|
29
|
+
* {@link MIN_SESSION_SECRET_LENGTH}. There is no hardcoded fallback — a
|
|
30
|
+
* missing secret must stop the boot, never silently sign sessions with a
|
|
31
|
+
* public constant.
|
|
32
|
+
* - **:** sets `cookie: { httpOnly, secure, sameSite, maxAge }` so session
|
|
33
|
+
* cookies are not readable by JS, are `Secure` in production, and carry a
|
|
34
|
+
* `SameSite` policy.
|
|
35
|
+
*
|
|
36
|
+
* Pass the result straight to `session(...)`.
|
|
37
|
+
*/
|
|
38
|
+
export declare function buildCoreSessionOptions(input: CoreSessionOptionsInput): {
|
|
39
|
+
secret: string;
|
|
40
|
+
resave: boolean;
|
|
41
|
+
saveUninitialized: boolean;
|
|
42
|
+
name: string | undefined;
|
|
43
|
+
store: any;
|
|
44
|
+
cookie: {
|
|
45
|
+
httpOnly: boolean;
|
|
46
|
+
secure: boolean;
|
|
47
|
+
sameSite: boolean | "none" | "lax" | "strict";
|
|
48
|
+
maxAge: number;
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Resolves the env file paths for `ConfigModule.forRoot({ envFilePath })`.
|
|
53
|
+
*
|
|
54
|
+
* Returns `['.env.<NODE_ENV>', '.env']`. `ConfigModule` gives the **first**
|
|
55
|
+
* existing file precedence, so environment-specific values in `.env.<NODE_ENV>`
|
|
56
|
+
* win, and a plain `.env` (what `appx-core create` generates) is loaded as a
|
|
57
|
+
* base/fallback. Without the `.env` fallback a freshly scaffolded project —
|
|
58
|
+
* which ships only `.env` — would load nothing and then fail the * `SESSION_SECRET` check at boot.
|
|
59
|
+
*
|
|
60
|
+
* **:** throws if `NODE_ENV` is unset instead of silently defaulting to
|
|
61
|
+
* `development` — a production deploy that forgets `NODE_ENV` must fail fast
|
|
62
|
+
* rather than load `.env.development` (dev DB creds, loose CORS, insecure
|
|
63
|
+
* cookies). Set `NODE_ENV` explicitly (`production` / `development` / `test`).
|
|
64
|
+
*/
|
|
65
|
+
export declare function coreEnvFilePath(): string[];
|
|
66
|
+
//# sourceMappingURL=core-config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"core-config.d.ts","sourceRoot":"","sources":["../../../src/common/setup/core-config.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,6DAA6D;AAC7D,eAAO,MAAM,yBAAyB,KAAK,CAAC;AAE5C,MAAM,WAAW,uBAAuB;IACpC,0DAA0D;IAC1D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,iEAAiE;IACjE,KAAK,EAAE,GAAG,CAAC;IACX,mBAAmB;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,sEAAsE;IACtE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,0CAA0C;IAC1C,QAAQ,CAAC,EAAE,OAAO,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;CAClD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,uBAAuB;;;;;;;;;;;;EAuBrE;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,IAAI,MAAM,EAAE,CAS1C"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Boot-time configuration helpers used by the scaffold's `main.ts` /
|
|
4
|
+
* `app.module.ts`. They fail fast on insecure or ambiguous configuration
|
|
5
|
+
* instead of silently falling back.
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.coreEnvFilePath = exports.buildCoreSessionOptions = exports.MIN_SESSION_SECRET_LENGTH = void 0;
|
|
9
|
+
/** Minimum length we require of a session signing secret. */
|
|
10
|
+
exports.MIN_SESSION_SECRET_LENGTH = 32;
|
|
11
|
+
/**
|
|
12
|
+
* Builds hardened `express-session` options.
|
|
13
|
+
*
|
|
14
|
+
* - **:** throws if `secret` is missing or shorter than
|
|
15
|
+
* {@link MIN_SESSION_SECRET_LENGTH}. There is no hardcoded fallback — a
|
|
16
|
+
* missing secret must stop the boot, never silently sign sessions with a
|
|
17
|
+
* public constant.
|
|
18
|
+
* - **:** sets `cookie: { httpOnly, secure, sameSite, maxAge }` so session
|
|
19
|
+
* cookies are not readable by JS, are `Secure` in production, and carry a
|
|
20
|
+
* `SameSite` policy.
|
|
21
|
+
*
|
|
22
|
+
* Pass the result straight to `session(...)`.
|
|
23
|
+
*/
|
|
24
|
+
function buildCoreSessionOptions(input) {
|
|
25
|
+
const { secret, store, cookieName, ttlSeconds = 86400, secure, sameSite = 'lax' } = input;
|
|
26
|
+
if (!secret || secret.length < exports.MIN_SESSION_SECRET_LENGTH) {
|
|
27
|
+
throw new Error(`SESSION_SECRET must be set and at least ${exports.MIN_SESSION_SECRET_LENGTH} characters. ` +
|
|
28
|
+
'Refusing to start with a missing or weak session secret.');
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
secret,
|
|
32
|
+
resave: false,
|
|
33
|
+
saveUninitialized: false,
|
|
34
|
+
name: cookieName,
|
|
35
|
+
store,
|
|
36
|
+
cookie: {
|
|
37
|
+
httpOnly: true,
|
|
38
|
+
secure: secure ?? process.env.NODE_ENV === 'production',
|
|
39
|
+
sameSite,
|
|
40
|
+
maxAge: ttlSeconds * 1000,
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
exports.buildCoreSessionOptions = buildCoreSessionOptions;
|
|
45
|
+
/**
|
|
46
|
+
* Resolves the env file paths for `ConfigModule.forRoot({ envFilePath })`.
|
|
47
|
+
*
|
|
48
|
+
* Returns `['.env.<NODE_ENV>', '.env']`. `ConfigModule` gives the **first**
|
|
49
|
+
* existing file precedence, so environment-specific values in `.env.<NODE_ENV>`
|
|
50
|
+
* win, and a plain `.env` (what `appx-core create` generates) is loaded as a
|
|
51
|
+
* base/fallback. Without the `.env` fallback a freshly scaffolded project —
|
|
52
|
+
* which ships only `.env` — would load nothing and then fail the * `SESSION_SECRET` check at boot.
|
|
53
|
+
*
|
|
54
|
+
* **:** throws if `NODE_ENV` is unset instead of silently defaulting to
|
|
55
|
+
* `development` — a production deploy that forgets `NODE_ENV` must fail fast
|
|
56
|
+
* rather than load `.env.development` (dev DB creds, loose CORS, insecure
|
|
57
|
+
* cookies). Set `NODE_ENV` explicitly (`production` / `development` / `test`).
|
|
58
|
+
*/
|
|
59
|
+
function coreEnvFilePath() {
|
|
60
|
+
const env = process.env.NODE_ENV;
|
|
61
|
+
if (!env) {
|
|
62
|
+
throw new Error('NODE_ENV must be explicitly set (e.g. "production" or "development"). ' +
|
|
63
|
+
'Refusing to default to development.');
|
|
64
|
+
}
|
|
65
|
+
return [`.env.${env}`, '.env'];
|
|
66
|
+
}
|
|
67
|
+
exports.coreEnvFilePath = coreEnvFilePath;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { INestApplication, ValidationPipeOptions } from '@nestjs/common';
|
|
2
|
+
/**
|
|
3
|
+
* Minimal CORS options accepted by {@link setupCoreSecurity}. Mirrors the
|
|
4
|
+
* subset of Nest's `CorsOptions` that most apps set; extra keys are passed
|
|
5
|
+
* through to `app.enableCors` untouched. Declared locally to avoid a fragile
|
|
6
|
+
* deep import into Nest internals.
|
|
7
|
+
*/
|
|
8
|
+
export interface CoreCorsOptions {
|
|
9
|
+
origin?: string | string[] | boolean | RegExp | (string | RegExp)[];
|
|
10
|
+
credentials?: boolean;
|
|
11
|
+
[key: string]: any;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Options for {@link setupCoreSecurity}. Each concern can be disabled by
|
|
15
|
+
* passing `false`, or tuned by passing an options object that is merged over
|
|
16
|
+
* the secure defaults.
|
|
17
|
+
*/
|
|
18
|
+
export interface CoreSecurityOptions {
|
|
19
|
+
/**
|
|
20
|
+
* Global `ValidationPipe`. Secure default:
|
|
21
|
+
* `{ transform: true, whitelist: true, forbidNonWhitelisted: true }`.
|
|
22
|
+
*
|
|
23
|
+
* `whitelist` strips body properties that carry no validation decorator;
|
|
24
|
+
* `forbidNonWhitelisted` turns such properties into a `400` instead of
|
|
25
|
+
* silently dropping them. Combined with DTOs that declare every accepted
|
|
26
|
+
* field, this rejects unknown body properties on `POST /auth/register`
|
|
27
|
+
* and every other class-validated request body.
|
|
28
|
+
*
|
|
29
|
+
* Pass `false` to skip (you must then register your own pipe), or an
|
|
30
|
+
* options object to override individual flags.
|
|
31
|
+
*/
|
|
32
|
+
validationPipe?: ValidationPipeOptions | false;
|
|
33
|
+
/**
|
|
34
|
+
* CORS. Default: `{ origin: 'http://localhost:3000', credentials: true }`
|
|
35
|
+
* — the scaffold's historical default. Read `origin` from configuration
|
|
36
|
+
* (e.g. a `CORS_ORIGIN` env var) and pass it here so the allowed origin is
|
|
37
|
+
* deployment-configurable rather than hardcoded.
|
|
38
|
+
*
|
|
39
|
+
* Guardrail: `origin: '*'` together with `credentials: true` throws at
|
|
40
|
+
* setup — that combination is rejected by browsers and is a common footgun.
|
|
41
|
+
*
|
|
42
|
+
* Pass `false` to skip (you manage CORS yourself).
|
|
43
|
+
*/
|
|
44
|
+
cors?: CoreCorsOptions | false;
|
|
45
|
+
/**
|
|
46
|
+
* Security headers via Helmet: `Content-Security-Policy`,
|
|
47
|
+
* `X-Content-Type-Options`, `Strict-Transport-Security`, frame-ancestors, etc.
|
|
48
|
+
* Default: enabled with Helmet's defaults. Pass an options object to tune
|
|
49
|
+
* (e.g. a custom CSP for the AdminJS UI), or `false` to skip.
|
|
50
|
+
*/
|
|
51
|
+
helmet?: Record<string, any> | false;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Applies AppX Core's baseline HTTP hardening to a Nest application.
|
|
55
|
+
*
|
|
56
|
+
* Call once in `main.ts`, after `NestFactory.create(...)` and before
|
|
57
|
+
* `app.listen(...)`. New scaffolds call it for you; existing projects should
|
|
58
|
+
* add the call when upgrading — see the CHANGELOG migration note for the
|
|
59
|
+
* version that introduced each concern.
|
|
60
|
+
*/
|
|
61
|
+
export declare function setupCoreSecurity(app: INestApplication, options?: CoreSecurityOptions): void;
|
|
62
|
+
//# sourceMappingURL=setup-core-security.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setup-core-security.d.ts","sourceRoot":"","sources":["../../../src/common/setup/setup-core-security.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,gBAAgB,EAAkB,qBAAqB,EAAC,MAAM,gBAAgB,CAAC;AAEvF;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC5B,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,OAAO,GAAG,MAAM,GAAG,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,CAAC;IACpE,WAAW,CAAC,EAAE,OAAO,CAAC;IAEtB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACtB;AAED;;;;GAIG;AACH,MAAM,WAAW,mBAAmB;IAChC;;;;;;;;;;;;OAYG;IACH,cAAc,CAAC,EAAE,qBAAqB,GAAG,KAAK,CAAC;IAE/C;;;;;;;;;;OAUG;IACH,IAAI,CAAC,EAAE,eAAe,GAAG,KAAK,CAAC;IAE/B;;;;;OAKG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,KAAK,CAAC;CACxC;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAC7B,GAAG,EAAE,gBAAgB,EACrB,OAAO,GAAE,mBAAwB,GAClC,IAAI,CAkCN"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.setupCoreSecurity = void 0;
|
|
4
|
+
const common_1 = require("@nestjs/common");
|
|
5
|
+
/**
|
|
6
|
+
* Applies AppX Core's baseline HTTP hardening to a Nest application.
|
|
7
|
+
*
|
|
8
|
+
* Call once in `main.ts`, after `NestFactory.create(...)` and before
|
|
9
|
+
* `app.listen(...)`. New scaffolds call it for you; existing projects should
|
|
10
|
+
* add the call when upgrading — see the CHANGELOG migration note for the
|
|
11
|
+
* version that introduced each concern.
|
|
12
|
+
*/
|
|
13
|
+
function setupCoreSecurity(app, options = {}) {
|
|
14
|
+
if (options.validationPipe !== false) {
|
|
15
|
+
app.useGlobalPipes(new common_1.ValidationPipe({
|
|
16
|
+
transform: true,
|
|
17
|
+
whitelist: true,
|
|
18
|
+
forbidNonWhitelisted: true,
|
|
19
|
+
...(options.validationPipe ?? {}),
|
|
20
|
+
}));
|
|
21
|
+
}
|
|
22
|
+
if (options.cors !== false) {
|
|
23
|
+
const cors = options.cors ?? {};
|
|
24
|
+
const origin = cors.origin ?? 'http://localhost:3000';
|
|
25
|
+
const credentials = cors.credentials ?? true;
|
|
26
|
+
if (credentials && origin === '*') {
|
|
27
|
+
throw new Error("setupCoreSecurity: CORS origin '*' is incompatible with credentials:true — " +
|
|
28
|
+
'browsers reject it and it exposes credentialed endpoints to every origin. ' +
|
|
29
|
+
'Set an explicit origin (e.g. via a CORS_ORIGIN env var) or disable credentials.');
|
|
30
|
+
}
|
|
31
|
+
app.enableCors({ ...cors, origin, credentials });
|
|
32
|
+
}
|
|
33
|
+
if (options.helmet !== false) {
|
|
34
|
+
// Lazy require: helmet is a peer dependency, only needed when enabled.
|
|
35
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
36
|
+
const helmet = require('helmet');
|
|
37
|
+
app.use(typeof options.helmet === 'object' ? helmet(options.helmet) : helmet());
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
exports.setupCoreSecurity = setupCoreSecurity;
|
|
@@ -43,6 +43,10 @@ console.log('Generating Services...');
|
|
|
43
43
|
(0, child_process_1.execSync)(`node ${path.join(scriptsDir, 'generate-services.js')}`, {
|
|
44
44
|
stdio: 'inherit',
|
|
45
45
|
});
|
|
46
|
+
console.log('Generating DTOs...');
|
|
47
|
+
(0, child_process_1.execSync)(`node ${path.join(scriptsDir, 'generate-dtos.js')}`, {
|
|
48
|
+
stdio: 'inherit',
|
|
49
|
+
});
|
|
46
50
|
console.log('Generating Controllers...');
|
|
47
51
|
(0, child_process_1.execSync)(`node ${path.join(scriptsDir, 'generate-controllers.js')}`, {
|
|
48
52
|
stdio: 'inherit',
|
|
@@ -34,10 +34,12 @@ const modulesOutputPath = path.join(process.cwd(), 'src/modules');
|
|
|
34
34
|
* @param folder
|
|
35
35
|
*/
|
|
36
36
|
const genericControllerTemplate = (model, folder) => `
|
|
37
|
-
import { Controller } from '@nestjs/common';
|
|
37
|
+
import { Body, Controller, Param, Post, Put } from '@nestjs/common';
|
|
38
38
|
import { ${model}Service } from './${folder}.service';
|
|
39
39
|
import { ${model} } from '@prisma/client';
|
|
40
|
-
import { CoreController } from '@appxdigital/appx-core';
|
|
40
|
+
import { CoreController, Permission } from '@appxdigital/appx-core';
|
|
41
|
+
import { Create${model}Dto } from './dto/create-${folder}.dto';
|
|
42
|
+
import { Update${model}Dto } from './dto/update-${folder}.dto';
|
|
41
43
|
|
|
42
44
|
@Controller('${model.toLowerCase()}s')
|
|
43
45
|
export class ${model}Controller extends CoreController<${model}> {
|
|
@@ -48,6 +50,21 @@ export class ${model}Controller extends CoreController<${model}> {
|
|
|
48
50
|
static get entityName(): string {
|
|
49
51
|
return '${model}';
|
|
50
52
|
}
|
|
53
|
+
|
|
54
|
+
// Overridden so the @Body() type is a concrete DTO — this is what lets
|
|
55
|
+
// ValidationPipe (whitelist) strip unknown fields.
|
|
56
|
+
// Custom validation goes in the DTO subclass, not here.
|
|
57
|
+
@Post()
|
|
58
|
+
@Permission('create')
|
|
59
|
+
async create(@Body() data: Create${model}Dto) {
|
|
60
|
+
return super.create(data as any);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
@Put(':id')
|
|
64
|
+
@Permission('updateMany')
|
|
65
|
+
async update(@Param('id') id: string, @Body() data: Update${model}Dto) {
|
|
66
|
+
return super.update(id, data as any);
|
|
67
|
+
}
|
|
51
68
|
}
|
|
52
69
|
`;
|
|
53
70
|
/**
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generate-dtos.d.ts","sourceRoot":"","sources":["../../src/config/generate-dtos.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
const path = __importStar(require("path"));
|
|
27
|
+
const utils_1 = require("./utils");
|
|
28
|
+
/**
|
|
29
|
+
* DTO generator.
|
|
30
|
+
*
|
|
31
|
+
* Generated CRUD controllers type `@Body()` as a generic, so NestJS's
|
|
32
|
+
* ValidationPipe has no concrete class to validate against and unknown fields
|
|
33
|
+
* are accepted. This generator emits a class-validator DTO per model+action so
|
|
34
|
+
* the controller can type its body concretely.
|
|
35
|
+
*
|
|
36
|
+
* Two-file, schema-is-source-of-truth model:
|
|
37
|
+
* - `src/generated/dto/<model>/create-<model>.generated.dto.ts` — the base
|
|
38
|
+
* class, derived from the Prisma schema, ALWAYS overwritten (gitignored).
|
|
39
|
+
* - `src/modules/<model>/dto/create-<model>.dto.ts` — a hand-owned subclass,
|
|
40
|
+
* generated ONCE, where the developer adds custom validation. Never
|
|
41
|
+
* overwritten. The controller imports this subclass.
|
|
42
|
+
*
|
|
43
|
+
* A scalar field is writable (appears in the DTO) unless it is:
|
|
44
|
+
* - the primary key (`@id`),
|
|
45
|
+
* - a server-managed timestamp (`@default(now())` or `@updatedAt`),
|
|
46
|
+
* - annotated `/// @NoWrite`, or
|
|
47
|
+
* - annotated `/// @Role(none)` (readable by nobody ⇒ not CRUD-writable).
|
|
48
|
+
* Relation navigation fields are excluded; their scalar foreign keys are kept.
|
|
49
|
+
*/
|
|
50
|
+
const cwd = process.cwd();
|
|
51
|
+
// Read the consumer's generated Prisma client for the DMMF (authoritative
|
|
52
|
+
// field metadata incl. `documentation`, which carries /// @Role / @NoWrite).
|
|
53
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
54
|
+
const { Prisma } = require(path.join(cwd, 'node_modules', '@prisma/client'));
|
|
55
|
+
const generatedDtoRoot = path.join(cwd, 'src/generated/dto');
|
|
56
|
+
const modulesRoot = path.join(cwd, 'src/modules');
|
|
57
|
+
function isNoWrite(doc) {
|
|
58
|
+
if (!doc)
|
|
59
|
+
return false;
|
|
60
|
+
if (/@NoWrite\b/i.test(doc))
|
|
61
|
+
return true;
|
|
62
|
+
// @Role(none): readable by nobody ⇒ treat as not CRUD-writable.
|
|
63
|
+
const roleMatch = doc.match(/@Role\((.*?)\)/);
|
|
64
|
+
if (roleMatch) {
|
|
65
|
+
const roles = roleMatch[1].split(',').map((r) => r.trim().toLowerCase());
|
|
66
|
+
if (roles.includes('none'))
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
function isServerManagedTimestamp(field) {
|
|
72
|
+
if (field.isUpdatedAt)
|
|
73
|
+
return true;
|
|
74
|
+
const def = field.default;
|
|
75
|
+
return !!def && typeof def === 'object' && def.name === 'now';
|
|
76
|
+
}
|
|
77
|
+
/** Decision for a scalar/enum field: base validator, TS type, optional enum arg. */
|
|
78
|
+
function mapField(field) {
|
|
79
|
+
if (field.kind === 'enum') {
|
|
80
|
+
return { validator: 'IsEnum', tsType: field.type, enumArg: field.type };
|
|
81
|
+
}
|
|
82
|
+
switch (field.type) {
|
|
83
|
+
case 'String':
|
|
84
|
+
return { validator: 'IsString', tsType: 'string' };
|
|
85
|
+
case 'Boolean':
|
|
86
|
+
return { validator: 'IsBoolean', tsType: 'boolean' };
|
|
87
|
+
case 'Int':
|
|
88
|
+
case 'BigInt':
|
|
89
|
+
return { validator: 'IsInt', tsType: 'number' };
|
|
90
|
+
case 'Float':
|
|
91
|
+
case 'Decimal':
|
|
92
|
+
return { validator: 'IsNumber', tsType: 'number' };
|
|
93
|
+
case 'DateTime':
|
|
94
|
+
return { validator: 'IsDateString', tsType: 'string' };
|
|
95
|
+
default:
|
|
96
|
+
// Json, Bytes, and anything unrecognised: keep the field (so it isn't
|
|
97
|
+
// stripped by whitelist) without a strict type validator.
|
|
98
|
+
return { validator: 'Allow', tsType: 'any' };
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
function writableFields(model) {
|
|
102
|
+
return model.fields.filter((f) => {
|
|
103
|
+
if (f.kind === 'object')
|
|
104
|
+
return false; // relation navigation
|
|
105
|
+
if (f.isId)
|
|
106
|
+
return false;
|
|
107
|
+
if (isServerManagedTimestamp(f))
|
|
108
|
+
return false;
|
|
109
|
+
if (isNoWrite(f.documentation))
|
|
110
|
+
return false;
|
|
111
|
+
return true;
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
/** Renders a base DTO class body + its import block. */
|
|
115
|
+
function renderBaseDto(className, fields, mode) {
|
|
116
|
+
const validatorImports = new Set();
|
|
117
|
+
const enumImports = new Set();
|
|
118
|
+
const lines = [];
|
|
119
|
+
for (const field of fields) {
|
|
120
|
+
const m = mapField(field);
|
|
121
|
+
validatorImports.add(m.validator);
|
|
122
|
+
if (m.enumArg)
|
|
123
|
+
enumImports.add(m.enumArg);
|
|
124
|
+
// update: everything optional. create: optional if not required or has a default.
|
|
125
|
+
const optional = mode === 'update' || !field.isRequired || field.hasDefaultValue;
|
|
126
|
+
const isList = field.isList;
|
|
127
|
+
const tsType = isList ? `${m.tsType}[]` : m.tsType;
|
|
128
|
+
// Compose the validator decorator. @Allow takes no options; for other
|
|
129
|
+
// validators on a list field, add `{ each: true }` (+ @IsArray).
|
|
130
|
+
let validatorDecorator;
|
|
131
|
+
if (m.validator === 'Allow') {
|
|
132
|
+
validatorDecorator = '@Allow()';
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
const args = [];
|
|
136
|
+
if (m.enumArg)
|
|
137
|
+
args.push(m.enumArg);
|
|
138
|
+
if (isList)
|
|
139
|
+
args.push('{ each: true }');
|
|
140
|
+
validatorDecorator = `@${m.validator}(${args.join(', ')})`;
|
|
141
|
+
}
|
|
142
|
+
const decoratorLines = [];
|
|
143
|
+
if (optional) {
|
|
144
|
+
validatorImports.add('IsOptional');
|
|
145
|
+
decoratorLines.push(' @IsOptional()');
|
|
146
|
+
}
|
|
147
|
+
if (isList && m.validator !== 'Allow') {
|
|
148
|
+
validatorImports.add('IsArray');
|
|
149
|
+
decoratorLines.push(' @IsArray()');
|
|
150
|
+
}
|
|
151
|
+
decoratorLines.push(` ${validatorDecorator}`);
|
|
152
|
+
lines.push(decoratorLines.join('\n'));
|
|
153
|
+
lines.push(` ${field.name}${optional ? '?' : '!'}: ${tsType};`);
|
|
154
|
+
lines.push('');
|
|
155
|
+
}
|
|
156
|
+
const importLines = [];
|
|
157
|
+
if (validatorImports.size > 0) {
|
|
158
|
+
importLines.push(`import { ${[...validatorImports].sort().join(', ')} } from 'class-validator';`);
|
|
159
|
+
}
|
|
160
|
+
if (enumImports.size > 0) {
|
|
161
|
+
importLines.push(`import { ${[...enumImports].sort().join(', ')} } from '@prisma/client';`);
|
|
162
|
+
}
|
|
163
|
+
return (utils_1.GENERATED_BANNER +
|
|
164
|
+
'\n' +
|
|
165
|
+
(importLines.length ? importLines.join('\n') + '\n\n' : '') +
|
|
166
|
+
`export class ${className} {\n` +
|
|
167
|
+
(lines.length ? lines.map((l) => l).join('\n') : '') +
|
|
168
|
+
`}\n`);
|
|
169
|
+
}
|
|
170
|
+
/** Renders the hand-owned subclass (generated once, then customizable). */
|
|
171
|
+
function renderSubclassDto(subclass, baseClass, importPath) {
|
|
172
|
+
return (`import { ${baseClass} } from '${importPath}';\n\n` +
|
|
173
|
+
`/**\n` +
|
|
174
|
+
` * Hand-owned DTO for the CRUD endpoint. Add custom class-validator rules\n` +
|
|
175
|
+
` * here; this file is generated once and never overwritten by \`appx generate\`.\n` +
|
|
176
|
+
` * The base class is regenerated from the Prisma schema.\n` +
|
|
177
|
+
` */\n` +
|
|
178
|
+
`export class ${subclass} extends ${baseClass} {}\n`);
|
|
179
|
+
}
|
|
180
|
+
for (const model of Prisma.dmmf.datamodel.models) {
|
|
181
|
+
const modelName = model.name; // Pascal, e.g. ProjectMember
|
|
182
|
+
const folder = (0, utils_1.pascalToKebabCase)(modelName); // kebab, e.g. project-member
|
|
183
|
+
if (utils_1.IGNORE_FOLDERS.includes(folder))
|
|
184
|
+
continue;
|
|
185
|
+
const fields = writableFields(model);
|
|
186
|
+
for (const mode of ['create', 'update']) {
|
|
187
|
+
const capMode = mode.charAt(0).toUpperCase() + mode.slice(1);
|
|
188
|
+
const baseClass = `${capMode}${modelName}GeneratedDto`;
|
|
189
|
+
const subclass = `${capMode}${modelName}Dto`;
|
|
190
|
+
// Base (overwritten, gitignored).
|
|
191
|
+
const basePath = path.join(generatedDtoRoot, folder, `${mode}-${folder}.generated.dto.ts`);
|
|
192
|
+
(0, utils_1.writeGeneratedFile)(basePath, renderBaseDto(baseClass, fields, mode));
|
|
193
|
+
// Subclass (once, hand-owned, committed).
|
|
194
|
+
const subPath = path.join(modulesRoot, folder, 'dto', `${mode}-${folder}.dto.ts`);
|
|
195
|
+
const importPath = `../../../generated/dto/${folder}/${mode}-${folder}.generated.dto`;
|
|
196
|
+
(0, utils_1.createFileIfNotExists)(subPath, renderSubclassDto(subclass, baseClass, importPath));
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
console.log('DTOs generated.');
|
package/dist/config/utils.d.ts
CHANGED
|
@@ -1,4 +1,18 @@
|
|
|
1
1
|
export declare const kebabToPascalCase: (str: string) => string;
|
|
2
|
+
export declare const pascalToKebabCase: (str: string) => string;
|
|
2
3
|
export declare const createFileIfNotExists: (filePath: string, content: string) => void;
|
|
4
|
+
/**
|
|
5
|
+
* Banner stamped on every fully-generated file. These files live under
|
|
6
|
+
* `src/generated/` (gitignored) and are OVERWRITTEN on every `appx generate`.
|
|
7
|
+
* Never hand-edit them — customize the hand-owned subclass under
|
|
8
|
+
* `src/modules/<model>/dto/` instead.
|
|
9
|
+
*/
|
|
10
|
+
export declare const GENERATED_BANNER: string;
|
|
11
|
+
/**
|
|
12
|
+
* Writes a fully-generated file, always overwriting. Use only for artifacts
|
|
13
|
+
* under `src/generated/` that carry {@link GENERATED_BANNER}. Creates parent
|
|
14
|
+
* directories as needed.
|
|
15
|
+
*/
|
|
16
|
+
export declare const writeGeneratedFile: (filePath: string, content: string) => void;
|
|
3
17
|
export declare const IGNORE_FOLDERS: string[];
|
|
4
18
|
//# sourceMappingURL=utils.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/config/utils.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/config/utils.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,iBAAiB,QAAS,MAAM,WAK5C,CAAC;AAEF,eAAO,MAAM,iBAAiB,QAAS,MAAM,WAK5C,CAAC;AAEF,eAAO,MAAM,qBAAqB,aAAc,MAAM,WAAW,MAAM,SAStE,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,QAGoD,CAAC;AAElF;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,aAAc,MAAM,WAAW,MAAM,SAInE,CAAC;AAEF,eAAO,MAAM,cAAc,UAS1B,CAAC"}
|
package/dist/config/utils.js
CHANGED
|
@@ -23,8 +23,9 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|
|
23
23
|
return result;
|
|
24
24
|
};
|
|
25
25
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
-
exports.IGNORE_FOLDERS = exports.createFileIfNotExists = exports.kebabToPascalCase = void 0;
|
|
26
|
+
exports.IGNORE_FOLDERS = exports.writeGeneratedFile = exports.GENERATED_BANNER = exports.createFileIfNotExists = exports.pascalToKebabCase = exports.kebabToPascalCase = void 0;
|
|
27
27
|
const fs = __importStar(require("fs"));
|
|
28
|
+
const path = __importStar(require("path"));
|
|
28
29
|
const kebabToPascalCase = (str) => {
|
|
29
30
|
return str
|
|
30
31
|
.split("-")
|
|
@@ -32,9 +33,17 @@ const kebabToPascalCase = (str) => {
|
|
|
32
33
|
.join("");
|
|
33
34
|
};
|
|
34
35
|
exports.kebabToPascalCase = kebabToPascalCase;
|
|
36
|
+
const pascalToKebabCase = (str) => {
|
|
37
|
+
return str
|
|
38
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1-$2')
|
|
39
|
+
.replace(/([A-Z]+)([A-Z][a-z])/g, '$1-$2')
|
|
40
|
+
.toLowerCase();
|
|
41
|
+
};
|
|
42
|
+
exports.pascalToKebabCase = pascalToKebabCase;
|
|
35
43
|
const createFileIfNotExists = (filePath, content) => {
|
|
36
44
|
console.log(`Creating ${filePath}...`);
|
|
37
45
|
if (!fs.existsSync(filePath)) {
|
|
46
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
38
47
|
fs.writeFileSync(filePath, content);
|
|
39
48
|
console.log(`${filePath} created.`);
|
|
40
49
|
}
|
|
@@ -43,9 +52,33 @@ const createFileIfNotExists = (filePath, content) => {
|
|
|
43
52
|
}
|
|
44
53
|
};
|
|
45
54
|
exports.createFileIfNotExists = createFileIfNotExists;
|
|
55
|
+
/**
|
|
56
|
+
* Banner stamped on every fully-generated file. These files live under
|
|
57
|
+
* `src/generated/` (gitignored) and are OVERWRITTEN on every `appx generate`.
|
|
58
|
+
* Never hand-edit them — customize the hand-owned subclass under
|
|
59
|
+
* `src/modules/<model>/dto/` instead.
|
|
60
|
+
*/
|
|
61
|
+
exports.GENERATED_BANNER = '// @Generated by appx-core — do not edit.\n' +
|
|
62
|
+
'// Regenerated by `appx generate`; lives under src/generated (gitignored).\n' +
|
|
63
|
+
'// To customize, edit the hand-owned subclass in src/modules/<model>/dto.\n';
|
|
64
|
+
/**
|
|
65
|
+
* Writes a fully-generated file, always overwriting. Use only for artifacts
|
|
66
|
+
* under `src/generated/` that carry {@link GENERATED_BANNER}. Creates parent
|
|
67
|
+
* directories as needed.
|
|
68
|
+
*/
|
|
69
|
+
const writeGeneratedFile = (filePath, content) => {
|
|
70
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
71
|
+
fs.writeFileSync(filePath, content);
|
|
72
|
+
console.log(`Generated ${filePath}`);
|
|
73
|
+
};
|
|
74
|
+
exports.writeGeneratedFile = writeGeneratedFile;
|
|
46
75
|
exports.IGNORE_FOLDERS = [
|
|
47
76
|
'prisma',
|
|
48
77
|
'schema.gql',
|
|
49
78
|
'session',
|
|
50
|
-
'user-refresh-token'
|
|
79
|
+
'user-refresh-token',
|
|
80
|
+
// Generated DTOs live under src/generated/dto/. The model generators scan
|
|
81
|
+
// src/generated's top-level dirs as model names, so this must be skipped or
|
|
82
|
+
// they emit a bogus `dto` module.
|
|
83
|
+
'dto',
|
|
51
84
|
];
|
package/dist/index.d.ts
CHANGED
|
@@ -22,6 +22,8 @@ export * from './common/interfaces/storage-service.interface';
|
|
|
22
22
|
export * from './modules/file/file-upload.controller';
|
|
23
23
|
export * from './modules/file/file-upload.service';
|
|
24
24
|
export * from './common/decorators/permission.decorator';
|
|
25
|
+
export * from './common/setup/setup-core-security';
|
|
26
|
+
export * from './common/setup/core-config';
|
|
25
27
|
export * from './common/interceptors/file.interceptor';
|
|
26
28
|
export * from './modules/auth/authenticated.guard';
|
|
27
29
|
export * from './modules/auth/jwt-auth.guard';
|