@ask-ell/back-end 0.0.11 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.spec.swcrc +22 -0
- package/dist/tsconfig.lib.tsbuildinfo +1 -0
- package/eslint.config.mjs +22 -0
- package/jest.config.ts +21 -0
- package/package.json +16 -11
- package/project.json +29 -0
- package/src/add-ons/add-on.interface.ts +5 -0
- package/src/add-ons/add-on.wrapper.ts +36 -0
- package/src/add-ons/implementations/security/security.add-on.ts +18 -0
- package/src/add-ons/implementations/sentry/{sentry.add-on.interface.d.ts → sentry.add-on.interface.ts} +5 -3
- package/src/add-ons/implementations/sentry/sentry.add-on.state.interface.ts +5 -0
- package/src/add-ons/implementations/sentry/sentry.add-on.ts +25 -0
- package/src/add-ons/implementations/swagger/{swagger.add-on.interface.d.ts → swagger.add-on.interface.ts} +4 -3
- package/src/add-ons/implementations/swagger/swagger.add-on.state.interface.ts +6 -0
- package/src/add-ons/implementations/swagger/swagger.add-on.ts +43 -0
- package/src/add-ons/implementations/swagger/types.ts +1 -0
- package/src/add-ons/index.ts +1 -0
- package/src/add-ons/{types.d.ts → types.ts} +2 -2
- package/src/application.base.ts +15 -0
- package/src/{index.d.ts → index.ts} +3 -1
- package/src/keyv.store.adapter.factory.ts +37 -0
- package/src/requirement.module.ts +57 -0
- package/src/scripts/{index.d.ts → index.ts} +0 -1
- package/src/scripts/{move-keyv-data.script.js → move-keyv-data.script.ts} +36 -24
- package/src/secrets/docker.secret.getter.ts +8 -0
- package/src/secrets/file.secret.getter.ts +24 -0
- package/src/secrets/index.ts +2 -0
- package/src/secrets/local.secret.getter.ts +8 -0
- package/src/secrets/types.ts +1 -0
- package/src/utils/index.ts +1 -0
- package/src/utils/tmp.ts +16 -0
- package/tsconfig.json +16 -0
- package/tsconfig.lib.json +18 -0
- package/tsconfig.spec.json +23 -0
- package/src/add-ons/add-on.interface.d.ts +0 -5
- package/src/add-ons/add-on.interface.d.ts.map +0 -1
- package/src/add-ons/add-on.interface.js +0 -2
- package/src/add-ons/add-on.wrapper.d.ts +0 -11
- package/src/add-ons/add-on.wrapper.d.ts.map +0 -1
- package/src/add-ons/add-on.wrapper.js +0 -31
- package/src/add-ons/implementations/security/security.add-on.d.ts +0 -6
- package/src/add-ons/implementations/security/security.add-on.d.ts.map +0 -1
- package/src/add-ons/implementations/security/security.add-on.js +0 -17
- package/src/add-ons/implementations/sentry/sentry.add-on.d.ts +0 -8
- package/src/add-ons/implementations/sentry/sentry.add-on.d.ts.map +0 -1
- package/src/add-ons/implementations/sentry/sentry.add-on.interface.d.ts.map +0 -1
- package/src/add-ons/implementations/sentry/sentry.add-on.interface.js +0 -2
- package/src/add-ons/implementations/sentry/sentry.add-on.js +0 -19
- package/src/add-ons/implementations/sentry/sentry.add-on.state.interface.d.ts +0 -6
- package/src/add-ons/implementations/sentry/sentry.add-on.state.interface.d.ts.map +0 -1
- package/src/add-ons/implementations/sentry/sentry.add-on.state.interface.js +0 -2
- package/src/add-ons/implementations/swagger/swagger.add-on.d.ts +0 -9
- package/src/add-ons/implementations/swagger/swagger.add-on.d.ts.map +0 -1
- package/src/add-ons/implementations/swagger/swagger.add-on.interface.d.ts.map +0 -1
- package/src/add-ons/implementations/swagger/swagger.add-on.interface.js +0 -2
- package/src/add-ons/implementations/swagger/swagger.add-on.js +0 -26
- package/src/add-ons/implementations/swagger/swagger.add-on.state.interface.d.ts +0 -6
- package/src/add-ons/implementations/swagger/swagger.add-on.state.interface.d.ts.map +0 -1
- package/src/add-ons/implementations/swagger/swagger.add-on.state.interface.js +0 -2
- package/src/add-ons/implementations/swagger/types.d.ts +0 -2
- package/src/add-ons/implementations/swagger/types.d.ts.map +0 -1
- package/src/add-ons/implementations/swagger/types.js +0 -2
- package/src/add-ons/index.d.ts +0 -2
- package/src/add-ons/index.d.ts.map +0 -1
- package/src/add-ons/index.js +0 -4
- package/src/add-ons/types.d.ts.map +0 -1
- package/src/add-ons/types.js +0 -2
- package/src/application.base.d.ts +0 -5
- package/src/application.base.d.ts.map +0 -1
- package/src/application.base.js +0 -16
- package/src/index.d.ts.map +0 -1
- package/src/index.js +0 -7
- package/src/keyv.store.adapter.factory.d.ts +0 -12
- package/src/keyv.store.adapter.factory.d.ts.map +0 -1
- package/src/keyv.store.adapter.factory.js +0 -44
- package/src/requirement.module.d.ts +0 -3
- package/src/requirement.module.d.ts.map +0 -1
- package/src/requirement.module.js +0 -71
- package/src/scripts/index.d.ts.map +0 -1
- package/src/scripts/index.js +0 -4
- package/src/scripts/move-keyv-data.script.d.ts +0 -6
- package/src/scripts/move-keyv-data.script.d.ts.map +0 -1
- package/src/utils/index.d.ts +0 -2
- package/src/utils/index.d.ts.map +0 -1
- package/src/utils/index.js +0 -4
- package/src/utils/tmp.d.ts +0 -4
- package/src/utils/tmp.d.ts.map +0 -1
- package/src/utils/tmp.js +0 -16
package/.spec.swcrc
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"jsc": {
|
|
3
|
+
"target": "es2017",
|
|
4
|
+
"parser": {
|
|
5
|
+
"syntax": "typescript",
|
|
6
|
+
"decorators": true,
|
|
7
|
+
"dynamicImport": true
|
|
8
|
+
},
|
|
9
|
+
"transform": {
|
|
10
|
+
"decoratorMetadata": true,
|
|
11
|
+
"legacyDecorator": true
|
|
12
|
+
},
|
|
13
|
+
"keepClassNames": true,
|
|
14
|
+
"externalHelpers": true,
|
|
15
|
+
"loose": true
|
|
16
|
+
},
|
|
17
|
+
"module": {
|
|
18
|
+
"type": "es6"
|
|
19
|
+
},
|
|
20
|
+
"sourceMaps": true,
|
|
21
|
+
"exclude": []
|
|
22
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":"6.0.3"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import baseConfig from '../../eslint.config.mjs';
|
|
2
|
+
|
|
3
|
+
export default [
|
|
4
|
+
...baseConfig,
|
|
5
|
+
{
|
|
6
|
+
files: ['**/*.json'],
|
|
7
|
+
rules: {
|
|
8
|
+
'@nx/dependency-checks': [
|
|
9
|
+
'error',
|
|
10
|
+
{
|
|
11
|
+
ignoredFiles: ['{projectRoot}/eslint.config.{js,cjs,mjs,ts,cts,mts}'],
|
|
12
|
+
},
|
|
13
|
+
],
|
|
14
|
+
},
|
|
15
|
+
languageOptions: {
|
|
16
|
+
parser: await import('jsonc-eslint-parser'),
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
ignores: ['**/out-tsc'],
|
|
21
|
+
},
|
|
22
|
+
];
|
package/jest.config.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
import { readFileSync } from 'fs';
|
|
3
|
+
|
|
4
|
+
// Reading the SWC compilation config for the spec files
|
|
5
|
+
const swcJestConfig = JSON.parse(
|
|
6
|
+
readFileSync(`${__dirname}/.spec.swcrc`, 'utf-8')
|
|
7
|
+
);
|
|
8
|
+
|
|
9
|
+
// Disable .swcrc look-up by SWC core because we're passing in swcJestConfig ourselves
|
|
10
|
+
swcJestConfig.swcrc = false;
|
|
11
|
+
|
|
12
|
+
export default {
|
|
13
|
+
displayName: '@ask-ell/back-end',
|
|
14
|
+
preset: '../../jest.preset.js',
|
|
15
|
+
testEnvironment: 'node',
|
|
16
|
+
transform: {
|
|
17
|
+
'^.+\\.[tj]s$': ['@swc/jest', swcJestConfig],
|
|
18
|
+
},
|
|
19
|
+
moduleFileExtensions: ['ts', 'js', 'html'],
|
|
20
|
+
coverageDirectory: 'test-output/jest/coverage',
|
|
21
|
+
};
|
package/package.json
CHANGED
|
@@ -1,20 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ask-ell/back-end",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "./src/index.js",
|
|
7
7
|
"types": "./src/index.d.ts",
|
|
8
8
|
"dependencies": {
|
|
9
|
-
"
|
|
10
|
-
"@
|
|
11
|
-
"@
|
|
12
|
-
"@
|
|
13
|
-
"
|
|
14
|
-
"@
|
|
15
|
-
"@
|
|
16
|
-
"@
|
|
17
|
-
"@
|
|
18
|
-
"@
|
|
9
|
+
"@ask-ell/core": "*",
|
|
10
|
+
"@ask-ell/node": "*",
|
|
11
|
+
"@ask-ell/nest": "*",
|
|
12
|
+
"@keyv/postgres": "*",
|
|
13
|
+
"@keyv/sqlite": "*",
|
|
14
|
+
"@nestjs/common": "*",
|
|
15
|
+
"@nestjs/config": "*",
|
|
16
|
+
"@nestjs/core": "*",
|
|
17
|
+
"@nestjs/swagger": "*",
|
|
18
|
+
"@nestjs/throttler": "*",
|
|
19
|
+
"@sentry/nestjs": "*",
|
|
20
|
+
"helmet": "*",
|
|
21
|
+
"keyv": "*",
|
|
22
|
+
"pg": "*",
|
|
23
|
+
"tslib": "*"
|
|
19
24
|
}
|
|
20
25
|
}
|
package/project.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "back-end",
|
|
3
|
+
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
|
4
|
+
"sourceRoot": "packages/back-end/src",
|
|
5
|
+
"projectType": "library",
|
|
6
|
+
"tags": [],
|
|
7
|
+
"targets": {
|
|
8
|
+
"build": {
|
|
9
|
+
"executor": "@nx/js:tsc",
|
|
10
|
+
"dependsOn": ["^build"],
|
|
11
|
+
"outputs": [
|
|
12
|
+
"{options.outputPath}"
|
|
13
|
+
],
|
|
14
|
+
"options": {
|
|
15
|
+
"outputPath": "dist/packages/back-end",
|
|
16
|
+
"main": "packages/back-end/src/index.ts",
|
|
17
|
+
"tsConfig": "packages/back-end/tsconfig.lib.json",
|
|
18
|
+
"assets": [
|
|
19
|
+
"packages/back-end/*.md"
|
|
20
|
+
]
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"test": {
|
|
24
|
+
"options": {
|
|
25
|
+
"passWithNoTests": true
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { INestApplication } from "@nestjs/common";
|
|
2
|
+
|
|
3
|
+
import { IAddOn } from "./add-on.interface";
|
|
4
|
+
import { SecurityAddOn } from "./implementations/security/security.add-on";
|
|
5
|
+
import { AddOnWrapperParams } from "./types";
|
|
6
|
+
import { SwaggerAddon } from "./implementations/swagger/swagger.add-on";
|
|
7
|
+
import { SentryAddon } from "./implementations/sentry/sentry.add-on";
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
export class AddOnWrapper<T> implements IAddOn<T> {
|
|
11
|
+
private implementations: IAddOn<T>[] = [
|
|
12
|
+
new SecurityAddOn()
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
constructor(
|
|
16
|
+
private params: AddOnWrapperParams
|
|
17
|
+
) {
|
|
18
|
+
this.appendImplementations();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
apply(application: INestApplication<T>): void {
|
|
22
|
+
this.implementations.forEach((addOn: IAddOn<T>): void => {
|
|
23
|
+
addOn.apply(application);
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
private appendImplementations(): void {
|
|
28
|
+
const { sentry, swagger } = this.params;
|
|
29
|
+
if(swagger){
|
|
30
|
+
this.implementations.push(new SwaggerAddon(swagger));
|
|
31
|
+
}
|
|
32
|
+
if(sentry){
|
|
33
|
+
this.implementations.push(new SentryAddon(sentry));
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { INestApplication, ValidationPipe } from "@nestjs/common";
|
|
2
|
+
import helmet from 'helmet';
|
|
3
|
+
|
|
4
|
+
import { IAddOn } from "../../add-on.interface";
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
export class SecurityAddOn<T> implements IAddOn<T> {
|
|
8
|
+
apply(application: INestApplication<T>): void {
|
|
9
|
+
application.enableCors();
|
|
10
|
+
application.use(helmet());
|
|
11
|
+
application.useGlobalPipes(
|
|
12
|
+
new ValidationPipe({
|
|
13
|
+
transform: true,
|
|
14
|
+
whitelist: false
|
|
15
|
+
})
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { IEntity } from "@ask-ell/core";
|
|
2
|
+
|
|
2
3
|
import { ISentryAddOnState } from "./sentry.add-on.state.interface";
|
|
3
4
|
import { IAddOn } from "../../add-on.interface";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
export interface ISentryAddOn<T>
|
|
8
|
+
extends IEntity<ISentryAddOnState>, IAddOn<T> { }
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Entity, type IResult, ok } from "@ask-ell/core";
|
|
2
|
+
import { NestSentryConfiguration } from "@ask-ell/nest"
|
|
3
|
+
|
|
4
|
+
import { ISentryAddOnState } from "./sentry.add-on.state.interface";
|
|
5
|
+
import { ISentryAddOn } from "./sentry.add-on.interface";
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
export class SentryAddon<T>
|
|
9
|
+
extends Entity<ISentryAddOnState>
|
|
10
|
+
implements ISentryAddOn<T>
|
|
11
|
+
{
|
|
12
|
+
checkStateValidity(_newState: ISentryAddOnState): IResult<void> {
|
|
13
|
+
return ok();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
apply(): void {
|
|
17
|
+
const { host, projectId, publicKey } = this.getSnapshot();
|
|
18
|
+
|
|
19
|
+
new NestSentryConfiguration({
|
|
20
|
+
publicKey,
|
|
21
|
+
host,
|
|
22
|
+
projectId
|
|
23
|
+
}).init();
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { IEntity } from "@ask-ell/core";
|
|
2
|
+
|
|
2
3
|
import { ISwaggerAddOnState } from "./swagger.add-on.state.interface";
|
|
3
4
|
import { IAddOn } from "../../add-on.interface";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
|
|
6
|
+
export interface ISwaggerAddOn<T>
|
|
7
|
+
extends IEntity<ISwaggerAddOnState>, IAddOn<T> { }
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Entity, type IResult, ok } from "@ask-ell/core";
|
|
2
|
+
import { type INestApplication } from "@nestjs/common";
|
|
3
|
+
import {
|
|
4
|
+
DocumentBuilder,
|
|
5
|
+
type OpenAPIObject,
|
|
6
|
+
SwaggerModule,
|
|
7
|
+
} from "@nestjs/swagger";
|
|
8
|
+
|
|
9
|
+
import { ISwaggerAddOnState } from "./swagger.add-on.state.interface";
|
|
10
|
+
import { ISwaggerAddOn } from "./swagger.add-on.interface";
|
|
11
|
+
import { AuthMethod } from "./types";
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
export class SwaggerAddon<T>
|
|
15
|
+
extends Entity<ISwaggerAddOnState>
|
|
16
|
+
implements ISwaggerAddOn<T>
|
|
17
|
+
{
|
|
18
|
+
checkStateValidity(_newState: ISwaggerAddOnState): IResult<void> {
|
|
19
|
+
return ok();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
apply(application: INestApplication<T>): void {
|
|
23
|
+
const { title, auths } = this.getSnapshot();
|
|
24
|
+
|
|
25
|
+
const documentBuilder: DocumentBuilder = new DocumentBuilder().setTitle(title)
|
|
26
|
+
|
|
27
|
+
auths?.forEach((auth: AuthMethod): void => {
|
|
28
|
+
if (auth === 'bearer') {
|
|
29
|
+
documentBuilder.addBearerAuth();
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const documentBuild: Omit<OpenAPIObject, "paths"> = documentBuilder.build();
|
|
34
|
+
|
|
35
|
+
const documentFactory = (): OpenAPIObject =>
|
|
36
|
+
SwaggerModule.createDocument(application, documentBuild);
|
|
37
|
+
|
|
38
|
+
SwaggerModule.setup("/swagger", application, documentFactory, {
|
|
39
|
+
customSiteTitle: title,
|
|
40
|
+
jsonDocumentUrl: "/swagger/json",
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type AuthMethod = 'bearer' | 'cookie';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './add-on.wrapper'
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ISentryAddOnState } from "./implementations/sentry/sentry.add-on.state.interface";
|
|
2
2
|
import { ISwaggerAddOnState } from "./implementations/swagger/swagger.add-on.state.interface";
|
|
3
|
+
|
|
3
4
|
export type AddOnWrapperParams = Partial<{
|
|
4
5
|
swagger: ISwaggerAddOnState;
|
|
5
6
|
sentry: ISentryAddOnState;
|
|
6
|
-
}>;
|
|
7
|
-
//# sourceMappingURL=types.d.ts.map
|
|
7
|
+
}>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { DynamicModule, Provider } from "@nestjs/common";
|
|
2
|
+
|
|
3
|
+
import { RequirementModule } from "./requirement.module";
|
|
4
|
+
|
|
5
|
+
export class ApplicationBase {
|
|
6
|
+
static withProviders(providers: Provider[]): DynamicModule {
|
|
7
|
+
const providersAndExports: Provider[] = [...providers];
|
|
8
|
+
return {
|
|
9
|
+
module: this,
|
|
10
|
+
imports: [RequirementModule],
|
|
11
|
+
providers: providersAndExports,
|
|
12
|
+
exports: [...providersAndExports]
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { Injectable } from "@nestjs/common";
|
|
2
|
+
import { KeyvStoreAdapter } from "keyv";
|
|
3
|
+
import KeyvSqlite from '@keyv/sqlite';
|
|
4
|
+
import KeyvPostgres from '@keyv/postgres';
|
|
5
|
+
import { ILogger, MaybeUndefined } from "@ask-ell/core";
|
|
6
|
+
import { NestLogger } from '@ask-ell/nest';
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
|
|
9
|
+
import { createTmpFolder, TMP_FOLDER_PATH } from "./utils";
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
type CreateKeyvStoreAdapterOptions = {
|
|
13
|
+
postgresUri: MaybeUndefined<string>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
@Injectable()
|
|
17
|
+
export class KeyvStoreAdapterFactory {
|
|
18
|
+
private logger: ILogger = NestLogger.fromClass(KeyvStoreAdapterFactory);
|
|
19
|
+
|
|
20
|
+
create(options: CreateKeyvStoreAdapterOptions): KeyvStoreAdapter {
|
|
21
|
+
const adapter: KeyvStoreAdapter = this.getKeyvStoreAdapter(options);
|
|
22
|
+
this.logger.log(`Connected to database with adapter "${adapter.constructor.name}"`);
|
|
23
|
+
return adapter;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
private getKeyvStoreAdapter({ postgresUri }: CreateKeyvStoreAdapterOptions): KeyvStoreAdapter {
|
|
27
|
+
if (!postgresUri) {
|
|
28
|
+
createTmpFolder(this.logger);
|
|
29
|
+
const localDatabaseFilePath: string = join(TMP_FOLDER_PATH, 'database.sqlite');
|
|
30
|
+
return new KeyvSqlite(localDatabaseFilePath);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return new KeyvPostgres({
|
|
34
|
+
uri: postgresUri,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { Global, Module } from "@nestjs/common";
|
|
2
|
+
import { ConfigModule } from "@nestjs/config";
|
|
3
|
+
import { APP_FILTER, APP_GUARD, APP_INTERCEPTOR } from "@nestjs/core";
|
|
4
|
+
import { ThrottlerGuard, ThrottlerModule } from "@nestjs/throttler";
|
|
5
|
+
import type { ILogger } from "@ask-ell/core";
|
|
6
|
+
import { NestLogger, HealthModule, ResponseFormatInterceptor, HttpExceptionFilter } from "@ask-ell/nest";
|
|
7
|
+
import { SentryGlobalFilter, SentryModule } from '@sentry/nestjs/setup';
|
|
8
|
+
|
|
9
|
+
import { KeyvStoreAdapterFactory } from "./keyv.store.adapter.factory";
|
|
10
|
+
import { dockerSecretGetter, localSecretGetter } from "./secrets";
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
const configModuleLogger: ILogger = NestLogger.fromClass(ConfigModule);
|
|
14
|
+
|
|
15
|
+
@Global()
|
|
16
|
+
@Module({
|
|
17
|
+
imports: [
|
|
18
|
+
HealthModule,
|
|
19
|
+
SentryModule.forRoot(),
|
|
20
|
+
ThrottlerModule.forRoot([{
|
|
21
|
+
ttl: 60000,
|
|
22
|
+
limit: 30,
|
|
23
|
+
}]),
|
|
24
|
+
ConfigModule.forRoot({
|
|
25
|
+
isGlobal: true,
|
|
26
|
+
load: [
|
|
27
|
+
dockerSecretGetter(configModuleLogger),
|
|
28
|
+
localSecretGetter(configModuleLogger)
|
|
29
|
+
]
|
|
30
|
+
})
|
|
31
|
+
],
|
|
32
|
+
providers: [
|
|
33
|
+
KeyvStoreAdapterFactory,
|
|
34
|
+
{
|
|
35
|
+
provide: APP_GUARD,
|
|
36
|
+
useClass: ThrottlerGuard
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
provide: APP_INTERCEPTOR,
|
|
40
|
+
useClass: ResponseFormatInterceptor
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
provide: APP_FILTER,
|
|
44
|
+
useClass: HttpExceptionFilter
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
provide: APP_FILTER,
|
|
48
|
+
useClass: SentryGlobalFilter
|
|
49
|
+
}
|
|
50
|
+
],
|
|
51
|
+
exports: [
|
|
52
|
+
ConfigModule,
|
|
53
|
+
SentryModule,
|
|
54
|
+
KeyvStoreAdapterFactory
|
|
55
|
+
]
|
|
56
|
+
})
|
|
57
|
+
export class RequirementModule { }
|
|
@@ -1,55 +1,67 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
import { PercentageIncrementor } from '@ask-ell/core';
|
|
2
|
+
import { Client, QueryResult } from 'pg';
|
|
3
|
+
import { Script, ExecuteOptions } from '@ask-ell/node';
|
|
4
|
+
import { NestLogger } from '@ask-ell/nest';
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
type KeyvRowData = {
|
|
8
|
+
key: string;
|
|
9
|
+
value: any;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export class MoveKeyvDataScript extends Script {
|
|
9
13
|
constructor() {
|
|
10
14
|
super({
|
|
11
15
|
arguments: [
|
|
12
16
|
{ name: 'origin', description: 'Origin Postgres database URI', required: true },
|
|
13
17
|
{ name: 'target', description: 'Target Postgres database URI', required: true }
|
|
14
18
|
],
|
|
15
|
-
logger:
|
|
19
|
+
logger: NestLogger.fromClass(MoveKeyvDataScript)
|
|
16
20
|
});
|
|
17
21
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const
|
|
22
|
+
|
|
23
|
+
async execute({ logger, arguments: { origin, target }, prompts: { confirm } }: ExecuteOptions): Promise<void> {
|
|
24
|
+
const originPostgresClient: Client = new Client(origin);
|
|
25
|
+
const targetPostgresClient: Client = new Client(target);
|
|
26
|
+
|
|
21
27
|
logger.log('Reading databases...');
|
|
28
|
+
|
|
22
29
|
await Promise.all([
|
|
23
30
|
originPostgresClient.connect(),
|
|
24
31
|
targetPostgresClient.connect()
|
|
25
32
|
]);
|
|
33
|
+
|
|
26
34
|
try {
|
|
27
|
-
const { rows } = await originPostgresClient.query('SELECT * FROM keyv');
|
|
28
|
-
|
|
35
|
+
const { rows }: QueryResult<KeyvRowData> = await originPostgresClient.query('SELECT * FROM keyv');
|
|
36
|
+
|
|
37
|
+
const rowsCount: number = rows.length;
|
|
29
38
|
logger.log(`You're going to insert ${rowsCount} keyv entries from the original database to the target database.`);
|
|
30
|
-
|
|
39
|
+
|
|
40
|
+
const confirmed: boolean = await confirm('Are you sure you want to proceed?');
|
|
41
|
+
|
|
31
42
|
if (confirmed) {
|
|
32
43
|
logger.log('Inserting entries into the target database...');
|
|
33
|
-
const incrementor = new
|
|
34
|
-
incrementor.onIncrement(({ achivmentOnPercent }) => {
|
|
44
|
+
const incrementor: PercentageIncrementor = new PercentageIncrementor(rowsCount);
|
|
45
|
+
incrementor.onIncrement(({ achivmentOnPercent }): void => {
|
|
35
46
|
logger.log(`Progress: ${achivmentOnPercent}%`);
|
|
36
47
|
});
|
|
48
|
+
|
|
37
49
|
for (const { key, value } of rows) {
|
|
38
50
|
await targetPostgresClient.query('INSERT INTO keyv (key, value) VALUES ($1, $2)', [key, value]);
|
|
39
51
|
incrementor.increment();
|
|
40
52
|
}
|
|
53
|
+
|
|
41
54
|
logger.log(`${rowsCount} entries inserted into the target database.`);
|
|
42
55
|
logger.log('Migration completed successfully.');
|
|
43
|
-
}
|
|
44
|
-
else {
|
|
56
|
+
} else {
|
|
45
57
|
logger.log('Migration cancelled.');
|
|
46
58
|
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
59
|
+
} finally {
|
|
60
|
+
await Promise.all([
|
|
61
|
+
originPostgresClient.end(),
|
|
62
|
+
targetPostgresClient.end()
|
|
63
|
+
]);
|
|
51
64
|
logger.log('Database connections closed.');
|
|
52
65
|
}
|
|
53
66
|
}
|
|
54
67
|
}
|
|
55
|
-
exports.MoveKeyvDataScript = MoveKeyvDataScript;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { FileSecretGetter, fileSecretGetter } from "./file.secret.getter";
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
const DOCKER_SECRETS_DIRECTORY = '/run/secrets';
|
|
5
|
+
|
|
6
|
+
export const dockerSecretGetter: FileSecretGetter = fileSecretGetter
|
|
7
|
+
(DOCKER_SECRETS_DIRECTORY)
|
|
8
|
+
('Application is not running inside Docker container. Docker secrets will be unavailable.')
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Dirent, existsSync, readdirSync, readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { ILogger } from "@ask-ell/core";
|
|
4
|
+
|
|
5
|
+
import { SecretGetter } from "./types";
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
export type FileSecretGetter = (logger: ILogger) => SecretGetter;
|
|
9
|
+
|
|
10
|
+
export const fileSecretGetter = (rootDirectoryPath: string) => (warningMessage: string): FileSecretGetter => (logger: ILogger): SecretGetter => (): object => {
|
|
11
|
+
const isDirectoryExists: boolean = existsSync(rootDirectoryPath);
|
|
12
|
+
if(!isDirectoryExists){
|
|
13
|
+
logger.warn(warningMessage);
|
|
14
|
+
return {};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return readdirSync(rootDirectoryPath, { withFileTypes: true })
|
|
18
|
+
.filter((entry: Dirent<string>): boolean => entry.isFile())
|
|
19
|
+
.reduce((secrets: Record<string, string>, entry): object => {
|
|
20
|
+
const filePath: string = join(rootDirectoryPath, entry.name);
|
|
21
|
+
secrets[entry.name] = readFileSync(filePath, 'utf-8').trim();
|
|
22
|
+
return secrets;
|
|
23
|
+
}, {});
|
|
24
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { fileSecretGetter, FileSecretGetter } from "./file.secret.getter";
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
const LOCAL_SECRETS_DIRECTORY = '.secrets';
|
|
5
|
+
|
|
6
|
+
export const localSecretGetter: FileSecretGetter = fileSecretGetter
|
|
7
|
+
(LOCAL_SECRETS_DIRECTORY)
|
|
8
|
+
('Secrets directory not exists on this project. Local secrets will be unavailable.');
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type SecretGetter = () => object;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './tmp';
|
package/src/utils/tmp.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { ILogger } from "@ask-ell/core";
|
|
2
|
+
import { existsSync, mkdirSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
export const TMP_FOLDER_PATH: string = join(process.cwd(), 'tmp');
|
|
7
|
+
|
|
8
|
+
export const createTmpFolder = (logger: ILogger): void => {
|
|
9
|
+
const isTmpFolderExists: boolean = existsSync(TMP_FOLDER_PATH);
|
|
10
|
+
if(isTmpFolderExists){
|
|
11
|
+
logger.info(`"${TMP_FOLDER_PATH}" folder already exists.`);
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
mkdirSync(TMP_FOLDER_PATH);
|
|
15
|
+
logger.info(`"${TMP_FOLDER_PATH}" folder created.`);
|
|
16
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../tsconfig.base.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"baseUrl": ".",
|
|
5
|
+
"rootDir": "src",
|
|
6
|
+
"outDir": "dist",
|
|
7
|
+
"tsBuildInfoFile": "dist/tsconfig.lib.tsbuildinfo",
|
|
8
|
+
"emitDeclarationOnly": false,
|
|
9
|
+
"module": "nodenext",
|
|
10
|
+
"moduleResolution": "nodenext",
|
|
11
|
+
"forceConsistentCasingInFileNames": true,
|
|
12
|
+
"types": ["node"],
|
|
13
|
+
"ignoreDeprecations": "6.0"
|
|
14
|
+
},
|
|
15
|
+
"include": ["src/**/*.ts"],
|
|
16
|
+
"references": [],
|
|
17
|
+
"exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"]
|
|
18
|
+
}
|