Merge pull request 'Y2024 release' (#1) from 2024edition into main
Reviewed-on: #1
This commit is contained in:
commit
682126e043
119 changed files with 3223 additions and 342 deletions
1
.husky/pre-commit
Normal file
1
.husky/pre-commit
Normal file
|
|
@ -0,0 +1 @@
|
|||
npm test
|
||||
156
data/gifts.json
Normal file
156
data/gifts.json
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
[{
|
||||
"prizeID": 1,
|
||||
"name": "Тесто для лепки невкусное",
|
||||
"isGifted": false
|
||||
},
|
||||
{
|
||||
"prizeID": 2,
|
||||
"name": "Палочки с ароматом лучших публичных домов Бангкока",
|
||||
"isGifted": false
|
||||
},
|
||||
{
|
||||
"prizeID": 3,
|
||||
"name": "2 метра хюгге",
|
||||
"isGifted": false
|
||||
},
|
||||
{
|
||||
"prizeID": 4,
|
||||
"name": "Палку светящуюся бесполезную",
|
||||
"isGifted": false
|
||||
},
|
||||
{
|
||||
"prizeID": 5,
|
||||
"name": "Тёрку для лилипутов",
|
||||
"isGifted": false
|
||||
},
|
||||
{
|
||||
"prizeID": 6,
|
||||
"name": "Мёртвую белочку",
|
||||
"isGifted": false
|
||||
},
|
||||
{
|
||||
"prizeID": 7,
|
||||
"name": "Лучшего друга Спанч боба засушенного",
|
||||
"isGifted": false
|
||||
},
|
||||
{
|
||||
"prizeID": 8,
|
||||
"name": "Подарок для любителей помесить глину",
|
||||
"isGifted": false
|
||||
},
|
||||
{
|
||||
"prizeID": 9,
|
||||
"name": "Палку чесательную полезную",
|
||||
"isGifted": false
|
||||
},
|
||||
{
|
||||
"prizeID": 10,
|
||||
"name": "Красного петуха - своё тотемное животное",
|
||||
"isGifted": false
|
||||
},
|
||||
{
|
||||
"prizeID": 11,
|
||||
"name": "Набор свечей романтишный",
|
||||
"isGifted": false
|
||||
},
|
||||
{
|
||||
"prizeID": 12,
|
||||
"name": "Хранилище для денег патриотическое",
|
||||
"isGifted": false
|
||||
},
|
||||
{
|
||||
"prizeID": 13,
|
||||
"name": "Мерч от Каца",
|
||||
"isGifted": false
|
||||
},
|
||||
{
|
||||
"prizeID": 14,
|
||||
"name": "Чупа-чупс со вкусом патриотизма",
|
||||
"isGifted": false
|
||||
},
|
||||
{
|
||||
"prizeID": 15,
|
||||
"name": "Тренажеры для легких разноцветные",
|
||||
"isGifted": false
|
||||
},
|
||||
{
|
||||
"prizeID": 16,
|
||||
"name": "Паззл предсказуемый",
|
||||
"isGifted": false
|
||||
},
|
||||
{
|
||||
"prizeID": 17,
|
||||
"name": "Жопный блокнот",
|
||||
"isGifted": false
|
||||
},
|
||||
{
|
||||
"prizeID": 18,
|
||||
"name": "Носки от батьки праздничные",
|
||||
"isGifted": false
|
||||
},
|
||||
{
|
||||
"prizeID": 19,
|
||||
"name": "Носки женские миленькие",
|
||||
"isGifted": false
|
||||
},
|
||||
{
|
||||
"prizeID": 20,
|
||||
"name": "Набор художника-нумеролога",
|
||||
"isGifted": false
|
||||
},
|
||||
{
|
||||
"prizeID": 21,
|
||||
"name": "Карандаш вечный как Путин",
|
||||
"isGifted": false
|
||||
},
|
||||
{
|
||||
"prizeID": 22,
|
||||
"name": "Массажёр для жопы",
|
||||
"isGifted": false
|
||||
},
|
||||
{
|
||||
"prizeID": 23,
|
||||
"name": "Сладкий подарок рот в рот",
|
||||
"isGifted": false
|
||||
},
|
||||
{
|
||||
"prizeID": 24,
|
||||
"name": "Мотоцикл (ненастоящий)",
|
||||
"isGifted": false
|
||||
},
|
||||
{
|
||||
"prizeID": 25,
|
||||
"name": "Вышивку для эскортниц (алмазную)",
|
||||
"isGifted": false
|
||||
},
|
||||
{
|
||||
"prizeID": 26,
|
||||
"name": "Звенящие бубенцы",
|
||||
"isGifted": false
|
||||
},
|
||||
{
|
||||
"prizeID": 27,
|
||||
"name": "Спонж для умывания твоих кислых щей",
|
||||
"isGifted": false
|
||||
},
|
||||
{
|
||||
"prizeID": 28,
|
||||
"name": "Мочалку с портретом дракона",
|
||||
"isGifted": false
|
||||
},
|
||||
{
|
||||
"prizeID": 29,
|
||||
"name": "Тетрадь для чётких квадроберов",
|
||||
"isGifted": false
|
||||
},
|
||||
{
|
||||
"prizeID": 30,
|
||||
"name": "Костюм для руки незнакомки эротишный",
|
||||
"isGifted": false
|
||||
},
|
||||
{
|
||||
"prizeID": 31,
|
||||
"name": "Плакат с кумиром детства нарядный",
|
||||
"isGifted": false
|
||||
}
|
||||
]
|
||||
1034
data/questions.json
Normal file
1034
data/questions.json
Normal file
File diff suppressed because it is too large
Load diff
110
data/versus.json
Normal file
110
data/versus.json
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
[
|
||||
{
|
||||
"text":"угадайка",
|
||||
"description":"Угадай кто я - по стикеру на лбу"
|
||||
},
|
||||
{
|
||||
"text":"тест на устойчивость к юмору",
|
||||
"description": "Кто первый засмеется с водой во рту"
|
||||
},
|
||||
{
|
||||
"text":"лучший китаец",
|
||||
"description": "Кто быстрее съест палочками для суши зеленый горошек или консервированную кукурузу"
|
||||
},
|
||||
{
|
||||
"text": "Прыжки в длину",
|
||||
"description": "тут надо самим угадать"
|
||||
},
|
||||
{
|
||||
"text": "грузинские буквы",
|
||||
"description": "Кто отгадает больше грузинских букв и быстрее"
|
||||
},
|
||||
{
|
||||
"text": "лучший котик на тусовке",
|
||||
"description": "кто лучше изобразит квадробера"
|
||||
},
|
||||
{
|
||||
"text": "Гонки на ложках",
|
||||
"description": "перенести шарик на ложке, зажатой в зубах, до финиша"
|
||||
},
|
||||
{
|
||||
"text": "Сванская башня",
|
||||
"description": "за 1 минуту построить башню из пластиковых стаканов"
|
||||
},
|
||||
{
|
||||
"text": "Скоростное рисование",
|
||||
"description": "нарисовать лошадь за минуту"
|
||||
},
|
||||
{
|
||||
"text": "нарисуй хуйло",
|
||||
"description": "нарисовать путина за минуту"
|
||||
},
|
||||
{
|
||||
"text": "сотрудник GWP",
|
||||
"description": "кто точнее наполнит стакан до края"
|
||||
},
|
||||
{
|
||||
"text": "Скоростная чистка овоща",
|
||||
"description": "кто быстрее очистит картофелину"
|
||||
},
|
||||
{
|
||||
"text": "Стрельба из рогатки",
|
||||
"description": "попасть в цель мячиками"
|
||||
},
|
||||
{
|
||||
"text": "Найди отличия",
|
||||
"description": "кто быстрее найдёт отличия на двух картинках"
|
||||
},
|
||||
{
|
||||
"text": "Переводка предмета без рук",
|
||||
"description": "перенести мелкий предмет, держа его между коленями"
|
||||
},
|
||||
{
|
||||
"text": "менеджер GWP",
|
||||
"description": "перенести воду в ложке, не пролив"
|
||||
},
|
||||
{
|
||||
"text": "Бой подушками",
|
||||
"description": "пока кто-то не выронит подушку."
|
||||
},
|
||||
{
|
||||
"text": "шарик",
|
||||
"description": "кто быстрее надует воздушный шарик"
|
||||
},
|
||||
{
|
||||
"text": "Камень, ножницы, бумага",
|
||||
"description": "Сыграть три раунда и определить победителя"
|
||||
},
|
||||
{
|
||||
"text": "Сложи бумажный самолетик и запусти",
|
||||
"description": "Чей самолетик пролетит дальше"
|
||||
},
|
||||
{
|
||||
"text": "Лимбо",
|
||||
"description": "Пройти под планкой, не задев её, при каждом раунде ниже"
|
||||
},
|
||||
{
|
||||
"text": "Пой без слов",
|
||||
"description": "Напеть мелодию песни, чтобы другой отгадал"
|
||||
},
|
||||
{
|
||||
"text": "Нарисуй вслепую",
|
||||
"description": "Нарисовать предмет с закрытыми глазами"
|
||||
},
|
||||
{
|
||||
"text": "Балансировка книги на голове",
|
||||
"description": "Кто дольше продержится с книгой на голове, выполняя задания"
|
||||
},
|
||||
{
|
||||
"text": "Быстрый переводчик",
|
||||
"description": "Перевести фразы на другой язык быстрее соперника"
|
||||
},
|
||||
{
|
||||
"text": "Словесный бой",
|
||||
"description": "Назвать слова на заданную букву, пока не закончится время"
|
||||
},
|
||||
{
|
||||
"text": "Бумажный самолетик на точность",
|
||||
"description": "Запустить самолетик так, чтобы он попал в цель"
|
||||
}
|
||||
]
|
||||
34
package-lock.json
generated
34
package-lock.json
generated
|
|
@ -29,6 +29,7 @@
|
|||
"class-transformer": "^0.5.1",
|
||||
"cyrillic-to-translit-js": "^3.2.1",
|
||||
"dotenv": "^16.3.1",
|
||||
"husky": "^9.1.6",
|
||||
"latin-to-cyrillic": "^1.0.1",
|
||||
"mongodb": "^6.2.0",
|
||||
"mongoose": "^8.0.0",
|
||||
|
|
@ -41,7 +42,7 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@nestjs/schematics": "^10.0.3",
|
||||
"@nestjs/testing": "^10.2.8",
|
||||
"@nestjs/testing": "^10.4.7",
|
||||
"@types/cron": "^2.0.1",
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/jest": "^29.5.8",
|
||||
|
|
@ -1905,12 +1906,13 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@nestjs/testing": {
|
||||
"version": "10.2.8",
|
||||
"resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-10.2.8.tgz",
|
||||
"integrity": "sha512-9Kj5IQhM67/nj/MT6Wi2OmWr5YQnCMptwKVFrX1TDaikpY12196v7frk0jVjdT7wms7rV07GZle9I2z0aSjqtQ==",
|
||||
"version": "10.4.7",
|
||||
"resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-10.4.7.tgz",
|
||||
"integrity": "sha512-aS3sQ0v4g8cyHDzW3xJv1+8MiFAkxUNXmnau588IFFI/nBIo/kevLNHNPr85keYekkJ/lwNDW72h8UGg8BYd9w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tslib": "2.6.2"
|
||||
"tslib": "2.7.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
|
|
@ -1931,6 +1933,13 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@nestjs/testing/node_modules/tslib": {
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz",
|
||||
"integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==",
|
||||
"dev": true,
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/@nestjs/websockets": {
|
||||
"version": "10.2.8",
|
||||
"resolved": "https://registry.npmjs.org/@nestjs/websockets/-/websockets-10.2.8.tgz",
|
||||
|
|
@ -5544,6 +5553,21 @@
|
|||
"ms": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/husky": {
|
||||
"version": "9.1.6",
|
||||
"resolved": "https://registry.npmjs.org/husky/-/husky-9.1.6.tgz",
|
||||
"integrity": "sha512-sqbjZKK7kf44hfdE94EoX8MZNk0n7HeW37O4YrVGCF4wzgQjp+akPAkfUK5LZ6KuR/6sqeAVuXHji+RzQgOn5A==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"husky": "bin.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/typicode"
|
||||
}
|
||||
},
|
||||
"node_modules/iconv-lite": {
|
||||
"version": "0.4.24",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||
|
|
|
|||
|
|
@ -18,7 +18,8 @@
|
|||
"test:watch": "jest --watch",
|
||||
"test:cov": "jest --coverage",
|
||||
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
|
||||
"test:e2e": "jest --config ./test/jest-e2e.json"
|
||||
"test:e2e": "jest --config ./test/jest-e2e.json",
|
||||
"prepare": "husky"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nestjs/axios": "3.0.1",
|
||||
|
|
@ -41,6 +42,7 @@
|
|||
"class-transformer": "^0.5.1",
|
||||
"cyrillic-to-translit-js": "^3.2.1",
|
||||
"dotenv": "^16.3.1",
|
||||
"husky": "^9.1.6",
|
||||
"latin-to-cyrillic": "^1.0.1",
|
||||
"mongodb": "^6.2.0",
|
||||
"mongoose": "^8.0.0",
|
||||
|
|
@ -53,7 +55,7 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@nestjs/schematics": "^10.0.3",
|
||||
"@nestjs/testing": "^10.2.8",
|
||||
"@nestjs/testing": "^10.4.7",
|
||||
"@types/cron": "^2.0.1",
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/jest": "^29.5.8",
|
||||
|
|
|
|||
6
src/Consts/FeatureFlags.consts.ts
Normal file
6
src/Consts/FeatureFlags.consts.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
export class FeatureFlagsConsts {
|
||||
static EnableEndgamePoints = 'EnableEndgamePoints';
|
||||
static DontMarkQuestionsAsCompleted = 'DontMarkQuestionsAsCompleted';
|
||||
static DisableVoice = 'DisableVoice';
|
||||
static StartVersusIfPlayersAnsweredInSameTime = 'StartVersusIfPlayersAnsweredInSameTime';
|
||||
}
|
||||
|
|
@ -9,4 +9,6 @@ export class CommandsConsts {
|
|||
static GetCards = 'GetCards';
|
||||
static ApplyDebuff = 'ApplyDebuff';
|
||||
static CompleteQueue = 'CompleteQueue';
|
||||
}
|
||||
static GetQuestion = 'GetQuestion';
|
||||
static QuestionAnswer = "QuestionAnswer";
|
||||
}
|
||||
|
|
|
|||
5
src/Consts/game-state.consts.ts
Normal file
5
src/Consts/game-state.consts.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
export class GameStateConsts {
|
||||
static Main = 'main';
|
||||
static EndgamePoints = 'endgamepoints';
|
||||
static Finish = 'finish';
|
||||
}
|
||||
4
src/Consts/guest-property-names.consts.ts
Normal file
4
src/Consts/guest-property-names.consts.ts
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
export class GuestPropertyNamesConsts {
|
||||
static VersusWonCount = 'versusWonCount';
|
||||
static VersusLoseCount = 'versusLoseCount';
|
||||
}
|
||||
65
src/Consts/types.d.ts
vendored
Normal file
65
src/Consts/types.d.ts
vendored
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
import {GameQueueTypes} from "../schemas/game-queue.schema";
|
||||
|
||||
export interface IStateInfo {
|
||||
state:string;
|
||||
value:string;
|
||||
}
|
||||
|
||||
export interface IValidAnswerReceivedSocketEvent {
|
||||
telegramId: number;
|
||||
validAnswer: string;
|
||||
note: string;
|
||||
}
|
||||
|
||||
export interface IUserInfoMinimal {
|
||||
telegramId: number;
|
||||
}
|
||||
|
||||
export interface IUserBasicInfo extends IUserInfoMinimal {
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface IVersusBeginSocketEvent {
|
||||
player1: number;
|
||||
player2: number;
|
||||
player1name: string;
|
||||
player2name: string;
|
||||
}
|
||||
|
||||
export interface IVersusEndSocketEvent {
|
||||
winner: number;
|
||||
}
|
||||
|
||||
export interface IScoreChangedSocketEvent extends IUserInfoMinimal {
|
||||
newScore: number;
|
||||
}
|
||||
|
||||
export interface IUserCardChangedEvent extends IUserInfoMinimal {
|
||||
cards: string[];
|
||||
}
|
||||
|
||||
export interface IEmptyNotification {}
|
||||
|
||||
export interface ISocketNotificationEvent {
|
||||
text: string;
|
||||
timeout: number;
|
||||
}
|
||||
|
||||
export interface IUserPropertyChangedEvent {
|
||||
user: number;
|
||||
property: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface ICardPlayedSocketEvent extends IUserInfoMinimal{
|
||||
card: string;
|
||||
name: string;
|
||||
timeout: number;
|
||||
}
|
||||
export interface IGameQueueSocketEvent {
|
||||
_id: any;
|
||||
completed: boolean;
|
||||
target: number;
|
||||
type: GameQueueTypes;
|
||||
text: string;
|
||||
}
|
||||
|
|
@ -20,6 +20,8 @@ import {ConfigModule} from "@nestjs/config";
|
|||
import {MessagingModule} from "./messaging/messaging.module";
|
||||
import * as process from "process";
|
||||
import {OpenaiModule} from "./openai/openai.module";
|
||||
import { FeatureflagController } from './featureflag/featureflag.controller';
|
||||
import { FeatureflagService } from './featureflag/featureflag.service';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
|
|
@ -41,8 +43,8 @@ import {OpenaiModule} from "./openai/openai.module";
|
|||
GiftsModule,
|
||||
OpenaiModule
|
||||
],
|
||||
controllers: [AppController],
|
||||
providers: [AppService, SocketGateway, SchedulerService],
|
||||
exports: [AppService, SocketGateway],
|
||||
controllers: [AppController, FeatureflagController],
|
||||
providers: [AppService, SocketGateway, SchedulerService, FeatureflagService],
|
||||
exports: [AppService, SocketGateway, FeatureflagService],
|
||||
})
|
||||
export class AppModule {}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { CardsController } from './cards.controller';
|
||||
import {CardsService} from "./cards.service";
|
||||
import {CardsServiceMock} from "../mocks/cards-service.mock";
|
||||
|
||||
describe('CardsController', () => {
|
||||
let controller: CardsController;
|
||||
|
|
@ -7,6 +9,9 @@ describe('CardsController', () => {
|
|||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
controllers: [CardsController],
|
||||
providers: [
|
||||
{ provide: CardsService, useValue: CardsServiceMock },
|
||||
]
|
||||
}).compile();
|
||||
|
||||
controller = module.get<CardsController>(CardsController);
|
||||
|
|
|
|||
|
|
@ -1,12 +1,24 @@
|
|||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { CardsService } from './cards.service';
|
||||
import {ConfigService} from "@nestjs/config";
|
||||
import {ConfigServiceMock} from "../mocks/config-service.mock";
|
||||
import {getModelToken} from "@nestjs/mongoose";
|
||||
import {Card} from "../schemas/cards.schema";
|
||||
import {Model} from "mongoose";
|
||||
import {EventBus} from "@nestjs/cqrs";
|
||||
import {EventbusMock} from "../mocks/eventbus.mock";
|
||||
|
||||
describe('CardsService', () => {
|
||||
let service: CardsService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [CardsService],
|
||||
providers: [
|
||||
CardsService,
|
||||
{ provide: ConfigService, useValue: ConfigServiceMock },
|
||||
{ provide: getModelToken(Card.name), useValue: Model },
|
||||
{ provide: EventBus, useValue: EventbusMock },
|
||||
],
|
||||
}).compile();
|
||||
|
||||
service = module.get<CardsService>(CardsService);
|
||||
|
|
|
|||
49
src/featureflag/featureflag.controller.spec.ts
Normal file
49
src/featureflag/featureflag.controller.spec.ts
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { FeatureflagController } from './featureflag.controller';
|
||||
import {FeatureflagService} from "./featureflag.service";
|
||||
import {FeatureflagServiceMock} from "../mocks/featureflag-service.mock";
|
||||
|
||||
describe('FeatureflagController', () => {
|
||||
let controller: FeatureflagController;
|
||||
let featureflagService: FeatureflagService;
|
||||
|
||||
beforeEach(async () => {
|
||||
jest.resetAllMocks();
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
controllers: [FeatureflagController],
|
||||
providers: [
|
||||
{ provide: FeatureflagService, useValue: FeatureflagServiceMock },
|
||||
]
|
||||
}).compile();
|
||||
controller = module.get<FeatureflagController>(FeatureflagController);
|
||||
featureflagService = module.get<FeatureflagService>(FeatureflagService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(controller).toBeDefined();
|
||||
});
|
||||
|
||||
it('should call feature flag service to get state', async() => {
|
||||
const ffNameToTest = "TestFeature";
|
||||
const getFFMock = jest.spyOn(featureflagService, 'getFeatureFlag')
|
||||
.mockImplementation(
|
||||
(name) => Promise.resolve({ name: name, state: true})
|
||||
);
|
||||
|
||||
await controller.getFeatureFlag({ ffname: ffNameToTest });
|
||||
|
||||
expect(getFFMock).toHaveBeenCalled();
|
||||
expect(getFFMock).toHaveBeenCalledWith(ffNameToTest);
|
||||
});
|
||||
|
||||
it('should call feature flag service to set state', async () => {
|
||||
const ffNameToTest = "TestFeature";
|
||||
const setFFMock = jest.spyOn(featureflagService, 'setFeatureFlag')
|
||||
.mockImplementation((id, status) => Promise.resolve({ name: id, state: false}));
|
||||
|
||||
await controller.setFeatureFlag({ name: ffNameToTest, state: true });
|
||||
|
||||
expect(setFFMock).toHaveBeenCalled();
|
||||
expect(setFFMock).toHaveBeenCalledWith(ffNameToTest, true);
|
||||
});
|
||||
});
|
||||
18
src/featureflag/featureflag.controller.ts
Normal file
18
src/featureflag/featureflag.controller.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
import {Body, Controller, Get, Param, Post} from '@nestjs/common';
|
||||
import {FeatureflagService} from "./featureflag.service";
|
||||
|
||||
@Controller('featureflag')
|
||||
export class FeatureflagController {
|
||||
constructor(private featureflagService: FeatureflagService) {
|
||||
}
|
||||
|
||||
@Get(':ffname')
|
||||
async getFeatureFlag(@Param() params: { ffname: string}) {
|
||||
return await this.featureflagService.getFeatureFlag(params.ffname);
|
||||
}
|
||||
|
||||
@Post()
|
||||
async setFeatureFlag(@Body() ffState: { name: string, state: boolean }) {
|
||||
return await this.featureflagService.setFeatureFlag(ffState.name, ffState.state );
|
||||
}
|
||||
}
|
||||
51
src/featureflag/featureflag.service.spec.ts
Normal file
51
src/featureflag/featureflag.service.spec.ts
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { FeatureflagService } from './featureflag.service';
|
||||
import {SharedService} from "../shared/shared.service";
|
||||
import {SharedServiceMock} from "../mocks/shared-service.mock";
|
||||
|
||||
|
||||
describe('FeatureflagService', () => {
|
||||
let service: FeatureflagService;
|
||||
let sharedService: SharedService;
|
||||
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks();
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
FeatureflagService,
|
||||
{ provide: SharedService,useValue: SharedServiceMock },
|
||||
],
|
||||
}).compile();
|
||||
|
||||
service = module.get<FeatureflagService>(FeatureflagService);
|
||||
sharedService = module.get<SharedService>(SharedService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
|
||||
it('should set feature flag state', async () => {
|
||||
const testFeatureName = 'TestFeatureFlag';
|
||||
const testFeatureState = true;
|
||||
const setConfigMock = jest
|
||||
.spyOn(sharedService,'setConfig')
|
||||
.mockImplementation((name: string, value: string) => Promise.resolve({ key: name, value: value}));
|
||||
await service.setFeatureFlag(testFeatureName, testFeatureState);
|
||||
expect(setConfigMock).toHaveBeenCalledWith(`featureflag/${testFeatureName}`, testFeatureState.toString());
|
||||
expect(setConfigMock).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should return state of the feature flag', async () => {
|
||||
const testFeatureName = 'TestFeatureFlag';
|
||||
const testFeatureState = true;
|
||||
const getConfigMock = jest
|
||||
.spyOn(sharedService, 'getConfig')
|
||||
.mockImplementation((key: string) => Promise.resolve({ key:key, value: testFeatureState.toString() }));
|
||||
await service.getFeatureFlag(testFeatureName);
|
||||
|
||||
expect(getConfigMock).toHaveBeenCalledTimes(1);
|
||||
expect(getConfigMock).toHaveBeenCalledWith(`featureflag/${testFeatureName}`);
|
||||
|
||||
});
|
||||
});
|
||||
43
src/featureflag/featureflag.service.ts
Normal file
43
src/featureflag/featureflag.service.ts
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
import {Injectable, Logger} from '@nestjs/common';
|
||||
import {SharedService} from "../shared/shared.service";
|
||||
import {ClientNotificationType} from "../socket/socket.gateway";
|
||||
|
||||
export interface IFeatureFlagStatus {
|
||||
name: string;
|
||||
state: boolean;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class FeatureflagService {
|
||||
private logger = new Logger(FeatureflagService.name);
|
||||
constructor(private sharedService: SharedService ) {
|
||||
}
|
||||
|
||||
async getFeatureFlag(id: string): Promise<IFeatureFlagStatus> {
|
||||
this.logger.verbose(`[getFeatureFlag] Getting feature flag status for ${id}`);
|
||||
const configRecord = await this.sharedService.getConfig(`featureflag/${id}`);
|
||||
let ffState;
|
||||
if(!configRecord) {
|
||||
ffState = false;
|
||||
} else {
|
||||
ffState = configRecord.value !== 'false'
|
||||
}
|
||||
this.logger.verbose(`[getFeatureFlag] Feature flag status for ${id} is ${ffState}`);
|
||||
return {
|
||||
name: id,
|
||||
state: ffState
|
||||
}
|
||||
}
|
||||
|
||||
async setFeatureFlag(id: string, status: boolean) : Promise<IFeatureFlagStatus> {
|
||||
this.logger.verbose(`Setting feature flag status for ${id} to ${status} `);
|
||||
const result = await this.sharedService.setConfig(`featureflag/${id}`, status.toString());
|
||||
const ffStatus: IFeatureFlagStatus = {
|
||||
name: id,
|
||||
state: result.value !== 'false',
|
||||
}
|
||||
this.sharedService.notifyAllClients<IFeatureFlagStatus>(ClientNotificationType.FeatureFlagChanged, ffStatus);
|
||||
return ffStatus;
|
||||
}
|
||||
|
||||
}
|
||||
13
src/game/comand-handlers/begin-versus-command.handler.ts
Normal file
13
src/game/comand-handlers/begin-versus-command.handler.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
import {CommandHandler, ICommandHandler} from "@nestjs/cqrs";
|
||||
import {BeginVersusCommand} from "../commands/begin-versus.command";
|
||||
import {VersusService} from "../versus/versus.service";
|
||||
|
||||
@CommandHandler(BeginVersusCommand)
|
||||
export class BeginVersusCommandHandler implements ICommandHandler<BeginVersusCommand> {
|
||||
constructor(private versusService:VersusService) {
|
||||
}
|
||||
execute(command: BeginVersusCommand): Promise<any> {
|
||||
return this.versusService.beginVersus(command.sourceId,command.destinationId);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,15 +1,19 @@
|
|||
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs';
|
||||
import { CreateNewQueueItemCommand } from '../commands/create-new-queue-item.command';
|
||||
import { GameService } from '../game.service';
|
||||
import {Logger} from "@nestjs/common";
|
||||
|
||||
@CommandHandler(CreateNewQueueItemCommand)
|
||||
export class CreateNewQueueItemCommandHandler implements ICommandHandler<CreateNewQueueItemCommand> {
|
||||
private logger = new Logger(CreateNewQueueItemCommandHandler.name);
|
||||
constructor(
|
||||
private gameService: GameService,
|
||||
|
||||
) {
|
||||
}
|
||||
|
||||
async execute(command: CreateNewQueueItemCommand): Promise<any> {
|
||||
this.logger.verbose(`Adding new queue item ${command.type} for ${command.target}`);
|
||||
await this.gameService.addTaskToGameQueue(command.target, command.type, command.text);
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { GiveOutAPrizeCommand } from '../commands/give-out-a-prize.command';
|
|||
import { GameService } from '../game.service';
|
||||
import { Logger } from '@nestjs/common';
|
||||
import { GameQueueTypes } from '../../schemas/game-queue.schema';
|
||||
import {GuestsService} from "../../guests/guests.service";
|
||||
|
||||
@CommandHandler(GiveOutAPrizeCommand)
|
||||
export class GameGiveOutAPrizeCommandHandler
|
||||
|
|
@ -10,11 +11,12 @@ export class GameGiveOutAPrizeCommandHandler
|
|||
|
||||
private readonly logger = new Logger(GameGiveOutAPrizeCommandHandler.name);
|
||||
|
||||
constructor(private gameService: GameService) {
|
||||
constructor(private gameService: GameService, private guestService: GuestsService) {
|
||||
}
|
||||
|
||||
async execute(command: GiveOutAPrizeCommand): Promise<any> {
|
||||
this.logger.verbose(`Player winning a prize ${command.telegramId}`);
|
||||
await this.guestService.incrementPrizeCount(command.telegramId);
|
||||
return this.gameService.addTaskToGameQueue(
|
||||
command.telegramId,
|
||||
GameQueueTypes.giveOutAPrize,
|
||||
|
|
|
|||
|
|
@ -1,13 +1,14 @@
|
|||
import { CommandBus, CommandHandler, EventBus, ICommandHandler } from '@nestjs/cqrs';
|
||||
import { ProceedGameQueueCommand } from '../commands/proceed-game-queue.command';
|
||||
import { GameService } from '../game.service';
|
||||
import { NextQuestionCommand } from '../commands/next-question.command';
|
||||
import { SharedService } from '../../shared/shared.service';
|
||||
import { SocketEvents } from '../../shared/events.consts';
|
||||
import { Logger } from '@nestjs/common';
|
||||
import { GameQueueTypes } from '../../schemas/game-queue.schema';
|
||||
import { QuizAnswerStateChangedEvent } from '../events/quiz-answer-state-changed.event';
|
||||
import { QuizAnswerStateEnum } from '../entities/quiz-answer-state.enum';
|
||||
import {CommandBus, CommandHandler, EventBus, ICommandHandler} from '@nestjs/cqrs';
|
||||
import {ProceedGameQueueCommand} from '../commands/proceed-game-queue.command';
|
||||
import {GameService} from '../game.service';
|
||||
import {NextQuestionCommand} from '../commands/next-question.command';
|
||||
import {SharedService} from '../../shared/shared.service';
|
||||
import {Logger} from '@nestjs/common';
|
||||
import {GameQueueTypes} from '../../schemas/game-queue.schema';
|
||||
import {QuizAnswerStateChangedEvent} from '../events/quiz-answer-state-changed.event';
|
||||
import {QuizAnswerStateEnum} from '../entities/quiz-answer-state.enum';
|
||||
import {IGameQueueSocketEvent} from "../../Consts/types";
|
||||
import {ClientNotificationType} from "../../socket/socket.gateway";
|
||||
|
||||
@CommandHandler(ProceedGameQueueCommand)
|
||||
export class GameProceedGameQueueCommandHandler
|
||||
|
|
@ -25,16 +26,13 @@ export class GameProceedGameQueueCommandHandler
|
|||
if (!item) {
|
||||
return this.cmdBus.execute(new NextQuestionCommand());
|
||||
}
|
||||
this.sharedService.sendSocketNotificationToAllClients(
|
||||
SocketEvents.GameQueueItem,
|
||||
{
|
||||
_id: item.id,
|
||||
completed: item.completed,
|
||||
target: item.target,
|
||||
type: item.type,
|
||||
text: item.text
|
||||
},
|
||||
);
|
||||
this.sharedService.notifyAllClients<IGameQueueSocketEvent>(ClientNotificationType.GameQueueItem, {
|
||||
_id: item._id,
|
||||
completed: item.completed,
|
||||
target: item.target,
|
||||
type: item.type,
|
||||
text: item.text
|
||||
});
|
||||
switch (item.type) {
|
||||
case GameQueueTypes.giveOutAPrize:
|
||||
this.eventBus.publish(
|
||||
|
|
|
|||
|
|
@ -18,18 +18,21 @@ export class SelectTargetPlayerHandler implements ICommandHandler<SelectTargetPl
|
|||
}
|
||||
async execute(command: SelectTargetPlayerCommand): Promise<any> {
|
||||
this.logger.verbose('enter');
|
||||
//const user = await this.guestService.findById(command.player);
|
||||
const allUsers = await this.guestService.findAll();
|
||||
let allUsers = await this.guestService.findAll();
|
||||
const user = allUsers.find(x => x.telegramId === command.player);
|
||||
if(!user) {
|
||||
throw new Error(`Cant find current user ${command.player}`);
|
||||
}
|
||||
if(!command.allowSelf) {
|
||||
allUsers = allUsers.filter((x) => x.telegramId !== command.player);
|
||||
}
|
||||
const buttons = allUsers.map((x) => {
|
||||
return [{
|
||||
text: `${Messages.EMOJI_PLAYER} ${x.name}`,
|
||||
callback_data: `{ "card": "${command.debuffName}", "value": "${command.value}", "user": "${x.telegramId}" }`
|
||||
}]
|
||||
});
|
||||
console.log(buttons);
|
||||
|
||||
this.telegramService.send<MqtMessageModel,ChatMessageRequestModel>(
|
||||
{ cmd: CommandsConsts.SendMessage},
|
||||
|
|
|
|||
6
src/game/commands/begin-versus.command.ts
Normal file
6
src/game/commands/begin-versus.command.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
|
||||
export class BeginVersusCommand {
|
||||
constructor(public sourceId: number, public destinationId: number) {
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
export class SelectTargetPlayerCommand {
|
||||
constructor(public player,public debuffName: string, public value: string|number) {
|
||||
constructor(public player,public debuffName: string, public value: string|number, public allowSelf = true) {
|
||||
}
|
||||
}
|
||||
|
|
@ -19,8 +19,9 @@ import {SetGuestPropertyCommand} from "../../guests/command/set-guest-property.c
|
|||
import {StringHelper} from "../../helpers/stringhelper";
|
||||
import {GetGuestQuery} from "../../guests/queries/getguest.query";
|
||||
import {CardsSetChangedEvent} from "../events/cards-events/cards-set-changed.event";
|
||||
import {GetGuestPropertyQuery} from "../../guests/command/get-guest-property.handler";
|
||||
import {GuestPropertiesConsts} from "../../schemas/properties.consts";
|
||||
import {BeginVersusCommand} from "../commands/begin-versus.command";
|
||||
import {CheckIfAnotherVersusInProgressQuery} from "../queries/check-if-another-versus-in-progress.query";
|
||||
|
||||
export interface IGameCard {
|
||||
setupHandlers(eventBus: EventBus, commandBus: CommandBus, queryBus: QueryBus): void;
|
||||
|
|
@ -74,7 +75,7 @@ export class DoubleTreasureCard extends GameCard {
|
|||
await this.commandBus.execute(
|
||||
new GiveOutAPrizeCommand(this.telegramId),
|
||||
);
|
||||
const userSrc = await this.queryBus.execute(new GetGuestQuery(this.telegramId));;
|
||||
const userSrc = await this.queryBus.execute(new GetGuestQuery(this.telegramId));
|
||||
const subjcaseFrom = userSrc.get(StringHelper.getPropertyName(GuestPropertiesConsts.NameSubjectiveCase));
|
||||
const message = `${subjcaseFrom} решает удвоить приз!`;
|
||||
await this.commandBus.execute(new SendToastCommand(message, 8000));
|
||||
|
|
@ -174,6 +175,43 @@ export class AvoidPenaltyCard extends GameCard {
|
|||
|
||||
}
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class VersusCard extends GameCard {
|
||||
dealOnStart = true;
|
||||
name = VersusCard.name;
|
||||
chance = 10;
|
||||
emoji = '🆚';
|
||||
description = 'Поединок';
|
||||
mightBePlayed = QuizAnswerStateEnum.betweenRounds;
|
||||
async setupHandlers(eventBus: EventBus, commandBus: CommandBus, queryBus: QueryBus) {
|
||||
super.setupHandlers(eventBus, commandBus, queryBus);
|
||||
eventBus.pipe(
|
||||
ofType(DebuffCardPlayedEvent),
|
||||
filter(x => x.debufName == DebuffsConsts.versus))
|
||||
.subscribe(async (r) =>{
|
||||
const versusInProgress = await queryBus.execute(new CheckIfAnotherVersusInProgressQuery());
|
||||
this.logger.verbose(`versusInProgress ${versusInProgress}`);
|
||||
if(versusInProgress) {
|
||||
this.logger.warn(`another versus in progress`);
|
||||
return;
|
||||
}
|
||||
const destUser = await queryBus.execute(new GetGuestQuery(r.dest))
|
||||
const sourceUser = await queryBus.execute(new GetGuestQuery(r.from));
|
||||
await commandBus.execute(new BeginVersusCommand(sourceUser.telegramId, destUser.telegramId));
|
||||
});
|
||||
}
|
||||
async handle() {
|
||||
await this.commandBus.execute(
|
||||
new SelectTargetPlayerCommand(this.telegramId, DebuffsConsts.versus, 0, false)
|
||||
)
|
||||
await this.queryBus.execute(new FilterGuestsWithPropertyQuery(null,null,null));
|
||||
this.eventBus.subscribe((data) =>{
|
||||
this.logger.verbose(`Response from cmdBus: ${data}`);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class BanPlayer extends GameCard {
|
||||
dealOnStart = true;
|
||||
|
|
@ -197,7 +235,6 @@ export class BanPlayer extends GameCard {
|
|||
eventBus.publish(new CardsSetChangedEvent(sourceUser.telegramId));
|
||||
})
|
||||
eventBus.pipe(ofType(NextQuestionEvent)).subscribe(async (r)=> {
|
||||
this.logger.verbose(`next event`);
|
||||
const players = await queryBus.execute(new FilterGuestsWithPropertyQuery(DebuffsConsts.bannedFor, '$gt', 0))
|
||||
this.logger.verbose(`enter: ban card handler, banned players count ${players.length}`);
|
||||
players.map(async (player) => {
|
||||
|
|
@ -213,7 +250,7 @@ export class BanPlayer extends GameCard {
|
|||
|
||||
async handle() {
|
||||
await this.commandBus.execute(
|
||||
new SelectTargetPlayerCommand(this.telegramId, DebuffsConsts.bannedFor, 2)
|
||||
new SelectTargetPlayerCommand(this.telegramId, DebuffsConsts.bannedFor, getRandomInt(2,3), false)
|
||||
)
|
||||
await this.queryBus.execute(new FilterGuestsWithPropertyQuery(null,null,null));
|
||||
this.eventBus.subscribe((data) =>{
|
||||
|
|
@ -225,8 +262,9 @@ export class BanPlayer extends GameCard {
|
|||
export const gameCards: typeof GameCard[] = [
|
||||
DoubleTreasureCard,
|
||||
StolePrizeCard,
|
||||
ShitCard,
|
||||
// ShitCard,
|
||||
LuckyCard,
|
||||
AvoidPenaltyCard,
|
||||
BanPlayer
|
||||
BanPlayer,
|
||||
VersusCard,
|
||||
];
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
export class DebuffsConsts {
|
||||
static bannedFor = 'bannedFor';
|
||||
static versus = 'versus';
|
||||
}
|
||||
|
|
@ -10,6 +10,6 @@ export class QuizAnsweredEventHandler
|
|||
}
|
||||
|
||||
async handle(event: QuizAnsweredEvent) {
|
||||
await this.commandBus.execute(new HideKeyboardCommand(`На вопрос ответил: ${event.name}`));
|
||||
// await this.commandBus.execute(new HideKeyboardCommand(`На вопрос ответил: ${event.name}`));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,9 +10,6 @@ export class GameWrongAnswerReceivedEventHandler
|
|||
}
|
||||
|
||||
async handle(event: WrongAnswerReceivedEvent) {
|
||||
await this.gameService.addTaskToGameQueue(
|
||||
event.tId,
|
||||
GameQueueTypes.penalty,
|
||||
);
|
||||
//
|
||||
}
|
||||
}
|
||||
|
|
|
|||
4
src/game/events/state-changed.event.ts
Normal file
4
src/game/events/state-changed.event.ts
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
export class StateChangedEvent {
|
||||
constructor(state: string) {
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,9 @@
|
|||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { GameController } from './game.controller';
|
||||
import {GameService} from "./game.service";
|
||||
import {GameServiceMock} from "../mocks/game-service.mock";
|
||||
import {VersusService} from "./versus/versus.service";
|
||||
import {VersusServiceMock} from "../mocks/versus-service.mock";
|
||||
|
||||
describe('GameController', () => {
|
||||
let controller: GameController;
|
||||
|
|
@ -7,6 +11,10 @@ describe('GameController', () => {
|
|||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
controllers: [GameController],
|
||||
providers: [
|
||||
{ provide: GameService, useValue: GameServiceMock },
|
||||
{ provide: VersusService, useValue: VersusServiceMock },
|
||||
]
|
||||
}).compile();
|
||||
|
||||
controller = module.get<GameController>(GameController);
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
import { Controller, Get, Param, Post } from '@nestjs/common';
|
||||
import { Controller, Get, Logger, Param, Post } from '@nestjs/common';
|
||||
import { GameService } from './game.service';
|
||||
import {VersusService} from "./versus/versus.service";
|
||||
|
||||
@Controller('game')
|
||||
export class GameController {
|
||||
constructor(private gameService: GameService) {
|
||||
private readonly logger = new Logger(GameController.name);
|
||||
constructor(private gameService: GameService, private versusService: VersusService) {
|
||||
}
|
||||
|
||||
@Post(':id/complete')
|
||||
|
|
@ -30,4 +32,23 @@ export class GameController {
|
|||
async playExtraCards() {
|
||||
return this.gameService.playExtraCards();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Get('state-details')
|
||||
async getStateDetails() {
|
||||
return this.gameService.getStateDetails();
|
||||
}
|
||||
|
||||
@Post('clear-queue')
|
||||
async clearQueue() {
|
||||
this.logger.warn(`[clearQueue] enter`);
|
||||
await this.gameService.clearGameQueue();
|
||||
}
|
||||
|
||||
@Post('simulate-valid-answer')
|
||||
async simulateValidAnswer() {
|
||||
this.logger.verbose(`[simulateValidAnswer] enter`);
|
||||
return await this.gameService.simulateValidAnswer();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,11 @@ import {ConfigService} from "@nestjs/config";
|
|||
import {SelectTargetPlayerHandler} from "./comand-handlers/select-target-player.handler";
|
||||
import {SendBetweenRoundsActionsHandler} from "../guests/command/send-between-rounds-actions.command";
|
||||
import {GuestsModule} from "../guests/guests.module";
|
||||
import { VersusService } from './versus/versus.service';
|
||||
import { VersusController } from './versus/versus.controller';
|
||||
import {Versus, VersusSchema} from "../schemas/versus.schema";
|
||||
import {BeginVersusCommandHandler} from "./comand-handlers/begin-versus-command.handler";
|
||||
import {CheckIfAnotherVersusInProgressHandler} from "./queries/handlers/check-if-another-versus-in-progress.handler";
|
||||
|
||||
|
||||
const eventHandlers = [
|
||||
|
|
@ -34,19 +39,23 @@ const commandHandlers = [
|
|||
GamePrizeChanceIncreasedEventHandler,
|
||||
GameProceedGameQueueCommandHandler,
|
||||
SelectTargetPlayerHandler,
|
||||
SendBetweenRoundsActionsHandler
|
||||
SendBetweenRoundsActionsHandler,
|
||||
BeginVersusCommandHandler,
|
||||
];
|
||||
|
||||
const queryHandlers = [CheckIfAnotherVersusInProgressHandler];
|
||||
@Global()
|
||||
@Module({
|
||||
imports: [
|
||||
CqrsModule,
|
||||
MongooseModule.forFeature([
|
||||
{ name: GameQueue.name, schema: GameQueueSchema },
|
||||
{ name: Versus.name, schema: VersusSchema }
|
||||
]),
|
||||
forwardRef(() => GuestsModule)
|
||||
],
|
||||
providers: [GameService, ConfigService, ...eventHandlers, ...commandHandlers],
|
||||
providers: [GameService, ConfigService, ...eventHandlers, ...commandHandlers,...queryHandlers, VersusService],
|
||||
exports: [GameService],
|
||||
controllers: [GameController],
|
||||
controllers: [GameController, VersusController],
|
||||
})
|
||||
export class GameModule {}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,35 @@
|
|||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { GameService } from './game.service';
|
||||
import {CommandBus, EventBus, QueryBus} from "@nestjs/cqrs";
|
||||
import {CommandbusMock} from "../mocks/commandbus.mock";
|
||||
import {EventbusMock} from "../mocks/eventbus.mock";
|
||||
import {getModelToken} from "@nestjs/mongoose";
|
||||
import {GameQueue} from "../schemas/game-queue.schema";
|
||||
import {Model} from "mongoose";
|
||||
import {ConfigService} from "@nestjs/config";
|
||||
import {ConfigServiceMock} from "../mocks/config-service.mock";
|
||||
import {SharedService} from "../shared/shared.service";
|
||||
import {SharedServiceMock} from "../mocks/shared-service.mock";
|
||||
import {QueryBusMock} from "../mocks/querybus.mock";
|
||||
import {Guest} from "../schemas/guest.schema";
|
||||
import {GuestsService} from "../guests/guests.service";
|
||||
import {GuestsServiceMock} from "../mocks/guests-service.mock";
|
||||
|
||||
describe('GameService', () => {
|
||||
let service: GameService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [GameService],
|
||||
providers: [
|
||||
GameService,
|
||||
{ provide: CommandBus, useValue: CommandbusMock },
|
||||
{ provide: EventBus, useValue: EventbusMock },
|
||||
{ provide: getModelToken(GameQueue.name), useValue: Model },
|
||||
{ provide: ConfigService, useValue: ConfigServiceMock },
|
||||
{ provide: SharedService, useValue: SharedServiceMock },
|
||||
{ provide: QueryBus, useValue: QueryBusMock },
|
||||
{ provide: GuestsService, useValue: GuestsServiceMock }
|
||||
],
|
||||
}).compile();
|
||||
|
||||
service = module.get<GameService>(GameService);
|
||||
|
|
|
|||
|
|
@ -1,18 +1,17 @@
|
|||
import {Injectable, InternalServerErrorException, Logger, OnApplicationBootstrap} from '@nestjs/common';
|
||||
import {CommandBus, EventBus, QueryBus} from '@nestjs/cqrs';
|
||||
import { CardSelectionTimeExceedCommand } from './commands/card-selection-time-exceed.command';
|
||||
import { InjectModel } from '@nestjs/mongoose';
|
||||
import {
|
||||
GameQueue,
|
||||
GameQueueDocument,
|
||||
GameQueueTypes,
|
||||
} from '../schemas/game-queue.schema';
|
||||
import {CardSelectionTimeExceedCommand} from './commands/card-selection-time-exceed.command';
|
||||
import {InjectModel} from '@nestjs/mongoose';
|
||||
import {GameQueue, GameQueueDocument, GameQueueTypes,} from '../schemas/game-queue.schema';
|
||||
import {Model, Promise} from 'mongoose';
|
||||
import { ProceedGameQueueCommand } from './commands/proceed-game-queue.command';
|
||||
import { SharedService } from '../shared/shared.service';
|
||||
import { SocketEvents } from '../shared/events.consts';
|
||||
import {ProceedGameQueueCommand} from './commands/proceed-game-queue.command';
|
||||
import {SharedService} from '../shared/shared.service';
|
||||
import {ConfigService} from "@nestjs/config";
|
||||
import {gameCards} from "./entities/cards.entities";
|
||||
import {IEmptyNotification} from "../Consts/types";
|
||||
import {ClientNotificationType} from "../socket/socket.gateway";
|
||||
import {GetGuestQuery} from "../guests/queries/getguest.query";
|
||||
import {ValidAnswerReceivedEvent} from "./events/valid-answer.recieved";
|
||||
|
||||
@Injectable()
|
||||
export class GameService implements OnApplicationBootstrap{
|
||||
|
|
@ -53,7 +52,26 @@ export class GameService implements OnApplicationBootstrap{
|
|||
}
|
||||
|
||||
async getGameQueueItem() {
|
||||
return this.gameQueueModel.findOne({ completed: false }).exec();
|
||||
const item = await this.gameQueueModel.aggregate([
|
||||
{
|
||||
$match: { completed: false }
|
||||
},
|
||||
{
|
||||
$addFields: {
|
||||
priority: {
|
||||
$cond: [{ $eq: ["$type", "versus"] }, 1, 0]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
$sort: { priority: -1 }
|
||||
},
|
||||
{
|
||||
$limit: 1
|
||||
}
|
||||
]).exec();
|
||||
console.log(item[0]);
|
||||
return item[0];
|
||||
}
|
||||
|
||||
async markQueueAsCompleted(id: string| null) {
|
||||
|
|
@ -63,35 +81,27 @@ export class GameService implements OnApplicationBootstrap{
|
|||
} else {
|
||||
qItem = await this.gameQueueModel.findById(id).exec();
|
||||
}
|
||||
this.logger.verbose(`Set ${id} in queue as completed`);
|
||||
this.logger.verbose(`Set ${qItem.id} in queue as completed`);
|
||||
if (!qItem) {
|
||||
throw new InternalServerErrorException('no such item');
|
||||
|
||||
}
|
||||
qItem.completed = true;
|
||||
await qItem.save();
|
||||
this.sharedService.sendSocketNotificationToAllClients(
|
||||
SocketEvents.QUEUE_COMPLETED,
|
||||
{},
|
||||
);
|
||||
this.sharedService.notifyAllClients<IEmptyNotification>(ClientNotificationType.QueueCompleted, {});
|
||||
await this.cmdBus.execute(new ProceedGameQueueCommand());
|
||||
return qItem;
|
||||
}
|
||||
|
||||
async pauseGame() {
|
||||
await this.sharedService.setConfig('game_state', 'paused');
|
||||
await this.sharedService.sendSocketNotificationToAllClients(
|
||||
SocketEvents.GAME_PAUSED,
|
||||
{},
|
||||
);
|
||||
this.sharedService.notifyAllClients<IEmptyNotification>(ClientNotificationType.GamePaused, {});
|
||||
return Promise.resolve({ result: true });
|
||||
}
|
||||
|
||||
async resumeGame() {
|
||||
await this.sharedService.setConfig('game_state', 'running');
|
||||
await this.sharedService.sendSocketNotificationToAllClients(
|
||||
SocketEvents.GAME_RESUMED,
|
||||
{},
|
||||
);
|
||||
this.sharedService.notifyAllClients<IEmptyNotification>(ClientNotificationType.GameResumed,{});
|
||||
return Promise.resolve({ result: true });
|
||||
}
|
||||
|
||||
|
|
@ -112,4 +122,17 @@ export class GameService implements OnApplicationBootstrap{
|
|||
cardInstance.setupHandlers(this.eventBus, this.commandBus, this.queryBus);
|
||||
})
|
||||
}
|
||||
|
||||
async getStateDetails() {
|
||||
return await this.sharedService.getConfig('current_action') || null;
|
||||
}
|
||||
|
||||
async clearGameQueue() {
|
||||
await this.gameQueueModel.deleteMany({}).exec();
|
||||
return { result: true };
|
||||
}
|
||||
|
||||
async simulateValidAnswer() {
|
||||
this.eventBus.publish(new ValidAnswerReceivedEvent(11178819, 'test', ''));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
export class CheckIfAnotherVersusInProgressQuery {
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
import {IQueryHandler, QueryHandler} from "@nestjs/cqrs";
|
||||
import {CheckIfAnotherVersusInProgressQuery} from "../check-if-another-versus-in-progress.query";
|
||||
import {VersusService} from "../../versus/versus.service";
|
||||
|
||||
@QueryHandler(CheckIfAnotherVersusInProgressQuery)
|
||||
export class CheckIfAnotherVersusInProgressHandler implements IQueryHandler<CheckIfAnotherVersusInProgressHandler> {
|
||||
constructor(private versusService: VersusService) {
|
||||
}
|
||||
async execute(query: CheckIfAnotherVersusInProgressHandler): Promise<any> {
|
||||
return await this.versusService.checkIfAnotherVersusInProgress();
|
||||
}
|
||||
|
||||
}
|
||||
23
src/game/versus/versus.controller.spec.ts
Normal file
23
src/game/versus/versus.controller.spec.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { VersusController } from './versus.controller';
|
||||
import {VersusService} from "./versus.service";
|
||||
import {VersusServiceMock} from "../../mocks/versus-service.mock";
|
||||
|
||||
describe('VersusController', () => {
|
||||
let controller: VersusController;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
controllers: [VersusController],
|
||||
providers: [
|
||||
{ provide: VersusService, useValue: VersusServiceMock },
|
||||
]
|
||||
}).compile();
|
||||
|
||||
controller = module.get<VersusController>(VersusController);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(controller).toBeDefined();
|
||||
});
|
||||
});
|
||||
37
src/game/versus/versus.controller.ts
Normal file
37
src/game/versus/versus.controller.ts
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
import {Body, Controller, Get, Logger, Post} from '@nestjs/common';
|
||||
import {VersusService} from "./versus.service";
|
||||
import {VersusDto} from "./versus.types";
|
||||
|
||||
@Controller('versus')
|
||||
export class VersusController {
|
||||
private logger = new Logger(VersusController.name);
|
||||
constructor(private versusService: VersusService) {
|
||||
|
||||
}
|
||||
|
||||
@Post('simulate-versus')
|
||||
async SimulateVersus() {
|
||||
this.logger.verbose('[SimulateVersus] enter');
|
||||
return this.versusService.simulateVersus();
|
||||
}
|
||||
|
||||
@Post('import')
|
||||
async Import(@Body() data: VersusDto[]) {
|
||||
return await this.versusService.importVersus(data);
|
||||
}
|
||||
|
||||
@Get()
|
||||
async GetVersusTask() {
|
||||
return await this.versusService.getVersusTask();
|
||||
}
|
||||
|
||||
@Post('complete')
|
||||
async Completed(@Body() payload: { winner: number, loser: number }) {
|
||||
return await this.versusService.complete(payload.winner, payload.loser);
|
||||
}
|
||||
|
||||
@Post('reset-all')
|
||||
async markAllUncompleted() {
|
||||
return await this.versusService.markAllAsUncompleted();
|
||||
}
|
||||
}
|
||||
61
src/game/versus/versus.service.spec.ts
Normal file
61
src/game/versus/versus.service.spec.ts
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { VersusService } from './versus.service';
|
||||
import {GuestsService} from "../../guests/guests.service";
|
||||
import {GuestsServiceMock} from "../../mocks/guests-service.mock";
|
||||
import {SharedService} from "../../shared/shared.service";
|
||||
import {getModelToken} from "@nestjs/mongoose";
|
||||
import {Versus, VersusDocument} from "../../schemas/versus.schema";
|
||||
import {Model} from "mongoose";
|
||||
import {CommandBus, QueryBus} from "@nestjs/cqrs";
|
||||
import {QueryBusMock} from "../../mocks/querybus.mock";
|
||||
|
||||
const mockVersusModel = {
|
||||
aggregate: jest.fn().mockReturnThis(),
|
||||
exec: jest.fn(),
|
||||
}
|
||||
|
||||
describe('VersusService', () => {
|
||||
let service: VersusService;
|
||||
let versusModel: Model<Versus>;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
VersusService,
|
||||
{ provide: GuestsService, useValue: GuestsServiceMock },
|
||||
{ provide: SharedService, useValue: SharedService },
|
||||
{ provide: getModelToken(Versus.name), useValue: mockVersusModel },
|
||||
{ provide: CommandBus, useValue: CommandBus },
|
||||
{ provide: QueryBus, useValue: QueryBusMock },
|
||||
],
|
||||
}).compile();
|
||||
|
||||
service = module.get<VersusService>(VersusService);
|
||||
versusModel = module.get<Model<VersusDocument>>(getModelToken(Versus.name));
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
|
||||
describe('validateVersusTasksAndResetIfNecessary', () => {
|
||||
it('should reset all tasks if no remaining', async () => {
|
||||
// setup
|
||||
mockVersusModel.exec.mockResolvedValue([]);
|
||||
const markCompletedSpy = jest.spyOn(service, 'markAllAsUncompleted').mockResolvedValue(null);
|
||||
|
||||
// act
|
||||
await service.validateVersusTasksAndResetIfNecessary();
|
||||
|
||||
// validate
|
||||
expect(markCompletedSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not reset tasks if it is presented', async () => {
|
||||
mockVersusModel.exec.mockReturnValue(['item1', 'item2']);
|
||||
const markCompletedSpy = jest.spyOn(service,'markAllAsUncompleted').mockResolvedValue(null);
|
||||
await service.validateVersusTasksAndResetIfNecessary();
|
||||
expect(markCompletedSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
})
|
||||
});
|
||||
145
src/game/versus/versus.service.ts
Normal file
145
src/game/versus/versus.service.ts
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
import {Injectable, Logger} from '@nestjs/common';
|
||||
import {GuestsService} from "../../guests/guests.service";
|
||||
import {SharedService} from "../../shared/shared.service";
|
||||
import {InjectModel} from "@nestjs/mongoose";
|
||||
import {Versus, VersusDocument} from "../../schemas/versus.schema";
|
||||
import {Model} from "mongoose";
|
||||
import {VersusDto} from "./versus.types";
|
||||
import {CommandBus, QueryBus} from "@nestjs/cqrs";
|
||||
import {IncreasePlayerScoreCommand} from "../../guests/command/increase-player-score.command";
|
||||
import {IncreasePlayerWinningRateCommand} from "../commands/increase-player-winning-rate.command";
|
||||
import {GetGuestPropertyQuery} from "../../guests/command/get-guest-property.handler";
|
||||
import {GuestPropertyNamesConsts} from "../../Consts/guest-property-names.consts";
|
||||
import {SetGuestPropertyCommand} from "../../guests/command/set-guest-property.command";
|
||||
import {IVersusBeginSocketEvent, IVersusEndSocketEvent} from "../../Consts/types";
|
||||
import {ClientNotificationType} from "../../socket/socket.gateway";
|
||||
import {CreateNewQueueItemCommand} from "../commands/create-new-queue-item.command";
|
||||
import {GameQueueTypes} from "../../schemas/game-queue.schema";
|
||||
|
||||
@Injectable()
|
||||
export class VersusService {
|
||||
static configKeyCurrentAction = 'current_action';
|
||||
static configKeyActiveVersus = 'active_versus';
|
||||
private logger = new Logger(VersusService.name);
|
||||
constructor(
|
||||
private guestService: GuestsService,
|
||||
private sharedService: SharedService,
|
||||
private queryBus: QueryBus,
|
||||
@InjectModel(Versus.name) private versusModel: Model<VersusDocument>,
|
||||
private cmdBus: CommandBus,
|
||||
) {
|
||||
}
|
||||
async simulateVersus() {
|
||||
const guests = (await this.guestService.findAll()).slice(0,2).map((guest) => {
|
||||
return {
|
||||
id: guest.telegramId,
|
||||
name: guest.name,
|
||||
}
|
||||
});
|
||||
if(guests.length < 2) {
|
||||
throw new Error("Can't simulate, in db less than 2 players")
|
||||
}
|
||||
await this.beginVersus(guests[0].id, guests[1].id);
|
||||
}
|
||||
|
||||
async beginVersus(player1: number, player2: number) {
|
||||
const [p1data,p2data] = await Promise.all([this.guestService.findById(player1), this.guestService.findById(player2)]);
|
||||
await this.cmdBus.execute(new CreateNewQueueItemCommand(player1, GameQueueTypes.versus));
|
||||
await this.sharedService.setConfig(VersusService.configKeyCurrentAction, JSON.stringify({
|
||||
action:'versus',
|
||||
data: {
|
||||
player1: player1,
|
||||
player2: player2,
|
||||
player1name: p1data.name,
|
||||
player2name: p2data.name,
|
||||
}
|
||||
}));
|
||||
this.sharedService.notifyAllClients<IVersusBeginSocketEvent>(ClientNotificationType.BeginVersus, {
|
||||
player1,
|
||||
player2,
|
||||
player1name: p1data.name,
|
||||
player2name: p2data.name
|
||||
});
|
||||
}
|
||||
|
||||
async importVersus(data: VersusDto[]) {
|
||||
data.map(async (record) => {
|
||||
const item = new this.versusModel({
|
||||
...record
|
||||
});
|
||||
await item.save();
|
||||
});
|
||||
return { result: true };
|
||||
}
|
||||
|
||||
async validateVersusTasksAndResetIfNecessary() {
|
||||
const versus = await this.versusModel.aggregate([{ $match: { completed: false } }, { $sample: { size: 1 } }]).exec();
|
||||
if(versus.length == 0 ) {
|
||||
await this.markAllAsUncompleted();
|
||||
}
|
||||
}
|
||||
|
||||
async getVersusTask() {
|
||||
await this.validateVersusTasksAndResetIfNecessary();
|
||||
const rand = await this.versusModel
|
||||
.aggregate([{ $match: { completed: false } }, { $sample: { size: 1 } }])
|
||||
.exec();
|
||||
// @TODO check who win with telegram
|
||||
|
||||
const item = await this.versusModel.findOne({ _id: rand[0]._id }).exec();
|
||||
await this.sharedService.setConfig(VersusService.configKeyActiveVersus, item.id);
|
||||
return item;
|
||||
}
|
||||
async markAllAsUncompleted() {
|
||||
const versuses = await this.versusModel.find().exec();
|
||||
versuses.map(async (versus) => {
|
||||
versus.completed = false;
|
||||
await versus.save();
|
||||
});
|
||||
return { result: true };
|
||||
}
|
||||
|
||||
async complete(winner: number, loser: number) {
|
||||
const activeVersus = await this.sharedService.getConfig(VersusService.configKeyActiveVersus);
|
||||
const item = await this.versusModel.findOne({ _id: activeVersus.value }).exec();
|
||||
item.completed = true;
|
||||
await item.save();
|
||||
const tasks = [];
|
||||
tasks.push(this.cmdBus.execute(new IncreasePlayerScoreCommand(winner, 1)));
|
||||
tasks.push(this.cmdBus.execute(new IncreasePlayerWinningRateCommand(winner, 20)));
|
||||
tasks.push(this.sharedService.setConfig(VersusService.configKeyCurrentAction, ''));
|
||||
let wonCount = await this.queryBus.execute(new GetGuestPropertyQuery(winner, GuestPropertyNamesConsts.VersusWonCount));
|
||||
let loseCount = await this.queryBus.execute(new GetGuestPropertyQuery(loser, GuestPropertyNamesConsts.VersusLoseCount));
|
||||
if(!wonCount) {
|
||||
wonCount = 1;
|
||||
} else {
|
||||
wonCount = +wonCount++;
|
||||
}
|
||||
if(!loseCount) {
|
||||
loseCount = 1;
|
||||
} else {
|
||||
loseCount = +loseCount++;
|
||||
}
|
||||
this.logger.verbose(`Set loseCount for ${loser} to ${loseCount}`);
|
||||
this.logger.verbose(`Set win count for ${winner} to ${wonCount}`);
|
||||
tasks.push(await this.cmdBus.execute(new SetGuestPropertyCommand(winner, GuestPropertyNamesConsts.VersusWonCount, wonCount.toString)));
|
||||
tasks.push(await this.cmdBus.execute(new SetGuestPropertyCommand(loser, GuestPropertyNamesConsts.VersusWonCount, loseCount.toString)));
|
||||
|
||||
await Promise.all(tasks);
|
||||
this.sharedService.notifyAllClients<IVersusEndSocketEvent>(ClientNotificationType.EndVersus, {
|
||||
winner: winner
|
||||
}
|
||||
);
|
||||
return item;
|
||||
}
|
||||
|
||||
async checkIfAnotherVersusInProgress() {
|
||||
this.logger.debug(`checkIfAnotherVersusInProgress enter`)
|
||||
const currentAction = await this.sharedService.getConfig(VersusService.configKeyCurrentAction);
|
||||
if(!currentAction) {
|
||||
return false;
|
||||
}
|
||||
return currentAction.value !== '';
|
||||
|
||||
}
|
||||
}
|
||||
6
src/game/versus/versus.types.d.ts
vendored
Normal file
6
src/game/versus/versus.types.d.ts
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
export interface VersusDto {
|
||||
id: string;
|
||||
text: string;
|
||||
completed: boolean;
|
||||
description: string;
|
||||
}
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { GiftsController } from './gifts.controller';
|
||||
import {GiftsService} from "./gifts.service";
|
||||
import {GiftServiceMock} from "../mocks/gift-service.mock";
|
||||
|
||||
describe('GiftsController', () => {
|
||||
let controller: GiftsController;
|
||||
|
|
@ -7,6 +9,9 @@ describe('GiftsController', () => {
|
|||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
controllers: [GiftsController],
|
||||
providers: [
|
||||
{ provide: GiftsService, useValue: GiftServiceMock },
|
||||
]
|
||||
}).compile();
|
||||
|
||||
controller = module.get<GiftsController>(GiftsController);
|
||||
|
|
|
|||
|
|
@ -1,12 +1,18 @@
|
|||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { GiftsService } from './gifts.service';
|
||||
import {getModelToken} from "@nestjs/mongoose";
|
||||
import {Prize} from "../schemas/prize.schema";
|
||||
import {Model} from "mongoose";
|
||||
|
||||
describe('GiftsService', () => {
|
||||
let service: GiftsService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [GiftsService],
|
||||
providers: [
|
||||
GiftsService,
|
||||
{ provide: getModelToken(Prize.name), useValue: Model },
|
||||
],
|
||||
}).compile();
|
||||
|
||||
service = module.get<GiftsService>(GiftsService);
|
||||
|
|
|
|||
|
|
@ -15,9 +15,8 @@ export class GetGuestPropertyHandler implements ICommandHandler<GetGuestProperty
|
|||
async execute(command: GetGuestPropertyQuery): Promise<string> {
|
||||
this.logger.verbose(`entering`);
|
||||
const guest = await this.guestService.findById(command.user);
|
||||
console.log(command);
|
||||
this.logger.verbose(command);
|
||||
if(!command.property.startsWith('properties.')) {
|
||||
|
||||
command.property = `properties.${command.property}`;
|
||||
this.logger.warn(`update prop ${command.property}`);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,13 +28,16 @@ export class TgPostCardsToUserCommandHandler implements ICommandHandler<PostCard
|
|||
},
|
||||
}
|
||||
if (command.cards.length === 0) {
|
||||
this.telegramService.emit({ cmd: CommandsConsts.SendMessage }, { chatId: command.chatId, message: "У вас нет карт которые можно сейчас использовать"});
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
command.cards.forEach((card) => {
|
||||
extra.reply_markup.keyboard.push([
|
||||
{text: Messages.EMOJI_CARD + ' ' + card},
|
||||
]);
|
||||
extra_Inline.reply_markup.inline_keyboard.push([{ text: Messages.EMOJI_CARD + ' ' + card}])
|
||||
extra_Inline.reply_markup.inline_keyboard.push([{ text: Messages.EMOJI_CARD + ' ' + card, callback_data: `card/${card}`}])
|
||||
});
|
||||
await this.sharedService.setConfig(`buttons_${command.chatId}`,
|
||||
JSON.stringify(extra),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
import {CommandHandler, ICommandHandler} from "@nestjs/cqrs";
|
||||
import {IncreasePlayerScoreCommand} from "../increase-player-score.command";
|
||||
import { Logger } from "@nestjs/common";
|
||||
import {GuestsService} from "../../guests.service";
|
||||
|
||||
@CommandHandler(IncreasePlayerScoreCommand)
|
||||
export class IncreasePlayerScoreCommandHandler implements ICommandHandler<IncreasePlayerScoreCommand> {
|
||||
private logger = new Logger(IncreasePlayerScoreCommandHandler.name);
|
||||
constructor(private guestService: GuestsService) {
|
||||
}
|
||||
|
||||
|
||||
async execute(command: IncreasePlayerScoreCommand): Promise<any> {
|
||||
this.logger.verbose(`IncreasePlayerScoreCommandHandler: entering, arguments: player: ${command.user}, amount: ${command.score}`);
|
||||
await this.guestService.updatePlayerScore(command.user, command.score);
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
4
src/guests/command/increase-player-score.command.ts
Normal file
4
src/guests/command/increase-player-score.command.ts
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
export class IncreasePlayerScoreCommand {
|
||||
constructor(public user: number, public score: number) {
|
||||
}
|
||||
}
|
||||
|
|
@ -20,36 +20,36 @@ export class RemoveCardFromUserCommandHandler implements ICommandHandler<RemoveC
|
|||
}
|
||||
|
||||
async execute(command: RemoveCardFromUserCommand): Promise<any> {
|
||||
const guest = await this.guestService.findById(command.telegramId);
|
||||
const data = await this.sharedService.getConfig(`buttons_${command.telegramId}`);
|
||||
const extra = {
|
||||
reply_markup: {
|
||||
remove_keyboard: false,
|
||||
keyboard: [],
|
||||
},
|
||||
};
|
||||
const buttons = JSON.parse(data.value);
|
||||
let found = false;
|
||||
buttons.reply_markup.keyboard.forEach((item) => {
|
||||
if (item[0].text.includes(command.card.description) && !found) {
|
||||
found = true;
|
||||
} else {
|
||||
extra.reply_markup.keyboard.push(
|
||||
[ { ...item[0] } ]
|
||||
)
|
||||
}
|
||||
});
|
||||
if (extra.reply_markup.keyboard.length === 0) {
|
||||
extra.reply_markup.remove_keyboard = true;
|
||||
}
|
||||
await this.sharedService.setConfig(`buttons_${command.telegramId}`, JSON.stringify(extra));
|
||||
this.telegramService.emit<MqtMessageModel, ChatMessageRequestModel>({
|
||||
cmd: CommandsConsts.SendMessage,
|
||||
}, {
|
||||
chatId: guest.chatId,
|
||||
message: Messages.SELECT_CARD,
|
||||
extra: extra,
|
||||
})
|
||||
// const guest = await this.guestService.findById(command.telegramId);
|
||||
// const data = await this.sharedService.getConfig(`buttons_${command.telegramId}`);
|
||||
// const extra = {
|
||||
// reply_markup: {
|
||||
// remove_keyboard: false,
|
||||
// keyboard: [],
|
||||
// },
|
||||
// };
|
||||
// const buttons = JSON.parse(data.value);
|
||||
// let found = false;
|
||||
// buttons.reply_markup.keyboard.forEach((item) => {
|
||||
// if (item[0].text.includes(command.card.description) && !found) {
|
||||
// found = true;
|
||||
// } else {
|
||||
// extra.reply_markup.keyboard.push(
|
||||
// [ { ...item[0] } ]
|
||||
// )
|
||||
// }
|
||||
// });
|
||||
// if (extra.reply_markup.keyboard.length === 0) {
|
||||
// extra.reply_markup.remove_keyboard = true;
|
||||
// }
|
||||
// await this.sharedService.setConfig(`buttons_${command.telegramId}`, JSON.stringify(extra));
|
||||
// this.telegramService.emit<MqtMessageModel, ChatMessageRequestModel>({
|
||||
// cmd: CommandsConsts.SendMessage,
|
||||
// }, {
|
||||
// chatId: guest.chatId,
|
||||
// message: Messages.SELECT_CARD,
|
||||
// extra: extra,
|
||||
// })
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -3,7 +3,7 @@ import {Promise} from "mongoose";
|
|||
import {GuestsService} from "../guests.service";
|
||||
|
||||
export class SendBetweenRoundsActionsCommand {
|
||||
constructor(public user: number, public inline: boolean = false) {
|
||||
constructor(public user: number, public inline: boolean = true) {
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,14 +21,14 @@ export class GuestValidAnswerReceivedEventHandler
|
|||
async handle(event: ValidAnswerReceivedEvent) {
|
||||
await this.guestService.notifyAboutValidAnswer(event.tId);
|
||||
await this.guestService.sendValidAnswerActions(event.tId);
|
||||
await this.guestService.updatePlayerScore(event.tId, 1);
|
||||
// await this.guestService.updatePlayerScore(event.tId, 1);
|
||||
if (event.extraDetails) {
|
||||
await this.commandBus.execute(
|
||||
new SendToastCommand(event.extraDetails, 4000),
|
||||
);
|
||||
}
|
||||
const coef = +(await this.guestService.getCoefficient());
|
||||
await this.guestService.changeWinningChance(event.tId, 29 * coef);
|
||||
// await this.guestService.changeWinningChance(event.tId, 29 * coef);
|
||||
await this.guestService.resetInvalidAnswersInTheRow(event.tId);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
5
src/guests/guest.types.d.ts
vendored
Normal file
5
src/guests/guest.types.d.ts
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
export interface GuestNamesInCases {
|
||||
SubjectiveCase: string;
|
||||
AccusativeCase: string;
|
||||
GenitiveCase: string;
|
||||
}
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { GuestsController } from './guests.controller';
|
||||
import {GuestsService} from "./guests.service";
|
||||
import {GuestsServiceMock} from "../mocks/guests-service.mock";
|
||||
|
||||
describe('GuestsController', () => {
|
||||
let controller: GuestsController;
|
||||
|
|
@ -7,6 +9,9 @@ describe('GuestsController', () => {
|
|||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
controllers: [GuestsController],
|
||||
providers: [
|
||||
{ provide: GuestsService, useValue: GuestsServiceMock },
|
||||
]
|
||||
}).compile();
|
||||
|
||||
controller = module.get<GuestsController>(GuestsController);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import {
|
||||
Controller,
|
||||
Get, Param, Res
|
||||
Get, Param, Post, Res
|
||||
} from "@nestjs/common";
|
||||
import { GuestsService } from './guests.service';
|
||||
import { Response } from 'express';
|
||||
|
|
@ -42,4 +42,9 @@ export class GuestsController {
|
|||
// this.echoService.enterQuiz(u.chatId);
|
||||
});
|
||||
}
|
||||
@Post('reset-score')
|
||||
async resetAllPlayersScore() {
|
||||
await this.guestService.resetPlayersScore();
|
||||
return { result: true };
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Module } from '@nestjs/common';
|
||||
import {forwardRef, Module} from '@nestjs/common';
|
||||
import { GuestsService } from './guests.service';
|
||||
import { MongooseModule } from '@nestjs/mongoose';
|
||||
import { Guest, GuestSchema } from '../schemas/guest.schema';
|
||||
|
|
@ -23,8 +23,12 @@ import {GetGuestPropertyHandler} from "./command/get-guest-property.handler";
|
|||
import {QueryHandlers } from "./queries";
|
||||
import {SetGuestPropertyCommandHandler} from "./command/handlers/set-guest-property.handler";
|
||||
import {WrongAnswerReceivedGuestEventHandler} from "./event-handlers/wrong-answer-received-guest-event.handler";
|
||||
import {VoiceService} from "../voice/voice.service";
|
||||
|
||||
import {VoiceModule} from "../voice/voice.module";
|
||||
import {IncreasePlayerScoreCommandHandler} from "./command/handlers/increase-player-score-command.handler";
|
||||
import {QuizService} from "../quiz/quiz.service";
|
||||
import {QuizModule} from "../quiz/quiz.module";
|
||||
|
||||
|
||||
const commandHandlers = [
|
||||
GuestsRemoveKeyboardHandler,
|
||||
|
|
@ -32,6 +36,7 @@ const commandHandlers = [
|
|||
IncreasePlayerWinningRateCommandHandler,
|
||||
GetGuestPropertyHandler,
|
||||
SetGuestPropertyCommandHandler,
|
||||
IncreasePlayerScoreCommandHandler,
|
||||
];
|
||||
|
||||
|
||||
|
|
@ -60,7 +65,7 @@ const eventHandlers = [
|
|||
CardsModule,
|
||||
],
|
||||
providers: [GuestsService, ConfigService, ...commandHandlers,...QueryHandlers, ...eventHandlers, ],
|
||||
exports: [GuestsService],
|
||||
exports: [GuestsService,],
|
||||
controllers: [GuestsController],
|
||||
})
|
||||
export class GuestsModule {}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,39 @@
|
|||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { GuestsService } from './guests.service';
|
||||
import {getModelToken} from "@nestjs/mongoose";
|
||||
import {Guest} from "../schemas/guest.schema";
|
||||
import {Model} from "mongoose";
|
||||
import {Config} from "../schemas/config.schema";
|
||||
import {ClientProxy} from "@nestjs/microservices";
|
||||
import {ClientProxyMock} from "../mocks/client-proxy.mock";
|
||||
import {CommandBus, EventBus, QueryBus} from "@nestjs/cqrs";
|
||||
import {EventbusMock} from "../mocks/eventbus.mock";
|
||||
import {CommandbusMock} from "../mocks/commandbus.mock";
|
||||
import {CardsServiceMock} from "../mocks/cards-service.mock";
|
||||
import {CardsService} from "../cards/cards.service";
|
||||
import {ConfigService} from "@nestjs/config";
|
||||
import {ConfigServiceMock} from "../mocks/config-service.mock";
|
||||
import {QueryBusMock} from "../mocks/querybus.mock";
|
||||
import {VoiceService} from "../voice/voice.service";
|
||||
import {VoiceServiceMock} from "../mocks/voice-service.mock";
|
||||
|
||||
describe('GuestsService', () => {
|
||||
let service: GuestsService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [GuestsService],
|
||||
providers: [
|
||||
GuestsService,
|
||||
{ provide: getModelToken(Guest.name), useValue: Model },
|
||||
{ provide: getModelToken(Config.name), useValue: Model },
|
||||
{ provide: 'Telegram', useValue: ClientProxyMock },
|
||||
{ provide: EventBus, useValue: EventbusMock },
|
||||
{ provide: CommandBus, useValue: CommandbusMock },
|
||||
{ provide: CardsService, useValue: CardsServiceMock },
|
||||
{ provide: ConfigService, useValue: ConfigServiceMock },
|
||||
{ provide: QueryBus, useValue: QueryBusMock },
|
||||
{ provide: VoiceService, useValue: VoiceServiceMock },
|
||||
],
|
||||
}).compile();
|
||||
|
||||
service = module.get<GuestsService>(GuestsService);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import {Inject, Injectable, Logger} from '@nestjs/common';
|
||||
import {InjectModel} from '@nestjs/mongoose';
|
||||
import {Guest, GuestDocument} from '../schemas/guest.schema';
|
||||
import {Model} from 'mongoose';
|
||||
import {Document, Model} from 'mongoose';
|
||||
import {CreateGuestDto} from './dto/create-guest.dto';
|
||||
import {QuestionDto} from '../quiz/dto/question.dto';
|
||||
import {Messages} from '../messaging/tg.text';
|
||||
|
|
@ -24,11 +24,11 @@ import {MqtMessageModel} from "../messaging/models/mqt-message.model";
|
|||
import {ConfigService} from "@nestjs/config";
|
||||
import {StringHelper} from "../helpers/stringhelper";
|
||||
import {DebuffsConsts} from "../game/entities/debuffs.consts";
|
||||
import {CreateNewQueueItemCommand} from "../game/commands/create-new-queue-item.command";
|
||||
import {GameQueueTypes} from "../schemas/game-queue.schema";
|
||||
import {VoiceService} from "../voice/voice.service";
|
||||
import {screpaDictManyInvalidAnswersDict} from "../voice/dicts/screpa-dict-many-invalid-answers.dict";
|
||||
import {GuestPropertiesConsts} from "../schemas/properties.consts";
|
||||
import {GuestNamesInCases} from "./guest.types";
|
||||
import {QuizService} from "../quiz/quiz.service";
|
||||
|
||||
@Injectable()
|
||||
export class GuestsService {
|
||||
|
|
@ -66,12 +66,21 @@ export class GuestsService {
|
|||
return this.guestModel.find().exec();
|
||||
}
|
||||
|
||||
getModel() {
|
||||
return this.guestModel;
|
||||
}
|
||||
|
||||
async filter(properties: object) {
|
||||
return this.guestModel.find(properties).exec();
|
||||
}
|
||||
|
||||
async findById(id: number) {
|
||||
return this.guestModel.findOne({ telegramId: id }).exec();
|
||||
const result = await this.guestModel.findOne({ telegramId: id }).exec();
|
||||
if(!result) {
|
||||
return null;
|
||||
}
|
||||
delete result.photo;
|
||||
return result;
|
||||
}
|
||||
|
||||
async hideKeyboard(text: string) {
|
||||
|
|
@ -84,17 +93,26 @@ export class GuestsService {
|
|||
this.logger.verbose(`Keyboard hidden`);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
async postQuestion(questionDto: QuestionDto, targetId = null) {
|
||||
const guests = await this.findAll();
|
||||
const extra = {
|
||||
reply_markup: {
|
||||
keyboard: [],
|
||||
inline_keyboard: [],
|
||||
},
|
||||
};
|
||||
questionDto.answers.forEach((item, index) => {
|
||||
extra.reply_markup.keyboard.push([
|
||||
{ text: this.nums[index] + ' ' + item },
|
||||
]);
|
||||
// extra.reply_markup.keyboard.push([
|
||||
// { text: this.nums[index] + ' ' + item },
|
||||
// ]);
|
||||
if(item !== null){
|
||||
extra.reply_markup.inline_keyboard.push(
|
||||
[ { text: item, callback_data: `${item.substring(0,50)}` },
|
||||
]);
|
||||
}
|
||||
});
|
||||
if (!targetId) {
|
||||
guests.forEach((guest) => {
|
||||
|
|
@ -310,6 +328,15 @@ export class GuestsService {
|
|||
await this.guestModel.updateMany({}, { prizeChance: 0 });
|
||||
}
|
||||
|
||||
async getGuestNameInCases(telegramId: number): Promise<GuestNamesInCases> {
|
||||
const guest = await this.findById(telegramId);
|
||||
return {
|
||||
SubjectiveCase: guest.get(StringHelper.getPropertyName(GuestPropertiesConsts.NameSubjectiveCase)),
|
||||
AccusativeCase: guest.get(StringHelper.getPropertyName(GuestPropertiesConsts.NameAccusativeCase)),
|
||||
GenitiveCase: guest.get(StringHelper.getPropertyName(GuestPropertiesConsts.NameGenitiveCase))
|
||||
}
|
||||
}
|
||||
|
||||
async incrementInvalidAnswersCount(tId: number) {
|
||||
this.logger.verbose(`Increment invalid answers in the row for ${tId}`);
|
||||
const guest = await this.findById(tId);
|
||||
|
|
@ -317,7 +344,6 @@ export class GuestsService {
|
|||
this.logger.error(`Can't find user ${tId} for incrementing invalid answers count`);
|
||||
return;
|
||||
}
|
||||
|
||||
guest.invalidAnswers = +guest.invalidAnswers + 1;
|
||||
guest.invalidAnswersInRow = +guest.invalidAnswersInRow + 1;
|
||||
this.logger.verbose(`Invalid answers: ${guest.invalidAnswers}, inRow: ${guest.invalidAnswersInRow}`);
|
||||
|
|
@ -327,8 +353,8 @@ export class GuestsService {
|
|||
AccusativeCase: guest.get(StringHelper.getPropertyName(GuestPropertiesConsts.NameAccusativeCase)),
|
||||
GenitiveCase: guest.get(StringHelper.getPropertyName(GuestPropertiesConsts.NameGenitiveCase))
|
||||
}, [...screpaDictManyInvalidAnswersDict])
|
||||
await this.commandBus.execute(new CreateNewQueueItemCommand(guest.telegramId, GameQueueTypes.screpaAnounce, text));
|
||||
await this.commandBus.execute(new CreateNewQueueItemCommand(guest.telegramId, GameQueueTypes.penalty));
|
||||
//await this.commandBus.execute(new CreateNewQueueItemCommand(guest.telegramId, GameQueueTypes.screpaAnounce, text));
|
||||
// await this.commandBus.execute(new CreateNewQueueItemCommand(guest.telegramId, GameQueueTypes.penalty));
|
||||
this.logger.verbose(`Reset invalidAnswerInRow, since user received penalty`);
|
||||
guest.invalidAnswersInRow = 0;
|
||||
}
|
||||
|
|
@ -341,4 +367,16 @@ export class GuestsService {
|
|||
guest.invalidAnswersInRow = 0;
|
||||
await guest.save();
|
||||
}
|
||||
|
||||
async incrementPrizeCount(telegramId: number) {
|
||||
const guest = await this.findById(telegramId);
|
||||
guest.rewardsReceived += 1;
|
||||
await guest.save();
|
||||
}
|
||||
|
||||
async updatePenaltiesCount(user: number) {
|
||||
const guest = await this.findById(user);
|
||||
guest.penaltiesReceived += 1;
|
||||
await guest.save();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import {IQueryHandler, QueryHandler} from "@nestjs/cqrs";
|
||||
import {GetGuestQuery} from "../getguest.query";
|
||||
import {Promise} from "mongoose";
|
||||
import {GuestsService} from "../../guests.service";
|
||||
|
||||
@QueryHandler(GetGuestQuery)
|
||||
|
|
|
|||
|
|
@ -1,20 +1,21 @@
|
|||
import {Controller, Logger} from "@nestjs/common";
|
||||
import {Ctx, EventPattern, MessagePattern, Payload, RmqContext} from "@nestjs/microservices";
|
||||
import {Ctx, MessagePattern, Payload, RmqContext} from "@nestjs/microservices";
|
||||
import {GetGuestInfoModel} from "./models/get-guest-info.model";
|
||||
import {GuestsService} from "../guests/guests.service";
|
||||
import {RegisterUserModel} from "./models/register-user.model";
|
||||
import {SharedService} from "../shared/shared.service";
|
||||
import {SocketEvents} from "../shared/events.consts";
|
||||
import {CommandsConsts} from "../Consts/commands.consts";
|
||||
import {EventBus} from "@nestjs/cqrs";
|
||||
import {PlayerCardSelectedEvent} from "../game/events/player-card-selected.event";
|
||||
import {getCard} from "../helpers/card-parser";
|
||||
import {QuizService} from "../quiz/quiz.service";
|
||||
import {ClientNotificationType} from "../socket/socket.gateway";
|
||||
|
||||
@Controller()
|
||||
export class GuestsMessageController {
|
||||
private readonly logger = new Logger(GuestsMessageController.name);
|
||||
|
||||
constructor(private guestService: GuestsService, private sharedService: SharedService, private eventBus: EventBus) {
|
||||
constructor(private guestService: GuestsService, private sharedService: SharedService, private eventBus: EventBus, private quizService: QuizService) {
|
||||
}
|
||||
@MessagePattern({ cmd: 'GuestInfo'} )
|
||||
async getGuestInformation(@Payload() data: GetGuestInfoModel, @Ctx() context: RmqContext) {
|
||||
|
|
@ -48,7 +49,7 @@ export class GuestsMessageController {
|
|||
@MessagePattern({ cmd: CommandsConsts.PhotoUpdated })
|
||||
async photoUpdated(@Payload() data: { id: number}) {
|
||||
this.logger.verbose(`Photo updated event, send notification`);
|
||||
this.sharedService.sendSocketNotificationToAllClients(SocketEvents.PHOTOS_UPDATED_EVENT, data);
|
||||
this.sharedService.notifyAllClients<{id: number}>(ClientNotificationType.PhotosUpdated, data)
|
||||
}
|
||||
|
||||
@MessagePattern({ cmd: CommandsConsts.CardPlayed })
|
||||
|
|
@ -58,4 +59,10 @@ export class GuestsMessageController {
|
|||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@MessagePattern({ cmd: CommandsConsts.GetQuestion})
|
||||
async getQuestion(@Payload() data: { user: number, inline: false}) {
|
||||
await this.quizService.displayQuestionForUser(data.user);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -2,4 +2,10 @@ export interface ValidateAnswerModel {
|
|||
answer: string;
|
||||
user: number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface ValidateAnswerInline {
|
||||
answer: string;
|
||||
user: number;
|
||||
name: string;
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import {Controller, Logger} from "@nestjs/common";
|
||||
import {MessagePattern, Payload} from "@nestjs/microservices";
|
||||
import {CommandsConsts} from "../Consts/commands.consts";
|
||||
import {ValidateAnswerModel} from "./models/validate-answer.model";
|
||||
import {ValidateAnswerInline, ValidateAnswerModel} from "./models/validate-answer.model";
|
||||
import {QuizAnsweredEvent} from "../game/events/quiz.answered";
|
||||
import {QuizService} from "../quiz/quiz.service";
|
||||
import {CommandBus, EventBus} from "@nestjs/cqrs";
|
||||
|
|
@ -15,11 +15,12 @@ export class QuizMessagingController {
|
|||
constructor(private quizService: QuizService, private eventBus: EventBus, private cmdBus: CommandBus, private gameService: GameService) {
|
||||
}
|
||||
|
||||
@MessagePattern({ cmd: CommandsConsts.ValidateAnswer})
|
||||
async validateAnswer(@Payload() data: ValidateAnswerModel) {
|
||||
|
||||
@MessagePattern({ cmd: CommandsConsts.QuestionAnswer})
|
||||
async getQuestionAnswer(@Payload() data: ValidateAnswerInline) {
|
||||
this.logger.verbose(`Validate answer ${data}`);
|
||||
this.eventBus.publish(new QuizAnsweredEvent(data.name));
|
||||
const result = await this.quizService.validateAnswer(
|
||||
const result = await this.quizService.validateAnswerInline(
|
||||
data.answer,
|
||||
data.user,
|
||||
);
|
||||
|
|
@ -30,12 +31,13 @@ export class QuizMessagingController {
|
|||
async completeQueueItem(@Payload() data: any) {
|
||||
this.logger.verbose(`complete item`)
|
||||
await this.gameService.markQueueAsCompleted(null);
|
||||
//await this.quizService.proceedWithGame();
|
||||
}
|
||||
|
||||
@MessagePattern({ cmd: CommandsConsts.GetCards})
|
||||
async getCardsForUser(@Payload() data: { user: number, inline: boolean}) {
|
||||
this.logger.verbose(`getCardsForUser ${data}`);
|
||||
await this.cmdBus.execute(new SendBetweenRoundsActionsCommand(data.user, data.inline))
|
||||
await this.cmdBus.execute(new SendBetweenRoundsActionsCommand(data.user, true))
|
||||
}
|
||||
|
||||
@MessagePattern({ cmd: CommandsConsts.ApplyDebuff})
|
||||
|
|
|
|||
3
src/mocks/cards-service.mock.ts
Normal file
3
src/mocks/cards-service.mock.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export const CardsServiceMock = {
|
||||
|
||||
}
|
||||
3
src/mocks/client-proxy.mock.ts
Normal file
3
src/mocks/client-proxy.mock.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export const ClientProxyMock = {
|
||||
|
||||
}
|
||||
3
src/mocks/commandbus.mock.ts
Normal file
3
src/mocks/commandbus.mock.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export const CommandbusMock = {
|
||||
execute: jest.fn(),
|
||||
}
|
||||
3
src/mocks/config-service.mock.ts
Normal file
3
src/mocks/config-service.mock.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export const ConfigServiceMock = {
|
||||
get: jest.fn(),
|
||||
}
|
||||
3
src/mocks/eventbus.mock.ts
Normal file
3
src/mocks/eventbus.mock.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export const EventbusMock = {
|
||||
|
||||
}
|
||||
4
src/mocks/featureflag-service.mock.ts
Normal file
4
src/mocks/featureflag-service.mock.ts
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
export const FeatureflagServiceMock = {
|
||||
getFeatureFlag: jest.fn(() => Promise.resolve(false)),
|
||||
setFeatureFlag: jest.fn(() => Promise.resolve(false))
|
||||
}
|
||||
3
src/mocks/game-service.mock.ts
Normal file
3
src/mocks/game-service.mock.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export const GameServiceMock = {
|
||||
|
||||
}
|
||||
3
src/mocks/gift-service.mock.ts
Normal file
3
src/mocks/gift-service.mock.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export const GiftServiceMock = {
|
||||
getRemainingPrizeCount: () => jest.fn(),
|
||||
}
|
||||
3
src/mocks/guests-service.mock.ts
Normal file
3
src/mocks/guests-service.mock.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export const GuestsServiceMock = {
|
||||
updatePenaltiesCount: jest.fn(),
|
||||
}
|
||||
3
src/mocks/httpservice.mock.ts
Normal file
3
src/mocks/httpservice.mock.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export const HttpServiceMock = {
|
||||
|
||||
}
|
||||
3
src/mocks/penalty-service.mock.ts
Normal file
3
src/mocks/penalty-service.mock.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export const PenaltyServiceMock = {
|
||||
|
||||
}
|
||||
3
src/mocks/querybus.mock.ts
Normal file
3
src/mocks/querybus.mock.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export const QueryBusMock = {
|
||||
|
||||
}
|
||||
3
src/mocks/quiz-service.mock.ts
Normal file
3
src/mocks/quiz-service.mock.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export const QuizServiceMock = {
|
||||
getRemainQuestionCount: () =>jest.fn(),
|
||||
}
|
||||
5
src/mocks/shared-service.mock.ts
Normal file
5
src/mocks/shared-service.mock.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
export const SharedServiceMock = {
|
||||
setConfig: jest.fn(),
|
||||
getConfig: jest.fn(),
|
||||
notifyAllClients: jest.fn(),
|
||||
}
|
||||
3
src/mocks/socket-gateway.mock.ts
Normal file
3
src/mocks/socket-gateway.mock.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export const SocketGatewayMock = {
|
||||
|
||||
}
|
||||
3
src/mocks/state-service.mock.ts
Normal file
3
src/mocks/state-service.mock.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export const StateServiceMock = {
|
||||
setState: jest.fn(),
|
||||
}
|
||||
3
src/mocks/versus-service.mock.ts
Normal file
3
src/mocks/versus-service.mock.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export class VersusServiceMock {
|
||||
|
||||
}
|
||||
3
src/mocks/voice-service.mock.ts
Normal file
3
src/mocks/voice-service.mock.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export const VoiceServiceMock = {
|
||||
|
||||
}
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { PenaltyController } from './penalty.controller';
|
||||
import {PenaltyService} from "./penalty.service";
|
||||
import {PenaltyServiceMock} from "../mocks/penalty-service.mock";
|
||||
|
||||
describe('PenaltyController', () => {
|
||||
let controller: PenaltyController;
|
||||
|
|
@ -7,6 +9,9 @@ describe('PenaltyController', () => {
|
|||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
controllers: [PenaltyController],
|
||||
providers: [
|
||||
{ provide: PenaltyService, useValue: PenaltyServiceMock },
|
||||
]
|
||||
}).compile();
|
||||
|
||||
controller = module.get<PenaltyController>(PenaltyController);
|
||||
|
|
|
|||
|
|
@ -1,12 +1,18 @@
|
|||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { PenaltyService } from './penalty.service';
|
||||
import {getModelToken} from "@nestjs/mongoose";
|
||||
import {Penalty} from "../schemas/penalty.schema";
|
||||
import {Model} from "mongoose";
|
||||
|
||||
describe('PenaltyService', () => {
|
||||
let service: PenaltyService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [PenaltyService],
|
||||
providers: [
|
||||
PenaltyService,
|
||||
{ provide: getModelToken(Penalty.name), useValue: Model },
|
||||
],
|
||||
}).compile();
|
||||
|
||||
service = module.get<PenaltyService>(PenaltyService);
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
export interface QuestionDto {
|
||||
id: string;
|
||||
text: string;
|
||||
answers: string[];
|
||||
valid: string;
|
||||
note: string | null;
|
||||
qId: string;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
17
src/quiz/event-handlers/state-changed-event.handler.ts
Normal file
17
src/quiz/event-handlers/state-changed-event.handler.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
import {EventsHandler, IEventHandler} from "@nestjs/cqrs";
|
||||
import {StateChangedEvent} from "../../game/events/state-changed.event";
|
||||
import {QuizService} from "../quiz.service";
|
||||
import {Logger} from "@nestjs/common";
|
||||
|
||||
@EventsHandler(StateChangedEvent)
|
||||
export class StateChangedEventHandler implements IEventHandler<StateChangedEvent> {
|
||||
logger = new Logger(StateChangedEventHandler.name);
|
||||
constructor(private quizService: QuizService) {
|
||||
}
|
||||
|
||||
async handle(event: StateChangedEvent) {
|
||||
this.logger.verbose(`[StateChangedEventHandler] enter, event: ${event}}`)
|
||||
await this.quizService.calculateEndgamePoints();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,15 +1,22 @@
|
|||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { QuizController } from './quiz.controller';
|
||||
import {QuizService} from "./quiz.service";
|
||||
import {QuizServiceMock} from "../mocks/quiz-service.mock";
|
||||
|
||||
describe('QuizController', () => {
|
||||
let controller: QuizController;
|
||||
let quizService: QuizService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
controllers: [QuizController],
|
||||
providers: [
|
||||
{ provide: QuizService, useValue: QuizServiceMock },
|
||||
]
|
||||
}).compile();
|
||||
|
||||
controller = module.get<QuizController>(QuizController);
|
||||
quizService = module.get<QuizService>(QuizService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { Body, Controller, Get, Post } from "@nestjs/common";
|
||||
import { QuestionDto, QuestionDtoExcel } from './dto/question.dto';
|
||||
import { QuizService } from "./quiz.service";
|
||||
import { ExtraQuestionDto } from './dto/extra-question.dto';
|
||||
import {Body, Controller, Get, Post} from "@nestjs/common";
|
||||
import {QuestionDto, QuestionDtoExcel} from './dto/question.dto';
|
||||
import {QuizService} from "./quiz.service";
|
||||
import {ExtraQuestionDto} from './dto/extra-question.dto';
|
||||
|
||||
@Controller('quiz')
|
||||
export class QuizController {
|
||||
|
|
@ -16,11 +16,21 @@ export class QuizController {
|
|||
return await this.quizService.setQuestion(qustionDto);
|
||||
}
|
||||
|
||||
@Get('question-results')
|
||||
async GetQuestionResults() {
|
||||
return await this.quizService.getQuestionResults();
|
||||
}
|
||||
|
||||
@Post('proceed')
|
||||
async Get() {
|
||||
return this.quizService.proceedWithGame();
|
||||
}
|
||||
|
||||
@Post('timeout')
|
||||
async Timeout() {
|
||||
return await this.quizService.questionTimeout();
|
||||
}
|
||||
|
||||
@Post('questions')
|
||||
async postQuestion(@Body() questionDto: QuestionDto[]) {
|
||||
return await this.quizService.populateQuestions(questionDto);
|
||||
|
|
@ -45,4 +55,15 @@ export class QuizController {
|
|||
async dealPrize() {
|
||||
return this.quizService.dealPrize();
|
||||
}
|
||||
|
||||
@Post('calculate-endgame-extrapoints')
|
||||
async endgameExtrapoints()
|
||||
{
|
||||
return await this.quizService.calculateEndgamePoints();
|
||||
}
|
||||
|
||||
@Get('endgame-results')
|
||||
async endgameResults() {
|
||||
return await this.quizService.getEndgameResults();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,12 +11,18 @@ import { GameNextQuestionCommandHandler } from './command-handlers/next-question
|
|||
import { MarkQuestionsAsUnansweredCommandHandler } from './command-handlers/mark-questions-as-unanswred-command.handler';
|
||||
import { PenaltyModule } from '../penalty/penalty.module';
|
||||
import {ConfigModule, ConfigService} from "@nestjs/config";
|
||||
import {Config, ConfigSchema} from "../schemas/config.schema";
|
||||
import {StateChangedEventHandler} from "./event-handlers/state-changed-event.handler";
|
||||
|
||||
const cmdHandlers = [
|
||||
GameNextQuestionCommandHandler,
|
||||
MarkQuestionsAsUnansweredCommandHandler,
|
||||
];
|
||||
|
||||
const eventHandlers = [
|
||||
StateChangedEventHandler
|
||||
]
|
||||
|
||||
@Global()
|
||||
@Module({
|
||||
imports: [
|
||||
|
|
@ -32,6 +38,6 @@ const cmdHandlers = [
|
|||
],
|
||||
controllers: [QuizController],
|
||||
exports: [QuizService],
|
||||
providers: [QuizService,ConfigService, ...cmdHandlers],
|
||||
providers: [QuizService,ConfigService, ...cmdHandlers, ...eventHandlers],
|
||||
})
|
||||
export class QuizModule {}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,260 @@
|
|||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { QuizService } from './quiz.service';
|
||||
import {Test, TestingModule} from '@nestjs/testing';
|
||||
import {QuizService} from './quiz.service';
|
||||
import {getModelToken} from "@nestjs/mongoose";
|
||||
import {Question} from "../schemas/question.schema";
|
||||
import {Model} from "mongoose";
|
||||
import {QuestionStorage} from "../schemas/question-storage.schema";
|
||||
import {GuestsService} from "../guests/guests.service";
|
||||
import {GuestsServiceMock} from "../mocks/guests-service.mock";
|
||||
import {SharedService} from "../shared/shared.service";
|
||||
import {SharedServiceMock} from "../mocks/shared-service.mock";
|
||||
import {CommandBus, EventBus, ICommand} from "@nestjs/cqrs";
|
||||
import {EventbusMock} from "../mocks/eventbus.mock";
|
||||
import {CommandbusMock} from "../mocks/commandbus.mock";
|
||||
import {FeatureflagService, IFeatureFlagStatus} from "../featureflag/featureflag.service";
|
||||
import {FeatureflagServiceMock} from "../mocks/featureflag-service.mock";
|
||||
import {IncreasePlayerWinningRateCommand} from "../game/commands/increase-player-winning-rate.command";
|
||||
import {IncreasePlayerScoreCommand} from "../guests/command/increase-player-score.command";
|
||||
import {getRandomInt} from "../helpers/rand-number";
|
||||
import {CreateNewQueueItemCommand} from "../game/commands/create-new-queue-item.command";
|
||||
import {GameQueueTypes} from "../schemas/game-queue.schema";
|
||||
import {BeginVersusCommand} from "../game/commands/begin-versus.command"
|
||||
import spyOn = jest.spyOn;
|
||||
import clearAllMocks = jest.clearAllMocks;
|
||||
|
||||
jest.mock('../../src/helpers/rand-number');
|
||||
|
||||
describe('QuizService', () => {
|
||||
let service: QuizService;
|
||||
let cmdBus: CommandBus;
|
||||
let guestService: GuestsService;
|
||||
let featureFlagService: FeatureflagService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [QuizService],
|
||||
providers: [
|
||||
QuizService,
|
||||
{provide: getModelToken(Question.name), useValue: Model},
|
||||
{provide: getModelToken(QuestionStorage.name), useValue: Model},
|
||||
{provide: GuestsService, useValue: GuestsServiceMock},
|
||||
{provide: SharedService, useValue: SharedServiceMock},
|
||||
{provide: EventBus, useValue: EventbusMock},
|
||||
{provide: CommandBus, useValue: CommandbusMock},
|
||||
{provide: FeatureflagService, useValue: FeatureflagServiceMock}
|
||||
],
|
||||
}).compile();
|
||||
|
||||
service = module.get<QuizService>(QuizService);
|
||||
service = await module.resolve<QuizService>(QuizService);
|
||||
cmdBus = await module.resolve<CommandBus>(CommandBus);
|
||||
guestService = await module.resolve<GuestsService>(GuestsService);
|
||||
featureFlagService = await module.resolve<FeatureflagService>(FeatureflagService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
|
||||
describe('calculateScore()', () => {
|
||||
let cmdBusExecSpy: jest.SpyInstance<Promise<unknown>, [command: ICommand], any>;
|
||||
let getSpy;
|
||||
const questionDocumentMock = {
|
||||
text: 'test question',
|
||||
answered: false,
|
||||
valid: 'option1',
|
||||
answers: ['option1', 'option2', 'option3', 'option4'],
|
||||
answeredBy: 1,
|
||||
note: '',
|
||||
qId: 'xx-xxx-xxx',
|
||||
userAnswers: [{
|
||||
user: 1,
|
||||
time: new Date(),
|
||||
valid: false,
|
||||
}, {
|
||||
user: 2,
|
||||
time: new Date(new Date().setSeconds((new Date).getSeconds() - 5)),
|
||||
valid: false,
|
||||
}, {
|
||||
user: 3,
|
||||
time: new Date(),
|
||||
valid: true,
|
||||
}, {
|
||||
user: 4,
|
||||
time: new Date(),
|
||||
valid: false,
|
||||
}],
|
||||
scoreCalculated: false,
|
||||
save: jest.fn(),
|
||||
};
|
||||
beforeEach(() => {
|
||||
cmdBusExecSpy = jest.spyOn(cmdBus, 'execute').mockResolvedValue(null);
|
||||
getSpy = jest.spyOn(service,'get').mockResolvedValue(questionDocumentMock as any);
|
||||
});
|
||||
|
||||
it('should not calculate score if it is already calculated', async () => {
|
||||
// setup
|
||||
questionDocumentMock.scoreCalculated = true;
|
||||
|
||||
// act
|
||||
await service.calculateScore();
|
||||
|
||||
// validate
|
||||
expect(getSpy).toHaveBeenCalled();
|
||||
expect(cmdBusExecSpy).not.toHaveBeenCalled();
|
||||
})
|
||||
|
||||
it('should assign points to winner', async () => {
|
||||
//setup
|
||||
questionDocumentMock.scoreCalculated = false;
|
||||
|
||||
// act
|
||||
await service.calculateScore();
|
||||
|
||||
// validate
|
||||
const validUser = questionDocumentMock.userAnswers.find(user => user.valid)
|
||||
expect(cmdBusExecSpy).toHaveBeenNthCalledWith(1,new IncreasePlayerWinningRateCommand(validUser.user, expect.anything()));
|
||||
expect(cmdBusExecSpy).toHaveBeenNthCalledWith(2, new IncreasePlayerScoreCommand(validUser.user, 1));
|
||||
expect(cmdBusExecSpy).toHaveBeenNthCalledWith(4, new IncreasePlayerScoreCommand(validUser.user, 1));
|
||||
})
|
||||
|
||||
|
||||
it('should randomly add penalty to last answer if rnd > 50', async () => {
|
||||
// setup
|
||||
(getRandomInt as jest.Mock).mockReturnValue(65);
|
||||
questionDocumentMock.scoreCalculated = false;
|
||||
const whoShouldGetPenalty = questionDocumentMock.userAnswers.find(x => x.user == 2);
|
||||
|
||||
//act
|
||||
await service.calculateScore();
|
||||
|
||||
//validate
|
||||
expect(getRandomInt).toHaveBeenCalledWith(0,100);
|
||||
expect(cmdBusExecSpy).toHaveBeenCalledWith(new CreateNewQueueItemCommand(whoShouldGetPenalty.user, GameQueueTypes.penalty));
|
||||
});
|
||||
|
||||
it('should not add penalty to last answer if rnd < 50', async () => {
|
||||
// setup
|
||||
jest.clearAllMocks();
|
||||
(getRandomInt as jest.Mock).mockReturnValue(10);
|
||||
questionDocumentMock.scoreCalculated = false;
|
||||
const whoShouldGetPenalty = questionDocumentMock.userAnswers.find(x => x.user == 2);
|
||||
|
||||
//act
|
||||
await service.calculateScore();
|
||||
|
||||
//validate
|
||||
expect(getRandomInt).toHaveBeenCalledWith(0,100);
|
||||
expect(cmdBusExecSpy).not.toHaveBeenCalledWith(new CreateNewQueueItemCommand(whoShouldGetPenalty.user, GameQueueTypes.penalty));
|
||||
|
||||
})
|
||||
|
||||
it('should set score calculated after calculation', async () => {
|
||||
// setup
|
||||
questionDocumentMock.scoreCalculated = false;
|
||||
const saveSpy = jest.spyOn(questionDocumentMock,'save').mockResolvedValue(true);
|
||||
// act
|
||||
await service.calculateScore();
|
||||
|
||||
//validate
|
||||
expect(saveSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should add show results in queue', async () => {
|
||||
// setup
|
||||
questionDocumentMock.scoreCalculated = false;
|
||||
const cmdBusExecSpy = jest.spyOn(cmdBus, 'execute');
|
||||
const validUser = questionDocumentMock.userAnswers.find(user => user.valid)
|
||||
jest.spyOn(service, 'get').mockResolvedValue(questionDocumentMock as any);
|
||||
|
||||
|
||||
// act
|
||||
await service.calculateScore();
|
||||
|
||||
// validate
|
||||
expect(cmdBusExecSpy).toHaveBeenCalledWith(new CreateNewQueueItemCommand(expect.anything(), GameQueueTypes.showresults));
|
||||
});
|
||||
|
||||
|
||||
it('should start versus if user replied in less than 5 seconds if ff enabled', async () => {
|
||||
// setup
|
||||
questionDocumentMock.scoreCalculated = false;
|
||||
const ffstate: IFeatureFlagStatus = {
|
||||
name: '',
|
||||
state: true,
|
||||
}
|
||||
spyOn(featureFlagService,'getFeatureFlag').mockResolvedValue(ffstate);
|
||||
questionDocumentMock.userAnswers = [{
|
||||
user: 1,
|
||||
time: new Date(new Date().setSeconds(new Date().getSeconds() - 5)),
|
||||
valid: true,
|
||||
},
|
||||
{
|
||||
user: 2,
|
||||
time: new Date(),
|
||||
valid: true,
|
||||
}
|
||||
]
|
||||
getSpy = jest.spyOn(service,'get').mockResolvedValue(questionDocumentMock as any);
|
||||
|
||||
// act
|
||||
await service.calculateScore();
|
||||
|
||||
// validate
|
||||
expect(cmdBusExecSpy).toHaveBeenCalledWith(new BeginVersusCommand(expect.anything(), expect.anything()));
|
||||
});
|
||||
|
||||
it('should not start versus if FF is off and gap less than 5', async () => {
|
||||
// setup
|
||||
jest.clearAllMocks();
|
||||
questionDocumentMock.scoreCalculated = false;
|
||||
const ffstate: IFeatureFlagStatus = {
|
||||
name: '',
|
||||
state: false,
|
||||
}
|
||||
spyOn(featureFlagService,'getFeatureFlag').mockResolvedValue(ffstate);
|
||||
questionDocumentMock.userAnswers = [{
|
||||
user: 1,
|
||||
time: new Date(new Date().setSeconds(new Date().getSeconds() - 3)),
|
||||
valid: true,
|
||||
},
|
||||
{
|
||||
user: 2,
|
||||
time: new Date(),
|
||||
valid: true,
|
||||
}
|
||||
]
|
||||
getSpy = jest.spyOn(service,'get').mockResolvedValue(questionDocumentMock as any);
|
||||
|
||||
// act
|
||||
await service.calculateScore();
|
||||
|
||||
// validate
|
||||
expect(cmdBusExecSpy).not.toHaveBeenCalledWith(new BeginVersusCommand(expect.anything(), expect.anything()));
|
||||
|
||||
});
|
||||
it('should not start versus if gap more than 5 seconds', async () => {
|
||||
// setup
|
||||
questionDocumentMock.scoreCalculated = false;
|
||||
questionDocumentMock.userAnswers = [{
|
||||
user: 1,
|
||||
time: new Date(new Date().setSeconds(new Date().getSeconds() - 7)),
|
||||
valid: true,
|
||||
},
|
||||
{
|
||||
user: 2,
|
||||
time: new Date(),
|
||||
valid: true,
|
||||
}
|
||||
]
|
||||
getSpy = jest.spyOn(service,'get').mockResolvedValue(questionDocumentMock as any);
|
||||
|
||||
// act
|
||||
await service.calculateScore();
|
||||
// validate
|
||||
expect(cmdBusExecSpy).not.toHaveBeenCalledWith(new BeginVersusCommand(expect.anything(), expect.anything()));
|
||||
});
|
||||
|
||||
it('should not start versus if only one player answered correctly', () => {
|
||||
|
||||
})
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -10,15 +10,20 @@ import {ValidAnswerReceivedEvent} from '../game/events/valid-answer.recieved';
|
|||
import {QuestionStorage, QuestionStorageDocument,} from '../schemas/question-storage.schema';
|
||||
import {WrongAnswerReceivedEvent} from '../game/events/wrong-answer-received.event';
|
||||
import {ProceedGameQueueCommand} from '../game/commands/proceed-game-queue.command';
|
||||
import {getRandomInt} from 'src/helpers/rand-number';
|
||||
import {getRandomInt} from '../helpers/rand-number';
|
||||
import {Messages} from "../messaging/tg.text";
|
||||
import {CreateNewQueueItemCommand} from "../game/commands/create-new-queue-item.command";
|
||||
import {GameQueueTypes} from "../schemas/game-queue.schema";
|
||||
import {ConfigService} from "@nestjs/config";
|
||||
import {IncreasePlayerWinningRateCommand} from "../game/commands/increase-player-winning-rate.command";
|
||||
import {IncreasePlayerScoreCommand} from "../guests/command/increase-player-score.command";
|
||||
import {FeatureflagService} from "../featureflag/featureflag.service";
|
||||
import {FeatureFlagsConsts} from "../Consts/FeatureFlags.consts";
|
||||
import {QuizEndGameResults} from "./quiz.types";
|
||||
import {ClientNotificationType} from "../socket/socket.gateway";
|
||||
import {BeginVersusCommand} from "../game/commands/begin-versus.command";
|
||||
|
||||
@Injectable({ scope: Scope.TRANSIENT })
|
||||
export class QuizService {
|
||||
private readonly answerNumbers = Messages.answerNumbers;
|
||||
private readonly logger = new Logger(QuizService.name);
|
||||
constructor(
|
||||
@InjectModel(Question.name) private questionModel: Model<QuestionDocument>,
|
||||
|
|
@ -27,54 +32,59 @@ export class QuizService {
|
|||
private guestService: GuestsService,
|
||||
private sharedService: SharedService,
|
||||
private eventBus: EventBus,
|
||||
private configService: ConfigService,
|
||||
private commandBus: CommandBus,
|
||||
) {}
|
||||
private featureFlagService: FeatureflagService,
|
||||
) {
|
||||
|
||||
}
|
||||
|
||||
async get(): Promise<QuestionDocument> {
|
||||
return this.questionModel.find().sort({ _id: -1 }).findOne();
|
||||
}
|
||||
|
||||
async setQuestion(questionDto: QuestionDto, target: number = null) {
|
||||
await this.sharedService.setConfig('currentQuestion', questionDto.id)
|
||||
const item = new this.questionModel(questionDto);
|
||||
await item.save();
|
||||
this.logger.verbose(`Question updated`);
|
||||
await this.guestService.postQuestion(questionDto, target);
|
||||
this.sharedService.sendSocketNotificationToAllClients(
|
||||
'question_changed',
|
||||
questionDto,
|
||||
);
|
||||
this.sharedService.notifyAllClients<QuestionDto>(ClientNotificationType.QuestionChanged, questionDto);
|
||||
return item.save();
|
||||
}
|
||||
|
||||
async validateAnswer(answer: string, id: number) {
|
||||
this.logger.verbose(`enter validate answer ${answer} ${id}`);
|
||||
async validateAnswerInline(answer:string, id: number) {
|
||||
this.logger.verbose(`[validateAnswer] enter ${answer} ${id}`);
|
||||
const question = await this.get();
|
||||
if (question.answered) {
|
||||
this.logger.verbose(`Question already answered`);
|
||||
return false;
|
||||
}
|
||||
question.answered = true;
|
||||
await question.save();
|
||||
const regexp = new RegExp(
|
||||
Object.keys(this.answerNumbers)
|
||||
.map((x) => {
|
||||
x = this.answerNumbers[x].replace('.', '.').replace(' ', ' ');
|
||||
return x;
|
||||
})
|
||||
.join('|'),
|
||||
'gi',
|
||||
);
|
||||
this.logger.verbose(
|
||||
`Validating answer for question: ${JSON.stringify(question.text)}`,
|
||||
);
|
||||
const filtered = answer.replace(regexp, '').trim();
|
||||
if (question.valid === filtered) {
|
||||
question.answered = true;
|
||||
// check that answer exist
|
||||
const shortAnswers = question.answers.map((answer) => answer.substring(0,50));
|
||||
if(question.countdownFinished) {
|
||||
return;
|
||||
}
|
||||
const shortValidAnswer = question.valid.substring(0,50);
|
||||
if(shortAnswers.indexOf(answer) === -1) {
|
||||
this.logger.warn(`[validateAnswer] this question is not on game now`);
|
||||
return;
|
||||
}
|
||||
const isAnswerValid = shortValidAnswer === answer;
|
||||
if(question.userAnswers.find(answer => answer.user === id)) {
|
||||
this.logger.verbose("question->user answer is already containing record");
|
||||
return;
|
||||
}
|
||||
question.userAnswers.push({
|
||||
user: id,
|
||||
valid: isAnswerValid,
|
||||
time: new Date()
|
||||
})
|
||||
await question.save();
|
||||
this.logger.verbose("question saved with user details")
|
||||
if (shortValidAnswer=== answer) {
|
||||
question.answeredBy = id;
|
||||
this.logger.verbose(`extra ${question.note}`);
|
||||
this.eventBus.publish(
|
||||
new ValidAnswerReceivedEvent(id, filtered, question.note),
|
||||
new ValidAnswerReceivedEvent(id, answer, question.note),
|
||||
);
|
||||
await question.save();
|
||||
await this.markQuestionStorageAsAnsweredCorrectly(question.text);
|
||||
|
|
@ -107,16 +117,118 @@ export class QuizService {
|
|||
|
||||
async proceedWithGame() {
|
||||
this.logger.verbose(`[proceedWithGame] Executing proceed with game`);
|
||||
await this.calculateScore();
|
||||
await this.commandBus.execute(new ProceedGameQueueCommand());
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
private checkIfWeShouldStartVersus(answers: { valid: boolean; time: number; user: number }[]) {
|
||||
if(answers.length === 0 && answers.length <= 2) {
|
||||
return false;
|
||||
}
|
||||
const diff = Math.abs(new Date(answers[0].time).getTime() - new Date(answers[1].time).getTime()) / 1000;
|
||||
return diff <= 1;
|
||||
}
|
||||
|
||||
async calculateScore() {
|
||||
const question = await this.get();
|
||||
if(question.scoreCalculated) {
|
||||
return;
|
||||
}
|
||||
if(!await this.featureFlagService.getFeatureFlag(FeatureFlagsConsts.DontMarkQuestionsAsCompleted)) {
|
||||
this.logger.verbose(`[proceedWithGame]: DontMarkQuestionsAsCompleted disabled, marking as complete`);
|
||||
question.answered = true;
|
||||
}
|
||||
this.logger.verbose(`[calculateScore] enter `);
|
||||
const playerAnswers = question.userAnswers.map((answer) => {
|
||||
return {
|
||||
user: answer.user,
|
||||
valid: answer.valid,
|
||||
time: answer.time.getTime()
|
||||
}
|
||||
});
|
||||
const sortedAnswers = playerAnswers.sort((a, b) => a.time - b.time);
|
||||
const winner = sortedAnswers.find((answer) => answer.valid);
|
||||
let targetUser = 0;
|
||||
if(winner) {
|
||||
const totalWinningScore = 50;
|
||||
sortedAnswers.filter(x => x.valid).forEach((answer) => {
|
||||
this.logger.debug(`Giving 1 point to all who answered right`);
|
||||
this.commandBus.execute(new IncreasePlayerWinningRateCommand(answer.user,
|
||||
totalWinningScore / sortedAnswers.filter((answer) => answer.valid).length));
|
||||
this.commandBus.execute(new IncreasePlayerScoreCommand(answer.user,1));
|
||||
});
|
||||
const ffState = await this.featureFlagService.getFeatureFlag(FeatureFlagsConsts.StartVersusIfPlayersAnsweredInSameTime)
|
||||
if(ffState.state) {
|
||||
if(this.checkIfWeShouldStartVersus(sortedAnswers.filter(x => x.valid))) {
|
||||
await this.commandBus.execute(
|
||||
new BeginVersusCommand(
|
||||
sortedAnswers.filter(x => x.valid)[0].user,
|
||||
sortedAnswers.filter(x => x.valid)[1].user,
|
||||
));
|
||||
}
|
||||
}
|
||||
await this.commandBus.execute(new IncreasePlayerWinningRateCommand(sortedAnswers[0].user, 5));
|
||||
this.logger.debug(`Giving 1 point to first`);
|
||||
await this.commandBus.execute(new IncreasePlayerScoreCommand(winner.user,1));
|
||||
targetUser = winner.user;
|
||||
}
|
||||
|
||||
const invalidAnswers = sortedAnswers.filter((answer) => !answer.valid)
|
||||
if(invalidAnswers.length > 0) {
|
||||
//const lastInvalidAnswer = invalidAnswers[invalidAnswers.length - 1];
|
||||
const lastInvalidAnswer = invalidAnswers.sort((a,b) => a.time - b.time)[0];
|
||||
if(!lastInvalidAnswer) {
|
||||
return;
|
||||
}
|
||||
const random = getRandomInt(0,100);
|
||||
if(random > 50) {
|
||||
await this.guestService.updatePenaltiesCount(lastInvalidAnswer.user);
|
||||
await this.commandBus.execute(new CreateNewQueueItemCommand(lastInvalidAnswer.user, GameQueueTypes.penalty));
|
||||
targetUser = lastInvalidAnswer.user;
|
||||
}
|
||||
}
|
||||
await this.commandBus.execute(new CreateNewQueueItemCommand(targetUser, GameQueueTypes.showresults));
|
||||
question.scoreCalculated = true;
|
||||
await question.save();
|
||||
}
|
||||
|
||||
public async calculateEndgamePoints(): Promise<QuizEndGameResults> {
|
||||
const maxInvalidAnswersPromise = this.guestService.getModel().find({}).sort({ ['invalidAnswers']: 'desc'}).exec();
|
||||
const maxRewardsPromise = this.guestService.getModel().find({}).sort({['rewardsReceived']: "desc"}).exec();
|
||||
const maxPenaltiesPromise = this.guestService.getModel().find({}).sort({['penaltiesReceived']: 'desc'}).exec();
|
||||
|
||||
const [maxRewards, maxInvalidAnswers, maxPenaltiesReceived] = await Promise.all([maxRewardsPromise, maxInvalidAnswersPromise, maxPenaltiesPromise]);
|
||||
const result = {
|
||||
maxInvalidAnswers: {
|
||||
id: maxInvalidAnswers[0].telegramId,
|
||||
count: maxInvalidAnswers[0].invalidAnswers,
|
||||
name: maxInvalidAnswers[0].name,
|
||||
},
|
||||
maxRewards: {
|
||||
id: maxRewards[0].telegramId,
|
||||
count: maxRewards[0].rewardsReceived,
|
||||
name: maxRewards[0].name,
|
||||
},
|
||||
maxPenalties: {
|
||||
id: maxPenaltiesReceived[0].telegramId,
|
||||
count: maxPenaltiesReceived[0].penaltiesReceived,
|
||||
name: maxPenaltiesReceived[0].name,
|
||||
}
|
||||
}
|
||||
await this.sharedService.setConfig('endgame-points', JSON.stringify(result));
|
||||
await this.commandBus.execute(new IncreasePlayerScoreCommand(result.maxInvalidAnswers.id, 2));
|
||||
await this.commandBus.execute(new IncreasePlayerScoreCommand(result.maxPenalties.id, 2));
|
||||
await this.commandBus.execute(new IncreasePlayerScoreCommand(result.maxRewards.id, -2));
|
||||
return result;
|
||||
}
|
||||
|
||||
private async getNextQuestion() {
|
||||
let question = await this.questionStorageModel
|
||||
.findOne({ isAnswered: false })
|
||||
.exec();
|
||||
if (!question) {
|
||||
const unanswered = await this.getRemainQuestionWithouValidAnswer();
|
||||
const unanswered = await this.getRemainQuestionWithoutValidAnswer();
|
||||
const skipRand = getRandomInt(0, unanswered);
|
||||
question = await this.questionStorageModel
|
||||
.findOne({ isAnsweredCorrectly: false })
|
||||
|
|
@ -131,6 +243,8 @@ export class QuizService {
|
|||
const question = await this.getNextQuestion();
|
||||
question.isAnswered = true;
|
||||
await this.setQuestion({
|
||||
qId: question.id,
|
||||
id: question.id,
|
||||
text: question.text,
|
||||
answers: question.answers,
|
||||
valid: question.valid,
|
||||
|
|
@ -143,7 +257,7 @@ export class QuizService {
|
|||
const question = await this.getNextQuestion();
|
||||
this.logger.verbose(`playExtraQuestion: ${question.text}`);
|
||||
await this.setQuestion(
|
||||
{ text: question.text, answers: question.answers, valid: question.valid, note: question.note },
|
||||
{ qId: question.id, id: question.id, text: question.text, answers: question.answers, valid: question.valid, note: question.note },
|
||||
telegramId,
|
||||
);
|
||||
question.isAnswered = true;
|
||||
|
|
@ -166,7 +280,7 @@ export class QuizService {
|
|||
return questions.length;
|
||||
}
|
||||
|
||||
async getRemainQuestionWithouValidAnswer(): Promise<number> {
|
||||
async getRemainQuestionWithoutValidAnswer(): Promise<number> {
|
||||
const questions = await this.questionStorageModel.find({
|
||||
isAnsweredCorrectly: false,
|
||||
});
|
||||
|
|
@ -209,4 +323,34 @@ export class QuizService {
|
|||
await newQuestion.save();
|
||||
}
|
||||
}
|
||||
|
||||
async getQuestionResults() {
|
||||
const question = await this.get();
|
||||
return question.userAnswers;
|
||||
}
|
||||
|
||||
async displayQuestionForUser(telegramId: number) {
|
||||
const question = await this.get();
|
||||
const dto: QuestionDto = {
|
||||
id: question.id,
|
||||
text: question.text,
|
||||
answers: question.answers,
|
||||
valid: question.valid,
|
||||
note: question.note,
|
||||
qId: question.qId,
|
||||
}
|
||||
await this.guestService.postQuestion(dto, telegramId);
|
||||
}
|
||||
|
||||
async getEndgameResults() {
|
||||
const res = await this.sharedService.getConfig('endgame-points');
|
||||
return JSON.parse(res.value);
|
||||
}
|
||||
|
||||
async questionTimeout() {
|
||||
const question = await this.get();
|
||||
question.countdownFinished = true;
|
||||
await question.save();
|
||||
return question;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
11
src/quiz/quiz.types.d.ts
vendored
Normal file
11
src/quiz/quiz.types.d.ts
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
export interface QuizEndGameResultsDetails {
|
||||
id: number;
|
||||
count: number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface QuizEndGameResults {
|
||||
maxInvalidAnswers: QuizEndGameResultsDetails;
|
||||
maxRewards: QuizEndGameResultsDetails;
|
||||
maxPenalties: QuizEndGameResultsDetails;
|
||||
}
|
||||
|
|
@ -1,18 +1,66 @@
|
|||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { SchedulerService } from './scheduler.service';
|
||||
import {GiftsService} from "../gifts/gifts.service";
|
||||
import {GiftServiceMock} from "../mocks/gift-service.mock";
|
||||
import {StateService} from "../state/state.service";
|
||||
import {StateServiceMock} from "../mocks/state-service.mock";
|
||||
import {QuizService} from "../quiz/quiz.service";
|
||||
import {QuizServiceMock} from "../mocks/quiz-service.mock";
|
||||
import {SharedService} from "../shared/shared.service";
|
||||
import {SharedServiceMock} from "../mocks/shared-service.mock";
|
||||
import {FeatureflagService} from "../featureflag/featureflag.service";
|
||||
import {FeatureflagServiceMock} from "../mocks/featureflag-service.mock";
|
||||
import {GuestsService} from "../guests/guests.service";
|
||||
import {GuestsServiceMock} from "../mocks/guests-service.mock";
|
||||
import {CommandBus} from "@nestjs/cqrs";
|
||||
import {CommandbusMock} from "../mocks/commandbus.mock";
|
||||
|
||||
|
||||
describe('SchedulerService', () => {
|
||||
let service: SchedulerService;
|
||||
|
||||
let giftService: GiftsService;
|
||||
let sharedService: SharedService;
|
||||
let stateService: StateService;
|
||||
let featureFlagService: FeatureflagService;
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks();
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [SchedulerService],
|
||||
providers: [SchedulerService,
|
||||
{ provide: GiftsService, useValue: GiftServiceMock },
|
||||
{ provide: StateService, useValue: StateServiceMock },
|
||||
{ provide: QuizService, useValue: QuizServiceMock },
|
||||
{ provide: SharedService, useValue: SharedServiceMock },
|
||||
{ provide: FeatureflagService, useValue: FeatureflagServiceMock },
|
||||
{ provide: CommandBus, useValue: CommandbusMock },
|
||||
{ provide: GuestsService, useValue: GuestsServiceMock },
|
||||
],
|
||||
}).compile();
|
||||
|
||||
service = module.get<SchedulerService>(SchedulerService);
|
||||
giftService = module.get<GiftsService>(GiftsService);
|
||||
sharedService = module.get<SharedService>(SharedService);
|
||||
stateService = module.get<StateService>(StateService);
|
||||
featureFlagService = module.get<FeatureflagService>(FeatureflagService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
|
||||
it('should finish game if prizes count is 0', async () => {
|
||||
const getRemainingPrizeCountFn = jest.spyOn(giftService, 'getRemainingPrizeCount').mockImplementation(() => Promise.resolve(0));
|
||||
const notificationFn = jest.spyOn(sharedService,'notifyAllClients').mockImplementation();
|
||||
const setStateFn = jest.spyOn(stateService,'setState').mockImplementation((name,newstate) => Promise.resolve({ state: name, value: newstate}));
|
||||
await service.gameStatus();
|
||||
expect(getRemainingPrizeCountFn).toHaveBeenCalled();
|
||||
expect(notificationFn).toHaveBeenCalled();
|
||||
expect(notificationFn).toHaveBeenCalledWith('state_changed', expect.objectContaining({ state: 'main', value: 'finish'}));
|
||||
});
|
||||
|
||||
it('should not finish game if prizes count above 0', async () => {
|
||||
const getRemainingPrizeCountFn = jest.spyOn(giftService, 'getRemainingPrizeCount').mockImplementation(() => Promise.resolve(5));
|
||||
const notificationFn = jest.spyOn(sharedService,'notifyAllClients').mockImplementation()
|
||||
await service.gameStatus();
|
||||
expect(notificationFn).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,14 +1,16 @@
|
|||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { Cron } from '@nestjs/schedule';
|
||||
import { StateService } from '../state/state.service';
|
||||
import {CommandBus, QueryBus} from '@nestjs/cqrs';
|
||||
import { GiftsService } from 'src/gifts/gifts.service';
|
||||
import { QuizService } from 'src/quiz/quiz.service';
|
||||
import { SharedService } from 'src/shared/shared.service';
|
||||
import {GetGuestPropertyQuery} from "../guests/command/get-guest-property.handler";
|
||||
import {GuestPropertiesConsts} from "../schemas/properties.consts";
|
||||
import {GetGuestQuery} from "../guests/queries/getguest.query";
|
||||
import {StringHelper} from "../helpers/stringhelper";
|
||||
import {Injectable, Logger} from '@nestjs/common';
|
||||
import {Cron} from '@nestjs/schedule';
|
||||
import {StateService} from '../state/state.service';
|
||||
import {QuizService} from '../quiz/quiz.service';
|
||||
import {GiftsService} from '../gifts/gifts.service';
|
||||
import {SharedService} from '../shared/shared.service';
|
||||
import {FeatureflagService} from "../featureflag/featureflag.service";
|
||||
import {FeatureFlagsConsts} from "../Consts/FeatureFlags.consts";
|
||||
import {CommandBus} from "@nestjs/cqrs";
|
||||
import {GameStateConsts} from "../Consts/game-state.consts";
|
||||
import {IStateInfo} from "../Consts/types";
|
||||
import {ClientNotificationType} from "../socket/socket.gateway";
|
||||
|
||||
@Injectable()
|
||||
export class SchedulerService {
|
||||
private readonly logger = new Logger(SchedulerService.name);
|
||||
|
|
@ -17,11 +19,11 @@ export class SchedulerService {
|
|||
|
||||
constructor(
|
||||
private stateService: StateService,
|
||||
private cmdBus: CommandBus,
|
||||
private queryBus: QueryBus,
|
||||
private giftsService: GiftsService,
|
||||
private quizService: QuizService,
|
||||
private sharedService: SharedService,
|
||||
private featureFlagService: FeatureflagService,
|
||||
private commandBus: CommandBus,
|
||||
) {}
|
||||
|
||||
@Cron('* * * * *')
|
||||
|
|
@ -29,6 +31,21 @@ export class SchedulerService {
|
|||
await this.updateState();
|
||||
}
|
||||
|
||||
|
||||
|
||||
async finishGame() {
|
||||
if(await this.featureFlagService.getFeatureFlag(FeatureFlagsConsts.EnableEndgamePoints)) {
|
||||
this.logger.verbose(`Feature flag ${FeatureFlagsConsts.EnableEndgamePoints} is enabled`);
|
||||
const endgamePoints = await this.quizService.calculateEndgamePoints();
|
||||
const state = await this.stateService.setState(GameStateConsts.Main, GameStateConsts.EndgamePoints);
|
||||
this.sharedService.notifyAllClients<IStateInfo>(ClientNotificationType.StateChanged, state);
|
||||
} else {
|
||||
const state = await this.stateService.setState('main', 'finish');
|
||||
this.sharedService.notifyAllClients<IStateInfo>(ClientNotificationType.StateChanged, state);
|
||||
this.logger.warn(`Gifts is ended, finishing game`);
|
||||
}
|
||||
}
|
||||
|
||||
private async updateState() {
|
||||
this.state = (await this.stateService.getState('main')).value;
|
||||
this.logger.verbose(`Game state is: ${this.state}`);
|
||||
|
|
@ -37,12 +54,7 @@ export class SchedulerService {
|
|||
async gameStatus() {
|
||||
const giftsLeft = await this.giftsService.getRemainingPrizeCount();
|
||||
if (giftsLeft === 0) {
|
||||
const state = await this.stateService.setState('main', 'finish');
|
||||
this.sharedService.sendSocketNotificationToAllClients(
|
||||
'state_changed',
|
||||
state,
|
||||
);
|
||||
this.logger.warn(`Gifts is ended, finishing game`);
|
||||
await this.finishGame();
|
||||
}
|
||||
const questionsLeft = await this.quizService.getRemainQuestionCount();
|
||||
this.logger.verbose(
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
|
||||
import { Document } from 'mongoose';
|
||||
|
||||
export type ConfigDocument = Config & Document;
|
||||
|
||||
@Schema()
|
||||
export class Config {
|
||||
@Prop()
|
||||
|
|
@ -12,3 +10,4 @@ export class Config {
|
|||
}
|
||||
|
||||
export const ConfigSchema = SchemaFactory.createForClass(Config);
|
||||
export type ConfigDocument = Config & Document;
|
||||
|
|
@ -7,6 +7,9 @@ export enum GameQueueTypes {
|
|||
penalty = 'penalty',
|
||||
playExtraCard = 'play_extra_card',
|
||||
screpaAnounce = 'screpa',
|
||||
showresults = 'show_results',
|
||||
extra_points = 'extra_points',
|
||||
versus = 'versus',
|
||||
}
|
||||
|
||||
export type GameQueueDocument = GameQueue & Document;
|
||||
|
|
|
|||
|
|
@ -25,8 +25,6 @@ export class Guest {
|
|||
@Prop({ default: 10 })
|
||||
prizeChance: number;
|
||||
@Prop({ default: 0 })
|
||||
prizesCount: number;
|
||||
@Prop({ default: 0 })
|
||||
validAnswers: number;
|
||||
@Prop({ default: 0 })
|
||||
invalidAnswers: number;
|
||||
|
|
@ -34,6 +32,8 @@ export class Guest {
|
|||
invalidAnswersInRow: number;
|
||||
@Prop({ default:0 })
|
||||
rewardsReceived: number;
|
||||
@Prop({ default: 0})
|
||||
penaltiesReceived: number;
|
||||
@Prop({ type: Map })
|
||||
properties: Record<string, string>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,16 @@
|
|||
import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose";
|
||||
import { Document } from 'mongoose';
|
||||
|
||||
|
||||
export class QuestionAnswer {
|
||||
user: number;
|
||||
time: Date;
|
||||
valid: boolean;
|
||||
}
|
||||
|
||||
export type QuestionDocument = Question & Document;
|
||||
|
||||
|
||||
@Schema()
|
||||
export class Question {
|
||||
@Prop()
|
||||
|
|
@ -15,8 +23,15 @@ export class Question {
|
|||
answered: boolean;
|
||||
@Prop()
|
||||
answeredBy: number;
|
||||
|
||||
@Prop()
|
||||
note: string | null;
|
||||
@Prop()
|
||||
qId: string;
|
||||
@Prop([ { user: { type: Number }, time: { type: Date }, valid: { type: Boolean}}])
|
||||
userAnswers: QuestionAnswer[];
|
||||
@Prop({ default: false })
|
||||
scoreCalculated: boolean;
|
||||
@Prop({ default: false})
|
||||
countdownFinished: boolean;
|
||||
}
|
||||
export const QuestionSchema = SchemaFactory.createForClass(Question);
|
||||
|
|
|
|||
15
src/schemas/versus.schema.ts
Normal file
15
src/schemas/versus.schema.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import {Prop, Schema, SchemaFactory} from "@nestjs/mongoose";
|
||||
import {Document} from "mongoose";
|
||||
|
||||
@Schema()
|
||||
export class Versus {
|
||||
@Prop()
|
||||
text: string;
|
||||
@Prop({ default: false})
|
||||
completed: boolean;
|
||||
@Prop()
|
||||
description: string;
|
||||
}
|
||||
|
||||
export type VersusDocument = Versus & Document;
|
||||
export const VersusSchema = SchemaFactory.createForClass(Versus);
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
export enum SocketEvents {
|
||||
PHOTOS_UPDATED_EVENT = 'photos_updated',
|
||||
VALID_ANSWER_RECEIVED = 'answer_received',
|
||||
WRONG_ANSWER_RECEIVED = 'wrong_answer_received',
|
||||
USER_ADDED = 'user_added',
|
||||
USER_PROPERTY_CHANGED = 'user_property_changed',
|
||||
CARDS_CHANGED_EVENT = 'cards_changed',
|
||||
CARD_PLAYED = 'card_played',
|
||||
SCORE_CHANGED = 'score_changed',
|
||||
GameQueueItem = 'game_queue',
|
||||
QUEUE_COMPLETED = 'queue_completed',
|
||||
GAME_PAUSED = 'game_paused',
|
||||
GAME_RESUMED = 'game_resumed',
|
||||
NOTIFICATION = 'notification',
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@ import { ClientProxyFactory, Transport } from '@nestjs/microservices';
|
|||
import * as process from "process";
|
||||
import {ConfigModule} from "@nestjs/config";
|
||||
import {CqrsModule} from "@nestjs/cqrs";
|
||||
import {FeatureflagService} from "../featureflag/featureflag.service";
|
||||
@Global()
|
||||
@Module({
|
||||
imports: [
|
||||
|
|
@ -17,7 +18,7 @@ import {CqrsModule} from "@nestjs/cqrs";
|
|||
GameModule,
|
||||
MongooseModule.forFeature([{ name: Config.name, schema: ConfigSchema }]),
|
||||
],
|
||||
providers: [SharedService, {
|
||||
providers: [SharedService,FeatureflagService, {
|
||||
provide: 'Telegram',
|
||||
useFactory: () =>
|
||||
ClientProxyFactory.create({
|
||||
|
|
@ -31,7 +32,7 @@ import {CqrsModule} from "@nestjs/cqrs";
|
|||
},
|
||||
}),
|
||||
}],
|
||||
exports: [SharedService, 'Telegram'],
|
||||
exports: [SharedService, 'Telegram',FeatureflagService],
|
||||
})
|
||||
export class SharedModule {
|
||||
constructor() {
|
||||
|
|
|
|||
|
|
@ -1,12 +1,21 @@
|
|||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { SharedService } from './shared.service';
|
||||
import {SocketGateway} from "../socket/socket.gateway";
|
||||
import {SocketGatewayMock} from "../mocks/socket-gateway.mock";
|
||||
import {getModelToken} from "@nestjs/mongoose";
|
||||
import {Config} from "../schemas/config.schema";
|
||||
import {Model} from "mongoose";
|
||||
|
||||
describe('SharedService', () => {
|
||||
let service: SharedService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [SharedService],
|
||||
providers: [
|
||||
SharedService,
|
||||
{ provide: SocketGateway, useValue: SocketGatewayMock },
|
||||
{ provide: getModelToken(Config.name), useValue: Model },
|
||||
],
|
||||
}).compile();
|
||||
|
||||
service = module.get<SharedService>(SharedService);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { SocketGateway } from '../socket/socket.gateway';
|
||||
import {ClientNotificationType, SocketGateway} from '../socket/socket.gateway';
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { InjectModel } from '@nestjs/mongoose';
|
||||
import { Config, ConfigDocument } from '../schemas/config.schema';
|
||||
|
|
@ -9,18 +9,24 @@ export class SharedService {
|
|||
private logger = new Logger(SharedService.name);
|
||||
constructor(
|
||||
private socketGateway: SocketGateway,
|
||||
private eventBus: EventBus,
|
||||
@InjectModel(Config.name)
|
||||
private configModel: Model<ConfigDocument>,
|
||||
) {
|
||||
}
|
||||
|
||||
async getConfig(key: string) {
|
||||
return this.configModel
|
||||
const res = await this.configModel
|
||||
.findOne({
|
||||
key,
|
||||
})
|
||||
.exec();
|
||||
if(!res) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
key: res.key,
|
||||
value: res.value,
|
||||
}
|
||||
}
|
||||
|
||||
async setConfig(key: string, value: string) {
|
||||
|
|
@ -35,16 +41,33 @@ export class SharedService {
|
|||
value,
|
||||
});
|
||||
await record.save();
|
||||
return record;
|
||||
return {
|
||||
key: record.key,
|
||||
value: record.value,
|
||||
}
|
||||
}
|
||||
cfgItem.value = value;
|
||||
await cfgItem.save();
|
||||
return cfgItem;
|
||||
return {
|
||||
key: cfgItem.key,
|
||||
value: cfgItem.value,
|
||||
}
|
||||
}
|
||||
|
||||
sendSocketNotificationToAllClients(event: string, payload?: any) {
|
||||
/**
|
||||
* Notifies all connected clients via the socket gateway with the given event and payload.
|
||||
*
|
||||
* @template T - The type of the payload.
|
||||
*
|
||||
* @param event - The event name to be sent to the clients.
|
||||
* @param payload - The data to be sent along with the event.
|
||||
*
|
||||
* @returns {void} - This function does not return any value.
|
||||
*/
|
||||
notifyAllClients<T>(event: ClientNotificationType, payload: T): void {
|
||||
this.logger.verbose(`Sending notification to client: ${event}, ${JSON.stringify(payload)}`);
|
||||
this.socketGateway.notifyAllClients(event, payload);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue