Создание конфигурируемого Webhook-модуля для NestJS-приложении
В рамках этой статьи я опишу создание двух NestJS-модулей с различным способом конфигурирования: утилитарный модуль и бизнес-модуль со своей базой данных.
Текста и кода тут очень много, кому лень читать, могут просто посмотреть изменения в коде проекта: https://github.com/nestjs-mod/nestjs-mod-fullstack/compare/460257364bb4ce8e23fe761fbc9ca7462bc89b61..ec8de9d574a6dbcef3c3339e876ce156a3974aae
1. Придумываем и расписываем фичу
Перед тем как реализов ывать некий функционал, нужно сперва выписать всё, что он должен уметь, и описать сущности с компонентами, которые нам известны по этому функционалу.
Описание
При разработке сильно изолированного NestJS
-модуля интеграцию с другими модулями мы производим либо через конфигурацию модуля, либо через брокер или очередь.
На моей практике основная часть интеграций это - публикация событий для которых уже пишем обработчики в слое интеграции двух функциональных модулей.
А что, если нужно предоставить доступ к этим событиям внешним сайтам, то для этого придется писать дополнительную логику по регистрации таких сайтов и логику проверки доступов.
Данный модуль вэбхуков предоставляет доступ к событиям приложения и модулей и имеет CRUD
-операции для управления вэбхуками.
Характеристики модуля
- Название - WebhookModule;
- Масштабируемость - модуль не имеет возможности масштабироваться, может работать только в монолите;
- Имеется ли возможность вызывать
forFeature
- нет, так как события и обработчики докидываются черезforRoot
-опции; - Имеет ли собственную базу данных - да, модуль идет вместе с миграциями и
Prisma
-схемой для генерации клиента для работы с базой данных; - Мультитенантность - да, так как сайт может работать по схеме B2B и каждый новый бизнес это новый externalTenantId;
- Софтделет - нет, софтделет будет подключчаться отдельно после завершения всего проекта и только там где реально нужен, в дальнейшем появится модуль для включения и выключения аудита базы данных и можно будет посмотрет историю изменений по идентификаторам записей;
- Другие хозяева записей в таблице кроме externalTenantId - нет, запись общая на весь externalTenantId.
Таблицы
- WebhookUser - таблица с пользователями модуля
- id:uuid - идентификатор;
- externalTenantId:uuid - идентификатор компании;
- externalUserId:uuid - идентификатор пользователя сервера авторизации;
- userRole:WebhookRole - роль пользователя;
- createdAt:Date - дата создания;
- updatedAt:Date - дата изменения.
- Webhook - таблица с вэбхуками
- id:uuid - идентификатор;
- eventName:string(512) - уникальное название события;
- endpoint:string(512) - удален ный сайт на который будет отправлен
POST
запрос; - enabled:boolean - активен ли вэбхук;
- headers?:JSON - заголовки которые будут переданы при оправке на удаленный сайт;
- requestTimeout?:number - нужно ли ждать ответа от удаленного сайта и максимально количество миллисекунд для ожидания (по умолчанию 5 секунд);
- externalTenantId:uuid - идентификатор компании;
- createdBy:uuid - кто создал запись;
- updatedBy:uuid - кто обновил запись;
- createdAt:Date - дата создания;
- updatedAt:Date - дата изменения.
- WebhookLog - таблица с историей вызовов вэбхука
- id:uuid - идентификатор;
- request:JSON - запрос на удаленный сайт;
- responseStatus:string(20) - статус ответа удаленного сайт;
- response?:JSON - ответ удаленного сайт;
- webhookStatus:WebhookStatus - статус;
- webhookId:uuid - идентификатор вэбхука;
- externalTenantId:uuid - идентификатор компании;
- createdAt:Date - дата создания;
- updatedAt:Date - дата изменения.
Словари
- WebhookRole - словарь ролей
- Admin - может читать/создавать/менять/удалять любые сущности любого externalTenantId;
- User - может читать/создавать/менять/удалять любые сущности только своего externalTenantId.
- WebhookStatus - словарь статусов запросов
- Pending - в очереди;
- Process - в обработке;
- Success - успешный вызов;
- Error - вызов вернул ошибку;
- Timeout - вызов не успел отработать.
Кто может работать с модулем
- Админ -
REST
-запрос у которого вRequest
есть свойство externalUserId=ИД_ЮЗЕРА, у которого роль Admin, имеет полный доступ ко всемCRUD
-операциям (WebhookController
- на урл/api/webhook
); - Пользователь -
REST
-запрос у которого вRequest
есть свойство externalUserId=ИД_ЮЗЕРА, у которого роль User, имеет полный доступ ко всемCRUD
-операциям, но только в рамках своего externalTenantId.
2. Создаем все необходимые пустые библиотеки
Так как у нас появятся дополнительные общие утил иты для работы с Prisma
, то нам необходимо создать для этого специальную библиотеку.
Команды
./node_modules/.bin/nx g @nestjs-mod/schematics:library prisma-tools --buildable --publishable --directory=libs/core/prisma-tools --simpleName=true --projectNameAndRootFormat=as-provided --strict=true
Вывод консоли
$ ./node_modules/.bin/nx g @nestjs-mod/schematics:library prisma-tools --buildable --publishable --directory=libs/core/prisma-tools --simpleName=true --projectNameAndRootFormat=as-provided --strict=true
NX Generating @nestjs-mod/schematics:library
CREATE libs/core/prisma-tools/tsconfig.json
CREATE libs/core/prisma-tools/src/index.ts
CREATE libs/core/prisma-tools/tsconfig.lib.json
CREATE libs/core/prisma-tools/README.md
CREATE libs/core/prisma-tools/package.json
CREATE libs/core/prisma-tools/project.json
CREATE libs/core/prisma-tools/.eslintrc.json
UPDATE package.json
CREATE libs/core/prisma-tools/jest.config.ts
CREATE libs/core/prisma-tools/tsconfig.spec.json
UPDATE tsconfig.base.json
CREATE libs/core/prisma-tools/src/lib/prisma-tools.configuration.ts
CREATE libs/core/prisma-tools/src/lib/prisma-tools.constants.ts
CREATE libs/core/prisma-tools/src/lib/prisma-tools.environments.ts
CREATE libs/core/prisma-tools/src/lib/prisma-tools.module.ts
Для того чтобы в рамках тестов не копировать схожий код из теста в тест, создадим библиотеку для тестовых утилит.
Команды
./node_modules/.bin/nx g @nestjs-mod/schematics:library testing --buildable --publishable --directory=libs/testing --simpleName=true --projectNameAndRootFormat=as-provided --strict=true
Вывод консоли
$ ./node_modules/.bin/nx g @nestjs-mod/schematics:library testing --buildable --publishable --directory=libs/testing --simpleName=true --projectNameAndRootFormat=as-provided --strict=true
NX Generating @nestjs-mod/schematics:library
CREATE libs/testing/tsconfig.json
CREATE libs/testing/src/index.ts
CREATE libs/testing/tsconfig.lib.json
CREATE libs/testing/README.md
CREATE libs/testing/package.json
CREATE libs/testing/project.json
CREATE libs/testing/.eslintrc.json
CREATE libs/testing/jest.config.ts
CREATE libs/testing/tsconfig.spec.json
UPDATE tsconfig.base.json
CREATE libs/testing/src/lib/testing.configuration.ts
CREATE libs/testing/src/lib/testing.constants.ts
CREATE libs/testing/src/lib/testing.environments.ts
CREATE libs/testing/src/lib/testing.module.ts
Кроме тестовых и Prisma
утилит у нас появятся еще и общие утилиты, для них тоже создадим свою либу.
Команды
./node_modules/.bin/nx g @nestjs-mod/schematics:library common --buildable --publishable --directory=libs/common --simpleName=true --projectNameAndRootFormat=as-provided --strict=true
Вывод консоли
$ ./node_modules/.bin/nx g @nestjs-mod/schematics:library common --buildable --publishable --directory=libs/common --simpleName=true --projectNameAndRootFormat=as-provided --strict=true
NX Generating @nestjs-mod/schematics:library
CREATE libs/common/tsconfig.json
CREATE libs/common/src/index.ts
CREATE libs/common/tsconfig.lib.json
CREATE libs/common/README.md
CREATE libs/common/package.json
CREATE libs/common/project.json
CREATE libs/common/.eslintrc.json
CREATE libs/common/jest.config.ts
CREATE libs/common/tsconfig.spec.json
UPDATE tsconfig.base.json
CREATE libs/common/src/lib/common.configuration.ts
CREATE libs/common/src/lib/common.constants.ts
CREATE libs/common/src/lib/common.environments.ts
CREATE libs/common/src/lib/common.module.ts
Теперь создаем бизнес модуль.
Команды
./node_modules/.bin/nx g @nestjs-mod/schematics:library webhook --buildable --publishable --directory=libs/feature/webhook --simpleName=true --projectNameAndRootFormat=as-provided --strict=true
Вывод консоли
$ ./node_modules/.bin/nx g @nestjs-mod/schematics:library webhook --buildable --publishable --directory=libs/feature/webhook --simpleName=true --projectNameAndRootFormat=as-provided --strict=true
NX Generating @nestjs-mod/schematics:library
CREATE libs/feature/webhook/tsconfig.json
CREATE libs/feature/webhook/src/index.ts
CREATE libs/feature/webhook/tsconfig.lib.json
CREATE libs/feature/webhook/README.md
CREATE libs/feature/webhook/package.json
CREATE libs/feature/webhook/project.json
CREATE libs/feature/webhook/.eslintrc.json
CREATE libs/feature/webhook/jest.config.ts
CREATE libs/feature/webhook/tsconfig.spec.json
UPDATE tsconfig.base.json
CREATE libs/feature/webhook/src/lib/webhook.configuration.ts
CREATE libs/feature/webhook/src/lib/webhook.constants.ts
CREATE libs/feature/webhook/src/lib/webhook.environments.ts
CREATE libs/feature/webhook/src/lib/webhook.module.ts
3. Добавляем NestJS-mod модуль для работы с миграциями и модуль для генерации Prisma-клиента
Так как для работы модуля @nestjs-mod/prisma
необходимо передать модуль с сгенерированным клиентом до нашей базы которого еще не существует, то мы передаем сам @nestjs-mod/prisma
, так как в ней имеется заглушка.
Добавляем новые модули в apps/server/src/main.ts
.
import { WEBHOOK_FEATURE, WEBHOOK_FOLDER } from '@nestjs-mod-fullstack/webhook';
// ...
bootstrapNestApplication({
modules: {
// ...
core: [
// ...
PrismaModule.forRoot({
staticConfiguration: {
schemaFile: join(rootFolder, WEBHOOK_FOLDER, 'src', 'prisma', PRISMA_SCHEMA_FILE),
featureName: WEBHOOK_FEATURE,
prismaModule: isInfrastructureMode() ? import(`@nestjs-mod/prisma`) : import(`@nestjs-mod/prisma`),
addMigrationScripts: false,
nxProjectJsonFile: join(rootFolder, WEBHOOK_FOLDER, PROJECT_JSON_FILE),
},
}),
],
// ...
infrastructure: [
// ...
DockerComposePostgreSQL.forFeatureAsync({
featureModuleName: WEBHOOK_FEATURE,
featureConfiguration: {
nxProjectJsonFile: join(rootFolder, WEBHOOK_FOLDER, PROJECT_JSON_FILE),
},
}),
Flyway.forRoot({
staticConfiguration: {
featureName: WEBHOOK_FEATURE,
migrationsFolder: join(rootFolder, WEBHOOK_FOLDER, 'src', 'migrations'),
configFile: join(rootFolder, FLYWAY_JS_CONFIG_FILE),
nxProjectJsonFile: join(rootFolder, WEBHOOK_FOLDER, PROJECT_JSON_FILE),
},
}),
],
},
});
Запускаем генерацию дополнительного кода по инфраструктуре и для призма клиентов.
Команды
npm run manual:prepare
Вывод консоли
$ npm run manual:prepare
> @nestjs-mod-fullstack/source@0.0.8 manual:prepare
> npm run generate && npm run docs:infrastructure && npm run test
> @nestjs-mod-fullstack/source@0.0.8 generate
> ./node_modules/.bin/nx run-many --exclude=@nestjs-mod-fullstack/source --all -t=generate --skip-nx-cache=true && npm run make-ts-list && npm run lint:fix
✔ nx run server:generate (12s)
————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
NX Successfully ran target generate for project server (12s)
> @nestjs-mod-fullstack/source@0.0.8 make-ts-list
> ./node_modules/.bin/rucken make-ts-list
> @nestjs-mod-fullstack/source@0.0.8 lint:fix
> npm run tsc:lint && ./node_modules/.bin/nx run-many --exclude=@nestjs-mod-fullstack/source --all -t=lint --fix
> @nestjs-mod-fullstack/source@0.0.8 tsc:lint
> ./node_modules/.bin/tsc --noEmit -p tsconfig.base.json
✔ nx run server-e2e:lint (1s)
✔ nx run app-angular-rest-sdk:lint (1s)
✔ nx run client:lint (1s)
✔ nx run server:lint (1s)
————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
NX Successfully ran target lint for 4 projects (2s)
With additional flags:
--fix=true
> @nestjs-mod-fullstack/source@0.0.8 docs:infrastructure
> export NESTJS_MODE=infrastructure && ./node_modules/.bin/nx run-many --exclude=@nestjs-mod-fullstack/source,client* --all -t=serve --parallel=false -- --watch=false --inspect=false
NX Running target serve for project server:
- server
With additional flags:
--watch=false
--inspect=false
—————————————————————————————————————————————————————————————————————————————— ——————————————————————————————————————————————————————————————————————————————————
> nx run server:serve:development --watch=false --inspect=false
chunk (runtime: main) main.js (main) 17 KiB [entry] [rendered]
webpack compiled successfully (f0ad59aa03def552)
[08:58:04.616] INFO (70001): Starting Nest application...
context: "NestFactory"
[08:58:04.617] INFO (70001): DefaultNestApp dependencies initialized
context: "InstanceLoader"
[08:58:04.617] INFO (70001): ProjectUtilsSettings dependencies initialized
context: "InstanceLoader"
[08:58:04.617] INFO (70001): DefaultNestApplicationInitializerSettings dependencies initialized
context: "InstanceLoader"
[08:58:04.617] INFO (70001): DefaultNestApplicationInitializerShared dependencies initialized
context: "InstanceLoader"
[08:58:04.617] INFO (70001): NestjsPinoLoggerModuleSettings dependencies initialized
context: "InstanceLoader"
[08:58:04.617] INFO (70001): NestjsPinoLoggerModuleShared dependencies initialized
context: "InstanceLoader"
[08:58:04.617] INFO (70001): TerminusHealthCheckModuleSettings dependencies initialized
context: "InstanceLoader"
[08:58:04.617] INFO (70001): DefaultNestApplicationListenerSettings dependencies initialized
context: "InstanceLoader"
[08:58:04.617] INFO (70001): DefaultNestApplicationListenerShared dependencies initialized
context: "InstanceLoader"
[08:58:04.617] INFO (70001): PrismaModuleSettings dependencies initialized
context: "InstanceLoader"
[08:58:04.617] INFO (70001): AppModuleSettings dependencies initialized
context: "InstanceLoader"
[08:58:04.617] INFO (70001): AppModuleShared dependencies initialized
context: "InstanceLoader"
[08:58:04.617] INFO (70001): PrismaModule dependencies initialized
context: "InstanceLoader"
[08:58:04.617] INFO (70001): InfrastructureMarkdownReportGeneratorSettings dependencies initialized
context: "InstanceLoader"
[08:58:04.617] INFO (70001): Pm2Settings dependencies initialized
context: "InstanceLoader"
[08:58:04.617] INFO (70001): Pm2Shared dependencies initialized
context: "InstanceLoader"
[08:58:04.617] INFO (70001): ProjectUtils dependencies initialized
context: "InstanceLoader"
[08:58:04.617] INFO (70001): DockerComposeSettings dependencies initialized
context: "InstanceLoader"
[08:58:04.617] INFO (70001): ProjectUtils dependencies initialized
context: "InstanceLoader"
[08:58:04.617] INFO (70001): DockerComposePostgreSQLSettings dependencies initialized
context: "InstanceLoader"
[08:58:04.617] INFO (70001): DockerCompose dependencies initialized
context: "InstanceLoader"
[08:58:04.617] INFO (70001): DockerComposePostgreSQL dependencies initialized
context: "InstanceLoader"
[08:58:04.617] INFO (70001): DockerComposePostgreSQLSettings dependencies initialized
context: "InstanceLoader"
[08:58:04.617] INFO (70001): DockerComposePostgreSQLShared dependencies initialized
context: "InstanceLoader"
[08:58:04.617] INFO (70001): FlywaySettings dependencies initialized
context: "InstanceLoader"
[08:58:04.617] INFO (70001): FlywayShared dependencies initialized
context: "InstanceLoader"
[08:58:04.617] INFO (70001): DockerComposePostgreSQL dependencies initialized
context: "InstanceLoader"
[08:58:04.617] INFO (70001): PrismaModuleSettings dependencies initialized
context: "InstanceLoader"
[08:58:04.617] INFO (70001): PrismaModuleShared dependencies initialized
context: "InstanceLoader"
[08:58:04.617] INFO (70001): ProjectUtils dependencies initialized
context: "InstanceLoader"
[08:58:04.617] INFO (70001): PrismaModuleSettings dependencies initialized
context: "InstanceLoader"
[08:58:04.617] INFO (70001): PrismaModuleShared dependencies initialized
context: "InstanceLoader"
[08:58:04.617] INFO (70001): ProjectUtils dependencies initialized
context: "InstanceLoader"
[08:58:04.617] INFO (70001): InfrastructureMarkdownReportGeneratorSettings dependencies initialized
context: "InstanceLoader"
[08:58:04.617] INFO (70001): ProjectUtils dependencies initialized
context: "InstanceLoader"
[08:58:04.617] INFO (70001): InfrastructureMarkdownReportStorage dependencies initialized
context: "InstanceLoader"
[08:58:04.617] INFO (70001): InfrastructureMarkdownReportStorageSettings dependencies initialized
context: "InstanceLoader"
[08:58:04.617] INFO (70001): ProjectUtils dependencies initialized
context: "InstanceLoader"
[08:58:04.617] INFO (70001): DockerCompose dependencies initialized
context: "InstanceLoader"
[08:58:04.617] INFO (70001): FlywaySettings dependencies initialized
context: "InstanceLoader"
[08:58:04.617] INFO (70001): FlywayShared dependencies initialized
context: "InstanceLoader"
[08:58:04.617] INFO (70001): ProjectUtils dependencies initialized
context: "InstanceLoader"
[08:58:04.617] INFO (70001): FlywaySettings dependencies initialized
context: "InstanceLoader"
[08:58:04.617] INFO (70001): FlywayShared dependencies initialized
context: "InstanceLoader"
[08:58:04.617] INFO (70001): ProjectUtils dependencies initialized
context: "InstanceLoader"
[08:58:04.617] INFO (70001): DefaultNestApplicationListenerSettings dependencies initialized
context: "InstanceLoader"
[08:58:04.617] INFO (70001): DefaultNestApplicationListenerShared dependencies initialized
context: "InstanceLoader"
[08:58:04.617] INFO (70001): DockerComposeShared dependencies initialized
context: "InstanceLoader"
[08:58:04.617] INFO (70001): InfrastructureMarkdownReportStorageShared dependencies initialized
context: "InstanceLoader"
[08:58:04.617] INFO (70001): ProjectUtils dependencies initialized
context: "InstanceLoader"
[08:58:04.617] INFO (70001): DefaultNestApplicationInitializer dependencies initialized
context: "InstanceLoader"
[08:58:04.617] INFO (70001): DefaultNestApplicationListener dependencies initialized
context: "InstanceLoader"
[08:58:04.617] INFO (70001): PrismaModule dependencies initialized
context: "InstanceLoader"
[08:58:04.617] INFO (70001): PrismaModule dependencies initialized
context: "InstanceLoader"
[08:58:04.617] INFO (70001): InfrastructureMarkdownReportGenerator dependencies initialized
context: "InstanceLoader"
[08:58:04.617] INFO (70001): DockerComposePostgreSQL dependencies initialized
context: "InstanceLoader"
[08:58:04.617] INFO (70001): Flyway dependencies initialized
context: "InstanceLoader"
[08:58:04.617] INFO (70001): Flyway dependencies initialized
context: "InstanceLoader"
[08:58:04.617] INFO (70001): DefaultNestApplicationListener dependencies initialized
context: "InstanceLoader"
[08:58:04.617] INFO (70001): NestjsPinoLoggerModule dependencies initialized
context: "InstanceLoader"
[08:58:04.617] INFO (70001): TerminusModule dependencies initialized
context: "InstanceLoader"
[08:58:04.617] INFO (70001): TerminusModule dependencies initialized
context: "InstanceLoader"
[08:58:04.617] INFO (70001): ServeStaticModule dependencies initialized
context: "InstanceLoader"
[08:58:04.617] INFO (70001): ProjectUtilsShared dependencies initialized
context: "InstanceLoader"
[08:58:04.617] INFO (70001): InfrastructureMarkdownReportGeneratorShared dependencies initialized
context: "InstanceLoader"
[08:58:04.617] INFO (70001): Pm2 dependencies initialized
context: "InstanceLoader"
[08:58:04.617] INFO (70001): DockerCompose dependencies initialized
context: "InstanceLoader"
[08:58:04.617] INFO (70001): DockerComposePostgreSQL dependencies initialized
context: "InstanceLoader"
[08:58:04.617] INFO (70001): PrismaModule dependencies initialized
context: "InstanceLoader"
[08:58:04.617] INFO (70001): PrismaModule dependencies initialized
context: "InstanceLoader"
[08:58:04.617] INFO (70001): InfrastructureMarkdownReportGeneratorShared dependencies initialized
context: "InstanceLoader"
[08:58:04.617] INFO (70001): Flyway dependencies initialized
context: "InstanceLoader"
[08:58:04.617] INFO (70001): Flyway dependencies initialized
context: "InstanceLoader"
[08:58:04.617] INFO (70001): InfrastructureMarkdownReportGenerator dependencies initialized
context: "InstanceLoader"
[08:58:04.617] INFO (70001): LoggerModule dependencies initialized
context: "InstanceLoader"
[08:58:04.617] INFO (70001): DockerComposePostgreSQLShared dependencies initialized
context: "InstanceLoader"
[08:58:04.617] INFO (70001): PrismaModuleShared dependencies initialized
context: "InstanceLoader"
[08:58:04.617] INFO (70001): TerminusHealthCheckModuleShared dependencies initialized
context: "InstanceLoader"
[08:58:04.617] INFO (70001): TerminusHealthCheckModule dependencies initialized
context: "InstanceLoader"
[08:58:04.617] INFO (70001): AppModule dependencies initialized
context: "InstanceLoader"
[08:58:04.637] INFO (70001): TerminusHealthCheckController {/api/health}:
context: "RoutesResolver"
[08:58:04.639] INFO (70001): Mapped {/api/health, GET} route
context: "RouterExplorer"
[08:58:04.639] INFO (70001): AppController {/api}:
context: "RoutesResolver"
[08:58:04.640] INFO (70001): Mapped {/api, GET} route
context: "RouterExplorer"
[08:58:04.640] INFO (70001): Mapped {/api/demo, POST} route
context: "RouterExplorer"
[08:58:04.640] INFO (70001): Mapped {/api/demo/:id, GET} route
context: "RouterExplorer"
[08:58:04.640] INFO (70001): Mapped {/api/demo/:id, DELETE} route
context: "RouterExplorer"
[08:58:04.641] INFO (70001): Mapped {/api/demo, GET} route
context: "RouterExplorer"
[08:58:04.642] INFO (70001): Connected to database!
context: "PrismaClient"
[08:58:04.687] DEBUG (70001):
0: "SERVER_ROOT_DATABASE_URL: Description='Connection string for PostgreSQL with root credentials (example: postgres://postgres:postgres_password@localhost:5432/postgres?schema=public, username must be \"postgres\")', Original Name='rootDatabaseUrl'"
1: "SERVER_PORT: Description='The port on which to run the server.', Default='3000', Original Name='port'"
2: "SERVER_HOSTNAME: Description='Hostname on which to listen for incoming packets.', Original Name='hostname'"
3: "SERVER_WEBHOOK_DATABASE_URL: Description='Connection string for PostgreSQL with module credentials (example: postgres://feat:feat_password@localhost:5432/feat?schema=public)', Original Name='databaseUrl'"
4: "SERVER_WEBHOOK_DATABASE_URL: Description='Connection string for PostgreSQL with module credentials (example: postgres://feat:feat_password@localhost:5432/feat?schema=public)', Original Name='databaseUrl'"
context: "All application environments"
[08:58:04.716] INFO (70001): Nest application successfully started
context: "NestApplication"
————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
NX Successfully ran target serve for project server
> @nestjs-mod-fullstack/source@0.0.8 test
> ./node_modules/.bin/nx run-many --exclude=@nestjs-mod-fullstack/source --all -t=test --skip-nx-cache=true --passWithNoTests --output-style=stream-without-prefixes
> nx run app-angular-rest-sdk:test --passWithNoTests
> nx run app-rest-sdk:test --passWithNoTests
> nx run webhook:test --passWithNoTests
NX Running target test for 8 projects
✔ nx run app-angular-rest-sdk:test (2s)
————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
NX Running target test for 8 projects
✔ nx run app-rest-sdk:test (2s)
————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
✔ nx run webhook:test (2s)
————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
✔ nx run prisma-tools:test (1s)
————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
✔ nx run testing:test (1s)
————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
✔ nx run common:test (2s)
——————————————————————————————————————————————————————————— —————————————————————————————————————————————————————————————————————————————————————————————————————
NX Running target test for 8 projects
With additional flags:
--passWithNoTests=true
→ Executing 2/2 remaining tasks in parallel...
✔ nx run client:test (6s)
————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
NX Running target test for 8 projects
With additional flags:
✔ nx run server:test (5s)
————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
NX Successfully ran target test for 8 projects (8s)
With additional flags:
--passWithNoTests=true
Теперь у нас сгенерировались все Prisma-клиенты и необходимо обновить импорт в файле apps/server/src/main.ts
.
import { WEBHOOK_FEATURE, WEBHOOK_FOLDER } from '@nestjs-mod-fullstack/webhook';
// ...
bootstrapNestApplication({
modules: {
// ...
core: [
// ...
PrismaModule.forRoot({
staticConfiguration: {
schemaFile: join(rootFolder, WEBHOOK_FOLDER, 'src', 'prisma', PRISMA_SCHEMA_FILE),
featureName: WEBHOOK_FEATURE,
prismaModule: isInfrastructureMode() ? import(`@nestjs-mod/prisma`) : import(`@nestjs-mod-fullstack/webhook`), // <-- update
addMigrationScripts: false,
nxProjectJsonFile: join(rootFolder, WEBHOOK_FOLDER, PROJECT_JSON_FILE),
},
}),
],
},
});
4. Добавляем миграции с необходимыми таблицами и словарями
Обычно я создаю файлы миграции руками по такому шаблону: V%Y%m%d%H%M__New Migration.sql
.
После запуска генерации дополнительного кода инфраструктуры в библиотеке появляются дополнительные команды для работы с миграциями, эти же команды можно запускать и из скриптов корневого package.json
.
Команды
# create migrations folder
mkdir ./libs/feature/webhook/src/migrations
npm run flyway:create:webhook
Вывод консоли
$ mkdir ./libs/feature/webhook/src/migrations
npm rumkdir ./libs/feature/webhook/src/migrations
$ npm run flyway:create:webhook
> @nestjs-mod-fullstack/source@0.0.8 flyway:create:webhook
> ./node_modules/.bin/nx run webhook:flyway-create-migration
> nx run webhook:flyway-create-migration
> echo 'select 1;' > ./libs/feature/webhook/src/migrations/V`date +%Y%m%d%H%M`__NewMigration.sql
——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— —————
NX Successfully ran target flyway-create-migration for project webhook (42ms)
NX Nx detected a flaky task
webhook:flyway-create-migration
Flaky tasks can disrupt your CI pipeline. Automatically retry them with Nx Cloud. Learn more at https://nx.dev/ci/features/flaky-tasks
Переименовываем новую миграцию с V202409250909__NewMigration.sql
в V202409250909__Init.sql
и описываем миграции для создания всех необходимых таблиц.
DO $$
BEGIN
CREATE TYPE "WebhookRole" AS enum(
'Admin',
'User'
);
EXCEPTION
WHEN duplicate_object THEN
NULL;
END
$$;
DO $$
BEGIN
CREATE TYPE "WebhookStatus" AS enum(
'Pending',
'Process',
'Success',
'Error',
'Timeout'
);
EXCEPTION
WHEN duplicate_object THEN
NULL;
END
$$;
CREATE TABLE IF NOT EXISTS "WebhookUser"(
"id" uuid NOT NULL DEFAULT uuid_generate_v4(),
"externalTenantId" uuid NOT NULL,
"externalUserId" uuid NOT NULL,
"userRole" "WebhookRole" NOT NULL,
"createdAt" timestamp(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" timestamp(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "PK_WEBHOOK_USER" PRIMARY KEY ("id")
);
CREATE UNIQUE INDEX IF NOT EXISTS "UQ_WEBHOOK_USER" ON "WebhookUser"("externalTenantId", "externalUserId");
CREATE INDEX IF NOT EXISTS "IDX_WEBHOOK_USER__EXTERNAL_TENANT_ID" ON "WebhookUser"("externalTenantId");
CREATE INDEX IF NOT EXISTS "IDX_WEBHOOK_USER__USER_ROLE" ON "WebhookUser"("externalTenantId", "userRole");
CREATE TABLE IF NOT EXISTS "Webhook"(
"id" uuid NOT NULL DEFAULT uuid_generate_v4(),
"eventName" varchar(512) NOT NULL,
"endpoint" varchar(512) NOT NULL,
"enabled" boolean NOT NULL,
"headers" jsonb,
"requestTimeout" int,
"externalTenantId" uuid NOT NULL,
"createdBy" uuid NOT NULL CONSTRAINT "FK_WEBHOOK__CREATED_BY" REFERENCES "WebhookUser",
"updatedBy" uuid NOT NULL CONSTRAINT "FK_WEBHOOK__UPDATED_BY" REFERENCES "WebhookUser",
"createdAt" timestamp(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" timestamp(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "PK_WEBHOOK" PRIMARY KEY ("id")
);
CREATE INDEX IF NOT EXISTS "IDX_WEBHOOK__EXTERNAL_TENANT_ID" ON "Webhook"("externalTenantId");
CREATE INDEX IF NOT EXISTS "IDX_WEBHOOK__ENABLED" ON "Webhook"("externalTenantId", "enabled");
CREATE INDEX IF NOT EXISTS "IDX_WEBHOOK__EVENT_NAME" ON "Webhook"("externalTenantId", "eventName");
CREATE TABLE IF NOT EXISTS "WebhookLog"(
"id" uuid NOT NULL DEFAULT uuid_generate_v4(),
"request" jsonb NOT NULL,
"responseStatus" varchar(20) NOT NULL,
"response" jsonb,
"webhookStatus" "WebhookStatus" NOT NULL,
"webhookId" uuid NOT NULL CONSTRAINT "FK_WEBHOOK__WEBHOOK_ID" REFERENCES "Webhook",
"externalTenantId" uuid NOT NULL,
"createdAt" timestamp(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" timestamp(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "PK_WEBHOOK_LOG" PRIMARY KEY ("id")
);
CREATE INDEX "IDX_WEBHOOK_LOG__EXTERNAL_TENANT_ID" ON "WebhookLog"("externalTenantId");
CREATE INDEX "IDX_WEBHOOK_LOG__WEBHOOK_ID" ON "WebhookLog"("externalTenantId", "webhookId");
CREATE INDEX "IDX_WEBHOOK_LOG__WEBHOOK_STATUS" ON "WebhookLog"("externalTenantId", "webhookStatus");
5. Добавляем новую переменную окружения во все ре жимы запуска проекта и параметры деплоя
Генератор кода инфраструктуры создал новую пустую переменную окружения для подключения к новой базе данных, необходимо ее заполнить.
Обновляем файл .env
и example.env
# ...
SERVER_WEBHOOK_DATABASE_URL=postgres://webhook:webhook_password@localhost:5432/webhook?schema=public
# ...
Обновляем файл .docker/docker-compose-full.env
# ...
SERVER_WEBHOOK_DATABASE_URL=postgres://webhook:webhook_password@nestjs-mod-fullstack-postgre-sql:5432/webhook?schema=public
# ...
Обновляем файл .docker/docker-compose-full.yml
# ...
services:
# ...
nestjs-mod-fullstack-postgre-sql-migrations:
# ...
environment:
# ...
SERVER_WEBHOOK_DATABASE_URL: '${SERVER_WEBHOOK_DATABASE_URL}'
nestjs-mod-fullstack-server:
# ...
environment:
# ...
SERVER_WEBHOOK_DATABASE_URL: '${SERVER_WEBHOOK_DATABASE_URL}'
Обновляем файл .github/workflows/docker-compose.workflows.yml
name: 'Docker Compose'
# ...
env:
# ...
jobs:
# ...
deploy:
environment: docker-compose-full
# ...
steps:
- name: Deploy
env:
# ...
SERVER_WEBHOOK_DATABASE_URL: ${{ secrets.SERVER_WEBHOOK_DATABASE_URL }}
Обновляем файл .kubernetes/templates/docker-compose-infra.yml
version: '3'
# ...
services:
# ...
nestjs-mod-fullstack-postgre-sql-migrations:
# ...
environment:
# ...
SERVER_WEBHOOK_DATABASE_URL: 'postgres://%SERVER_WEBHOOK_DATABASE_USERNAME%:%SERVER_WEBHOOK_DATABASE_PASSWORD%@nestjs-mod-fullstack-postgre-sql:5432/%SERVER_WEBHOOK_DATABASE_NAME%?schema=public'
Обновляем файл .kubernetes/templates/server/1.configmap.yaml
apiVersion: v1
# ...
data:
# ...
SERVER_WEBHOOK_DATABASE_URL: 'postgres://%SERVER_WEBHOOK_DATABASE_USERNAME%:%SERVER_WEBHOOK_DATABASE_PASSWORD%@10.0.1.1:5432/%SERVER_WEBHOOK_DATABASE_NAME%?schema=public'
Обновляем файл .github/workflows/kubernetes.yml
name: 'Kubernetes'
# ...
env:
# ...
jobs:
# ...
deploy:
environment: kubernetes
# ...
steps:
# ...
- name: Deploy infrastructure
# ...
env:
# ...
SERVER_WEBHOOK_DATABASE_NAME: ${{ secrets.SERVER_WEBHOOK_DATABASE_NAME }}
SERVER_WEBHOOK_DATABASE_PASSWORD: ${{ secrets.SERVER_WEBHOOK_DATABASE_PASSWORD }}
SERVER_WEBHOOK_DATABASE_USERNAME: ${{ secrets.SERVER_WEBHOOK_DATABASE_USERNAME }}
Обновляем файл .kubernetes/set-env.sh
#!/bin/bash
# ...
# server: webhook database
if [ -z "${SERVER_WEBHOOK_DATABASE_PASSWORD}" ]; then
export SERVER_WEBHOOK_DATABASE_PASSWORD=webhook_password
fi
if [ -z "${SERVER_WEBHOOK_DATABASE_USERNAME}" ]; then
export SERVER_WEBHOOK_DATABASE_USERNAME=${NAMESPACE}_webhook
fi
if [ -z "${SERVER_WEBHOOK_DATABASE_NAME}" ]; then
export SERVER_WEBHOOK_DATABASE_NAME=${NAMESPACE}_webhook
fi