diff --git a/.github/workflows/ci-v2.yml b/.github/workflows/ci-v2.yml index af71b178d9..33a6c94506 100644 --- a/.github/workflows/ci-v2.yml +++ b/.github/workflows/ci-v2.yml @@ -21,17 +21,22 @@ jobs: with: fetch-depth: 0 - # - name: set SHAs - # run: | - # # run code only on main branch - # TAG_VERSION=$(git describe --first-parent --match 'v*' --abbrev=0 --tags) + - name: Derive appropriate SHAs for base and head for `nx affected` commands + uses: nrwl/nx-set-shas@v4 + + - name: Ovverride Base to be last release + run: | + # run code only on main branch + TAG_VERSION=$(git describe --first-parent --match 'v*' --abbrev=0 --tags) + + echo "Found tag: $TAG_VERSION" + LAST_TAG_HASH=$(git rev-list -n 1 $TAG_VERSION) + echo "Found hash: $LAST_TAG_HASH" + + # Set NX_BASE to the last tag + echo "NX_BASE=$LAST_TAG_HASH" >> $GITHUB_ENV - # echo "Found tag: $TAG_VERSION" - # LAST_TAG_HASH=$(git rev-list -n 1 $TAG_VERSION) - # echo "Found hash: $LAST_TAG_HASH" - # # Set NX_BASE to the last tag - # echo "NX_BASE=$LAST_TAG_HASH" >> $GITHUB_ENV - uses: oven-sh/setup-bun@v2 with: diff --git a/.vscode/settings.json b/.vscode/settings.json index 1634ca6008..46211fab0a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,7 @@ { "githubPullRequests.ignoredPullRequestBranches": ["develop"], - "github-actions.workflows.pinned.workflows": [".github/workflows/main.yml"] + "github-actions.workflows.pinned.workflows": [ + ".github/workflows/main.yml" + ], + "exportall.config.folderListener": [] } diff --git a/apps/api/jest.config.ts b/apps/api/jest.config.ts index ab504c9522..e1085dc975 100644 --- a/apps/api/jest.config.ts +++ b/apps/api/jest.config.ts @@ -3,8 +3,6 @@ export default { // displayName:api, preset: '../../jest.preset.js', testEnvironment: 'node', - passWithNoTests: true, - testTimeout: 1000 * 60 * 10, transform: { '^.+\\.[tj]s$': ['ts-jest', { tsconfig: '/tsconfig.spec.json' }], }, diff --git a/apps/badman/jest.config.ts b/apps/badman/jest.config.ts index 6360f4cd81..c792140bc3 100644 --- a/apps/badman/jest.config.ts +++ b/apps/badman/jest.config.ts @@ -1,10 +1,8 @@ -/* eslint-disable */ + export default { // displayName: badman, preset: '../../jest.preset.js', setupFilesAfterEnv: ['/src/test-setup.ts'], - passWithNoTests: true, - testTimeout: 1000 * 60 * 10, coverageDirectory: '../../coverage/apps/badman', transform: { '^.+\\.(ts|mjs|js|html)$': [ diff --git a/apps/badman/src/app/app.module.ts b/apps/badman/src/app/app.module.ts index b3b5d12a56..423dc09332 100644 --- a/apps/badman/src/app/app.module.ts +++ b/apps/badman/src/app/app.module.ts @@ -103,6 +103,10 @@ const APP_ROUTES: Routes = [ path: 'jobs', loadChildren: () => import('@badman/frontend-jobs').then((m) => m.JobModule), }, + { + path: 'rules', + loadChildren: () => import('@badman/frontend-rules').then((m) => m.RuleModule), + }, { path: 'transfers', loadChildren: () => import('@badman/frontend-transfers').then((m) => m.TransferModule), diff --git a/apps/scripts/jest.config.ts b/apps/scripts/jest.config.ts index 6790ce6f9c..f4a9349dba 100644 --- a/apps/scripts/jest.config.ts +++ b/apps/scripts/jest.config.ts @@ -3,8 +3,6 @@ export default { // displayName: scripts, preset: '../../jest.preset.js', testEnvironment: 'node', - passWithNoTests: true, - testTimeout: 1000 * 60 * 10, transform: { '^.+\\.[tj]s$': ['ts-jest', { tsconfig: '/tsconfig.spec.json' }], }, diff --git a/apps/worker/belgium/flanders/games/jest.config.ts b/apps/worker/belgium/flanders/games/jest.config.ts index 22f60000de..cf305ed4e7 100644 --- a/apps/worker/belgium/flanders/games/jest.config.ts +++ b/apps/worker/belgium/flanders/games/jest.config.ts @@ -3,8 +3,6 @@ export default { // displayName: 'worker-belgium-flanders-games', preset: '../../../../../jest.preset.js', testEnvironment: 'node', - passWithNoTests: true, - testTimeout: 1000 * 60 * 10, transform: { '^.+\\.[tj]s$': ['ts-jest', { tsconfig: '/tsconfig.spec.json' }], }, diff --git a/apps/worker/belgium/flanders/places/jest.config.ts b/apps/worker/belgium/flanders/places/jest.config.ts index b27dfef993..4d09b7ee6e 100644 --- a/apps/worker/belgium/flanders/places/jest.config.ts +++ b/apps/worker/belgium/flanders/places/jest.config.ts @@ -3,8 +3,6 @@ export default { // displayName: 'worker-belgium-flanders-places', preset: '../../../../../jest.preset.js', testEnvironment: 'node', - passWithNoTests: true, - testTimeout: 1000 * 60 * 10, transform: { '^.+\\.[tj]s$': ['ts-jest', { tsconfig: '/tsconfig.spec.json' }], }, diff --git a/apps/worker/belgium/flanders/points/jest.config.ts b/apps/worker/belgium/flanders/points/jest.config.ts index 0de95af50b..37a9d0062b 100644 --- a/apps/worker/belgium/flanders/points/jest.config.ts +++ b/apps/worker/belgium/flanders/points/jest.config.ts @@ -3,8 +3,6 @@ export default { // displayName: 'worker-belgium-flanders-points', preset: '../../../../../jest.preset.js', testEnvironment: 'node', - passWithNoTests: true, - testTimeout: 1000 * 60 * 10, transform: { '^.+\\.[tj]s$': ['ts-jest', { tsconfig: '/tsconfig.spec.json' }], }, diff --git a/apps/worker/ranking/jest.config.ts b/apps/worker/ranking/jest.config.ts index 7a1fabe6da..056f0a9e7e 100644 --- a/apps/worker/ranking/jest.config.ts +++ b/apps/worker/ranking/jest.config.ts @@ -3,8 +3,6 @@ export default { // displayName: 'worker-ranking', preset: '../../../jest.preset.js', testEnvironment: 'node', - passWithNoTests: true, - testTimeout: 1000 * 60 * 10, transform: { '^.+\\.[tj]s$': ['ts-jest', { tsconfig: '/tsconfig.spec.json' }], }, diff --git a/apps/worker/sync/jest.config.ts b/apps/worker/sync/jest.config.ts index 3201d35f3d..b28aecc93f 100644 --- a/apps/worker/sync/jest.config.ts +++ b/apps/worker/sync/jest.config.ts @@ -3,8 +3,6 @@ export default { // displayName: 'worker-sync', preset: '../../../jest.preset.js', testEnvironment: 'node', - passWithNoTests: true, - testTimeout: 1000 * 60 * 10, transform: { '^.+\\.[tj]s$': ['ts-jest', { tsconfig: '/tsconfig.spec.json' }], }, diff --git a/database/migrations/20220531163325-empty.js b/database/migrations/20220531163325-empty.js index 8c59af9608..9553375a8c 100644 --- a/database/migrations/20220531163325-empty.js +++ b/database/migrations/20220531163325-empty.js @@ -6,6 +6,7 @@ module.exports = { up: async (queryInterface, sequelize) => { return queryInterface.sequelize.transaction(async (t) => { try { + // } catch (err) { console.error('We errored with', err?.message ?? err); t.rollback(); @@ -16,6 +17,7 @@ module.exports = { down: async (queryInterface) => { return queryInterface.sequelize.transaction(async (t) => { try { + // } catch (err) { console.error('We errored with', err); t.rollback(); diff --git a/database/migrations/20240722120540-configuring validation rules form db.js b/database/migrations/20240722120540-configuring validation rules form db.js new file mode 100644 index 0000000000..5aaef2c440 --- /dev/null +++ b/database/migrations/20240722120540-configuring validation rules form db.js @@ -0,0 +1,100 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +'use strict'; + +/** @type {import('sequelize-cli').Migration} */ +module.exports = { + up: async (queryInterface, Sequelize) => { + return queryInterface.sequelize.transaction(async (t) => { + try { + await queryInterface.bulkInsert( + { + tableName: 'Claims', + schema: 'security', + }, + [ + { + name: 'change:rules', + description: 'Allow chaning ruless', + category: 'rules', + type: 'global', + createdAt: new Date(), + updatedAt: new Date(), + }, + ], + { transaction: t }, + ); + + // create Rule table + await queryInterface.createTable( + { + tableName: 'Rules', + schema: 'system', + }, + { + id: { + type: Sequelize.UUID, + defaultValue: Sequelize.UUIDV4, + primaryKey: true, + }, + name: { + type: Sequelize.STRING, + allowNull: false, + }, + group: { + type: Sequelize.STRING, + allowNull: false, + }, + description: { + type: Sequelize.STRING, + allowNull: true, + }, + meta: { + type: Sequelize.JSON, + allowNull: true, + }, + activated: { + type: Sequelize.BOOLEAN, + allowNull: false, + }, + createdAt: { + type: Sequelize.DATE, + allowNull: false, + }, + updatedAt: { + type: Sequelize.DATE, + allowNull: false, + }, + }, + { transaction: t }, + ); + } catch (err) { + console.error('We errored with', err?.message ?? err); + t.rollback(); + } + }); + }, + + down: async (queryInterface) => { + return queryInterface.sequelize.transaction(async (t) => { + try { + // remove enlist-any-event:team claim + await queryInterface.sequelize.query( + `DELETE FROM "security"."Claims" WHERE name = 'change:rules'`, + { transaction: t }, + ); + + // drop Rule table + await queryInterface.dropTable( + { + tableName: 'Rules', + schema: 'system', + }, + { transaction: t }, + ); + } catch (err) { + console.error('We errored with', err); + t.rollback(); + } + }); + }, +}; diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 65d20cb009..7e0e25cafe 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -29,7 +29,7 @@ services: environment: POSTGRES_PASSWORD: ${DB_PASSWORD} POSTGRES_USER: ${DB_USER} - POSTGRES_DB: ${DB_DATABASE} + POSTGRES_DB: postgres POSTGRES_MAX_CONNECTIONS: 500 ports: - '0.0.0.0:5432:5432' @@ -55,33 +55,33 @@ services: - api restart: unless-stopped - # # Tools - # pgadmin: - # container_name: pgadmin - # image: dpage/pgadmin4 - # environment: - # PGADMIN_DEFAULT_EMAIL: ${PGADMIN_DEFAULT_EMAIL:-pgadmin4@pgadmin.org} - # PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_DEFAULT_PASSWORD:-admin} - # GUNICORN_ACCESS_LOGFILE: '/dev/null' - # PGADMIN_CONFIG_SERVER_MODE: 'False' - # POSTGRES_USER: ${DB_USER} - # POSTGRES_PASSWORD: ${DB_PASSWORD} - # PGADMIN_CONFIG_UPGRADE_CHECK_ENABLED: 'False' - # volumes: - # - db-admin:/var/lib/pgadmin - # - ./database/backup:/home/backup - # - ./database/scripts:/home/scripts - # ports: - # - '${PGADMIN_PORT:-5050}:80' - # links: - # - postgres:postgres - # depends_on: - # - postgres - # networks: - # - postgres - # restart: unless-stopped - # logging: - # driver: 'none' + # Tools + pgadmin: + container_name: pgadmin + image: dpage/pgadmin4:8.9 + environment: + PGADMIN_DEFAULT_EMAIL: ${PGADMIN_DEFAULT_EMAIL:-pgadmin4@pgadmin.org} + PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_DEFAULT_PASSWORD:-admin} + GUNICORN_ACCESS_LOGFILE: '/dev/null' + PGADMIN_CONFIG_SERVER_MODE: 'False' + POSTGRES_USER: ${DB_USER} + POSTGRES_PASSWORD: ${DB_PASSWORD} + PGADMIN_CONFIG_UPGRADE_CHECK_ENABLED: 'False' + volumes: + - db-admin:/var/lib/pgadmin + - ./database/backup:/home/backup + - ./database/scripts:/home/scripts + ports: + - '${PGADMIN_PORT:-5050}:80' + links: + - postgres:postgres + depends_on: + - postgres + networks: + - postgres + restart: unless-stopped + logging: + driver: 'none' networks: api: diff --git a/libs/backend/authorization/jest.config.ts b/libs/backend/authorization/jest.config.ts index 114cbaf457..77fbfc0a9f 100644 --- a/libs/backend/authorization/jest.config.ts +++ b/libs/backend/authorization/jest.config.ts @@ -3,8 +3,6 @@ export default { // displayName: 'backend-authorization', preset: '../../../jest.preset.js', testEnvironment: 'node', - passWithNoTests: true, - testTimeout: 1000 * 60 * 10, transform: { '^.+\\.[tj]s$': ['ts-jest', { tsconfig: '/tsconfig.spec.json' }], }, diff --git a/libs/backend/belgium/flanders/games/jest.config.ts b/libs/backend/belgium/flanders/games/jest.config.ts index a795655409..edac50bbe4 100644 --- a/libs/backend/belgium/flanders/games/jest.config.ts +++ b/libs/backend/belgium/flanders/games/jest.config.ts @@ -3,8 +3,6 @@ export default { // displayName: 'belgium-flanders-game', preset: '../../../../../jest.preset.js', testEnvironment: 'node', - passWithNoTests: true, - testTimeout: 1000 * 60 * 10, transform: { '^.+\\.[tj]s$': ['ts-jest', { tsconfig: '/tsconfig.spec.json' }], }, diff --git a/libs/backend/belgium/flanders/places/jest.config.ts b/libs/backend/belgium/flanders/places/jest.config.ts index a0b5acd80e..b91f62d748 100644 --- a/libs/backend/belgium/flanders/places/jest.config.ts +++ b/libs/backend/belgium/flanders/places/jest.config.ts @@ -3,8 +3,6 @@ export default { // displayName: 'belgium-flanders-place', preset: '../../../../../jest.preset.js', testEnvironment: 'node', - passWithNoTests: true, - testTimeout: 1000 * 60 * 10, transform: { '^.+\\.[tj]s$': ['ts-jest', { tsconfig: '/tsconfig.spec.json' }], }, diff --git a/libs/backend/belgium/flanders/points/jest.config.ts b/libs/backend/belgium/flanders/points/jest.config.ts index e3f3a4751a..75bda49c98 100644 --- a/libs/backend/belgium/flanders/points/jest.config.ts +++ b/libs/backend/belgium/flanders/points/jest.config.ts @@ -3,8 +3,6 @@ export default { // displayName: 'belgium-flanders-point', preset: '../../../../../jest.preset.js', testEnvironment: 'node', - passWithNoTests: true, - testTimeout: 1000 * 60 * 10, transform: { '^.+\\.[tj]s$': ['ts-jest', { tsconfig: '/tsconfig.spec.json' }], }, diff --git a/libs/backend/cache/jest.config.ts b/libs/backend/cache/jest.config.ts index deee014a18..296cb4cd52 100644 --- a/libs/backend/cache/jest.config.ts +++ b/libs/backend/cache/jest.config.ts @@ -3,8 +3,6 @@ export default { // displayName: 'backend-cache', preset: '../../../jest.preset.js', testEnvironment: 'node', - passWithNoTests: true, - testTimeout: 1000 * 60 * 10, transform: { '^.+\\.[tj]s$': ['ts-jest', { tsconfig: '/tsconfig.spec.json' }], }, diff --git a/libs/backend/cluster/jest.config.ts b/libs/backend/cluster/jest.config.ts index 21efd36e0a..2869a52f14 100644 --- a/libs/backend/cluster/jest.config.ts +++ b/libs/backend/cluster/jest.config.ts @@ -3,8 +3,6 @@ export default { // displayName: 'backend-cluster', preset: '../../../jest.preset.js', testEnvironment: 'node', - passWithNoTests: true, - testTimeout: 1000 * 60 * 10, transform: { '^.+\\.[tj]s$': ['ts-jest', { tsconfig: '/tsconfig.spec.json' }], }, diff --git a/libs/backend/competition/assembly/jest.config.ts b/libs/backend/competition/assembly/jest.config.ts index 0d69f020e0..bc9b1c24c4 100644 --- a/libs/backend/competition/assembly/jest.config.ts +++ b/libs/backend/competition/assembly/jest.config.ts @@ -3,8 +3,6 @@ export default { // displayName: 'backend-assembly', preset: '../../../../jest.preset.js', testEnvironment: 'node', - passWithNoTests: true, - testTimeout: 1000 * 60 * 10, transform: { '^.+\\.[tj]s$': ['ts-jest', { tsconfig: '/tsconfig.spec.json' }], }, diff --git a/libs/backend/competition/assembly/package.json b/libs/backend/competition/assembly/package.json index b663810ae7..600390bf71 100644 --- a/libs/backend/competition/assembly/package.json +++ b/libs/backend/competition/assembly/package.json @@ -16,7 +16,8 @@ "graphql-type-json": "^0.3.2", "moment": "^2.30.1", "sequelize": "^6.37.3", - "tslib": "^2.6.2" + "tslib": "^2.6.2", + "@badman/backend-validation": "6.161.2" }, "type": "commonjs", "main": "./src/index.js", diff --git a/libs/backend/competition/assembly/src/assembly.module.ts b/libs/backend/competition/assembly/src/assembly.module.ts index 61aa1f3db9..54ae6e7ce3 100644 --- a/libs/backend/competition/assembly/src/assembly.module.ts +++ b/libs/backend/competition/assembly/src/assembly.module.ts @@ -1,11 +1,11 @@ import { CompileModule } from '@badman/backend-compile'; import { DatabaseModule } from '@badman/backend-database'; +import { ConfigType } from '@badman/utils'; import { Module } from '@nestjs/common'; import { ConfigModule, ConfigService } from '@nestjs/config'; import { join } from 'path'; import { AssemblyController } from './controllers'; import { AssemblyExportService, AssemblyValidationService } from './services'; -import { ConfigType } from '@badman/utils'; @Module({ imports: [ diff --git a/libs/backend/competition/assembly/src/controllers/pdf.controller.ts b/libs/backend/competition/assembly/src/controllers/pdf.controller.ts index deecff464c..12b639fda3 100644 --- a/libs/backend/competition/assembly/src/controllers/pdf.controller.ts +++ b/libs/backend/competition/assembly/src/controllers/pdf.controller.ts @@ -58,7 +58,7 @@ export class AssemblyController { @User() user: Player, ) { // compile the template that returns a buffer of the pdf - const pdf$ = await this.getTeamAssemblyPdf(req.body as inputBody); + const pdf$ = await this.getTeamAssemblyPdf(req.body as inputBody, user); if (!pdf$) { throw new Error('Pdf could not be generated'); @@ -81,27 +81,11 @@ export class AssemblyController { return new StreamableFile(pdf); } - private async getTeamAssemblyPdf(input: inputBody) { - const data = await this.assemblyService.getValidationData( - input.teamId, - input.encounterId, - - input.systemId, - input.single1, - input.single2, - input.single3, - input.single4, - input.double1, - input.double2, - input.double3, - input.double4, - input.subtitudes, - ); - - const validation = await this.assemblyService.validate( - data, - AssemblyValidationService.defaultValidators(), - ); + private async getTeamAssemblyPdf(input: inputBody, user: Player) { + const data = await this.assemblyService.fetchData(input); + const validation = await this.assemblyService.validate(input, { + playerId: user.id, + }); let homeTeam: Team; let awayTeam: Team | null; @@ -138,7 +122,9 @@ export class AssemblyController { const date = moment(data.encounter?.date).tz('Europe/Brussels').format('DD-MM-YYYY HH:mm'); - this.logger.debug(`Generating assembly for ${homeTeam.name} vs ${awayTeam?.name || 'empty'} on ${date}`); + this.logger.debug( + `Generating assembly for ${homeTeam.name} vs ${awayTeam?.name || 'empty'} on ${date}`, + ); const indexed: string[] = []; const based: string[] = []; diff --git a/libs/backend/competition/assembly/src/models/assembly.model.ts b/libs/backend/competition/assembly/src/models/assembly.model.ts index 162fa4eaf7..ebbe2fce82 100644 --- a/libs/backend/competition/assembly/src/models/assembly.model.ts +++ b/libs/backend/competition/assembly/src/models/assembly.model.ts @@ -68,7 +68,7 @@ export class AssemblyOutput { warnings?: AssemblyValidationError[]; @Field(() => Boolean, { nullable: true }) - valid?: boolean; + valid!: boolean; @Field(() => Int, { nullable: true }) baseTeamIndex?: number; diff --git a/libs/backend/competition/assembly/src/services/validate/assembly.service.spec.ts b/libs/backend/competition/assembly/src/services/validate/assembly.service.spec.ts index b8952e1175..04fcf943aa 100644 --- a/libs/backend/competition/assembly/src/services/validate/assembly.service.spec.ts +++ b/libs/backend/competition/assembly/src/services/validate/assembly.service.spec.ts @@ -69,12 +69,14 @@ describe('AssemblyValidationService', () => { ], }).compile(); - service = module.get(AssemblyValidationService); // Setup db const sequelize = module.get(Sequelize); await sequelize.sync({ force: true }); + service = module.get(AssemblyValidationService); + await service.onApplicationBootstrap(); + const group = SystemGroupBuilder.Create(); system = await SystemBuilder.Create(RankingSystems.BVL, 12, 75, 50) .AsPrimary() @@ -97,7 +99,7 @@ describe('AssemblyValidationService', () => { .WithSubEvent(subEventBuilder.WithDraw(drawBuilder.WithEnouncter(encounterBuilder))) .Build(); - draw = await drawBuilder.Build(); + draw = await drawBuilder.Build(); subEvent = await subEventBuilder.Build(); encounter = await encounterBuilder.Build(); }, 50000); @@ -227,28 +229,32 @@ describe('AssemblyValidationService', () => { }); it('should be a valid assembly', async () => { - const validation = await service.fetchAndValidate( - { - systemId: system.id, - teamId: team?.id, - encounterId: encounter.id, - single1: player666.id, - single2: player777.id, - single3: player888.id, - single4: player999.id, - double1: [player666.id, player777.id], - double2: [player666.id, player888.id], - double3: [player777.id, player999.id], - double4: [player888.id, player999.id], - }, - AssemblyValidationService.defaultValidators(), - ); + const validation = await service.validate({ + systemId: system.id, + teamId: team?.id, + encounterId: encounter.id, + single1: player666.id, + single2: player777.id, + single3: player888.id, + single4: player999.id, + double1: [player666.id, player777.id], + double2: [player666.id, player888.id], + double3: [player777.id, player999.id], + double4: [player888.id, player999.id], + }); expect(validation).toBeDefined(); expect(validation.valid).toBeTruthy(); }); describe('Rule [PlayerOrderRule]', () => { + beforeEach(async () => { + await service.clearRules(); + await service.registerRule(PlayerOrderRule, PlayerOrderRule.description, { + activated: true, + }); + }); + describe('valid', () => { const valid = [ [1, 2], @@ -257,16 +263,13 @@ describe('AssemblyValidationService', () => { ]; test.each(valid)('Single %p is better then Single %p', async (p1, p2) => { - const validation = await service.fetchAndValidate( - { - systemId: system.id, - teamId: team?.id, - encounterId: encounter.id, - [`single${p2}`]: player888.id, - [`single${p1}`]: player777.id, - }, - [new PlayerOrderRule()], - ); + const validation = await service.validate({ + systemId: system.id, + teamId: team?.id, + encounterId: encounter.id, + [`single${p2}`]: player888.id, + [`single${p1}`]: player777.id, + }); expect(validation).toBeDefined(); @@ -277,16 +280,13 @@ describe('AssemblyValidationService', () => { }); test.each(valid)('Double %p is better then Double %p', async (p1, p2) => { - const validation = await service.fetchAndValidate( - { - systemId: system.id, - teamId: team?.id, - encounterId: encounter.id, - [`double${p2}`]: [player777.id, player888.id], - [`double${p1}`]: [player666.id, player888.id], - }, - [new PlayerOrderRule()], - ); + const validation = await service.validate({ + systemId: system.id, + teamId: team?.id, + encounterId: encounter.id, + [`double${p2}`]: [player777.id, player888.id], + [`double${p1}`]: [player666.id, player888.id], + }); expect(validation).toBeDefined(); @@ -297,16 +297,13 @@ describe('AssemblyValidationService', () => { }); test.each(valid)('Double %p is not better then Double %p by level', async (p1, p2) => { - const validation = await service.fetchAndValidate( - { - systemId: system.id, - teamId: team?.id, - encounterId: encounter.id, - [`double${p2}`]: [player777.id, player888.id], - [`double${p1}`]: [player666.id, player999.id], - }, - [new PlayerOrderRule()], - ); + const validation = await service.validate({ + systemId: system.id, + teamId: team?.id, + encounterId: encounter.id, + [`double${p2}`]: [player777.id, player888.id], + [`double${p1}`]: [player666.id, player999.id], + }); expect(validation).toBeDefined(); @@ -325,16 +322,13 @@ describe('AssemblyValidationService', () => { ]; test.each(invalid)('Single %p is not better then Single %p', async (p1, p2) => { - const validation = await service.fetchAndValidate( - { - systemId: system.id, - teamId: team?.id, - encounterId: encounter.id, - [`single${p1}`]: player888.id, - [`single${p2}`]: player777.id, - }, - [new PlayerOrderRule()], - ); + const validation = await service.validate({ + systemId: system.id, + teamId: team?.id, + encounterId: encounter.id, + [`single${p1}`]: player888.id, + [`single${p2}`]: player777.id, + }); expect(validation).toBeDefined(); expect(validation.valid).toBeFalsy(); @@ -354,16 +348,13 @@ describe('AssemblyValidationService', () => { }); test.each(invalid)('Double %p is better then Double %p', async (p1, p2) => { - const validation = await service.fetchAndValidate( - { - systemId: system.id, - teamId: team?.id, - encounterId: encounter.id, - [`double${p1}`]: [player777.id, player888.id], - [`double${p2}`]: [player666.id, player888.id], - }, - [new PlayerOrderRule()], - ); + const validation = await service.validate({ + systemId: system.id, + teamId: team?.id, + encounterId: encounter.id, + [`double${p1}`]: [player777.id, player888.id], + [`double${p2}`]: [player666.id, player888.id], + }); expect(validation).toBeDefined(); expect(validation.valid).toBeFalsy(); @@ -389,16 +380,13 @@ describe('AssemblyValidationService', () => { }); test.each(invalid)('Double %p is not better then Double %p by level', async (p1, p2) => { - const validation = await service.fetchAndValidate( - { - systemId: system.id, - teamId: team?.id, - encounterId: encounter.id, - [`double${p1}`]: [player777.id, player888.id], - [`double${p2}`]: [player666.id, player999.id], - }, - [new PlayerOrderRule()], - ); + const validation = await service.validate({ + systemId: system.id, + teamId: team?.id, + encounterId: encounter.id, + [`double${p1}`]: [player777.id, player888.id], + [`double${p2}`]: [player666.id, player999.id], + }); expect(validation).toBeDefined(); expect(validation.valid).toBeFalsy(); @@ -425,20 +413,23 @@ describe('AssemblyValidationService', () => { }); describe('Rule [TeamSubeventIndexRule]', () => { + beforeEach(async () => { + await service.clearRules(); + await service.registerRule(TeamSubeventIndexRule, TeamSubeventIndexRule.description, { + activated: true, + }); + }); describe('valid', () => { it('should be valid', async () => { - const validation = await service.fetchAndValidate( - { - systemId: system.id, - teamId: team?.id, - encounterId: encounter.id, - single1: player666.id, - single2: player777.id, - single3: player888.id, - single4: player999.id, - }, - [new TeamSubeventIndexRule()], - ); + const validation = await service.validate({ + systemId: system.id, + teamId: team?.id, + encounterId: encounter.id, + single1: player666.id, + single2: player777.id, + single3: player888.id, + single4: player999.id, + }); expect(validation).toBeDefined(); @@ -451,18 +442,15 @@ describe('AssemblyValidationService', () => { describe('invalid', () => { it('should be invalid if team index lower then the base', async () => { - const validation = await service.fetchAndValidate( - { - systemId: system.id, - teamId: team?.id, - encounterId: encounter.id, - single1: player111.id, - single2: player777.id, - single3: player888.id, - single4: player999.id, - }, - [new TeamSubeventIndexRule()], - ); + const validation = await service.validate({ + systemId: system.id, + teamId: team?.id, + encounterId: encounter.id, + single1: player111.id, + single2: player777.id, + single3: player888.id, + single4: player999.id, + }); expect(validation).toBeDefined(); expect(validation.valid).toBeFalsy(); @@ -480,21 +468,25 @@ describe('AssemblyValidationService', () => { }); describe('Rule [TeamSubsIndexRule]', () => { + beforeEach(async () => { + await service.clearRules(); + await service.registerRule(TeamSubsIndexRule, TeamSubsIndexRule.description, { + activated: true, + }); + }); + describe('warning', () => { it.skip('should give warning if the sub is better than one of the players', async () => { - const validation = await service.fetchAndValidate( - { - systemId: system.id, - teamId: team?.id, - encounterId: encounter.id, - single1: player666.id, - single2: player777.id, - single3: player888.id, - single4: player999.id, - subtitudes: [player888.id], - }, - [new TeamSubsIndexRule()], - ); + const validation = await service.validate({ + systemId: system.id, + teamId: team?.id, + encounterId: encounter.id, + single1: player666.id, + single2: player777.id, + single3: player888.id, + single4: player999.id, + subtitudes: [player888.id], + }); expect(validation).toBeDefined(); @@ -509,20 +501,24 @@ describe('AssemblyValidationService', () => { }); describe('Rule [PlayerCompStatusRule]', () => { + beforeEach(async () => { + await service.clearRules(); + await service.registerRule(PlayerCompStatusRule, PlayerCompStatusRule.description, { + activated: true, + }); + }); + describe('valid', () => { it('should be valid', async () => { - const validation = await service.fetchAndValidate( - { - systemId: system.id, - teamId: team?.id, - encounterId: encounter.id, - single1: player666.id, - single2: player777.id, - single3: player888.id, - single4: player999.id, - }, - [new PlayerCompStatusRule()], - ); + const validation = await service.validate({ + systemId: system.id, + teamId: team?.id, + encounterId: encounter.id, + single1: player666.id, + single2: player777.id, + single3: player888.id, + single4: player999.id, + }); expect(validation).toBeDefined(); @@ -535,18 +531,15 @@ describe('AssemblyValidationService', () => { describe('invalid', () => { it("should be invalid if the player doesn't have competition status on true ", async () => { - const validation = await service.fetchAndValidate( - { - systemId: system.id, - teamId: team?.id, - encounterId: encounter.id, - single1: player555.id, - single2: player777.id, - single3: player888.id, - single4: player999.id, - }, - [new PlayerCompStatusRule()], - ); + const validation = await service.validate({ + systemId: system.id, + teamId: team?.id, + encounterId: encounter.id, + single1: player555.id, + single2: player777.id, + single3: player888.id, + single4: player999.id, + }); expect(validation).toBeDefined(); expect(validation.valid).toBeFalsy(); @@ -560,18 +553,15 @@ describe('AssemblyValidationService', () => { }); it("should be invalid if the players doesn't have competition status on true ", async () => { - const validation = await service.fetchAndValidate( - { - systemId: system.id, - teamId: team?.id, - encounterId: encounter.id, - single1: player111.id, - single2: player555.id, - single3: player888.id, - single4: player999.id, - }, - [new PlayerCompStatusRule()], - ); + const validation = await service.validate({ + systemId: system.id, + teamId: team?.id, + encounterId: encounter.id, + single1: player111.id, + single2: player555.id, + single3: player888.id, + single4: player999.id, + }); expect(validation).toBeDefined(); expect(validation.valid).toBeFalsy(); @@ -587,20 +577,24 @@ describe('AssemblyValidationService', () => { }); describe('Rule [PlayerMaxGamesRule]', () => { + beforeEach(async () => { + await service.clearRules(); + await service.registerRule(PlayerMaxGamesRule, PlayerMaxGamesRule.description, { + activated: true, + }); + }); + describe('valid', () => { it('should be valid single', async () => { - const validation = await service.fetchAndValidate( - { - systemId: system.id, - teamId: team?.id, - encounterId: encounter.id, - single1: player666.id, - single2: player777.id, - single3: player888.id, - single4: player999.id, - }, - [new PlayerMaxGamesRule()], - ); + const validation = await service.validate({ + systemId: system.id, + teamId: team?.id, + encounterId: encounter.id, + single1: player666.id, + single2: player777.id, + single3: player888.id, + single4: player999.id, + }); expect(validation).toBeDefined(); expect(validation.valid).toBeTruthy(); @@ -612,18 +606,15 @@ describe('AssemblyValidationService', () => { }); it('should be valid double', async () => { - const validation = await service.fetchAndValidate( - { - systemId: system.id, - teamId: team?.id, - encounterId: encounter.id, - double1: [player666.id, player777.id], - double2: [player666.id, player888.id], - double3: [player777.id, player999.id], - double4: [player888.id, player999.id], - }, - [new PlayerMaxGamesRule()], - ); + const validation = await service.validate({ + systemId: system.id, + teamId: team?.id, + encounterId: encounter.id, + double1: [player666.id, player777.id], + double2: [player666.id, player888.id], + double3: [player777.id, player999.id], + double4: [player888.id, player999.id], + }); expect(validation).toBeDefined(); expect(validation.valid).toBeTruthy(); @@ -637,18 +628,15 @@ describe('AssemblyValidationService', () => { describe('invalid', () => { it('should be invalid if the player has more then 1 single game ', async () => { - const validation = await service.fetchAndValidate( - { - systemId: system.id, - teamId: team?.id, - encounterId: encounter.id, - single1: player555.id, - single2: player555.id, - single3: player888.id, - single4: player999.id, - }, - [new PlayerMaxGamesRule()], - ); + const validation = await service.validate({ + systemId: system.id, + teamId: team?.id, + encounterId: encounter.id, + single1: player555.id, + single2: player555.id, + single3: player888.id, + single4: player999.id, + }); expect(validation).toBeDefined(); expect(validation.valid).toBeFalsy(); @@ -662,18 +650,15 @@ describe('AssemblyValidationService', () => { }); it('should be invalid if the player has more then 2 doubles', async () => { - const validation = await service.fetchAndValidate( - { - systemId: system.id, - teamId: team?.id, - encounterId: encounter.id, - double1: [player666.id, player777.id], - double2: [player666.id, player888.id], - double3: [player666.id, player999.id], - double4: [player888.id, player999.id], - }, - [new PlayerMaxGamesRule()], - ); + const validation = await service.validate({ + systemId: system.id, + teamId: team?.id, + encounterId: encounter.id, + double1: [player666.id, player777.id], + double2: [player666.id, player888.id], + double3: [player666.id, player999.id], + double4: [player888.id, player999.id], + }); expect(validation).toBeDefined(); expect(validation.valid).toBeFalsy(); @@ -689,24 +674,28 @@ describe('AssemblyValidationService', () => { }); describe('Rule [PlayerGenderRule]', () => { + beforeEach(async () => { + await service.clearRules(); + await service.registerRule(PlayerGenderRule, PlayerGenderRule.description, { + activated: true, + }); + }); + describe('valid', () => { it('should be valid single', async () => { - const validation = await service.fetchAndValidate( - { - systemId: system.id, - teamId: team?.id, - encounterId: encounter.id, - double1: [player666.id, player777.id], - double2: [player666.id, player888.id], - double3: [player777.id, player999.id], - double4: [player888.id, player999.id], - single1: player666.id, - single2: player777.id, - single3: player888.id, - single4: player999.id, - }, - [new PlayerGenderRule()], - ); + const validation = await service.validate({ + systemId: system.id, + teamId: team?.id, + encounterId: encounter.id, + double1: [player666.id, player777.id], + double2: [player666.id, player888.id], + double3: [player777.id, player999.id], + double4: [player888.id, player999.id], + single1: player666.id, + single2: player777.id, + single3: player888.id, + single4: player999.id, + }); expect(validation).toBeDefined(); expect(validation.valid).toBeTruthy(); @@ -725,15 +714,12 @@ describe('AssemblyValidationService', () => { const games = [[2]]; test.each(games)('should be invalid single if the player the wrong gender', async (g) => { - const validation = await service.fetchAndValidate( - { - systemId: system.id, - teamId: team?.id, - encounterId: encounter.id, - [`single${g}`]: player555.id, - }, - [new PlayerGenderRule()], - ); + const validation = await service.validate({ + systemId: system.id, + teamId: team?.id, + encounterId: encounter.id, + [`single${g}`]: player555.id, + }); expect(validation).toBeDefined(); expect(validation.valid).toBeFalsy(); @@ -747,15 +733,12 @@ describe('AssemblyValidationService', () => { }); test.each(games)('should be invalid double if the player the wrong gender', async (g) => { - const validation = await service.fetchAndValidate( - { - systemId: system.id, - teamId: team?.id, - encounterId: encounter.id, - [`double${g}`]: [player555.id, player666.id], - }, - [new PlayerGenderRule()], - ); + const validation = await service.validate({ + systemId: system.id, + teamId: team?.id, + encounterId: encounter.id, + [`double${g}`]: [player555.id, player666.id], + }); expect(validation).toBeDefined(); expect(validation.valid).toBeFalsy(); @@ -878,18 +861,22 @@ describe('AssemblyValidationService', () => { team = await teamB.Build(); }); describe('Rule [PlayerOrderRule]', () => { + beforeEach(async () => { + await service.clearRules(); + await service.registerRule(PlayerOrderRule, PlayerOrderRule.description, { + activated: true, + }); + }); + describe('valid', () => { it('Double 3 is better then Double 4', async () => { - const validation = await service.fetchAndValidate( - { - systemId: system.id, - teamId: team?.id, - encounterId: encounter.id, - double3: [player666.id, player999.id], - double4: [player777.id, player888.id], - }, - [new PlayerOrderRule()], - ); + const validation = await service.validate({ + systemId: system.id, + teamId: team?.id, + encounterId: encounter.id, + double3: [player666.id, player999.id], + double4: [player777.id, player888.id], + }); expect(validation).toBeDefined(); expect(validation.valid).toBeTruthy(); @@ -908,16 +895,13 @@ describe('AssemblyValidationService', () => { ]; test.each(invalid)('Single %p is not better then Single %p', async (p1, p2) => { - const validation = await service.fetchAndValidate( - { - systemId: system.id, - teamId: team?.id, - encounterId: encounter.id, - [`single${p1}`]: player888.id, - [`single${p2}`]: player777.id, - }, - [new PlayerOrderRule()], - ); + const validation = await service.validate({ + systemId: system.id, + teamId: team?.id, + encounterId: encounter.id, + [`single${p1}`]: player888.id, + [`single${p2}`]: player777.id, + }); expect(validation).toBeDefined(); expect(validation.valid).toBeFalsy(); @@ -936,16 +920,13 @@ describe('AssemblyValidationService', () => { }); it('Mixed double is better then other', async () => { - const validation = await service.fetchAndValidate( - { - systemId: system.id, - teamId: team?.id, - encounterId: encounter.id, - double3: [player777.id, player888.id], - double4: [player666.id, player888.id], - }, - [new PlayerOrderRule()], - ); + const validation = await service.validate({ + systemId: system.id, + teamId: team?.id, + encounterId: encounter.id, + double3: [player777.id, player888.id], + double4: [player666.id, player888.id], + }); expect(validation).toBeDefined(); expect(validation.valid).toBeFalsy(); @@ -970,16 +951,13 @@ describe('AssemblyValidationService', () => { }); it('Mixed double is better then other by level', async () => { - const validation = await service.fetchAndValidate( - { - systemId: system.id, - teamId: team?.id, - encounterId: encounter.id, - double3: [player777.id, player888.id], - double4: [player666.id, player999.id], - }, - [new PlayerOrderRule()], - ); + const validation = await service.validate({ + systemId: system.id, + teamId: team?.id, + encounterId: encounter.id, + double3: [player777.id, player888.id], + double4: [player666.id, player999.id], + }); expect(validation).toBeDefined(); expect(validation.valid).toBeFalsy(); @@ -1006,20 +984,24 @@ describe('AssemblyValidationService', () => { }); describe('Rule [PlayerMaxGamesRule]', () => { + beforeEach(async () => { + await service.clearRules(); + await service.registerRule(PlayerMaxGamesRule, PlayerMaxGamesRule.description, { + activated: true, + }); + }); + describe('valid', () => { it('should be valid doubles', async () => { - const validation = await service.fetchAndValidate( - { - systemId: system.id, - teamId: team?.id, - encounterId: encounter.id, - double1: [player666.id, player888.id], - double2: [player777.id, player999.id], - double3: [player666.id, player777.id], - double4: [player888.id, player999.id], - }, - [new PlayerMaxGamesRule()], - ); + const validation = await service.validate({ + systemId: system.id, + teamId: team?.id, + encounterId: encounter.id, + double1: [player666.id, player888.id], + double2: [player777.id, player999.id], + double3: [player666.id, player777.id], + double4: [player888.id, player999.id], + }); expect(validation).toBeDefined(); expect(validation.valid).toBeTruthy(); @@ -1033,16 +1015,13 @@ describe('AssemblyValidationService', () => { describe('invalid', () => { it('should be invalid if the player has more then 1 mixed', async () => { - const validation = await service.fetchAndValidate( - { - systemId: system.id, - teamId: team?.id, - encounterId: encounter.id, - double3: [player666.id, player999.id], - double4: [player888.id, player999.id], - }, - [new PlayerMaxGamesRule()], - ); + const validation = await service.validate({ + systemId: system.id, + teamId: team?.id, + encounterId: encounter.id, + double3: [player666.id, player999.id], + double4: [player888.id, player999.id], + }); expect(validation).toBeDefined(); expect(validation.valid).toBeFalsy(); @@ -1058,20 +1037,24 @@ describe('AssemblyValidationService', () => { }); describe('Rule [PlayerGenderRule]', () => { + beforeEach(async () => { + await service.clearRules(); + await service.registerRule(PlayerGenderRule, PlayerGenderRule.description, { + activated: true, + }); + }); + describe('valid', () => { it('should be valid doubles', async () => { - const validation = await service.fetchAndValidate( - { - systemId: system.id, - teamId: team?.id, - encounterId: encounter.id, - double1: [player666.id, player777.id], - double2: [player888.id, player999.id], - double3: [player666.id, player888.id], - double4: [player777.id, player999.id], - }, - [new PlayerGenderRule()], - ); + const validation = await service.validate({ + systemId: system.id, + teamId: team?.id, + encounterId: encounter.id, + double1: [player666.id, player777.id], + double2: [player888.id, player999.id], + double3: [player666.id, player888.id], + double4: [player777.id, player999.id], + }); expect(validation).toBeDefined(); expect(validation.valid).toBeTruthy(); @@ -1087,16 +1070,13 @@ describe('AssemblyValidationService', () => { describe('invalid', () => { it('should be invalid if a mixed 3 has 2 of the same gender', async () => { - const validation = await service.fetchAndValidate( - { - systemId: system.id, - teamId: team?.id, - encounterId: encounter.id, - double3: [player666.id, player777.id], - double4: [player888.id, player777.id], - }, - [new PlayerGenderRule()], - ); + const validation = await service.validate({ + systemId: system.id, + teamId: team?.id, + encounterId: encounter.id, + double3: [player666.id, player777.id], + double4: [player888.id, player777.id], + }); expect(validation).toBeDefined(); expect(validation.valid).toBeFalsy(); @@ -1111,16 +1091,13 @@ describe('AssemblyValidationService', () => { }); it('should be invalid if a mixed 4 has 2 of the same gender', async () => { - const validation = await service.fetchAndValidate( - { - systemId: system.id, - teamId: team?.id, - encounterId: encounter.id, - double3: [player666.id, player999.id], - double4: [player888.id, player999.id], - }, - [new PlayerGenderRule()], - ); + const validation = await service.validate({ + systemId: system.id, + teamId: team?.id, + encounterId: encounter.id, + double3: [player666.id, player999.id], + double4: [player888.id, player999.id], + }); expect(validation).toBeDefined(); expect(validation.valid).toBeFalsy(); @@ -1136,21 +1113,25 @@ describe('AssemblyValidationService', () => { }); }); - describe('Rule [PlayerMinLevel]', () => { + describe('Rule [PlayerMinLevelRule]', () => { + beforeEach(async () => { + await service.clearRules(); + await service.registerRule(PlayerMinLevelRule, PlayerMinLevelRule.description, { + activated: true, + }); + }); + describe('valid', () => { it('should be valid', async () => { - const validation = await service.fetchAndValidate( - { - systemId: system.id, - teamId: team?.id, - encounterId: encounter.id, - single1: player666.id, - single2: player777.id, - single3: player888.id, - single4: player999.id, - }, - [new PlayerMinLevelRule()], - ); + const validation = await service.validate({ + systemId: system.id, + teamId: team?.id, + encounterId: encounter.id, + single1: player666.id, + single2: player777.id, + single3: player888.id, + single4: player999.id, + }); expect(validation).toBeDefined(); @@ -1163,18 +1144,15 @@ describe('AssemblyValidationService', () => { describe('invalid', () => { it("should be invalid if the player doesn't have competition status on true ", async () => { - const validation = await service.fetchAndValidate( - { - systemId: system.id, - teamId: team?.id, - encounterId: encounter.id, - single1: player555.id, - single2: player777.id, - single3: player888.id, - single4: player999.id, - }, - [new PlayerMinLevelRule()], - ); + const validation = await service.validate({ + systemId: system.id, + teamId: team?.id, + encounterId: encounter.id, + single1: player555.id, + single2: player777.id, + single3: player888.id, + single4: player999.id, + }); expect(validation).toBeDefined(); expect(validation.valid).toBeFalsy(); @@ -1377,20 +1355,24 @@ describe('AssemblyValidationService', () => { }); describe('Rule [TeamBaseIndexRule]', () => { + beforeEach(async () => { + await service.clearRules(); + await service.registerRule(TeamBaseIndexRule, TeamBaseIndexRule.description, { + activated: true, + }); + }); + describe('valid', () => { it('First team is allowed to have higher team index then base', async () => { - const validation = await service.fetchAndValidate( - { - systemId: system.id, - teamId: teamA?.id, - encounterId: encounter.id, - single1: player555.id, - single2: playerT1r777.id, - single3: playerT1r888.id, - single4: playerT1r999.id, - }, - [new TeamBaseIndexRule()], - ); + const validation = await service.validate({ + systemId: system.id, + teamId: teamA?.id, + encounterId: encounter.id, + single1: player555.id, + single2: playerT1r777.id, + single3: playerT1r888.id, + single4: playerT1r999.id, + }); expect(validation).toBeDefined(); expect(validation.valid).toBeTruthy(); @@ -1402,18 +1384,15 @@ describe('AssemblyValidationService', () => { }); it('Second team not', async () => { - const validation = await service.fetchAndValidate( - { - systemId: system.id, - teamId: teamA?.id, - encounterId: encounter.id, - single1: playerT1r666.id, - single2: playerT1r777.id, - single3: playerT1r888.id, - single4: playerT1r999.id, - }, - [new TeamBaseIndexRule()], - ); + const validation = await service.validate({ + systemId: system.id, + teamId: teamA?.id, + encounterId: encounter.id, + single1: playerT1r666.id, + single2: playerT1r777.id, + single3: playerT1r888.id, + single4: playerT1r999.id, + }); expect(validation).toBeDefined(); expect(validation.valid).toBeTruthy(); @@ -1422,18 +1401,15 @@ describe('AssemblyValidationService', () => { describe('invalid', () => { it.skip('Second team not', async () => { - const validation = await service.fetchAndValidate( - { - systemId: system.id, - teamId: teamB?.id, - encounterId: encounter.id, - single1: player555.id, - single2: playerT1r777.id, - single3: playerT1r888.id, - single4: playerT1r999.id, - }, - [new TeamBaseIndexRule()], - ); + const validation = await service.validate({ + systemId: system.id, + teamId: teamB?.id, + encounterId: encounter.id, + single1: player555.id, + single2: playerT1r777.id, + single3: playerT1r888.id, + single4: playerT1r999.id, + }); expect(validation).toBeDefined(); expect(validation.valid).toBeFalsy(); @@ -1449,21 +1425,25 @@ describe('AssemblyValidationService', () => { }); }); - describe('Rule [TeamBaseIndexRule]', () => { + describe('Rule [TeamClubBaseRule]', () => { + beforeEach(async () => { + await service.clearRules(); + await service.registerRule(TeamClubBaseRule, TeamClubBaseRule.description, { + activated: true, + }); + }); + describe('invalid', () => { it('Second team not', async () => { - const validation = await service.fetchAndValidate( - { - systemId: system.id, - teamId: teamA?.id, - encounterId: encounter.id, - single1: playerT2r666.id, - single2: playerT1r777.id, - single3: playerT1r888.id, - single4: playerT1r999.id, - }, - [new TeamClubBaseRule()], - ); + const validation = await service.validate({ + systemId: system.id, + teamId: teamA?.id, + encounterId: encounter.id, + single1: playerT2r666.id, + single2: playerT1r777.id, + single3: playerT1r888.id, + single4: playerT1r999.id, + }); expect(validation).toBeDefined(); expect(validation.valid).toBeFalsy(); @@ -1478,21 +1458,25 @@ describe('AssemblyValidationService', () => { }); }); - describe('Rule [PlayerMinLevel]', () => { + describe('Rule [PlayerMinLevelRule]', () => { + beforeEach(async () => { + await service.clearRules(); + await service.registerRule(PlayerMinLevelRule, PlayerMinLevelRule.description, { + activated: true, + }); + }); + describe('valid', () => { it('should be valid', async () => { - const validation = await service.fetchAndValidate( - { - systemId: system.id, - teamId: teamA?.id, - encounterId: encounter.id, - single1: player555.id, - single2: playerT1r777.id, - single3: playerT1r888.id, - single4: playerT1r999.id, - }, - [new PlayerMinLevelRule()], - ); + const validation = await service.validate({ + systemId: system.id, + teamId: teamA?.id, + encounterId: encounter.id, + single1: player555.id, + single2: playerT1r777.id, + single3: playerT1r888.id, + single4: playerT1r999.id, + }); expect(validation).toBeDefined(); @@ -1505,18 +1489,15 @@ describe('AssemblyValidationService', () => { describe('invalid', () => { it("should be invalid if the player doesn't have competition status on true ", async () => { - const validation = await service.fetchAndValidate( - { - systemId: system.id, - teamId: teamB?.id, - encounterId: encounter.id, - single1: player555.id, - single2: playerT2r777.id, - single3: playerT2r888.id, - single4: playerT2r999.id, - }, - [new PlayerMinLevelRule()], - ); + const validation = await service.validate({ + systemId: system.id, + teamId: teamB?.id, + encounterId: encounter.id, + single1: player555.id, + single2: playerT2r777.id, + single3: playerT2r888.id, + single4: playerT2r999.id, + }); expect(validation).toBeDefined(); expect(validation.valid).toBeFalsy(); @@ -1653,28 +1634,32 @@ describe('AssemblyValidationService', () => { }); it('should be a valid assembly', async () => { - const validation = await service.fetchAndValidate( - { - systemId: system.id, - teamId: team?.id, - encounterId: encounter.id, - single1: player666.id, - single2: player777.id, - single3: player888.id, - single4: player999.id, - double1: [player666.id, player777.id], - double2: [player666.id, player888.id], - double3: [player777.id, player999.id], - double4: [player888.id, player999.id], - }, - AssemblyValidationService.defaultValidators(), - ); + const validation = await service.validate({ + systemId: system.id, + teamId: team?.id, + encounterId: encounter.id, + single1: player666.id, + single2: player777.id, + single3: player888.id, + single4: player999.id, + double1: [player666.id, player777.id], + double2: [player666.id, player888.id], + double3: [player777.id, player999.id], + double4: [player888.id, player999.id], + }); expect(validation).toBeDefined(); expect(validation.valid).toBeTruthy(); }); describe('Rule [PlayerOrderRule]', () => { + beforeEach(async () => { + await service.clearRules(); + await service.registerRule(PlayerOrderRule, PlayerOrderRule.description, { + activated: true, + }); + }); + describe('valid', () => { const valid = [ [1, 2], @@ -1683,16 +1668,13 @@ describe('AssemblyValidationService', () => { ]; test.each(valid)('Single %p is better then Single %p', async (p1, p2) => { - const validation = await service.fetchAndValidate( - { - systemId: system.id, - teamId: team?.id, - encounterId: encounter.id, - [`single${p2}`]: player888.id, - [`single${p1}`]: player777.id, - }, - [new PlayerOrderRule()], - ); + const validation = await service.validate({ + systemId: system.id, + teamId: team?.id, + encounterId: encounter.id, + [`single${p2}`]: player888.id, + [`single${p1}`]: player777.id, + }); expect(validation).toBeDefined(); @@ -1703,16 +1685,13 @@ describe('AssemblyValidationService', () => { }); test.each(valid)('Double %p is better then Double %p', async (p1, p2) => { - const validation = await service.fetchAndValidate( - { - systemId: system.id, - teamId: team?.id, - encounterId: encounter.id, - [`double${p2}`]: [player777.id, player888.id], - [`double${p1}`]: [player666.id, player888.id], - }, - [new PlayerOrderRule()], - ); + const validation = await service.validate({ + systemId: system.id, + teamId: team?.id, + encounterId: encounter.id, + [`double${p2}`]: [player777.id, player888.id], + [`double${p1}`]: [player666.id, player888.id], + }); expect(validation).toBeDefined(); @@ -1723,16 +1702,13 @@ describe('AssemblyValidationService', () => { }); test.each(valid)('Double %p is not better then Double %p by level', async (p1, p2) => { - const validation = await service.fetchAndValidate( - { - systemId: system.id, - teamId: team?.id, - encounterId: encounter.id, - [`double${p2}`]: [player777.id, player888.id], - [`double${p1}`]: [player666.id, player999.id], - }, - [new PlayerOrderRule()], - ); + const validation = await service.validate({ + systemId: system.id, + teamId: team?.id, + encounterId: encounter.id, + [`double${p2}`]: [player777.id, player888.id], + [`double${p1}`]: [player666.id, player999.id], + }); expect(validation).toBeDefined(); @@ -1751,16 +1727,13 @@ describe('AssemblyValidationService', () => { ]; test.each(invalid)('Single %p is not better then Single %p', async (p1, p2) => { - const validation = await service.fetchAndValidate( - { - systemId: system.id, - teamId: team?.id, - encounterId: encounter.id, - [`single${p1}`]: player888.id, - [`single${p2}`]: player777.id, - }, - [new PlayerOrderRule()], - ); + const validation = await service.validate({ + systemId: system.id, + teamId: team?.id, + encounterId: encounter.id, + [`single${p1}`]: player888.id, + [`single${p2}`]: player777.id, + }); expect(validation).toBeDefined(); expect(validation.valid).toBeFalsy(); @@ -1779,16 +1752,13 @@ describe('AssemblyValidationService', () => { }); test.each(invalid)('Double %p is better then Double %p', async (p1, p2) => { - const validation = await service.fetchAndValidate( - { - systemId: system.id, - teamId: team?.id, - encounterId: encounter.id, - [`double${p1}`]: [player777.id, player888.id], - [`double${p2}`]: [player666.id, player888.id], - }, - [new PlayerOrderRule()], - ); + const validation = await service.validate({ + systemId: system.id, + teamId: team?.id, + encounterId: encounter.id, + [`double${p1}`]: [player777.id, player888.id], + [`double${p2}`]: [player666.id, player888.id], + }); expect(validation).toBeDefined(); expect(validation.valid).toBeFalsy(); @@ -1813,16 +1783,13 @@ describe('AssemblyValidationService', () => { }); test.each(invalid)('Double %p is not better then Double %p by level', async (p1, p2) => { - const validation = await service.fetchAndValidate( - { - systemId: system.id, - teamId: team?.id, - encounterId: encounter.id, - [`double${p1}`]: [player777.id, player888.id], - [`double${p2}`]: [player666.id, player999.id], - }, - [new PlayerOrderRule()], - ); + const validation = await service.validate({ + systemId: system.id, + teamId: team?.id, + encounterId: encounter.id, + [`double${p1}`]: [player777.id, player888.id], + [`double${p2}`]: [player666.id, player999.id], + }); expect(validation).toBeDefined(); expect(validation.valid).toBeFalsy(); @@ -1849,20 +1816,24 @@ describe('AssemblyValidationService', () => { }); describe('Rule [TeamSubeventIndexRule]', () => { + beforeEach(async () => { + await service.clearRules(); + await service.registerRule(TeamSubeventIndexRule, TeamSubeventIndexRule.description, { + activated: true, + }); + }); + describe('valid', () => { it('should be valid', async () => { - const validation = await service.fetchAndValidate( - { - systemId: system.id, - teamId: team?.id, - encounterId: encounter.id, - single1: player666.id, - single2: player777.id, - single3: player888.id, - single4: player999.id, - }, - [new TeamSubeventIndexRule()], - ); + const validation = await service.validate({ + systemId: system.id, + teamId: team?.id, + encounterId: encounter.id, + single1: player666.id, + single2: player777.id, + single3: player888.id, + single4: player999.id, + }); expect(validation).toBeDefined(); @@ -1875,18 +1846,15 @@ describe('AssemblyValidationService', () => { describe('invalid', () => { it('should be invalid if team index lower then the base', async () => { - const validation = await service.fetchAndValidate( - { - systemId: system.id, - teamId: team?.id, - encounterId: encounter.id, - single1: player111.id, - single2: player777.id, - single3: player888.id, - single4: player999.id, - }, - [new TeamSubeventIndexRule()], - ); + const validation = await service.validate({ + systemId: system.id, + teamId: team?.id, + encounterId: encounter.id, + single1: player111.id, + single2: player777.id, + single3: player888.id, + single4: player999.id, + }); expect(validation).toBeDefined(); expect(validation.valid).toBeFalsy(); @@ -1904,20 +1872,24 @@ describe('AssemblyValidationService', () => { }); describe('Rule [PlayerCompStatusRule]', () => { + beforeEach(async () => { + await service.clearRules(); + await service.registerRule(PlayerCompStatusRule, PlayerCompStatusRule.description, { + activated: true, + }); + }); + describe('valid', () => { it('should be valid', async () => { - const validation = await service.fetchAndValidate( - { - systemId: system.id, - teamId: team?.id, - encounterId: encounter.id, - single1: player666.id, - single2: player777.id, - single3: player888.id, - single4: player999.id, - }, - [new PlayerCompStatusRule()], - ); + const validation = await service.validate({ + systemId: system.id, + teamId: team?.id, + encounterId: encounter.id, + single1: player666.id, + single2: player777.id, + single3: player888.id, + single4: player999.id, + }); expect(validation).toBeDefined(); @@ -1930,18 +1902,15 @@ describe('AssemblyValidationService', () => { describe('invalid', () => { it("should be invalid if the player doesn't have competition status on true ", async () => { - const validation = await service.fetchAndValidate( - { - systemId: system.id, - teamId: team?.id, - encounterId: encounter.id, - single1: player555.id, - single2: player777.id, - single3: player888.id, - single4: player999.id, - }, - [new PlayerCompStatusRule()], - ); + const validation = await service.validate({ + systemId: system.id, + teamId: team?.id, + encounterId: encounter.id, + single1: player555.id, + single2: player777.id, + single3: player888.id, + single4: player999.id, + }); expect(validation).toBeDefined(); expect(validation.valid).toBeFalsy(); @@ -1955,18 +1924,15 @@ describe('AssemblyValidationService', () => { }); it("should be invalid if the players doesn't have competition status on true ", async () => { - const validation = await service.fetchAndValidate( - { - systemId: system.id, - teamId: team?.id, - encounterId: encounter.id, - single1: player111.id, - single2: player555.id, - single3: player888.id, - single4: player999.id, - }, - [new PlayerCompStatusRule()], - ); + const validation = await service.validate({ + systemId: system.id, + teamId: team?.id, + encounterId: encounter.id, + single1: player111.id, + single2: player555.id, + single3: player888.id, + single4: player999.id, + }); expect(validation).toBeDefined(); expect(validation.valid).toBeFalsy(); @@ -1982,20 +1948,24 @@ describe('AssemblyValidationService', () => { }); describe('Rule [PlayerMaxGamesRule]', () => { + beforeEach(async () => { + await service.clearRules(); + await service.registerRule(PlayerMaxGamesRule, PlayerMaxGamesRule.description, { + activated: true, + }); + }); + describe('valid', () => { it('should be valid single', async () => { - const validation = await service.fetchAndValidate( - { - systemId: system.id, - teamId: team?.id, - encounterId: encounter.id, - single1: player666.id, - single2: player777.id, - single3: player888.id, - single4: player999.id, - }, - [new PlayerMaxGamesRule()], - ); + const validation = await service.validate({ + systemId: system.id, + teamId: team?.id, + encounterId: encounter.id, + single1: player666.id, + single2: player777.id, + single3: player888.id, + single4: player999.id, + }); expect(validation).toBeDefined(); expect(validation.valid).toBeTruthy(); @@ -2007,18 +1977,15 @@ describe('AssemblyValidationService', () => { }); it('should be valid double', async () => { - const validation = await service.fetchAndValidate( - { - systemId: system.id, - teamId: team?.id, - encounterId: encounter.id, - double1: [player666.id, player777.id], - double2: [player666.id, player888.id], - double3: [player777.id, player999.id], - double4: [player888.id, player999.id], - }, - [new PlayerMaxGamesRule()], - ); + const validation = await service.validate({ + systemId: system.id, + teamId: team?.id, + encounterId: encounter.id, + double1: [player666.id, player777.id], + double2: [player666.id, player888.id], + double3: [player777.id, player999.id], + double4: [player888.id, player999.id], + }); expect(validation).toBeDefined(); expect(validation.valid).toBeTruthy(); @@ -2032,18 +1999,15 @@ describe('AssemblyValidationService', () => { describe('invalid', () => { it('should be invalid if the player has more then 1 single game ', async () => { - const validation = await service.fetchAndValidate( - { - systemId: system.id, - teamId: team?.id, - encounterId: encounter.id, - single1: player555.id, - single2: player555.id, - single3: player888.id, - single4: player999.id, - }, - [new PlayerMaxGamesRule()], - ); + const validation = await service.validate({ + systemId: system.id, + teamId: team?.id, + encounterId: encounter.id, + single1: player555.id, + single2: player555.id, + single3: player888.id, + single4: player999.id, + }); expect(validation).toBeDefined(); expect(validation.valid).toBeFalsy(); @@ -2057,18 +2021,15 @@ describe('AssemblyValidationService', () => { }); it('should be invalid if the player has more then 2 doubles', async () => { - const validation = await service.fetchAndValidate( - { - systemId: system.id, - teamId: team?.id, - encounterId: encounter.id, - double1: [player666.id, player777.id], - double2: [player666.id, player888.id], - double3: [player666.id, player999.id], - double4: [player888.id, player999.id], - }, - [new PlayerMaxGamesRule()], - ); + const validation = await service.validate({ + systemId: system.id, + teamId: team?.id, + encounterId: encounter.id, + double1: [player666.id, player777.id], + double2: [player666.id, player888.id], + double3: [player666.id, player999.id], + double4: [player888.id, player999.id], + }); expect(validation).toBeDefined(); expect(validation.valid).toBeFalsy(); @@ -2084,24 +2045,28 @@ describe('AssemblyValidationService', () => { }); describe('Rule [PlayerGenderRule]', () => { + beforeEach(async () => { + await service.clearRules(); + await service.registerRule(PlayerGenderRule, PlayerGenderRule.description, { + activated: true, + }); + }); + describe('valid', () => { it('should be valid single', async () => { - const validation = await service.fetchAndValidate( - { - systemId: system.id, - teamId: team?.id, - encounterId: encounter.id, - double1: [player666.id, player777.id], - double2: [player666.id, player888.id], - double3: [player777.id, player999.id], - double4: [player888.id, player999.id], - single1: player666.id, - single2: player777.id, - single3: player888.id, - single4: player999.id, - }, - [new PlayerGenderRule()], - ); + const validation = await service.validate({ + systemId: system.id, + teamId: team?.id, + encounterId: encounter.id, + double1: [player666.id, player777.id], + double2: [player666.id, player888.id], + double3: [player777.id, player999.id], + double4: [player888.id, player999.id], + single1: player666.id, + single2: player777.id, + single3: player888.id, + single4: player999.id, + }); expect(validation).toBeDefined(); expect(validation.valid).toBeTruthy(); @@ -2120,15 +2085,12 @@ describe('AssemblyValidationService', () => { const games = [[2]]; test.each(games)('should be invalid single if the player the wrong gender', async (g) => { - const validation = await service.fetchAndValidate( - { - systemId: system.id, - teamId: team?.id, - encounterId: encounter.id, - [`single${g}`]: player555.id, - }, - [new PlayerGenderRule()], - ); + const validation = await service.validate({ + systemId: system.id, + teamId: team?.id, + encounterId: encounter.id, + [`single${g}`]: player555.id, + }); expect(validation).toBeDefined(); expect(validation.valid).toBeFalsy(); @@ -2142,15 +2104,12 @@ describe('AssemblyValidationService', () => { }); test.each(games)('should be invalid double if the player the wrong gender', async (g) => { - const validation = await service.fetchAndValidate( - { - systemId: system.id, - teamId: team?.id, - encounterId: encounter.id, - [`double${g}`]: [player555.id, player666.id], - }, - [new PlayerGenderRule()], - ); + const validation = await service.validate({ + systemId: system.id, + teamId: team?.id, + encounterId: encounter.id, + [`double${g}`]: [player555.id, player666.id], + }); expect(validation).toBeDefined(); expect(validation.valid).toBeFalsy(); diff --git a/libs/backend/competition/assembly/src/services/validate/assembly.service.ts b/libs/backend/competition/assembly/src/services/validate/assembly.service.ts index 4a8ceb73c0..61ff700d2d 100644 --- a/libs/backend/competition/assembly/src/services/validate/assembly.service.ts +++ b/libs/backend/competition/assembly/src/services/validate/assembly.service.ts @@ -12,59 +12,78 @@ import { SubEventCompetition, Team, } from '@badman/backend-database'; +import { ValidationService } from '@badman/backend-validation'; import { getBestPlayers, getBestPlayersFromTeam, SubEventTypeEnum } from '@badman/utils'; -import { Injectable, Logger } from '@nestjs/common'; +import { Logger } from '@nestjs/common'; import moment from 'moment'; import { Op } from 'sequelize'; -import { AssemblyValidationData, AssemblyOutput, AssemblyValidationError } from '../../models'; +import { AssemblyOutput, AssemblyValidationData, AssemblyValidationError } from '../../models'; import { PlayerCompStatusRule, PlayerGenderRule, PlayerMaxGamesRule, PlayerMinLevelRule, PlayerOrderRule, - Rule, TeamBaseIndexRule, TeamClubBaseRule, TeamSubeventIndexRule, } from './rules'; -@Injectable() -export class AssemblyValidationService { +export class AssemblyValidationService extends ValidationService< + AssemblyValidationData, + AssemblyValidationError +> { + override group = 'team-assembly'; + private readonly _logger = new Logger(AssemblyValidationService.name); + override async onApplicationBootstrap() { + this._logger.log('Initializing rules'); + + await this.clearRules(); - async getValidationData( - teamId: string, - encounterId: string, + await this.registerRule(PlayerCompStatusRule, PlayerCompStatusRule.description); + await this.registerRule(TeamBaseIndexRule, TeamBaseIndexRule.description); + await this.registerRule(TeamSubeventIndexRule, TeamSubeventIndexRule.description); + await this.registerRule(TeamClubBaseRule, TeamClubBaseRule.description); + await this.registerRule(PlayerOrderRule, PlayerOrderRule.description); + await this.registerRule(PlayerMinLevelRule, PlayerMinLevelRule.description); + await this.registerRule(PlayerMaxGamesRule, PlayerMaxGamesRule.description); + await this.registerRule(PlayerGenderRule, PlayerGenderRule.description); - systemId?: string, + this._logger.log('Rules initialized'); + } + override async fetchData(args: { + teamId: string; + encounterId: string; - single1?: string, - single2?: string, - single3?: string, - single4?: string, + systemId?: string; - double1?: string[], - double2?: string[], - double3?: string[], - double4?: string[], + single1?: string; + single2?: string; + single3?: string; + single4?: string; - subtitudes?: string[], - ): Promise { + double1?: string[]; + double2?: string[]; + double3?: string[]; + double4?: string[]; + + subtitudes?: string[]; + }): Promise { const idPlayers = [ - single1, - single2, - single3, - single4, - ...(double1?.flat(1) ?? []), - ...(double2?.flat(1) ?? []), - ...(double3?.flat(1) ?? []), - ...(double4?.flat(1) ?? []), + args.single1, + args.single2, + args.single3, + args.single4, + ...(args.double1?.flat(1) ?? []), + ...(args.double2?.flat(1) ?? []), + ...(args.double3?.flat(1) ?? []), + ...(args.double4?.flat(1) ?? []), ]?.filter((p) => p !== undefined && p !== null) as string[]; - const idSubs = subtitudes?.filter((p) => p !== undefined && p !== null); + const idSubs = args.subtitudes?.filter((p) => p !== undefined && p !== null); - const team = await Team.findByPk(teamId, { + const team = await Team.findByPk(args.teamId, { attributes: ['id', 'name', 'type', 'teamNumber', 'clubId'], }); @@ -72,13 +91,12 @@ export class AssemblyValidationService { throw new Error('Team not found'); } - const encounter = await EncounterCompetition.findByPk(encounterId) || undefined; - + const encounter = (await EncounterCompetition.findByPk(args.encounterId)) || undefined; + let draw: DrawCompetition | null = null; let subEvent: SubEventCompetition | null = null; - - if (encounter) { + if (encounter) { draw = await encounter?.getDrawCompetition({ attributes: ['id', 'name', 'subeventId'], }); @@ -130,7 +148,9 @@ export class AssemblyValidationService { attributes: ['id', 'teamId', 'subEventId', 'meta'], where: { teamId: clubTeams?.map((t) => t.id), - subEventId: sameYearSubEvents?.map((e) => e.subEventCompetitions?.map((s) => s.id)).flat(1) as string[], + subEventId: sameYearSubEvents + ?.map((e) => e.subEventCompetitions?.map((s) => s.id)) + .flat(1) as string[], }, }); @@ -142,8 +162,8 @@ export class AssemblyValidationService { }); const system = - systemId !== null && systemId !== undefined - ? await RankingSystem.findByPk(systemId) + args.systemId !== null && args.systemId !== undefined + ? await RankingSystem.findByPk(args.systemId) : await RankingSystem.findOne({ where: { primary: true } }); if (!system) { @@ -151,7 +171,7 @@ export class AssemblyValidationService { } // Filter out this team's meta - let meta = filteredMemberships?.find((m) => m.teamId == teamId)?.meta; + let meta = filteredMemberships?.find((m) => m.teamId == args.teamId)?.meta; // If meta is found, create a new one if (!meta) { @@ -171,7 +191,7 @@ export class AssemblyValidationService { // Other teams meta const otherMeta = (filteredMemberships - ?.filter((m) => m.teamId !== teamId) + ?.filter((m) => m.teamId !== args.teamId) ?.map((m) => m.meta) ?? []) as MetaEntry[]; const year = event?.season; @@ -300,27 +320,27 @@ export class AssemblyValidationService { system, - single1: players?.find((p) => p.id === single1), - single2: players?.find((p) => p.id === single2), - single3: players?.find((p) => p.id === single3), - single4: players?.find((p) => p.id === single4), + single1: players?.find((p) => p.id === args.single1), + single2: players?.find((p) => p.id === args.single2), + single3: players?.find((p) => p.id === args.single3), + single4: players?.find((p) => p.id === args.single4), double1: players - ?.filter((p) => double1?.flat(1)?.includes(p.id)) + ?.filter((p) => args.double1?.flat(1)?.includes(p.id)) ?.sort( (a, b) => (a.rankingLastPlaces?.[0]?.double ?? system.amountOfLevels) - (b.rankingLastPlaces?.[0]?.double ?? system.amountOfLevels), ) as [Player, Player], double2: players - ?.filter((p) => double2?.flat(1)?.includes(p.id)) + ?.filter((p) => args.double2?.flat(1)?.includes(p.id)) ?.sort( (a, b) => (a.rankingLastPlaces?.[0]?.double ?? system.amountOfLevels) - (b.rankingLastPlaces?.[0]?.double ?? system.amountOfLevels), ) as [Player, Player], double3: players - ?.filter((p) => double3?.flat(1)?.includes(p.id)) + ?.filter((p) => args.double3?.flat(1)?.includes(p.id)) ?.sort((a, b) => { const ranking = type === SubEventTypeEnum.MX ? 'mix' : 'double'; return ( @@ -329,7 +349,7 @@ export class AssemblyValidationService { ); }) as [Player, Player], double4: players - ?.filter((p) => double4?.flat(1)?.includes(p.id)) + ?.filter((p) => args.double4?.flat(1)?.includes(p.id)) ?.sort((a, b) => { const ranking = type === SubEventTypeEnum.MX ? 'mix' : 'double'; return ( @@ -342,39 +362,15 @@ export class AssemblyValidationService { }; } + /** * Validate the assembly * * @param assembly Assembly configuaration * @returns Whether the assembly is valid or not */ - async validate(assembly: AssemblyValidationData, validators: Rule[]): Promise { - // get all errors and warnings from the validators in parallel - const results = await Promise.all(validators.map((v) => v.validate(assembly))); - - const errors = results - ?.map((r) => r.errors) - ?.flat(1) - ?.filter((e) => !!e) as AssemblyValidationError[]; - const warnings = results - ?.map((r) => r.warnings) - ?.flat(1) - ?.filter((e) => !!e) as AssemblyValidationError[]; - - return { - valid: errors.length === 0, - errors: errors, - warnings: warnings, - systemId: assembly.system?.id, - titularsIndex: assembly.teamIndex, - titularsPlayerData: assembly.teamPlayers, - baseTeamIndex: assembly.meta?.competition?.teamIndex, - basePlayersData: assembly.meta?.competition?.players, - }; - } - - async fetchAndValidate( - data: { + override async validate( + args: { teamId: string; encounterId: string; @@ -392,35 +388,30 @@ export class AssemblyValidationService { subtitudes?: string[]; }, - validators: Rule[], + runFor?: { + playerId?: string; + teamId?: string; + clubId?: string; + }, ) { - const dbData = await this.getValidationData( - data.teamId, - data.encounterId, - data.systemId, - data.single1, - data.single2, - data.single3, - data.single4, - data.double1, - data.double2, - data.double3, - data.double4, - data.subtitudes, - ); - return this.validate(dbData, validators); - } + const team = await Team.findByPk(args.teamId, { + attributes: ['clubId'], + }); + const data = await super.validate(args, { + ...runFor, + teamId: args.teamId, + clubId: team?.clubId, + }); - static defaultValidators(): Rule[] { - return [ - new TeamBaseIndexRule(), - new TeamSubeventIndexRule(), - new TeamClubBaseRule(), - new PlayerOrderRule(), - new PlayerCompStatusRule(), - new PlayerMinLevelRule(), - new PlayerMaxGamesRule(), - new PlayerGenderRule(), - ]; + return { + valid: data.valid, + errors: data.errors, + warnings: data.warnings, + systemId: data.system?.id, + titularsIndex: data.teamIndex, + titularsPlayerData: data.teamPlayers, + baseTeamIndex: data.meta?.competition?.teamIndex, + basePlayersData: data.meta?.competition?.players, + } as AssemblyOutput; } } diff --git a/libs/backend/competition/assembly/src/services/validate/rules/_rule.base.ts b/libs/backend/competition/assembly/src/services/validate/rules/_rule.base.ts index ba4bc7b634..894055671a 100644 --- a/libs/backend/competition/assembly/src/services/validate/rules/_rule.base.ts +++ b/libs/backend/competition/assembly/src/services/validate/rules/_rule.base.ts @@ -1,5 +1,4 @@ +import { ValidationRule } from '@badman/backend-validation'; import { AssemblyValidationData, AssemblyOutput } from '../../../models'; -export abstract class Rule { - abstract validate(assembly: AssemblyValidationData): Promise; -} +export abstract class Rule extends ValidationRule {} diff --git a/libs/backend/competition/assembly/src/services/validate/rules/player-comp-status.rule.ts b/libs/backend/competition/assembly/src/services/validate/rules/player-comp-status.rule.ts index fc88c2dfee..8f2b91ff4e 100644 --- a/libs/backend/competition/assembly/src/services/validate/rules/player-comp-status.rule.ts +++ b/libs/backend/competition/assembly/src/services/validate/rules/player-comp-status.rule.ts @@ -1,5 +1,5 @@ import { Player } from '@badman/backend-database'; -import { AssemblyValidationData, AssemblyOutput, AssemblyValidationError } from '../../../models'; +import { AssemblyOutput, AssemblyValidationData, AssemblyValidationError } from '../../../models'; import { Rule } from './_rule.base'; export type PlayerCompStatusRuleParams = { @@ -10,6 +10,8 @@ export type PlayerCompStatusRuleParams = { * Checks if all players have the competition status active */ export class PlayerCompStatusRule extends Rule { + static override description = 'all.rules.team-assembly.player-comp-status'; + async validate(assembly: AssemblyValidationData): Promise { const { single1, single2, single3, single4, double1, double2, double3, double4, subtitudes } = assembly; diff --git a/libs/backend/competition/assembly/src/services/validate/rules/player-gender.rule.ts b/libs/backend/competition/assembly/src/services/validate/rules/player-gender.rule.ts index d3abf2b430..1b075ff257 100644 --- a/libs/backend/competition/assembly/src/services/validate/rules/player-gender.rule.ts +++ b/libs/backend/competition/assembly/src/services/validate/rules/player-gender.rule.ts @@ -20,6 +20,8 @@ export type PlayerGenderRuleParams = * Checks if the player is the correct gender */ export class PlayerGenderRule extends Rule { + static override description = 'all.rules.team-assembly.player-gender'; + async validate(assembly: AssemblyValidationData): Promise { const { single1, single2, single3, single4, double1, double2, double3, double4, type } = assembly; diff --git a/libs/backend/competition/assembly/src/services/validate/rules/player-max-games.rule.ts b/libs/backend/competition/assembly/src/services/validate/rules/player-max-games.rule.ts index dfaf56d0df..cbefe299fb 100644 --- a/libs/backend/competition/assembly/src/services/validate/rules/player-max-games.rule.ts +++ b/libs/backend/competition/assembly/src/services/validate/rules/player-max-games.rule.ts @@ -1,6 +1,6 @@ import { Player } from '@badman/backend-database'; import { SubEventTypeEnum } from '@badman/utils'; -import { AssemblyValidationData, AssemblyOutput, AssemblyValidationError } from '../../../models'; +import { AssemblyOutput, AssemblyValidationData, AssemblyValidationError } from '../../../models'; import { Rule } from './_rule.base'; export type PlayerMaxGamesRuleParams = { @@ -12,6 +12,7 @@ export type PlayerMaxGamesRuleParams = { * Checks if a player has max 1 single game and 2 double game */ export class PlayerMaxGamesRule extends Rule { + static override description = 'all.rules.team-assembly.player-max-games'; async validate(assembly: AssemblyValidationData): Promise { const { single1, single2, single3, single4, double1, double2, double3, double4, type } = assembly; diff --git a/libs/backend/competition/assembly/src/services/validate/rules/player-min-level.rule.ts b/libs/backend/competition/assembly/src/services/validate/rules/player-min-level.rule.ts index 3f094ce51b..1b84cdbe5e 100644 --- a/libs/backend/competition/assembly/src/services/validate/rules/player-min-level.rule.ts +++ b/libs/backend/competition/assembly/src/services/validate/rules/player-min-level.rule.ts @@ -1,7 +1,7 @@ +import { Player } from '@badman/backend-database'; import { SubEventTypeEnum } from '@badman/utils'; -import { AssemblyValidationData, AssemblyOutput, AssemblyValidationError } from '../../../models'; +import { AssemblyOutput, AssemblyValidationData, AssemblyValidationError } from '../../../models'; import { Rule } from './_rule.base'; -import { Player } from '@badman/backend-database'; export type PlayerMinLevelRuleParams = { player: Partial & { ranking: number }; @@ -15,6 +15,7 @@ export type PlayerMinLevelRuleParams = { * If the player has a level exception, the player is allowed to be better than the max level */ export class PlayerMinLevelRule extends Rule { + static override description = 'all.rules.team-assembly.player-min-level'; async validate(assembly: AssemblyValidationData): Promise { const { system, diff --git a/libs/backend/competition/assembly/src/services/validate/rules/player-order.rule.ts b/libs/backend/competition/assembly/src/services/validate/rules/player-order.rule.ts index c6c7fbd3e7..da72013b9b 100644 --- a/libs/backend/competition/assembly/src/services/validate/rules/player-order.rule.ts +++ b/libs/backend/competition/assembly/src/services/validate/rules/player-order.rule.ts @@ -28,6 +28,7 @@ export type PlayerOrderRuleParams = PlayerOrderRuleSingleParams | PlayerOrderRul * Doubles: the team with the lowest ranking should be first, if the ranking is the same, the best player should be first */ export class PlayerOrderRule extends Rule { + static override description = 'all.rules.team-assembly.player-order'; async validate(assembly: AssemblyValidationData): Promise { const { single1, single2, single3, single4, double1, double2, double3, double4, type, system } = assembly; diff --git a/libs/backend/competition/assembly/src/services/validate/rules/team-base-index.rule.ts b/libs/backend/competition/assembly/src/services/validate/rules/team-base-index.rule.ts index 3869802a5b..01eb28bab0 100644 --- a/libs/backend/competition/assembly/src/services/validate/rules/team-base-index.rule.ts +++ b/libs/backend/competition/assembly/src/services/validate/rules/team-base-index.rule.ts @@ -10,6 +10,7 @@ export type TeamBaseIndexRuleParams = { * Checks if the teamIndex is beter than the baseIndex */ export class TeamBaseIndexRule extends Rule { + static override description = 'all.rules.team-assembly.team-base-index'; async validate(assembly: AssemblyValidationData): Promise { const { team, teamIndex, meta } = assembly; diff --git a/libs/backend/competition/assembly/src/services/validate/rules/team-club-base.rule.ts b/libs/backend/competition/assembly/src/services/validate/rules/team-club-base.rule.ts index 552e32994d..d98759bf6e 100644 --- a/libs/backend/competition/assembly/src/services/validate/rules/team-club-base.rule.ts +++ b/libs/backend/competition/assembly/src/services/validate/rules/team-club-base.rule.ts @@ -1,5 +1,5 @@ import { Player } from '@badman/backend-database'; -import { AssemblyValidationData, AssemblyOutput, AssemblyValidationError } from '../../../models'; +import { AssemblyOutput, AssemblyValidationData, AssemblyValidationError } from '../../../models'; import { Rule } from './_rule.base'; export type TeamClubBaseRuleParams = { @@ -9,6 +9,7 @@ export type TeamClubBaseRuleParams = { * Checks if the player is not in another base team that is higher than the current team or part of the same subevent */ export class TeamClubBaseRule extends Rule { + static override description = 'all.rules.team-assembly.team-club-base'; async validate(assembly: AssemblyValidationData): Promise { const { otherMeta, diff --git a/libs/backend/competition/assembly/src/services/validate/rules/team-subevent-index.rule.ts b/libs/backend/competition/assembly/src/services/validate/rules/team-subevent-index.rule.ts index 2ce593b1f1..4dc7e985c5 100644 --- a/libs/backend/competition/assembly/src/services/validate/rules/team-subevent-index.rule.ts +++ b/libs/backend/competition/assembly/src/services/validate/rules/team-subevent-index.rule.ts @@ -1,4 +1,4 @@ -import { AssemblyValidationData, AssemblyOutput, AssemblyValidationError } from '../../../models'; +import { AssemblyOutput, AssemblyValidationData, AssemblyValidationError } from '../../../models'; import { Rule } from './_rule.base'; export type TeamSubeventIndexRuleParams = { @@ -10,6 +10,7 @@ export type TeamSubeventIndexRuleParams = { * Checks if the teamIndex is not lower then the allowed minIndex of the subevent */ export class TeamSubeventIndexRule extends Rule { + static override description = 'all.rules.team-assembly.team-subevent-index'; async validate(assembly: AssemblyValidationData): Promise { const { teamIndex, subEvent } = assembly; diff --git a/libs/backend/competition/assembly/src/services/validate/rules/team-subtitudes.rule.ts b/libs/backend/competition/assembly/src/services/validate/rules/team-subtitudes.rule.ts index 05c8461cf0..962fb61360 100644 --- a/libs/backend/competition/assembly/src/services/validate/rules/team-subtitudes.rule.ts +++ b/libs/backend/competition/assembly/src/services/validate/rules/team-subtitudes.rule.ts @@ -14,6 +14,8 @@ export type TeamSubsIndexRuleParams = { * Checks if the substitudes are not better then players from active team (titulars) */ export class TeamSubsIndexRule extends Rule { + static override description = 'all.rules.team-assembly.team-subs-index'; + async validate(assembly: AssemblyValidationData): Promise { const { meta, type, team, subtitudes, system } = assembly; const warnings = [] as AssemblyValidationError[]; diff --git a/libs/backend/competition/change-encounter/jest.config.ts b/libs/backend/competition/change-encounter/jest.config.ts index 86d64c4a7d..7254f07681 100644 --- a/libs/backend/competition/change-encounter/jest.config.ts +++ b/libs/backend/competition/change-encounter/jest.config.ts @@ -3,8 +3,6 @@ export default { // displayName: 'backend-change-encounter', preset: '../../../../jest.preset.js', testEnvironment: 'node', - passWithNoTests: true, - testTimeout: 1000 * 60 * 10, transform: { '^.+\\.[tj]s$': ['ts-jest', { tsconfig: '/tsconfig.spec.json' }], }, diff --git a/libs/backend/competition/change-encounter/package.json b/libs/backend/competition/change-encounter/package.json index 718f7d6a0d..83ef711c91 100644 --- a/libs/backend/competition/change-encounter/package.json +++ b/libs/backend/competition/change-encounter/package.json @@ -12,7 +12,8 @@ "moment": "^2.30.1", "sequelize": "^6.37.3", "tslib": "^2.6.2", - "moment-timezone": "^0.5.45" + "moment-timezone": "^0.5.45", + "@badman/backend-validation": "6.161.2" }, "type": "commonjs", "main": "./src/index.js", diff --git a/libs/backend/competition/change-encounter/src/services/validate/change-encounter.service.ts b/libs/backend/competition/change-encounter/src/services/validate/change-encounter.service.ts index 2845fcaf6a..2a583e3537 100644 --- a/libs/backend/competition/change-encounter/src/services/validate/change-encounter.service.ts +++ b/libs/backend/competition/change-encounter/src/services/validate/change-encounter.service.ts @@ -1,30 +1,41 @@ -import { EncounterCompetition, Team, Location } from '@badman/backend-database'; -import { Injectable, Logger } from '@nestjs/common'; +import { EncounterCompetition, Location, Team } from '@badman/backend-database'; +import { ValidationService } from '@badman/backend-validation'; +import { Logger } from '@nestjs/common'; import { Op } from 'sequelize'; import { ChangeEncounterOutput, ChangeEncounterValidationData, ChangeEncounterValidationError, } from '../../models'; -import { - DatePeriodRule, - ExceptionRule, - Rule, - SemesterRule, - TeamClubRule, - LocationRule, -} from './rules'; - -@Injectable() -export class ChangeEncounterValidationService { +import { DatePeriodRule, ExceptionRule, LocationRule, SemesterRule, TeamClubRule } from './rules'; + +export class ChangeEncounterValidationService extends ValidationService< + ChangeEncounterValidationData, + ChangeEncounterValidationError +> { + override group = 'change-encounter'; + private readonly _logger = new Logger(ChangeEncounterValidationService.name); - async getValidationData( - teamId: string, - workingencounterId?: string, - suggestedDates?: Date[], - ): Promise { - const team = await Team.findByPk(teamId, { + override async onApplicationBootstrap() { + this._logger.log('Initializing rules'); + await this.clearRules(); + + await this.registerRule(SemesterRule, SemesterRule.description); + await this.registerRule(DatePeriodRule, DatePeriodRule.description); + await this.registerRule(TeamClubRule, TeamClubRule.description); + await this.registerRule(ExceptionRule, ExceptionRule.description); + await this.registerRule(LocationRule, LocationRule.description, { activated: false }); + + this._logger.log('Rules initialized'); + } + + override async fetchData(args: { + teamId: string; + workingencounterId?: string; + suggestedDates?: Date[]; + }): Promise { + const team = await Team.findByPk(args.teamId, { attributes: ['id', 'name', 'type', 'teamNumber', 'clubId', 'season'], }); @@ -106,8 +117,8 @@ export class ChangeEncounterValidationService { lowestYear, - workingencounterId, - suggestedDates, + workingencounterId: args.workingencounterId, + suggestedDates: args.suggestedDates, }; } @@ -117,52 +128,16 @@ export class ChangeEncounterValidationService { * @param changeEncounter ChangeEncounter configuaration * @returns Whether the ChangeEncounter is valid or not */ - async validate( - changeEncounter: ChangeEncounterValidationData, - validators: Rule[], - ): Promise { - // get all errors and warnings from the validators in parallel - const results = await Promise.all(validators.map((v) => v.validate(changeEncounter))); - - const errors = results - ?.map((r) => r.errors) - ?.flat(1) - ?.filter((e) => !!e) as ChangeEncounterValidationError[]; - const warnings = results - ?.map((r) => r.warnings) - ?.flat(1) - ?.filter((e) => !!e) as ChangeEncounterValidationError[]; - - return { - valid: errors.length === 0, - errors: errors, - warnings: warnings, - }; - } - - async fetchAndValidate( - data: { - teamId: string; - workingencounterId?: string; - suggestedDates?: Date[]; - }, - validators: Rule[], + override async validate( + args: { teamId: string; workingencounterId?: string; suggestedDates?: Date[] }, + runFor?: { playerId?: string; teamId?: string; clubId?: string }, ) { - const dbData = await this.getValidationData( - data.teamId, - data.workingencounterId, - data.suggestedDates, - ); - return this.validate(dbData, validators); - } + const data = await super.validate(args, runFor); - static defaultValidators(): Rule[] { - return [ - new SemesterRule(), - new DatePeriodRule(), - new TeamClubRule(), - new ExceptionRule(), - // new LocationRule(), // BUG: LocationIds are wrong - ]; + return { + valid: data.valid, + errors: data.errors, + warnings: data.warnings, + } as ChangeEncounterOutput; } } diff --git a/libs/backend/competition/change-encounter/src/services/validate/rules/_rule.base.ts b/libs/backend/competition/change-encounter/src/services/validate/rules/_rule.base.ts index b9e12b4ddf..1f5e059ef8 100644 --- a/libs/backend/competition/change-encounter/src/services/validate/rules/_rule.base.ts +++ b/libs/backend/competition/change-encounter/src/services/validate/rules/_rule.base.ts @@ -1,5 +1,6 @@ +import { ValidationRule } from '@badman/backend-validation'; import { ChangeEncounterValidationData, ChangeEncounterOutput } from '../../../models'; -export abstract class Rule { - abstract validate(changeEncounter: ChangeEncounterValidationData): Promise; -} + + +export abstract class Rule extends ValidationRule {} diff --git a/libs/backend/competition/change-encounter/src/services/validate/rules/dates-in-period.ts b/libs/backend/competition/change-encounter/src/services/validate/rules/dates-in-period.ts index a3fe978e61..85083510fa 100644 --- a/libs/backend/competition/change-encounter/src/services/validate/rules/dates-in-period.ts +++ b/libs/backend/competition/change-encounter/src/services/validate/rules/dates-in-period.ts @@ -17,6 +17,7 @@ export type DatePeriodRuleParams = { * Checks if all encounters are in the correct period */ export class DatePeriodRule extends Rule { + static override description = 'all.rules.change-encounter.date-period'; private readonly logger = new Logger(DatePeriodRule.name); async validate(changeEncounter: ChangeEncounterValidationData): Promise { diff --git a/libs/backend/competition/change-encounter/src/services/validate/rules/exceptions.rule.ts b/libs/backend/competition/change-encounter/src/services/validate/rules/exceptions.rule.ts index 92b87c5d9e..c6fe054825 100644 --- a/libs/backend/competition/change-encounter/src/services/validate/rules/exceptions.rule.ts +++ b/libs/backend/competition/change-encounter/src/services/validate/rules/exceptions.rule.ts @@ -18,10 +18,11 @@ export type ExceptionRuleParams = { * Checks if encounters against the same team are in a different semester */ export class ExceptionRule extends Rule { + static override description = 'all.rules.change-encounter.exceptions'; private readonly logger = new Logger(ExceptionRule.name); async validate(changeEncounter: ChangeEncounterValidationData): Promise { - this.logger.verbose('Validating exception rule'); + this.logger.verbose('Validating rule'); const errors = [] as ChangeEncounterValidationError[]; const warnings = [] as ChangeEncounterValidationError[]; const valid = true; @@ -68,17 +69,17 @@ export class ExceptionRule extends Rule { } findEncountersOnExceptionDays(encounters: EncounterCompetition[], infoEvents: InfoEvent[]) { - const errors: ChangeEncounterValidationError[] = []; + const warms: ChangeEncounterValidationError[] = []; if (!infoEvents) { - return errors; + return warms; } for (const infoEvent of infoEvents) { if (!(infoEvent.allowCompetition ?? false)) { for (const encounter of encounters) { if (moment(encounter.date).isBetween(infoEvent.start, infoEvent.end, 'day', '[]')) { - errors.push({ + warms.push({ message: 'all.competition.change-encounter.errors.exception-day', params: { encounterId: encounter.id, @@ -90,6 +91,6 @@ export class ExceptionRule extends Rule { } } - return errors.flat(); + return warms.flat(); } } diff --git a/libs/backend/competition/change-encounter/src/services/validate/rules/location-free.rule.ts b/libs/backend/competition/change-encounter/src/services/validate/rules/location-free.rule.ts index 332bc572fc..0cc62efd05 100644 --- a/libs/backend/competition/change-encounter/src/services/validate/rules/location-free.rule.ts +++ b/libs/backend/competition/change-encounter/src/services/validate/rules/location-free.rule.ts @@ -17,10 +17,10 @@ export type LocationRuleParams = { * Checks if there are enough locations available for the encounters */ export class LocationRule extends Rule { + static override description = 'all.rules.change-encounter.location'; private readonly logger = new Logger(LocationRule.name); async validate(changeEncounter: ChangeEncounterValidationData): Promise { - this.logger.verbose('Validating exception rule'); const errors = [] as ChangeEncounterValidationError[]; const warnings = [] as ChangeEncounterValidationError[]; const valid = true; diff --git a/libs/backend/competition/change-encounter/src/services/validate/rules/semester.rule.ts b/libs/backend/competition/change-encounter/src/services/validate/rules/semester.rule.ts index c928fa17ad..b8c2a67e7b 100644 --- a/libs/backend/competition/change-encounter/src/services/validate/rules/semester.rule.ts +++ b/libs/backend/competition/change-encounter/src/services/validate/rules/semester.rule.ts @@ -16,6 +16,8 @@ export type SemesterRuleParams = { * Checks if encounters against the same team are in a different semester */ export class SemesterRule extends Rule { + static override description = 'all.rules.change-encounter.semseter'; + private readonly logger = new Logger(SemesterRule.name); async validate(changeEncounter: ChangeEncounterValidationData): Promise { diff --git a/libs/backend/competition/change-encounter/src/services/validate/rules/team-club.rule.ts b/libs/backend/competition/change-encounter/src/services/validate/rules/team-club.rule.ts index 7dc18e5b5b..3df2b18d6e 100644 --- a/libs/backend/competition/change-encounter/src/services/validate/rules/team-club.rule.ts +++ b/libs/backend/competition/change-encounter/src/services/validate/rules/team-club.rule.ts @@ -16,6 +16,7 @@ export type TeamClubRuleParams = { * Checks if encounters against the same team are in a different semester */ export class TeamClubRule extends Rule { + static override description = 'all.rules.change-encounter.team-club'; private readonly logger = new Logger(TeamClubRule.name); async validate(changeEncounter: ChangeEncounterValidationData): Promise { diff --git a/libs/backend/competition/enrollment/jest.config.ts b/libs/backend/competition/enrollment/jest.config.ts index 906ae8f0c3..a6ff4a551d 100644 --- a/libs/backend/competition/enrollment/jest.config.ts +++ b/libs/backend/competition/enrollment/jest.config.ts @@ -3,8 +3,6 @@ export default { // displayName: 'backend-enrollment', preset: '../../../../jest.preset.js', testEnvironment: 'node', - passWithNoTests: true, - testTimeout: 1000 * 60 * 10, transform: { '^.+\\.[tj]s$': ['ts-jest', { tsconfig: '/tsconfig.spec.json' }], }, diff --git a/libs/backend/competition/transfer-loans/jest.config.ts b/libs/backend/competition/transfer-loans/jest.config.ts index b757b50f0f..39d7b217e8 100644 --- a/libs/backend/competition/transfer-loans/jest.config.ts +++ b/libs/backend/competition/transfer-loans/jest.config.ts @@ -3,8 +3,6 @@ export default { // displayName: 'backend-transfer-loan', preset: '../../../../jest.preset.js', testEnvironment: 'node', - passWithNoTests: true, - testTimeout: 1000 * 60 * 10, transform: { '^.+\\.[tj]s$': ['ts-jest', { tsconfig: '/tsconfig.spec.json' }], }, diff --git a/libs/backend/compile/jest.config.ts b/libs/backend/compile/jest.config.ts index 826d1eca2f..b02dd51278 100644 --- a/libs/backend/compile/jest.config.ts +++ b/libs/backend/compile/jest.config.ts @@ -3,8 +3,6 @@ export default { // displayName: 'backend-compile', preset: '../../../jest.preset.js', testEnvironment: 'node', - passWithNoTests: true, - testTimeout: 1000 * 60 * 10, transform: { '^.+\\.[tj]s$': ['ts-jest', { tsconfig: '/tsconfig.spec.json' }], }, diff --git a/libs/backend/database/jest.config.ts b/libs/backend/database/jest.config.ts index 92a02efb2c..1cd306e619 100644 --- a/libs/backend/database/jest.config.ts +++ b/libs/backend/database/jest.config.ts @@ -3,8 +3,6 @@ export default { // displayName: 'backend-database', preset: '../../../jest.preset.js', testEnvironment: 'node', - passWithNoTests: true, - testTimeout: 1000 * 60 * 10, transform: { '^.+\\.[tj]s$': ['ts-jest', { tsconfig: '/tsconfig.spec.json' }], }, diff --git a/libs/backend/database/src/models/system/index.ts b/libs/backend/database/src/models/system/index.ts index f59bbcee02..650526f1cd 100644 --- a/libs/backend/database/src/models/system/index.ts +++ b/libs/backend/database/src/models/system/index.ts @@ -1,3 +1,4 @@ export * from './service.model'; export * from './cron-job.model'; export * from './logging.model'; +export * from './rule.model'; diff --git a/libs/backend/database/src/models/system/rule.model.ts b/libs/backend/database/src/models/system/rule.model.ts new file mode 100644 index 0000000000..8b17ea943d --- /dev/null +++ b/libs/backend/database/src/models/system/rule.model.ts @@ -0,0 +1,43 @@ +import { Field } from '@nestjs/graphql'; +import { CreationOptional, InferAttributes, InferCreationAttributes } from 'sequelize'; +import { Column, DataType, Default, IsUUID, Model, PrimaryKey, Table } from 'sequelize-typescript'; + +@Table({ + timestamps: true, + schema: 'system', + tableName: 'Rules', +}) +export class Rule extends Model, InferCreationAttributes> { + @Default(DataType.UUIDV4) + @IsUUID(4) + @PrimaryKey + @Column(DataType.UUIDV4) + declare id: CreationOptional; + + declare updatedAt?: Date; + declare createdAt?: Date; + + + @Field(() => String) + @Column(DataType.STRING) + group!: string; + + @Field(() => String) + @Column(DataType.STRING) + name!: string; + + @Field(() => String) + @Column(DataType.STRING) + description!: string; + + @Field(() => Boolean) + @Column(DataType.BOOLEAN) + activated!: boolean; + + // @Field(() => String, { nullable: true }) + @Column({ + type: DataType.JSONB, + }) + meta?: unknown +} + diff --git a/libs/backend/database/src/provider/config.ts b/libs/backend/database/src/provider/config.ts index df12143779..3cf7b8bde5 100644 --- a/libs/backend/database/src/provider/config.ts +++ b/libs/backend/database/src/provider/config.ts @@ -70,7 +70,7 @@ export class SequelizeConfigProvider implements SequelizeOptionsFactory { }); } - // options.logging = true; + // options.logging = false; options.models = models; diff --git a/libs/backend/database/src/types/rule-meta.type.ts b/libs/backend/database/src/types/rule-meta.type.ts new file mode 100644 index 0000000000..2ca64342c5 --- /dev/null +++ b/libs/backend/database/src/types/rule-meta.type.ts @@ -0,0 +1,22 @@ +import { Field, ObjectType } from '@nestjs/graphql'; + +@ObjectType({ description: 'A AssemblyType' }) +export class RuleMetaType { + @Field(() => [String]) + activatedForUsers?: string[]; + + @Field(() => [String]) + activatedForTeams?: string[]; + + @Field(() => [String]) + activatedForClubs?: string[]; + + @Field(() => [String]) + deActivatedForUsers?: string[]; + + @Field(() => [String]) + deActivatedForTeams?: string[]; + + @Field(() => [String]) + deActivatedForClubs?: string[]; +} diff --git a/libs/backend/generator/jest.config.ts b/libs/backend/generator/jest.config.ts index 1afc691cbc..813fe40ae3 100644 --- a/libs/backend/generator/jest.config.ts +++ b/libs/backend/generator/jest.config.ts @@ -3,8 +3,6 @@ export default { // displayName: 'backend-generator', preset: '../../../jest.preset.js', testEnvironment: 'node', - passWithNoTests: true, - testTimeout: 1000 * 60 * 10, transform: { '^.+\\.[tj]s$': ['ts-jest', { tsconfig: '/tsconfig.spec.json' }], }, diff --git a/libs/backend/graphql/jest.config.ts b/libs/backend/graphql/jest.config.ts index 3659e7c0b1..fe207d7a82 100644 --- a/libs/backend/graphql/jest.config.ts +++ b/libs/backend/graphql/jest.config.ts @@ -3,8 +3,6 @@ export default { // displayName: 'backend-graphql', preset: '../../../jest.preset.js', testEnvironment: 'node', - passWithNoTests: true, - testTimeout: 1000 * 60 * 10, transform: { '^.+\\.[tj]s$': ['ts-jest', { tsconfig: '/tsconfig.spec.json' }], }, diff --git a/libs/backend/graphql/src/resolvers/event/competition/assembly.resolver.ts b/libs/backend/graphql/src/resolvers/event/competition/assembly.resolver.ts index b7eb6ae627..f1a3d880e2 100644 --- a/libs/backend/graphql/src/resolvers/event/competition/assembly.resolver.ts +++ b/libs/backend/graphql/src/resolvers/event/competition/assembly.resolver.ts @@ -19,11 +19,11 @@ export class AssemblyResolver { @Query(() => AssemblyOutput, { description: `Validate the assembly\n\r**note**: the levels are the ones from may!`, }) - async validateAssembly(@Args('assembly') assembly: AssemblyInput): Promise { - return this.assemblyService.fetchAndValidate( - assembly, - AssemblyValidationService.defaultValidators(), - ); + async validateAssembly( + @User() user: Player, + @Args('assembly') assembly: AssemblyInput, + ): Promise { + return this.assemblyService.validate(assembly, { playerId: user.id }); } @ResolveField(() => [PlayerRankingType]) diff --git a/libs/backend/graphql/src/resolvers/event/competition/encounter-change.resolver.ts b/libs/backend/graphql/src/resolvers/event/competition/encounter-change.resolver.ts index d59f3fc498..ed31a6da9c 100644 --- a/libs/backend/graphql/src/resolvers/event/competition/encounter-change.resolver.ts +++ b/libs/backend/graphql/src/resolvers/event/competition/encounter-change.resolver.ts @@ -83,10 +83,7 @@ export class EncounterChangeCompetitionResolver { async validateChangeEncounter( @Args('ChangeEncounter') data: ChangeEncounterInput, ): Promise { - return this.changeEncounterService.fetchAndValidate( - data, - ChangeEncounterValidationService.defaultValidators(), - ); + return this.changeEncounterService.validate(data); } @ResolveField(() => [EncounterChangeDate]) @@ -257,15 +254,20 @@ export class EncounterChangeCompetitionResolver { } // Notify the user + const updatedEncounter = await EncounterCompetition.findByPk(newChangeEncounter.encounterId); + if (!updatedEncounter) { + throw new NotFoundException(newChangeEncounter.encounterId); + } + if (newChangeEncounter.accepted) { - this.notificationService.notifyEncounterChangeFinished(encounter, locationHasChanged); + this.notificationService.notifyEncounterChangeFinished(updatedEncounter, locationHasChanged); // check if the location has changed if (locationHasChanged) { // this.notificationService.notifyEncounterLocationChanged(encounter); } } else { - this.notificationService.notifyEncounterChange(encounter, newChangeEncounter.home ?? false); + this.notificationService.notifyEncounterChange(updatedEncounter, newChangeEncounter.home ?? false); } return encounterChange; diff --git a/libs/backend/health/jest.config.ts b/libs/backend/health/jest.config.ts index 584a3e8d64..8e70ae2a12 100644 --- a/libs/backend/health/jest.config.ts +++ b/libs/backend/health/jest.config.ts @@ -3,8 +3,6 @@ export default { // displayName: 'backend-health', preset: '../../../jest.preset.js', testEnvironment: 'node', - passWithNoTests: true, - testTimeout: 1000 * 60 * 10, transform: { '^.+\\.[tj]s$': ['ts-jest', { tsconfig: '/tsconfig.spec.json' }], }, diff --git a/libs/backend/logging/jest.config.ts b/libs/backend/logging/jest.config.ts index 62f2c33dfa..8af05a81d9 100644 --- a/libs/backend/logging/jest.config.ts +++ b/libs/backend/logging/jest.config.ts @@ -3,8 +3,6 @@ export default { // displayName: 'backend-logging', preset: '../../../jest.preset.js', testEnvironment: 'node', - passWithNoTests: true, - testTimeout: 1000 * 60 * 10, transform: { '^.+\\.[tj]s$': ['ts-jest', { tsconfig: '/tsconfig.spec.json' }], }, diff --git a/libs/backend/mailing/jest.config.ts b/libs/backend/mailing/jest.config.ts index df7f5d8cad..88b05f3ef7 100644 --- a/libs/backend/mailing/jest.config.ts +++ b/libs/backend/mailing/jest.config.ts @@ -3,8 +3,6 @@ export default { // displayName: 'backend-mailing', preset: '../../../jest.preset.js', testEnvironment: 'node', - passWithNoTests: true, - testTimeout: 1000 * 60 * 10, transform: { '^.+\\.[tj]s$': ['ts-jest', { tsconfig: '/tsconfig.spec.json' }], }, diff --git a/libs/backend/micro/jest.config.ts b/libs/backend/micro/jest.config.ts index b40255aeaf..c413863905 100644 --- a/libs/backend/micro/jest.config.ts +++ b/libs/backend/micro/jest.config.ts @@ -3,8 +3,6 @@ export default { // displayName: 'backend-micro', preset: '../../../jest.preset.js', testEnvironment: 'node', - passWithNoTests: true, - testTimeout: 1000 * 60 * 10, transform: { '^.+\\.[tj]s$': ['ts-jest', { tsconfig: '/tsconfig.spec.json' }], }, diff --git a/libs/backend/notifications/jest.config.ts b/libs/backend/notifications/jest.config.ts index 0e12ff1c7c..441136d1ea 100644 --- a/libs/backend/notifications/jest.config.ts +++ b/libs/backend/notifications/jest.config.ts @@ -3,8 +3,6 @@ export default { // displayName: 'backend-notifications', preset: '../../../jest.preset.js', testEnvironment: 'node', - passWithNoTests: true, - testTimeout: 1000 * 60 * 10, transform: { '^.+\\.[tj]s$': ['ts-jest', { tsconfig: '/tsconfig.spec.json' }], }, diff --git a/libs/backend/notifications/src/services/notification/notification.service.ts b/libs/backend/notifications/src/services/notification/notification.service.ts index 67de886fc3..f66c97dc75 100644 --- a/libs/backend/notifications/src/services/notification/notification.service.ts +++ b/libs/backend/notifications/src/services/notification/notification.service.ts @@ -122,7 +122,7 @@ export class NotificationService { encounter.away = awayTeam; if (homeTeam.captain && homeTeam.email) { - const validation = await this._getValidationMessage(homeTeam.id); + const validation = await this._getValidationMessage(homeTeam); notifierFinished.notify( homeTeam.captain, encounter.id, @@ -132,7 +132,7 @@ export class NotificationService { } if (awayTeam.captain && awayTeam.email) { - const validation = await this._getValidationMessage(awayTeam.id); + const validation = await this._getValidationMessage(awayTeam); notifierFinished.notify( awayTeam.captain, @@ -382,10 +382,10 @@ export class NotificationService { } } - private async _getValidationMessage(teamId: string) { - const validation = await this.changeEncounterValidation.fetchAndValidate( - { teamId }, - ChangeEncounterValidationService.defaultValidators(), + private async _getValidationMessage(team: Team, captainId?: string) { + const validation = await this.changeEncounterValidation.validate( + { teamId: team.id }, + { playerId: captainId, teamId: team.id, clubId: team.clubId }, ); if (validation.valid) { diff --git a/libs/backend/orchestrator/jest.config.ts b/libs/backend/orchestrator/jest.config.ts index 7672e3c401..c1a282ba75 100644 --- a/libs/backend/orchestrator/jest.config.ts +++ b/libs/backend/orchestrator/jest.config.ts @@ -3,8 +3,6 @@ export default { // displayName: 'backend-orchestrator', preset: '../../../jest.preset.js', testEnvironment: 'node', - passWithNoTests: true, - testTimeout: 1000 * 60 * 10, transform: { '^.+\\.[tj]s$': ['ts-jest', { tsconfig: '/tsconfig.spec.json' }], }, diff --git a/libs/backend/pupeteer/jest.config.ts b/libs/backend/pupeteer/jest.config.ts index 3cdd1bb8ca..c0f8e51b0d 100644 --- a/libs/backend/pupeteer/jest.config.ts +++ b/libs/backend/pupeteer/jest.config.ts @@ -3,8 +3,6 @@ export default { // displayName: 'backend-pupeteer', preset: '../../../jest.preset.js', testEnvironment: 'node', - passWithNoTests: true, - testTimeout: 1000 * 60 * 10, transform: { '^.+\\.[tj]s$': ['ts-jest', { tsconfig: '/tsconfig.spec.json' }], }, diff --git a/libs/backend/queue/jest.config.ts b/libs/backend/queue/jest.config.ts index fb31676d35..6b9fc50d91 100644 --- a/libs/backend/queue/jest.config.ts +++ b/libs/backend/queue/jest.config.ts @@ -3,8 +3,6 @@ export default { // displayName: 'backend-queue', preset: '../../../jest.preset.js', testEnvironment: 'node', - passWithNoTests: true, - testTimeout: 1000 * 60 * 10, transform: { '^.+\\.[tj]s$': ['ts-jest', { tsconfig: '/tsconfig.spec.json' }], }, diff --git a/libs/backend/ranking/jest.config.ts b/libs/backend/ranking/jest.config.ts index a4d9f175fa..481c47fbe1 100644 --- a/libs/backend/ranking/jest.config.ts +++ b/libs/backend/ranking/jest.config.ts @@ -3,8 +3,6 @@ export default { // displayName: 'backend-ranking', preset: '../../../jest.preset.js', testEnvironment: 'node', - passWithNoTests: true, - testTimeout: 1000 * 60 * 10, transform: { '^.+\\.[tj]s$': ['ts-jest', { tsconfig: '/tsconfig.spec.json' }], }, diff --git a/libs/backend/search/jest.config.ts b/libs/backend/search/jest.config.ts index 7b6e05fe3d..b66069a2b8 100644 --- a/libs/backend/search/jest.config.ts +++ b/libs/backend/search/jest.config.ts @@ -3,8 +3,6 @@ export default { // displayName: 'backend-search', preset: '../../../jest.preset.js', testEnvironment: 'node', - passWithNoTests: true, - testTimeout: 1000 * 60 * 10, transform: { '^.+\\.[tj]s$': ['ts-jest', { tsconfig: '/tsconfig.spec.json' }], }, diff --git a/libs/backend/translate/jest.config.ts b/libs/backend/translate/jest.config.ts index 635325258a..40f158b4bd 100644 --- a/libs/backend/translate/jest.config.ts +++ b/libs/backend/translate/jest.config.ts @@ -3,8 +3,6 @@ export default { // displayName: 'backend-translate', preset: '../../../jest.preset.js', testEnvironment: 'node', - passWithNoTests: true, - testTimeout: 1000 * 60 * 10, transform: { '^.+\\.[tj]s$': ['ts-jest', { tsconfig: '/tsconfig.spec.json' }], }, diff --git a/libs/backend/twizzit/jest.config.ts b/libs/backend/twizzit/jest.config.ts index e0fe5ad945..66884084ef 100644 --- a/libs/backend/twizzit/jest.config.ts +++ b/libs/backend/twizzit/jest.config.ts @@ -3,8 +3,6 @@ export default { // displayName: 'backend-twizzit', preset: '../../../jest.preset.js', testEnvironment: 'node', - passWithNoTests: true, - testTimeout: 1000 * 60 * 10, transform: { '^.+\\.[tj]s$': ['ts-jest', { tsconfig: '/tsconfig.spec.json' }], }, diff --git a/libs/backend/utils/jest.config.ts b/libs/backend/utils/jest.config.ts index 98295cef68..83883821dc 100644 --- a/libs/backend/utils/jest.config.ts +++ b/libs/backend/utils/jest.config.ts @@ -3,8 +3,6 @@ export default { // displayName: 'backend-utils', preset: '../../../jest.preset.js', testEnvironment: 'node', - passWithNoTests: true, - testTimeout: 1000 * 60 * 10, transform: { '^.+\\.[tj]s$': ['ts-jest', { tsconfig: '/tsconfig.spec.json' }], }, diff --git a/libs/backend/validation/.swcrc b/libs/backend/validation/.swcrc new file mode 100644 index 0000000000..d54df2b947 --- /dev/null +++ b/libs/backend/validation/.swcrc @@ -0,0 +1,29 @@ +{ + "jsc": { + "target": "es2017", + "parser": { + "syntax": "typescript", + "decorators": true, + "dynamicImport": true + }, + "transform": { + "decoratorMetadata": true, + "legacyDecorator": true + }, + "keepClassNames": true, + "externalHelpers": true, + "loose": true + }, + "module": { + "type": "commonjs" + }, + "sourceMaps": true, + "exclude": [ + "jest.config.ts", + ".*\\.spec.tsx?$", + ".*\\.test.tsx?$", + "./src/jest-setup.ts$", + "./**/jest-setup.ts$", + ".*.js$" + ] +} diff --git a/libs/backend/validation/README.md b/libs/backend/validation/README.md new file mode 100644 index 0000000000..c1c6e2ba8f --- /dev/null +++ b/libs/backend/validation/README.md @@ -0,0 +1,11 @@ +# backend-validation + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build backend-validation` to build the library. + +## Running unit tests + +Run `nx test backend-validation` to execute the unit tests via [Jest](https://jestjs.io). diff --git a/libs/backend/validation/eslint.config.js b/libs/backend/validation/eslint.config.js new file mode 100644 index 0000000000..2a275669ec --- /dev/null +++ b/libs/backend/validation/eslint.config.js @@ -0,0 +1,32 @@ +const { FlatCompat } = require('@eslint/eslintrc'); +const baseConfig = require('../../../eslint.config.js'); +const js = require('@eslint/js'); + +const compat = new FlatCompat({ + baseDirectory: __dirname, + recommendedConfig: js.configs.recommended, +}); + +module.exports = [ + ...baseConfig, + { + files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'], + rules: {}, + }, + { + files: ['**/*.ts', '**/*.tsx'], + rules: {}, + }, + { + files: ['**/*.js', '**/*.jsx'], + rules: {}, + }, + ...compat.config({ parser: 'jsonc-eslint-parser' }).map((config) => ({ + ...config, + files: ['**/*.json'], + rules: { + ...config.rules, + '@nx/dependency-checks': 'error', + }, + })), +]; diff --git a/libs/backend/validation/jest.config.ts b/libs/backend/validation/jest.config.ts new file mode 100644 index 0000000000..6561fafa8b --- /dev/null +++ b/libs/backend/validation/jest.config.ts @@ -0,0 +1,11 @@ + +export default { + // displayName: 'backend-validation', + preset: '../../../jest.preset.js', + testEnvironment: 'node', + transform: { + '^.+\\.[tj]s$': ['ts-jest', { tsconfig: '/tsconfig.spec.json' }], + }, + moduleFileExtensions: ['ts', 'js', 'html'], + coverageDirectory: '../../../coverage/libs/backend/validation', +}; diff --git a/libs/backend/validation/package.json b/libs/backend/validation/package.json new file mode 100644 index 0000000000..829c1ff57c --- /dev/null +++ b/libs/backend/validation/package.json @@ -0,0 +1,13 @@ +{ + "name": "@badman/backend-validation", + "version": "6.161.2", + "dependencies": { + "@nestjs/common": "^10.3.8", + "@swc/helpers": "^0.5.11", + "@badman/backend-database": "6.161.4" + }, + "type": "commonjs", + "main": "./src/index.js", + "typings": "./src/index.d.ts", + "devDependencies": {} +} diff --git a/libs/backend/validation/project.json b/libs/backend/validation/project.json new file mode 100644 index 0000000000..97df40cbbe --- /dev/null +++ b/libs/backend/validation/project.json @@ -0,0 +1,33 @@ +{ + "name": "backend-validation", + "$schema": "../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "libs/backend/validation/src", + "projectType": "library", + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:swc", + "outputs": ["{options.outputPath}"], + "options": { + "outputPath": "{workspaceRoot}/dist/{projectRoot}", + "tsConfig": "{projectRoot}/tsconfig.lib.json", + "packageJson": "{projectRoot}/package.json", + "main": "{projectRoot}/src/index.ts", + "assets": ["{projectRoot/*.md"] + } + }, + "test": { + "executor": "@nx/jest:jest", + "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], + "options": { + "jestConfig": "{projectRoot}/jest.config.ts" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "options": { + "eslintConfig": "{projectRoot}/eslint.config.js" + } + } + } +} diff --git a/libs/backend/validation/src/index.ts b/libs/backend/validation/src/index.ts new file mode 100644 index 0000000000..82bbf96e0d --- /dev/null +++ b/libs/backend/validation/src/index.ts @@ -0,0 +1,2 @@ +export * from './validation.module'; +export * from './rules' \ No newline at end of file diff --git a/libs/backend/validation/src/rules/_rule.base.ts b/libs/backend/validation/src/rules/_rule.base.ts new file mode 100644 index 0000000000..fdd95ea0e2 --- /dev/null +++ b/libs/backend/validation/src/rules/_rule.base.ts @@ -0,0 +1,8 @@ +export abstract class ValidationRule { + // a description of the rule + // abstract description: string; + + static description: string; + + abstract validate(assembly: T): Promise; +} diff --git a/libs/backend/validation/src/rules/index.ts b/libs/backend/validation/src/rules/index.ts new file mode 100644 index 0000000000..c9f9c6a812 --- /dev/null +++ b/libs/backend/validation/src/rules/index.ts @@ -0,0 +1,2 @@ +export * from './validation.service'; +export * from './_rule.base'; diff --git a/libs/backend/validation/src/rules/validation.service.ts b/libs/backend/validation/src/rules/validation.service.ts new file mode 100644 index 0000000000..3fe2281b33 --- /dev/null +++ b/libs/backend/validation/src/rules/validation.service.ts @@ -0,0 +1,150 @@ +import { Rule } from '@badman/backend-database'; +import { Injectable, Logger, OnApplicationBootstrap } from '@nestjs/common'; +import { ValidationRule } from './_rule.base'; + +type ruleType = new () => ValidationRule< + T, + Partial<{ + valid: boolean; + errors?: V[]; + warnings?: V[]; + }> & + Partial +>; + +@Injectable() +export abstract class ValidationService implements OnApplicationBootstrap { + private readonly logger = new Logger(ValidationService.name); + private rules: Map> = new Map(); + + abstract onApplicationBootstrap(): Promise; + abstract group: string; + + abstract fetchData(args?: unknown): Promise; + + async registerRule( + rule: ruleType, + description: string, + args?: { meta?: object; activated?: boolean }, + ): Promise { + // find or create rule + await Rule.findOrCreate({ + where: { + group: this.group, + name: rule.name, + }, + defaults: { + group: this.group, + name: rule.name, + description: description, + activated: args?.activated ?? true, + meta: { + activatedForUsers: [], + activatedForTeams: [], + activatedForClubs: [], + + deactivatedForUsers: [], + deactivatedForTeams: [], + deactivatedForClubs: [], + ...(args?.meta ?? {}), + }, + }, + }); + + this.rules.set(`${this.group}_${rule.name}`, rule); + } + + async clearRules(): Promise { + this.rules.clear(); + } + + async validate( + args: unknown, + runFor?: { + playerId?: string; + teamId?: string; + clubId?: string; + }, + ): Promise< + Partial<{ + valid: boolean; + errors?: V[]; + warnings?: V[]; + }> & + Partial + > { + const configuredRules = await Rule.findAll({ + where: { + group: this.group, + }, + }); + + const activatedRules = configuredRules.filter((r) => r.activated); + + configuredRules + .filter((r) => !r.activated) + .filter((r) => { + const meta = r.meta as { + activatedForUsers: string[]; + activatedForTeams: string[]; + activatedForClubs: string[]; + + deactivatedForUsers: string[]; + deactivatedForTeams: string[]; + deactivatedForClubs: string[]; + }; + + return ( + ((runFor?.playerId && meta.activatedForUsers.includes(runFor.playerId)) || + (runFor?.teamId && meta.activatedForTeams.includes(runFor.teamId)) || + (runFor?.clubId && meta.activatedForClubs.includes(runFor.clubId))) && + !( + (runFor?.playerId && meta.deactivatedForUsers.includes(runFor.playerId)) || + (runFor?.teamId && meta.deactivatedForTeams.includes(runFor.teamId)) || + (runFor?.clubId && meta.deactivatedForClubs.includes(runFor.clubId)) + ) + ); + }) + .map((r) => activatedRules.push(r)); + + this.logger.verbose(`Found ${configuredRules.length} rules for group ${this.group}`); + + // fetch all rules for the group + const validators = configuredRules + .filter((r) => this.rules.has(`${this.group}_${r.name}`)) + .map((r) => { + const rule = this.rules.get(`${this.group}_${r.name}`); + + if (!rule) { + throw new Error(`Rule ${r.name} not found`); + } + + return new rule(); + }); + + // fetch data + const data = await this.fetchData(args); + + // // get all errors and warnings from the validators in parallel + const results = await Promise.all(validators.map((v) => v.validate(data))); + + const errors = results + ?.map((r) => r.errors) + ?.flat(1) + ?.filter((e) => !!e) as V[]; + const warnings = results + ?.map((r) => r.warnings) + ?.flat(1) + ?.filter((e) => !!e) as V[]; + + return { + valid: errors.length === 0, + errors: errors, + warnings: warnings, + // valid: true, + // errors: [], + // warnings: [], + ...(data as T), + }; + } +} diff --git a/libs/backend/validation/src/validation.module.ts b/libs/backend/validation/src/validation.module.ts new file mode 100644 index 0000000000..c25de77c0d --- /dev/null +++ b/libs/backend/validation/src/validation.module.ts @@ -0,0 +1,8 @@ +import { Module } from '@nestjs/common'; + +@Module({ + controllers: [], + providers: [], + exports: [], +}) +export class ValidationModule {} diff --git a/libs/backend/validation/tsconfig.json b/libs/backend/validation/tsconfig.json new file mode 100644 index 0000000000..8122543a9a --- /dev/null +++ b/libs/backend/validation/tsconfig.json @@ -0,0 +1,22 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + }, + { + "path": "./tsconfig.spec.json" + } + ] +} diff --git a/libs/backend/validation/tsconfig.lib.json b/libs/backend/validation/tsconfig.lib.json new file mode 100644 index 0000000000..39e4f3ad95 --- /dev/null +++ b/libs/backend/validation/tsconfig.lib.json @@ -0,0 +1,16 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../../dist/out-tsc", + "declaration": true, + "types": ["node"], + "target": "es2021", + "strictNullChecks": true, + "noImplicitAny": true, + "strictBindCallApply": true, + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["**/*.ts"], + "exclude": ["jest.config.ts", "**/*.spec.ts", "**/*.test.ts"] +} diff --git a/libs/backend/validation/tsconfig.spec.json b/libs/backend/validation/tsconfig.spec.json new file mode 100644 index 0000000000..b2ee74a6b1 --- /dev/null +++ b/libs/backend/validation/tsconfig.spec.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../../dist/out-tsc", + "module": "commonjs", + "types": ["jest", "node"] + }, + "include": ["jest.config.ts", "src/**/*.test.ts", "src/**/*.spec.ts", "src/**/*.d.ts"] +} diff --git a/libs/backend/visual/jest.config.ts b/libs/backend/visual/jest.config.ts index 9360c9cd49..c85d148287 100644 --- a/libs/backend/visual/jest.config.ts +++ b/libs/backend/visual/jest.config.ts @@ -3,8 +3,6 @@ export default { // displayName: 'backend-visual', preset: '../../../jest.preset.js', testEnvironment: 'node', - passWithNoTests: true, - testTimeout: 1000 * 60 * 10, transform: { '^.+\\.[tj]s$': ['ts-jest', { tsconfig: '/tsconfig.spec.json' }], }, diff --git a/libs/backend/websockets/jest.config.ts b/libs/backend/websockets/jest.config.ts index 4be3ab9fc8..0086b263b4 100644 --- a/libs/backend/websockets/jest.config.ts +++ b/libs/backend/websockets/jest.config.ts @@ -3,8 +3,6 @@ export default { // displayName: 'backend-websockets', preset: '../../../jest.preset.js', testEnvironment: 'node', - passWithNoTests: true, - testTimeout: 1000 * 60 * 10, transform: { '^.+\\.[tj]s$': ['ts-jest', { tsconfig: '/tsconfig.spec.json' }], }, diff --git a/libs/frontend/components/jest.config.ts b/libs/frontend/components/jest.config.ts index 3455fa39b0..741b22e2e0 100644 --- a/libs/frontend/components/jest.config.ts +++ b/libs/frontend/components/jest.config.ts @@ -3,8 +3,6 @@ export default { // displayName: 'frontend-components', preset: '../../../jest.preset.js', setupFilesAfterEnv: ['/src/test-setup.ts'], - passWithNoTests: true, - testTimeout: 1000 * 60 * 10, coverageDirectory: '../../../coverage/libs/frontend/components', transform: { '^.+\\.(ts|mjs|js|html)$': [ diff --git a/libs/frontend/components/src/shell/shell/shell.component.html b/libs/frontend/components/src/shell/shell/shell.component.html index 1fcbd9c511..caab2daea3 100644 --- a/libs/frontend/components/src/shell/shell/shell.component.html +++ b/libs/frontend/components/src/shell/shell/shell.component.html @@ -128,6 +128,14 @@ >{{ 'all.shell.sidebar.transfer.title' | translate }} + + {{ 'all.shell.sidebar.rules.title' | translate }} + diff --git a/libs/frontend/models/jest.config.ts b/libs/frontend/models/jest.config.ts index a7147e61ff..f7b1979700 100644 --- a/libs/frontend/models/jest.config.ts +++ b/libs/frontend/models/jest.config.ts @@ -3,8 +3,6 @@ export default { // displayName: 'frontend-models', preset: '../../../jest.preset.js', setupFilesAfterEnv: ['/src/test-setup.ts'], - passWithNoTests: true, - testTimeout: 1000 * 60 * 10, coverageDirectory: '../../../coverage/libs/frontend/models', transform: { '^.+\\.(ts|mjs|js|html)$': [ diff --git a/libs/frontend/modules/auth/jest.config.ts b/libs/frontend/modules/auth/jest.config.ts index 086b4b891a..7a5a47ee33 100644 --- a/libs/frontend/modules/auth/jest.config.ts +++ b/libs/frontend/modules/auth/jest.config.ts @@ -3,8 +3,6 @@ export default { // displayName: 'frontend-auth', preset: '../../../../jest.preset.js', setupFilesAfterEnv: ['/src/test-setup.ts'], - passWithNoTests: true, - testTimeout: 1000 * 60 * 10, coverageDirectory: '../../../../coverage/libs/frontend/modules/auth', transform: { '^.+\\.(ts|mjs|js|html)$': [ diff --git a/libs/frontend/modules/cp/jest.config.ts b/libs/frontend/modules/cp/jest.config.ts index 5f1f74f2ef..7c2a815fbd 100644 --- a/libs/frontend/modules/cp/jest.config.ts +++ b/libs/frontend/modules/cp/jest.config.ts @@ -3,8 +3,6 @@ export default { // displayName: 'frontend-cp', preset: '../../../../jest.preset.js', setupFilesAfterEnv: ['/src/test-setup.ts'], - passWithNoTests: true, - testTimeout: 1000 * 60 * 10, coverageDirectory: '../../../../coverage/libs/frontend/modules/cp', transform: { '^.+\\.(ts|mjs|js|html)$': [ diff --git a/libs/frontend/modules/excel/jest.config.ts b/libs/frontend/modules/excel/jest.config.ts index 535c7df76c..40ad4dee43 100644 --- a/libs/frontend/modules/excel/jest.config.ts +++ b/libs/frontend/modules/excel/jest.config.ts @@ -3,8 +3,6 @@ export default { // displayName: 'frontend-excel', preset: '../../../../jest.preset.js', setupFilesAfterEnv: ['/src/test-setup.ts'], - passWithNoTests: true, - testTimeout: 1000 * 60 * 10, coverageDirectory: '../../../../coverage/libs/frontend/modules/excel', transform: { '^.+\\.(ts|mjs|js|html)$': [ diff --git a/libs/frontend/modules/graphql/jest.config.ts b/libs/frontend/modules/graphql/jest.config.ts index fb868f3c7c..24e3278afb 100644 --- a/libs/frontend/modules/graphql/jest.config.ts +++ b/libs/frontend/modules/graphql/jest.config.ts @@ -3,8 +3,6 @@ export default { // displayName: 'frontend-graphql', preset: '../../../../jest.preset.js', setupFilesAfterEnv: ['/src/test-setup.ts'], - passWithNoTests: true, - testTimeout: 1000 * 60 * 10, coverageDirectory: '../../../../coverage/libs/frontend/modules/graphql', transform: { '^.+\\.(ts|mjs|js|html)$': [ diff --git a/libs/frontend/modules/html-injects/jest.config.ts b/libs/frontend/modules/html-injects/jest.config.ts index bb71bf88a5..11b8b1d7ce 100644 --- a/libs/frontend/modules/html-injects/jest.config.ts +++ b/libs/frontend/modules/html-injects/jest.config.ts @@ -3,8 +3,6 @@ export default { // displayName: 'frontend-html-injects', preset: '../../../../jest.preset.js', setupFilesAfterEnv: ['/src/test-setup.ts'], - passWithNoTests: true, - testTimeout: 1000 * 60 * 10, coverageDirectory: '../../../../coverage/libs/frontend/modules/html-injects', transform: { '^.+\\.(ts|mjs|js|html)$': [ diff --git a/libs/frontend/modules/pdf/jest.config.ts b/libs/frontend/modules/pdf/jest.config.ts index 4a1815461c..247a3f2161 100644 --- a/libs/frontend/modules/pdf/jest.config.ts +++ b/libs/frontend/modules/pdf/jest.config.ts @@ -3,8 +3,6 @@ export default { // displayName: 'frontend-pdf', preset: '../../../../jest.preset.js', setupFilesAfterEnv: ['/src/test-setup.ts'], - passWithNoTests: true, - testTimeout: 1000 * 60 * 10, coverageDirectory: '../../../../coverage/libs/frontend/modules/pdf', transform: { '^.+\\.(ts|mjs|js|html)$': [ diff --git a/libs/frontend/modules/queue/jest.config.ts b/libs/frontend/modules/queue/jest.config.ts index b3a5e2b34c..17fe90eab5 100644 --- a/libs/frontend/modules/queue/jest.config.ts +++ b/libs/frontend/modules/queue/jest.config.ts @@ -3,8 +3,6 @@ export default { // displayName: 'frontend-queue', preset: '../../../../jest.preset.js', setupFilesAfterEnv: ['/src/test-setup.ts'], - passWithNoTests: true, - testTimeout: 1000 * 60 * 10, coverageDirectory: '../../../../coverage/libs/frontend/modules/queue', transform: { '^.+\\.(ts|mjs|js|html)$': [ diff --git a/libs/frontend/modules/seo/jest.config.ts b/libs/frontend/modules/seo/jest.config.ts index 386f55297f..efc825846e 100644 --- a/libs/frontend/modules/seo/jest.config.ts +++ b/libs/frontend/modules/seo/jest.config.ts @@ -3,8 +3,6 @@ export default { // displayName: 'frontend-seo', preset: '../../../../jest.preset.js', setupFilesAfterEnv: ['/src/test-setup.ts'], - passWithNoTests: true, - testTimeout: 1000 * 60 * 10, coverageDirectory: '../../../../coverage/libs/frontend/modules/seo', transform: { '^.+\\.(ts|mjs|js|html)$': [ diff --git a/libs/frontend/modules/translation/jest.config.ts b/libs/frontend/modules/translation/jest.config.ts index d711aef182..6aa010cf35 100644 --- a/libs/frontend/modules/translation/jest.config.ts +++ b/libs/frontend/modules/translation/jest.config.ts @@ -3,8 +3,6 @@ export default { // displayName: 'frontend-translation', preset: '../../../../jest.preset.js', setupFilesAfterEnv: ['/src/test-setup.ts'], - passWithNoTests: true, - testTimeout: 1000 * 60 * 10, coverageDirectory: '../../../../coverage/libs/frontend/modules/translation', transform: { '^.+\\.(ts|mjs|js|html)$': [ diff --git a/libs/frontend/modules/twizzit/jest.config.ts b/libs/frontend/modules/twizzit/jest.config.ts index 5cf9f2504e..432a7fd245 100644 --- a/libs/frontend/modules/twizzit/jest.config.ts +++ b/libs/frontend/modules/twizzit/jest.config.ts @@ -3,8 +3,6 @@ export default { // displayName: 'frontend-twizzit', preset: '../../../../jest.preset.js', setupFilesAfterEnv: ['/src/test-setup.ts'], - passWithNoTests: true, - testTimeout: 1000 * 60 * 10, coverageDirectory: '../../../../coverage/libs/frontend/modules/twizzit', transform: { '^.+\\.(ts|mjs|js|html)$': [ diff --git a/libs/frontend/modules/utils/jest.config.ts b/libs/frontend/modules/utils/jest.config.ts index 55e28fa566..076983e281 100644 --- a/libs/frontend/modules/utils/jest.config.ts +++ b/libs/frontend/modules/utils/jest.config.ts @@ -3,8 +3,6 @@ export default { // displayName: 'frontend-utils', preset: '../../../../jest.preset.js', setupFilesAfterEnv: ['/src/test-setup.ts'], - passWithNoTests: true, - testTimeout: 1000 * 60 * 10, coverageDirectory: '../../../../coverage/libs/frontend/modules/utils', transform: { '^.+\\.(ts|mjs|js|html)$': [ diff --git a/libs/frontend/modules/vitals/jest.config.ts b/libs/frontend/modules/vitals/jest.config.ts index 30a4a019fc..a2b9b9df12 100644 --- a/libs/frontend/modules/vitals/jest.config.ts +++ b/libs/frontend/modules/vitals/jest.config.ts @@ -3,8 +3,6 @@ export default { // displayName: 'frontend-vitals', preset: '../../../../jest.preset.js', setupFilesAfterEnv: ['/src/test-setup.ts'], - passWithNoTests: true, - testTimeout: 1000 * 60 * 10, coverageDirectory: '../../../../coverage/libs/frontend/modules/vitals', transform: { '^.+\\.(ts|mjs|js|html)$': [ diff --git a/libs/frontend/pages/club/jest.config.ts b/libs/frontend/pages/club/jest.config.ts index e4d2e9461d..557cb00b2e 100644 --- a/libs/frontend/pages/club/jest.config.ts +++ b/libs/frontend/pages/club/jest.config.ts @@ -3,8 +3,6 @@ export default { // displayName: 'frontend-club', preset: '../../../../jest.preset.js', setupFilesAfterEnv: ['/src/test-setup.ts'], - passWithNoTests: true, - testTimeout: 1000 * 60 * 10, coverageDirectory: '../../../../coverage/libs/frontend/pages/club', transform: { '^.+\\.(ts|mjs|js|html)$': [ diff --git a/libs/frontend/pages/competition/change-encounter/jest.config.ts b/libs/frontend/pages/competition/change-encounter/jest.config.ts index 224aec057b..a3e46b08fb 100644 --- a/libs/frontend/pages/competition/change-encounter/jest.config.ts +++ b/libs/frontend/pages/competition/change-encounter/jest.config.ts @@ -3,8 +3,6 @@ export default { // displayName: 'frontend-change-encounter', preset: '../../../../../jest.preset.js', setupFilesAfterEnv: ['/src/test-setup.ts'], - passWithNoTests: true, - testTimeout: 1000 * 60 * 10, coverageDirectory: '../../../../../coverage/libs/frontend/pages/competition/change-encounter', transform: { '^.+\\.(ts|mjs|js|html)$': [ diff --git a/libs/frontend/pages/competition/change-encounter/src/components/calendar/calendar.component.ts b/libs/frontend/pages/competition/change-encounter/src/components/calendar/calendar.component.ts index 14e408ced0..820f8b2e98 100644 --- a/libs/frontend/pages/competition/change-encounter/src/components/calendar/calendar.component.ts +++ b/libs/frontend/pages/competition/change-encounter/src/components/calendar/calendar.component.ts @@ -481,6 +481,7 @@ export class CalendarComponent implements OnInit { homeTeamId awayTeamId encounterChange { + id accepted dates { locationId @@ -544,6 +545,7 @@ export class CalendarComponent implements OnInit { homeTeamId awayTeamId encounterChange { + id accepted dates { locationId diff --git a/libs/frontend/pages/competition/change-encounter/src/pages/change-encounter/components/show-requests/show-requests.component.html b/libs/frontend/pages/competition/change-encounter/src/pages/change-encounter/components/show-requests/show-requests.component.html index c1253e4d9b..90a213b3aa 100644 --- a/libs/frontend/pages/competition/change-encounter/src/pages/change-encounter/components/show-requests/show-requests.component.html +++ b/libs/frontend/pages/competition/change-encounter/src/pages/change-encounter/components/show-requests/show-requests.component.html @@ -42,7 +42,7 @@

[group]="date" [encounter]="this.encounter" [home]="this.home" - [warnings]="this.getWarnings(date?.value?.calendar?.date)()" + [warnings]="this.getWarnings(date?.getRawValue()?.calendar?.date)()" (removeDate)="this.removeDate(dateControls, i)" > } diff --git a/libs/frontend/pages/competition/change-encounter/src/pages/change-encounter/components/show-requests/show-requests.component.ts b/libs/frontend/pages/competition/change-encounter/src/pages/change-encounter/components/show-requests/show-requests.component.ts index 1dcf27a6fd..b8e38f2bbe 100644 --- a/libs/frontend/pages/competition/change-encounter/src/pages/change-encounter/components/show-requests/show-requests.component.ts +++ b/libs/frontend/pages/competition/change-encounter/src/pages/change-encounter/components/show-requests/show-requests.component.ts @@ -228,7 +228,7 @@ export class ShowRequestsComponent implements OnInit { console.warn(`Dependency ${this.dependsOn()} not found`, this.previous); } } - getWarnings(date: Date) { + getWarnings(date: Date | string) { return computed(() => this.warnings().filter( (r) => diff --git a/libs/frontend/pages/competition/event/jest.config.ts b/libs/frontend/pages/competition/event/jest.config.ts index ff04c92842..764f33190c 100644 --- a/libs/frontend/pages/competition/event/jest.config.ts +++ b/libs/frontend/pages/competition/event/jest.config.ts @@ -3,8 +3,6 @@ export default { // displayName: 'frontend-event', preset: '../../../../../jest.preset.js', setupFilesAfterEnv: ['/src/test-setup.ts'], - passWithNoTests: true, - testTimeout: 1000 * 60 * 10, coverageDirectory: '../../../../../coverage/libs/frontend/pages/competition/event', transform: { '^.+\\.(ts|mjs|js|html)$': [ diff --git a/libs/frontend/pages/competition/team-assembly/jest.config.ts b/libs/frontend/pages/competition/team-assembly/jest.config.ts index 390be0ff3b..ad90e17330 100644 --- a/libs/frontend/pages/competition/team-assembly/jest.config.ts +++ b/libs/frontend/pages/competition/team-assembly/jest.config.ts @@ -3,8 +3,6 @@ export default { // displayName: 'frontend-team-assembly', preset: '../../../../../jest.preset.js', setupFilesAfterEnv: ['/src/test-setup.ts'], - passWithNoTests: true, - testTimeout: 1000 * 60 * 10, coverageDirectory: '../../../../../coverage/libs/frontend/pages/competition/team-assembly', transform: { '^.+\\.(ts|mjs|js|html)$': [ diff --git a/libs/frontend/pages/competition/team-assembly/src/pages/create/components/assembly/assembly.component.ts b/libs/frontend/pages/competition/team-assembly/src/pages/create/components/assembly/assembly.component.ts index 95772ec6ab..629b3af9af 100644 --- a/libs/frontend/pages/competition/team-assembly/src/pages/create/components/assembly/assembly.component.ts +++ b/libs/frontend/pages/competition/team-assembly/src/pages/create/components/assembly/assembly.component.ts @@ -272,8 +272,6 @@ export class AssemblyComponent implements OnInit { .subscribe(async ([gotRequired, encounter]) => { this.gotRequired = gotRequired; - console.log('gotRequired', gotRequired); - if (gotRequired) { await this.loadData(encounter); this.updatedAssembly$ diff --git a/libs/frontend/pages/competition/team-enrollment/jest.config.ts b/libs/frontend/pages/competition/team-enrollment/jest.config.ts index 1a3ace047a..e4104481ce 100644 --- a/libs/frontend/pages/competition/team-enrollment/jest.config.ts +++ b/libs/frontend/pages/competition/team-enrollment/jest.config.ts @@ -3,8 +3,6 @@ export default { // displayName: 'frontend-team-enrollment', preset: '../../../../../jest.preset.js', setupFilesAfterEnv: ['/src/test-setup.ts'], - passWithNoTests: true, - testTimeout: 1000 * 60 * 10, coverageDirectory: '../../../../../coverage/libs/frontend/pages/competition/team-enrollment', transform: { '^.+\\.(ts|mjs|js|html)$': [ diff --git a/libs/frontend/pages/general/jest.config.ts b/libs/frontend/pages/general/jest.config.ts index 4da1590575..ba629d4186 100644 --- a/libs/frontend/pages/general/jest.config.ts +++ b/libs/frontend/pages/general/jest.config.ts @@ -3,8 +3,6 @@ export default { // displayName: 'frontend-general', preset: '../../../../jest.preset.js', setupFilesAfterEnv: ['/src/test-setup.ts'], - passWithNoTests: true, - testTimeout: 1000 * 60 * 10, coverageDirectory: '../../../../coverage/libs/frontend/pages/general', transform: { '^.+\\.(ts|mjs|js|html)$': [ diff --git a/libs/frontend/pages/job/jest.config.ts b/libs/frontend/pages/job/jest.config.ts index 3f5f2cb1c5..f118c781f5 100644 --- a/libs/frontend/pages/job/jest.config.ts +++ b/libs/frontend/pages/job/jest.config.ts @@ -3,8 +3,6 @@ export default { // displayName: 'frontend-job', preset: '../../../../jest.preset.js', setupFilesAfterEnv: ['/src/test-setup.ts'], - passWithNoTests: true, - testTimeout: 1000 * 60 * 10, coverageDirectory: '../../../../coverage/libs/frontend/pages/job', transform: { '^.+\\.(ts|mjs|js|html)$': [ diff --git a/libs/frontend/pages/notifications/jest.config.ts b/libs/frontend/pages/notifications/jest.config.ts index dbddd46788..06878f3ed4 100644 --- a/libs/frontend/pages/notifications/jest.config.ts +++ b/libs/frontend/pages/notifications/jest.config.ts @@ -3,8 +3,6 @@ export default { // displayName: 'frontend-notifications', preset: '../../../../jest.preset.js', setupFilesAfterEnv: ['/src/test-setup.ts'], - passWithNoTests: true, - testTimeout: 1000 * 60 * 10, coverageDirectory: '../../../../coverage/libs/frontend/pages/notifications', transform: { '^.+\\.(ts|mjs|js|html)$': [ diff --git a/libs/frontend/pages/player/jest.config.ts b/libs/frontend/pages/player/jest.config.ts index c72ad248aa..ac25b23cd3 100644 --- a/libs/frontend/pages/player/jest.config.ts +++ b/libs/frontend/pages/player/jest.config.ts @@ -3,8 +3,6 @@ export default { // displayName: 'frontend-player', preset: '../../../../jest.preset.js', setupFilesAfterEnv: ['/src/test-setup.ts'], - passWithNoTests: true, - testTimeout: 1000 * 60 * 10, coverageDirectory: '../../../../coverage/libs/frontend/pages/player', transform: { '^.+\\.(ts|mjs|js|html)$': [ diff --git a/libs/frontend/pages/ranking/jest.config.ts b/libs/frontend/pages/ranking/jest.config.ts index 986b1086d0..c423cc2af2 100644 --- a/libs/frontend/pages/ranking/jest.config.ts +++ b/libs/frontend/pages/ranking/jest.config.ts @@ -3,8 +3,6 @@ export default { // displayName: 'frontend-ranking', preset: '../../../../jest.preset.js', setupFilesAfterEnv: ['/src/test-setup.ts'], - passWithNoTests: true, - testTimeout: 1000 * 60 * 10, coverageDirectory: '../../../../coverage/libs/frontend/pages/ranking', transform: { '^.+\\.(ts|mjs|js|html)$': [ diff --git a/libs/frontend/pages/role/jest.config.ts b/libs/frontend/pages/role/jest.config.ts index 08ab338f54..b0892a5ba3 100644 --- a/libs/frontend/pages/role/jest.config.ts +++ b/libs/frontend/pages/role/jest.config.ts @@ -3,8 +3,6 @@ export default { // displayName: 'frontend-role', preset: '../../../../jest.preset.js', setupFilesAfterEnv: ['/src/test-setup.ts'], - passWithNoTests: true, - testTimeout: 1000 * 60 * 10, coverageDirectory: '../../../../coverage/libs/frontend/pages/role', transform: { '^.+\\.(ts|mjs|js|html)$': [ diff --git a/libs/frontend/pages/rules/README.md b/libs/frontend/pages/rules/README.md new file mode 100644 index 0000000000..729eee30c1 --- /dev/null +++ b/libs/frontend/pages/rules/README.md @@ -0,0 +1,7 @@ +# frontend-rule + +This library was generated with [Nx](https://nx.dev). + +## Running unit tests + +Run `nx test frontend-rule` to execute the unit tests. diff --git a/libs/frontend/pages/rules/_theme.scss b/libs/frontend/pages/rules/_theme.scss new file mode 100644 index 0000000000..69d7bee6f0 --- /dev/null +++ b/libs/frontend/pages/rules/_theme.scss @@ -0,0 +1,13 @@ +@use './src/pages/overview/overview.page.theme.scss' as overview; + +@mixin theme($theme) { + @include overview.theme($theme); +} + +@mixin typography($theme) { + @include overview.typography($theme); +} + +@mixin color($theme) { + @include overview.color($theme); +} diff --git a/libs/frontend/pages/rules/eslint.config.js b/libs/frontend/pages/rules/eslint.config.js new file mode 100644 index 0000000000..0b9577dd4c --- /dev/null +++ b/libs/frontend/pages/rules/eslint.config.js @@ -0,0 +1,54 @@ +const { FlatCompat } = require('@eslint/eslintrc'); +const baseConfig = require('../../../../eslint.config.js'); +const js = require('@eslint/js'); + +const compat = new FlatCompat({ + baseDirectory: __dirname, + recommendedConfig: js.configs.recommended, +}); + +module.exports = [ + ...baseConfig, + ...compat + .config({ + extends: ['plugin:@nx/angular', 'plugin:@angular-eslint/template/process-inline-templates'], + }) + .map((config) => ({ + ...config, + files: ['**/*.ts'], + rules: { + ...config.rules, + '@angular-eslint/directive-selector': [ + 'error', + { + type: 'attribute', + prefix: 'badman', + style: 'camelCase', + }, + ], + '@angular-eslint/component-selector': [ + 'error', + { + type: 'element', + prefix: 'badman', + style: 'kebab-case', + }, + ], + }, + })), + ...compat.config({ extends: ['plugin:@nx/angular-template'] }).map((config) => ({ + ...config, + files: ['**/*.html'], + rules: { + ...config.rules, + }, + })), + ...compat.config({ parser: 'jsonc-eslint-parser' }).map((config) => ({ + ...config, + files: ['**/*.json'], + rules: { + ...config.rules, + '@nx/dependency-checks': 'error', + }, + })), +]; diff --git a/libs/frontend/pages/rules/jest.config.ts b/libs/frontend/pages/rules/jest.config.ts new file mode 100644 index 0000000000..7b15a4df11 --- /dev/null +++ b/libs/frontend/pages/rules/jest.config.ts @@ -0,0 +1,22 @@ + +export default { + // displayName: 'frontend-rule', + preset: '../../../../jest.preset.js', + setupFilesAfterEnv: ['/src/test-setup.ts'], + coverageDirectory: '../../../../coverage/libs/frontend/pages/rule', + transform: { + '^.+\\.(ts|mjs|js|html)$': [ + 'jest-preset-angular', + { + tsconfig: '/tsconfig.spec.json', + stringifyContentPathRegex: '\\.(html|svg)$', + }, + ], + }, + transformIgnorePatterns: ['node_modules/(?!.*\\.mjs$)'], + snapshotSerializers: [ + 'jest-preset-angular/build/serializers/no-ng-attributes', + 'jest-preset-angular/build/serializers/ng-snapshot', + 'jest-preset-angular/build/serializers/html-comment', + ], +}; diff --git a/libs/frontend/pages/rules/ng-package.json b/libs/frontend/pages/rules/ng-package.json new file mode 100644 index 0000000000..0a9136e3de --- /dev/null +++ b/libs/frontend/pages/rules/ng-package.json @@ -0,0 +1,7 @@ +{ + "$schema": "../../../../node_modules/ng-packagr/ng-package.schema.json", + "dest": "../../../../dist/libs/frontend/pages/rule", + "lib": { + "entryFile": "src/index.ts" + } +} diff --git a/libs/frontend/pages/rules/package.json b/libs/frontend/pages/rules/package.json new file mode 100644 index 0000000000..daa443fcaa --- /dev/null +++ b/libs/frontend/pages/rules/package.json @@ -0,0 +1,22 @@ +{ + "name": "@badman/frontend-rule", + "version": "6.161.1", + "peerDependencies": { + "@badman/frontend-auth": "*", + "@badman/frontend-components": "*", + "@badman/frontend-models": "*", + "@badman/frontend-queue": "*", + "@angular/common": "18.1.0", + "@angular/core": "18.1.0", + "@angular/router": "18.1.0", + "@angular/material": "18.1.0", + "@ngx-translate/core": "^15.0.0", + "apollo-angular": "^7.0.1", + "ngx-socket-io": "^4.6.1", + "ngxtension": "^3.2.0", + "rxjs": "~7.8.1", + "@ng-matero/extensions": "^18.0.3" + }, + "dependencies": {}, + "sideEffects": false +} diff --git a/libs/frontend/pages/rules/project.json b/libs/frontend/pages/rules/project.json new file mode 100644 index 0000000000..55ae32a9ad --- /dev/null +++ b/libs/frontend/pages/rules/project.json @@ -0,0 +1,39 @@ +{ + "name": "frontend-rule", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "libs/frontend/pages/rule/src", + "prefix": "badman", + "projectType": "library", + "tags": [], + "targets": { + "build": { + "executor": "@nx/angular:ng-packagr-lite", + "outputs": ["{workspaceRoot}/dist/{projectRoot}"], + "options": { + "project": "{projectRoot}/ng-package.json" + }, + "configurations": { + "production": { + "tsConfig": "{projectRoot}/tsconfig.lib.prod.json" + }, + "development": { + "tsConfig": "{projectRoot}/tsconfig.lib.json" + } + }, + "defaultConfiguration": "production" + }, + "test": { + "executor": "@nx/jest:jest", + "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], + "options": { + "jestConfig": "{projectRoot}/jest.config.ts" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "options": { + "eslintConfig": "{projectRoot}/eslint.config.js" + } + } + } +} diff --git a/libs/frontend/pages/rules/src/index.ts b/libs/frontend/pages/rules/src/index.ts new file mode 100644 index 0000000000..c4d5063f29 --- /dev/null +++ b/libs/frontend/pages/rules/src/index.ts @@ -0,0 +1,3 @@ +export * from './rule.module'; +export * from './services'; +export * from './pages'; diff --git a/libs/frontend/pages/rules/src/pages/index.ts b/libs/frontend/pages/rules/src/pages/index.ts new file mode 100644 index 0000000000..829aaa8728 --- /dev/null +++ b/libs/frontend/pages/rules/src/pages/index.ts @@ -0,0 +1,3 @@ +// start:ng42.barrel +export * from './overview'; +// end:ng42.barrel diff --git a/libs/frontend/pages/rules/src/pages/overview/index.ts b/libs/frontend/pages/rules/src/pages/overview/index.ts new file mode 100644 index 0000000000..8baefcccfb --- /dev/null +++ b/libs/frontend/pages/rules/src/pages/overview/index.ts @@ -0,0 +1,3 @@ +// start:ng42.barrel +export * from './overview.page'; +// end:ng42.barrel diff --git a/libs/frontend/pages/rules/src/pages/overview/overview.page.html b/libs/frontend/pages/rules/src/pages/overview/overview.page.html new file mode 100644 index 0000000000..b328cd3fdf --- /dev/null +++ b/libs/frontend/pages/rules/src/pages/overview/overview.page.html @@ -0,0 +1,28 @@ + +
{{ 'all.rukes.title' | translate }}
+ +
+ + + + + +
+
+ + + + diff --git a/libs/frontend/pages/rules/src/pages/overview/overview.page.scss b/libs/frontend/pages/rules/src/pages/overview/overview.page.scss new file mode 100644 index 0000000000..7d3e8e8769 --- /dev/null +++ b/libs/frontend/pages/rules/src/pages/overview/overview.page.scss @@ -0,0 +1,36 @@ +:host { + display: flex; + padding: 1rem 1rem 0; + @media (max-width: 760px) { + padding: 0.5rem 0.5rem 0; + } + flex-direction: column; + gap: 1rem; +} + +#copySystem { + display: flex; + flex-direction: column; + gap: 1rem; +} + +.rule-arguments { + padding: 1rem 0; + display: flex; + flex-direction: column; + gap: 1rem; + + .calculate, + .date { + display: flex; + gap: 1rem; + } + + .calculate { + flex-direction: column; + } + + .date { + flex-direction: row; + } +} diff --git a/libs/frontend/pages/rules/src/pages/overview/overview.page.theme.scss b/libs/frontend/pages/rules/src/pages/overview/overview.page.theme.scss new file mode 100644 index 0000000000..f4c802b45d --- /dev/null +++ b/libs/frontend/pages/rules/src/pages/overview/overview.page.theme.scss @@ -0,0 +1,23 @@ +// info: https://material.angular.io/guide/theming-your-components + +@use 'sass:map'; +@use '@angular/material' as mat; + +@mixin color($theme) { + badman-ranking-overview { + + + + + a { + text-decoration: none; + color: mat.get-theme-color($theme, foreground, 'base'); + } + } +} + +@mixin typography($theme) { + badman-ranking-overview { + + } +} diff --git a/libs/frontend/pages/rules/src/pages/overview/overview.page.ts b/libs/frontend/pages/rules/src/pages/overview/overview.page.ts new file mode 100644 index 0000000000..241f70703a --- /dev/null +++ b/libs/frontend/pages/rules/src/pages/overview/overview.page.ts @@ -0,0 +1,111 @@ +import { Component } from '@angular/core'; +import { MatButtonModule } from '@angular/material/button'; +import { MatIconModule } from '@angular/material/icon'; +import { MatMenuModule } from '@angular/material/menu'; +import { PageHeaderComponent } from '@badman/frontend-components'; +import { MtxGridColumn, MtxGridModule } from '@ng-matero/extensions/grid'; +import { TranslateModule } from '@ngx-translate/core'; + +@Component({ + selector: 'badman-ranking-overview', + templateUrl: './overview.page.html', + styleUrls: ['./overview.page.scss'], + standalone: true, + imports: [ + MatButtonModule, + MatIconModule, + MatMenuModule, + + TranslateModule, + MtxGridModule, + + PageHeaderComponent, + ], +}) +export class OverviewPageComponent { + columns: MtxGridColumn[] = [ + { header: 'Name', field: 'name' }, + { + header: 'Weight', + field: 'weight', + type: 'number', + typeParameter: { + digitsInfo: '1.2-2', + }, + }, + { header: 'Gender', field: 'gender' }, + { header: 'Mobile', field: 'mobile' }, + { header: 'City', field: 'city' }, + { + header: 'Date', + field: 'date', + type: 'date', + typeParameter: { + format: 'yyyy-MM-dd', + }, + }, + ]; + + list = EXAMPLE_DATA; + + log(e: any) { + console.log(e); + } +} + +export const EXAMPLE_DATA: any[] = [ + { + position: 1, + name: 'Boron', + tag: [{ color: 'red', value: [1, 2] }], + weight: 10.811, + symbol: 'B', + gender: 'male', + mobile: '13198765432', + tele: '567891234', + city: 'Berlin', + address: 'Bernauer Str.111,13355', + date: '1423456765768', + website: 'www.matero.com', + company: 'matero', + email: 'Boron@gmail.com', + status: false, + cost: 4, + }, + { + position: 2, + name: 'Helium', + tag: [{ color: 'blue', value: [3, 4] }], + weight: 8.0026, + symbol: 'He', + gender: 'female', + mobile: '13034676675', + tele: '80675432', + city: 'Shanghai', + address: '88 Songshan Road', + date: '1423456765768', + website: 'www.matero.com', + company: 'matero', + email: 'Helium@gmail.com', + status: true, + cost: 5, + }, + { + position: 3, + name: 'Nitrogen', + tag: [{ color: 'yellow', value: [5, 6] }], + weight: 14.0067, + symbol: 'N', + gender: 'male', + mobile: '15811112222', + tele: '345678912', + city: 'Sydney', + address: 'Circular Quay, Sydney NSW 2000', + date: '1423456765768', + website: 'www.matero.com', + company: 'matero', + email: 'Nitrogen@gmail.com', + status: true, + cost: 2, + }, +]; diff --git a/libs/frontend/pages/rules/src/rule.module.ts b/libs/frontend/pages/rules/src/rule.module.ts new file mode 100644 index 0000000000..e481f99490 --- /dev/null +++ b/libs/frontend/pages/rules/src/rule.module.ts @@ -0,0 +1,31 @@ +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; +import { OverviewPageComponent } from './pages/overview'; +import { AuthGuard } from '@badman/frontend-auth'; + +const MODULE_ROUTES: Routes = [ + { + path: '', + component: OverviewPageComponent, + canActivate: [AuthGuard], + data: { + claims: { + any: ['change:rules'], + }, + }, + }, +]; + +@NgModule({ + imports: [CommonModule, RouterModule.forChild(MODULE_ROUTES)], + declarations: [], + providers: [], +}) +export class RuleModule {} + +// @NgModule({ +// declarations: [], +// providers: [JobsService], +// }) +// export class PublicJobModule {} diff --git a/libs/frontend/pages/rules/src/services/cronjob.service.ts b/libs/frontend/pages/rules/src/services/cronjob.service.ts new file mode 100644 index 0000000000..80d6c25907 --- /dev/null +++ b/libs/frontend/pages/rules/src/services/cronjob.service.ts @@ -0,0 +1,76 @@ +import { Injectable, inject } from '@angular/core'; +import { CronJob, Service } from '@badman/frontend-models'; +import { JobsService } from '@badman/frontend-queue'; +import { Apollo, gql } from 'apollo-angular'; +import { Socket } from 'ngx-socket-io'; +import { signalSlice } from 'ngxtension/signal-slice'; +import { Observable, merge } from 'rxjs'; +import { map, switchMap } from 'rxjs/operators'; + +export interface CronJobState { + cronJobs: CronJob[]; + loaded: boolean; +} + +@Injectable({ + providedIn: 'root', +}) +export class CronJobService { + socket = inject(Socket); + apollo = inject(Apollo); + jobService = inject(JobsService); + + initialState: CronJobState = { + cronJobs: [], + loaded: false, + }; + + // sources + private servicesLoaded$ = this.apollo + .query<{ + cronJobs: Service[]; + }>({ + query: gql` + query CronJobs { + cronJobs { + updatedAt + createdAt + id + name + cronTime + lastRun + nextRun + running + type + meta { + jobName + queueName + arguments + } + } + } + `, + }) + .pipe( + map((res) => res.data?.cronJobs?.map((item) => new CronJob(item)) ?? []), + map((cronJobs) => cronJobs.sort((a, b) => `${a.name}`.localeCompare(`${b.name}`))), + ); + + sources$ = merge( + this.servicesLoaded$.pipe( + map((cronJobs) => ({ + cronJobs, + loaded: true, + })), + ), + ); + + state = signalSlice({ + initialState: this.initialState, + sources: [this.sources$], + actionSources: { + queue: (_state, action$: Observable) => + action$.pipe(switchMap((rule) => this.jobService.queueJob(rule, {}))), + }, + }); +} diff --git a/libs/frontend/pages/rules/src/services/index.ts b/libs/frontend/pages/rules/src/services/index.ts new file mode 100644 index 0000000000..769c18478a --- /dev/null +++ b/libs/frontend/pages/rules/src/services/index.ts @@ -0,0 +1,3 @@ +// start:ng42.barrel +export * from './cronjob.service'; +// end:ng42.barrel diff --git a/libs/frontend/pages/rules/src/test-setup.ts b/libs/frontend/pages/rules/src/test-setup.ts new file mode 100644 index 0000000000..1100b3e8a6 --- /dev/null +++ b/libs/frontend/pages/rules/src/test-setup.ts @@ -0,0 +1 @@ +import 'jest-preset-angular/setup-jest'; diff --git a/libs/frontend/pages/rules/tsconfig.json b/libs/frontend/pages/rules/tsconfig.json new file mode 100644 index 0000000000..d9bdaa964b --- /dev/null +++ b/libs/frontend/pages/rules/tsconfig.json @@ -0,0 +1,32 @@ +{ + "compilerOptions": { + "target": "es2022", + "useDefineForClassFields": false, + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + }, + { + "path": "./tsconfig.lib.prod.json" + }, + { + "path": "./tsconfig.spec.json" + } + ], + "extends": "../../../../tsconfig.base.json", + "angularCompilerOptions": { + "enableI18nLegacyMessageIdFormat": false, + "strictInjectionParameters": true, + "strictInputAccessModifiers": true, + "strictTemplates": true + } +} diff --git a/libs/frontend/pages/rules/tsconfig.lib.json b/libs/frontend/pages/rules/tsconfig.lib.json new file mode 100644 index 0000000000..3321c94eec --- /dev/null +++ b/libs/frontend/pages/rules/tsconfig.lib.json @@ -0,0 +1,12 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "declarationMap": true, + "inlineSources": true, + "types": [] + }, + "exclude": ["src/**/*.spec.ts", "src/test-setup.ts", "jest.config.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/libs/frontend/pages/rules/tsconfig.lib.prod.json b/libs/frontend/pages/rules/tsconfig.lib.prod.json new file mode 100644 index 0000000000..61b523783f --- /dev/null +++ b/libs/frontend/pages/rules/tsconfig.lib.prod.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.lib.json", + "compilerOptions": { + "declarationMap": false + }, + "angularCompilerOptions": {} +} diff --git a/libs/frontend/pages/rules/tsconfig.spec.json b/libs/frontend/pages/rules/tsconfig.spec.json new file mode 100644 index 0000000000..7af5e2542a --- /dev/null +++ b/libs/frontend/pages/rules/tsconfig.spec.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../../../dist/out-tsc", + "module": "commonjs", + "target": "es2016", + "types": ["jest", "node"] + }, + "files": ["src/test-setup.ts"], + "include": ["jest.config.ts", "src/**/*.test.ts", "src/**/*.spec.ts", "src/**/*.d.ts"] +} diff --git a/libs/frontend/pages/team/jest.config.ts b/libs/frontend/pages/team/jest.config.ts index 01d52ccbc0..9a1d5ef460 100644 --- a/libs/frontend/pages/team/jest.config.ts +++ b/libs/frontend/pages/team/jest.config.ts @@ -3,8 +3,6 @@ export default { // displayName: 'frontend-team', preset: '../../../../jest.preset.js', setupFilesAfterEnv: ['/src/test-setup.ts'], - passWithNoTests: true, - testTimeout: 1000 * 60 * 10, coverageDirectory: '../../../../coverage/libs/frontend/pages/team', transform: { '^.+\\.(ts|mjs|js|html)$': [ diff --git a/libs/frontend/pages/tournament/jest.config.ts b/libs/frontend/pages/tournament/jest.config.ts index 79ede9097d..a9ebdd9fd9 100644 --- a/libs/frontend/pages/tournament/jest.config.ts +++ b/libs/frontend/pages/tournament/jest.config.ts @@ -3,8 +3,6 @@ export default { // displayName: 'frontend-tournament', preset: '../../../../jest.preset.js', setupFilesAfterEnv: ['/src/test-setup.ts'], - passWithNoTests: true, - testTimeout: 1000 * 60 * 10, coverageDirectory: '../../../../coverage/libs/frontend/pages/tournament', transform: { '^.+\\.(ts|mjs|js|html)$': [ diff --git a/libs/frontend/pages/transfers/jest.config.ts b/libs/frontend/pages/transfers/jest.config.ts index a2fb7dad0f..1f4bcd9f65 100644 --- a/libs/frontend/pages/transfers/jest.config.ts +++ b/libs/frontend/pages/transfers/jest.config.ts @@ -3,8 +3,6 @@ export default { // displayName: 'frontend-transfers', preset: '../../../../jest.preset.js', setupFilesAfterEnv: ['/src/test-setup.ts'], - passWithNoTests: true, - testTimeout: 1000 * 60 * 10, coverageDirectory: '../../../../coverage/libs/frontend/pages/transfer', transform: { '^.+\\.(ts|mjs|js|html)$': [ diff --git a/libs/utils/jest.config.ts b/libs/utils/jest.config.ts index d5963062c9..d86c4a45ea 100644 --- a/libs/utils/jest.config.ts +++ b/libs/utils/jest.config.ts @@ -3,8 +3,6 @@ export default { // displayName: 'backend-authorization', preset: '../../jest.preset.js', testEnvironment: 'node', - passWithNoTests: true, - testTimeout: 1000 * 60 * 10, transform: { '^.+\\.[tj]s$': ['ts-jest', { tsconfig: '/tsconfig.spec.json' }], }, diff --git a/libs/utils/src/lib/i18n.generated.ts b/libs/utils/src/lib/i18n.generated.ts index fbea8bb318..70b0d4d748 100644 --- a/libs/utils/src/lib/i18n.generated.ts +++ b/libs/utils/src/lib/i18n.generated.ts @@ -1,6 +1,6 @@ /* DO NOT EDIT, file generated by nestjs-i18n */ -/* eslint-disable */ + /* prettier-ignore */ import { Path } from "nestjs-i18n"; /* prettier-ignore */ diff --git a/nx.json b/nx.json index a754f27ba1..006aed65f6 100644 --- a/nx.json +++ b/nx.json @@ -144,7 +144,10 @@ }, "@nx/angular:library": { "linter": "eslint", - "unitTestRunner": "jest" + "unitTestRunner": "jest", + "style": "scss", + "prefix": "app", + "buildable": true }, "@nx/angular:component": { "style": "css" diff --git a/tsconfig.base.json b/tsconfig.base.json index 16e5e7e94a..ef7e334993 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -42,6 +42,7 @@ "@badman/backend-translate": ["libs/backend/translate/src/index.ts"], "@badman/backend-twizzit": ["libs/backend/twizzit/src/index.ts"], "@badman/backend-utils": ["libs/backend/utils/src/index.ts"], + "@badman/backend-validation": ["libs/backend/validation/src/index.ts"], "@badman/backend-visual": ["libs/backend/visual/src/index.ts"], "@badman/backend-websockets": ["libs/backend/websockets/src/index.ts"], "@badman/backend/comp-tet": ["libs/backend/comp-tet/src/index.ts"], @@ -68,6 +69,7 @@ "@badman/frontend-queue": ["libs/frontend/modules/queue/src/index.ts"], "@badman/frontend-ranking": ["libs/frontend/pages/ranking/src/index.ts"], "@badman/frontend-role": ["libs/frontend/pages/role/src/index.ts"], + "@badman/frontend-rules": ["libs/frontend/pages/rules/src/index.ts"], "@badman/frontend-seo": ["libs/frontend/modules/seo/src/index.ts"], "@badman/frontend-team": ["libs/frontend/pages/team/src/index.ts"], "@badman/frontend-team-assembly": [