netizen92-patch-1 #7
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",
|
"class-transformer": "^0.5.1",
|
||||||
"cyrillic-to-translit-js": "^3.2.1",
|
"cyrillic-to-translit-js": "^3.2.1",
|
||||||
"dotenv": "^16.3.1",
|
"dotenv": "^16.3.1",
|
||||||
|
"husky": "^9.1.6",
|
||||||
"latin-to-cyrillic": "^1.0.1",
|
"latin-to-cyrillic": "^1.0.1",
|
||||||
"mongodb": "^6.2.0",
|
"mongodb": "^6.2.0",
|
||||||
"mongoose": "^8.0.0",
|
"mongoose": "^8.0.0",
|
||||||
|
|
@ -41,7 +42,7 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@nestjs/schematics": "^10.0.3",
|
"@nestjs/schematics": "^10.0.3",
|
||||||
"@nestjs/testing": "^10.2.8",
|
"@nestjs/testing": "^10.4.7",
|
||||||
"@types/cron": "^2.0.1",
|
"@types/cron": "^2.0.1",
|
||||||
"@types/express": "^4.17.21",
|
"@types/express": "^4.17.21",
|
||||||
"@types/jest": "^29.5.8",
|
"@types/jest": "^29.5.8",
|
||||||
|
|
@ -1905,12 +1906,13 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@nestjs/testing": {
|
"node_modules/@nestjs/testing": {
|
||||||
"version": "10.2.8",
|
"version": "10.4.7",
|
||||||
"resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-10.2.8.tgz",
|
"resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-10.4.7.tgz",
|
||||||
"integrity": "sha512-9Kj5IQhM67/nj/MT6Wi2OmWr5YQnCMptwKVFrX1TDaikpY12196v7frk0jVjdT7wms7rV07GZle9I2z0aSjqtQ==",
|
"integrity": "sha512-aS3sQ0v4g8cyHDzW3xJv1+8MiFAkxUNXmnau588IFFI/nBIo/kevLNHNPr85keYekkJ/lwNDW72h8UGg8BYd9w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": "2.6.2"
|
"tslib": "2.7.0"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"type": "opencollective",
|
"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": {
|
"node_modules/@nestjs/websockets": {
|
||||||
"version": "10.2.8",
|
"version": "10.2.8",
|
||||||
"resolved": "https://registry.npmjs.org/@nestjs/websockets/-/websockets-10.2.8.tgz",
|
"resolved": "https://registry.npmjs.org/@nestjs/websockets/-/websockets-10.2.8.tgz",
|
||||||
|
|
@ -5544,6 +5553,21 @@
|
||||||
"ms": "^2.0.0"
|
"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": {
|
"node_modules/iconv-lite": {
|
||||||
"version": "0.4.24",
|
"version": "0.4.24",
|
||||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,8 @@
|
||||||
"test:watch": "jest --watch",
|
"test:watch": "jest --watch",
|
||||||
"test:cov": "jest --coverage",
|
"test:cov": "jest --coverage",
|
||||||
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
|
"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": {
|
"dependencies": {
|
||||||
"@nestjs/axios": "3.0.1",
|
"@nestjs/axios": "3.0.1",
|
||||||
|
|
@ -41,6 +42,7 @@
|
||||||
"class-transformer": "^0.5.1",
|
"class-transformer": "^0.5.1",
|
||||||
"cyrillic-to-translit-js": "^3.2.1",
|
"cyrillic-to-translit-js": "^3.2.1",
|
||||||
"dotenv": "^16.3.1",
|
"dotenv": "^16.3.1",
|
||||||
|
"husky": "^9.1.6",
|
||||||
"latin-to-cyrillic": "^1.0.1",
|
"latin-to-cyrillic": "^1.0.1",
|
||||||
"mongodb": "^6.2.0",
|
"mongodb": "^6.2.0",
|
||||||
"mongoose": "^8.0.0",
|
"mongoose": "^8.0.0",
|
||||||
|
|
@ -53,7 +55,7 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@nestjs/schematics": "^10.0.3",
|
"@nestjs/schematics": "^10.0.3",
|
||||||
"@nestjs/testing": "^10.2.8",
|
"@nestjs/testing": "^10.4.7",
|
||||||
"@types/cron": "^2.0.1",
|
"@types/cron": "^2.0.1",
|
||||||
"@types/express": "^4.17.21",
|
"@types/express": "^4.17.21",
|
||||||
"@types/jest": "^29.5.8",
|
"@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 GetCards = 'GetCards';
|
||||||
static ApplyDebuff = 'ApplyDebuff';
|
static ApplyDebuff = 'ApplyDebuff';
|
||||||
static CompleteQueue = 'CompleteQueue';
|
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 {MessagingModule} from "./messaging/messaging.module";
|
||||||
import * as process from "process";
|
import * as process from "process";
|
||||||
import {OpenaiModule} from "./openai/openai.module";
|
import {OpenaiModule} from "./openai/openai.module";
|
||||||
|
import { FeatureflagController } from './featureflag/featureflag.controller';
|
||||||
|
import { FeatureflagService } from './featureflag/featureflag.service';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
|
|
@ -41,8 +43,8 @@ import {OpenaiModule} from "./openai/openai.module";
|
||||||
GiftsModule,
|
GiftsModule,
|
||||||
OpenaiModule
|
OpenaiModule
|
||||||
],
|
],
|
||||||
controllers: [AppController],
|
controllers: [AppController, FeatureflagController],
|
||||||
providers: [AppService, SocketGateway, SchedulerService],
|
providers: [AppService, SocketGateway, SchedulerService, FeatureflagService],
|
||||||
exports: [AppService, SocketGateway],
|
exports: [AppService, SocketGateway, FeatureflagService],
|
||||||
})
|
})
|
||||||
export class AppModule {}
|
export class AppModule {}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
import { Test, TestingModule } from '@nestjs/testing';
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
import { CardsController } from './cards.controller';
|
import { CardsController } from './cards.controller';
|
||||||
|
import {CardsService} from "./cards.service";
|
||||||
|
import {CardsServiceMock} from "../mocks/cards-service.mock";
|
||||||
|
|
||||||
describe('CardsController', () => {
|
describe('CardsController', () => {
|
||||||
let controller: CardsController;
|
let controller: CardsController;
|
||||||
|
|
@ -7,6 +9,9 @@ describe('CardsController', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
controllers: [CardsController],
|
controllers: [CardsController],
|
||||||
|
providers: [
|
||||||
|
{ provide: CardsService, useValue: CardsServiceMock },
|
||||||
|
]
|
||||||
}).compile();
|
}).compile();
|
||||||
|
|
||||||
controller = module.get<CardsController>(CardsController);
|
controller = module.get<CardsController>(CardsController);
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,24 @@
|
||||||
import { Test, TestingModule } from '@nestjs/testing';
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
import { CardsService } from './cards.service';
|
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', () => {
|
describe('CardsService', () => {
|
||||||
let service: CardsService;
|
let service: CardsService;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
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();
|
}).compile();
|
||||||
|
|
||||||
service = module.get<CardsService>(CardsService);
|
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 { CommandHandler, ICommandHandler } from '@nestjs/cqrs';
|
||||||
import { CreateNewQueueItemCommand } from '../commands/create-new-queue-item.command';
|
import { CreateNewQueueItemCommand } from '../commands/create-new-queue-item.command';
|
||||||
import { GameService } from '../game.service';
|
import { GameService } from '../game.service';
|
||||||
|
import {Logger} from "@nestjs/common";
|
||||||
|
|
||||||
@CommandHandler(CreateNewQueueItemCommand)
|
@CommandHandler(CreateNewQueueItemCommand)
|
||||||
export class CreateNewQueueItemCommandHandler implements ICommandHandler<CreateNewQueueItemCommand> {
|
export class CreateNewQueueItemCommandHandler implements ICommandHandler<CreateNewQueueItemCommand> {
|
||||||
|
private logger = new Logger(CreateNewQueueItemCommandHandler.name);
|
||||||
constructor(
|
constructor(
|
||||||
private gameService: GameService,
|
private gameService: GameService,
|
||||||
|
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async execute(command: CreateNewQueueItemCommand): Promise<any> {
|
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);
|
await this.gameService.addTaskToGameQueue(command.target, command.type, command.text);
|
||||||
return Promise.resolve(undefined);
|
return Promise.resolve(undefined);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import { GiveOutAPrizeCommand } from '../commands/give-out-a-prize.command';
|
||||||
import { GameService } from '../game.service';
|
import { GameService } from '../game.service';
|
||||||
import { Logger } from '@nestjs/common';
|
import { Logger } from '@nestjs/common';
|
||||||
import { GameQueueTypes } from '../../schemas/game-queue.schema';
|
import { GameQueueTypes } from '../../schemas/game-queue.schema';
|
||||||
|
import {GuestsService} from "../../guests/guests.service";
|
||||||
|
|
||||||
@CommandHandler(GiveOutAPrizeCommand)
|
@CommandHandler(GiveOutAPrizeCommand)
|
||||||
export class GameGiveOutAPrizeCommandHandler
|
export class GameGiveOutAPrizeCommandHandler
|
||||||
|
|
@ -10,11 +11,12 @@ export class GameGiveOutAPrizeCommandHandler
|
||||||
|
|
||||||
private readonly logger = new Logger(GameGiveOutAPrizeCommandHandler.name);
|
private readonly logger = new Logger(GameGiveOutAPrizeCommandHandler.name);
|
||||||
|
|
||||||
constructor(private gameService: GameService) {
|
constructor(private gameService: GameService, private guestService: GuestsService) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async execute(command: GiveOutAPrizeCommand): Promise<any> {
|
async execute(command: GiveOutAPrizeCommand): Promise<any> {
|
||||||
this.logger.verbose(`Player winning a prize ${command.telegramId}`);
|
this.logger.verbose(`Player winning a prize ${command.telegramId}`);
|
||||||
|
await this.guestService.incrementPrizeCount(command.telegramId);
|
||||||
return this.gameService.addTaskToGameQueue(
|
return this.gameService.addTaskToGameQueue(
|
||||||
command.telegramId,
|
command.telegramId,
|
||||||
GameQueueTypes.giveOutAPrize,
|
GameQueueTypes.giveOutAPrize,
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,12 @@ import { ProceedGameQueueCommand } from '../commands/proceed-game-queue.command'
|
||||||
import {GameService} from '../game.service';
|
import {GameService} from '../game.service';
|
||||||
import {NextQuestionCommand} from '../commands/next-question.command';
|
import {NextQuestionCommand} from '../commands/next-question.command';
|
||||||
import {SharedService} from '../../shared/shared.service';
|
import {SharedService} from '../../shared/shared.service';
|
||||||
import { SocketEvents } from '../../shared/events.consts';
|
|
||||||
import {Logger} from '@nestjs/common';
|
import {Logger} from '@nestjs/common';
|
||||||
import {GameQueueTypes} from '../../schemas/game-queue.schema';
|
import {GameQueueTypes} from '../../schemas/game-queue.schema';
|
||||||
import {QuizAnswerStateChangedEvent} from '../events/quiz-answer-state-changed.event';
|
import {QuizAnswerStateChangedEvent} from '../events/quiz-answer-state-changed.event';
|
||||||
import {QuizAnswerStateEnum} from '../entities/quiz-answer-state.enum';
|
import {QuizAnswerStateEnum} from '../entities/quiz-answer-state.enum';
|
||||||
|
import {IGameQueueSocketEvent} from "../../Consts/types";
|
||||||
|
import {ClientNotificationType} from "../../socket/socket.gateway";
|
||||||
|
|
||||||
@CommandHandler(ProceedGameQueueCommand)
|
@CommandHandler(ProceedGameQueueCommand)
|
||||||
export class GameProceedGameQueueCommandHandler
|
export class GameProceedGameQueueCommandHandler
|
||||||
|
|
@ -25,16 +26,13 @@ export class GameProceedGameQueueCommandHandler
|
||||||
if (!item) {
|
if (!item) {
|
||||||
return this.cmdBus.execute(new NextQuestionCommand());
|
return this.cmdBus.execute(new NextQuestionCommand());
|
||||||
}
|
}
|
||||||
this.sharedService.sendSocketNotificationToAllClients(
|
this.sharedService.notifyAllClients<IGameQueueSocketEvent>(ClientNotificationType.GameQueueItem, {
|
||||||
SocketEvents.GameQueueItem,
|
_id: item._id,
|
||||||
{
|
|
||||||
_id: item.id,
|
|
||||||
completed: item.completed,
|
completed: item.completed,
|
||||||
target: item.target,
|
target: item.target,
|
||||||
type: item.type,
|
type: item.type,
|
||||||
text: item.text
|
text: item.text
|
||||||
},
|
});
|
||||||
);
|
|
||||||
switch (item.type) {
|
switch (item.type) {
|
||||||
case GameQueueTypes.giveOutAPrize:
|
case GameQueueTypes.giveOutAPrize:
|
||||||
this.eventBus.publish(
|
this.eventBus.publish(
|
||||||
|
|
|
||||||
|
|
@ -18,18 +18,21 @@ export class SelectTargetPlayerHandler implements ICommandHandler<SelectTargetPl
|
||||||
}
|
}
|
||||||
async execute(command: SelectTargetPlayerCommand): Promise<any> {
|
async execute(command: SelectTargetPlayerCommand): Promise<any> {
|
||||||
this.logger.verbose('enter');
|
this.logger.verbose('enter');
|
||||||
//const user = await this.guestService.findById(command.player);
|
let allUsers = await this.guestService.findAll();
|
||||||
const allUsers = await this.guestService.findAll();
|
|
||||||
const user = allUsers.find(x => x.telegramId === command.player);
|
const user = allUsers.find(x => x.telegramId === command.player);
|
||||||
if(!user) {
|
if(!user) {
|
||||||
throw new Error(`Cant find current user ${command.player}`);
|
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) => {
|
const buttons = allUsers.map((x) => {
|
||||||
return [{
|
return [{
|
||||||
text: `${Messages.EMOJI_PLAYER} ${x.name}`,
|
text: `${Messages.EMOJI_PLAYER} ${x.name}`,
|
||||||
callback_data: `{ "card": "${command.debuffName}", "value": "${command.value}", "user": "${x.telegramId}" }`
|
callback_data: `{ "card": "${command.debuffName}", "value": "${command.value}", "user": "${x.telegramId}" }`
|
||||||
}]
|
}]
|
||||||
});
|
});
|
||||||
|
console.log(buttons);
|
||||||
|
|
||||||
this.telegramService.send<MqtMessageModel,ChatMessageRequestModel>(
|
this.telegramService.send<MqtMessageModel,ChatMessageRequestModel>(
|
||||||
{ cmd: CommandsConsts.SendMessage},
|
{ 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 {
|
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 {StringHelper} from "../../helpers/stringhelper";
|
||||||
import {GetGuestQuery} from "../../guests/queries/getguest.query";
|
import {GetGuestQuery} from "../../guests/queries/getguest.query";
|
||||||
import {CardsSetChangedEvent} from "../events/cards-events/cards-set-changed.event";
|
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 {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 {
|
export interface IGameCard {
|
||||||
setupHandlers(eventBus: EventBus, commandBus: CommandBus, queryBus: QueryBus): void;
|
setupHandlers(eventBus: EventBus, commandBus: CommandBus, queryBus: QueryBus): void;
|
||||||
|
|
@ -74,7 +75,7 @@ export class DoubleTreasureCard extends GameCard {
|
||||||
await this.commandBus.execute(
|
await this.commandBus.execute(
|
||||||
new GiveOutAPrizeCommand(this.telegramId),
|
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 subjcaseFrom = userSrc.get(StringHelper.getPropertyName(GuestPropertiesConsts.NameSubjectiveCase));
|
||||||
const message = `${subjcaseFrom} решает удвоить приз!`;
|
const message = `${subjcaseFrom} решает удвоить приз!`;
|
||||||
await this.commandBus.execute(new SendToastCommand(message, 8000));
|
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()
|
@Injectable()
|
||||||
export class BanPlayer extends GameCard {
|
export class BanPlayer extends GameCard {
|
||||||
dealOnStart = true;
|
dealOnStart = true;
|
||||||
|
|
@ -197,7 +235,6 @@ export class BanPlayer extends GameCard {
|
||||||
eventBus.publish(new CardsSetChangedEvent(sourceUser.telegramId));
|
eventBus.publish(new CardsSetChangedEvent(sourceUser.telegramId));
|
||||||
})
|
})
|
||||||
eventBus.pipe(ofType(NextQuestionEvent)).subscribe(async (r)=> {
|
eventBus.pipe(ofType(NextQuestionEvent)).subscribe(async (r)=> {
|
||||||
this.logger.verbose(`next event`);
|
|
||||||
const players = await queryBus.execute(new FilterGuestsWithPropertyQuery(DebuffsConsts.bannedFor, '$gt', 0))
|
const players = await queryBus.execute(new FilterGuestsWithPropertyQuery(DebuffsConsts.bannedFor, '$gt', 0))
|
||||||
this.logger.verbose(`enter: ban card handler, banned players count ${players.length}`);
|
this.logger.verbose(`enter: ban card handler, banned players count ${players.length}`);
|
||||||
players.map(async (player) => {
|
players.map(async (player) => {
|
||||||
|
|
@ -213,7 +250,7 @@ export class BanPlayer extends GameCard {
|
||||||
|
|
||||||
async handle() {
|
async handle() {
|
||||||
await this.commandBus.execute(
|
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));
|
await this.queryBus.execute(new FilterGuestsWithPropertyQuery(null,null,null));
|
||||||
this.eventBus.subscribe((data) =>{
|
this.eventBus.subscribe((data) =>{
|
||||||
|
|
@ -225,8 +262,9 @@ export class BanPlayer extends GameCard {
|
||||||
export const gameCards: typeof GameCard[] = [
|
export const gameCards: typeof GameCard[] = [
|
||||||
DoubleTreasureCard,
|
DoubleTreasureCard,
|
||||||
StolePrizeCard,
|
StolePrizeCard,
|
||||||
ShitCard,
|
// ShitCard,
|
||||||
LuckyCard,
|
LuckyCard,
|
||||||
AvoidPenaltyCard,
|
AvoidPenaltyCard,
|
||||||
BanPlayer
|
BanPlayer,
|
||||||
|
VersusCard,
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
export class DebuffsConsts {
|
export class DebuffsConsts {
|
||||||
static bannedFor = 'bannedFor';
|
static bannedFor = 'bannedFor';
|
||||||
|
static versus = 'versus';
|
||||||
}
|
}
|
||||||
|
|
@ -10,6 +10,6 @@ export class QuizAnsweredEventHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
async handle(event: QuizAnsweredEvent) {
|
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) {
|
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 { Test, TestingModule } from '@nestjs/testing';
|
||||||
import { GameController } from './game.controller';
|
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', () => {
|
describe('GameController', () => {
|
||||||
let controller: GameController;
|
let controller: GameController;
|
||||||
|
|
@ -7,6 +11,10 @@ describe('GameController', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
controllers: [GameController],
|
controllers: [GameController],
|
||||||
|
providers: [
|
||||||
|
{ provide: GameService, useValue: GameServiceMock },
|
||||||
|
{ provide: VersusService, useValue: VersusServiceMock },
|
||||||
|
]
|
||||||
}).compile();
|
}).compile();
|
||||||
|
|
||||||
controller = module.get<GameController>(GameController);
|
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 { GameService } from './game.service';
|
||||||
|
import {VersusService} from "./versus/versus.service";
|
||||||
|
|
||||||
@Controller('game')
|
@Controller('game')
|
||||||
export class GameController {
|
export class GameController {
|
||||||
constructor(private gameService: GameService) {
|
private readonly logger = new Logger(GameController.name);
|
||||||
|
constructor(private gameService: GameService, private versusService: VersusService) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post(':id/complete')
|
@Post(':id/complete')
|
||||||
|
|
@ -30,4 +32,23 @@ export class GameController {
|
||||||
async playExtraCards() {
|
async playExtraCards() {
|
||||||
return this.gameService.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 {SelectTargetPlayerHandler} from "./comand-handlers/select-target-player.handler";
|
||||||
import {SendBetweenRoundsActionsHandler} from "../guests/command/send-between-rounds-actions.command";
|
import {SendBetweenRoundsActionsHandler} from "../guests/command/send-between-rounds-actions.command";
|
||||||
import {GuestsModule} from "../guests/guests.module";
|
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 = [
|
const eventHandlers = [
|
||||||
|
|
@ -34,19 +39,23 @@ const commandHandlers = [
|
||||||
GamePrizeChanceIncreasedEventHandler,
|
GamePrizeChanceIncreasedEventHandler,
|
||||||
GameProceedGameQueueCommandHandler,
|
GameProceedGameQueueCommandHandler,
|
||||||
SelectTargetPlayerHandler,
|
SelectTargetPlayerHandler,
|
||||||
SendBetweenRoundsActionsHandler
|
SendBetweenRoundsActionsHandler,
|
||||||
|
BeginVersusCommandHandler,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const queryHandlers = [CheckIfAnotherVersusInProgressHandler];
|
||||||
@Global()
|
@Global()
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
CqrsModule,
|
CqrsModule,
|
||||||
MongooseModule.forFeature([
|
MongooseModule.forFeature([
|
||||||
{ name: GameQueue.name, schema: GameQueueSchema },
|
{ name: GameQueue.name, schema: GameQueueSchema },
|
||||||
|
{ name: Versus.name, schema: VersusSchema }
|
||||||
]),
|
]),
|
||||||
forwardRef(() => GuestsModule)
|
forwardRef(() => GuestsModule)
|
||||||
],
|
],
|
||||||
providers: [GameService, ConfigService, ...eventHandlers, ...commandHandlers],
|
providers: [GameService, ConfigService, ...eventHandlers, ...commandHandlers,...queryHandlers, VersusService],
|
||||||
exports: [GameService],
|
exports: [GameService],
|
||||||
controllers: [GameController],
|
controllers: [GameController, VersusController],
|
||||||
})
|
})
|
||||||
export class GameModule {}
|
export class GameModule {}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,35 @@
|
||||||
import { Test, TestingModule } from '@nestjs/testing';
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
import { GameService } from './game.service';
|
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', () => {
|
describe('GameService', () => {
|
||||||
let service: GameService;
|
let service: GameService;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
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();
|
}).compile();
|
||||||
|
|
||||||
service = module.get<GameService>(GameService);
|
service = module.get<GameService>(GameService);
|
||||||
|
|
|
||||||
|
|
@ -2,17 +2,16 @@ import {Injectable, InternalServerErrorException, Logger, OnApplicationBootstrap
|
||||||
import {CommandBus, EventBus, QueryBus} from '@nestjs/cqrs';
|
import {CommandBus, EventBus, QueryBus} from '@nestjs/cqrs';
|
||||||
import {CardSelectionTimeExceedCommand} from './commands/card-selection-time-exceed.command';
|
import {CardSelectionTimeExceedCommand} from './commands/card-selection-time-exceed.command';
|
||||||
import {InjectModel} from '@nestjs/mongoose';
|
import {InjectModel} from '@nestjs/mongoose';
|
||||||
import {
|
import {GameQueue, GameQueueDocument, GameQueueTypes,} from '../schemas/game-queue.schema';
|
||||||
GameQueue,
|
|
||||||
GameQueueDocument,
|
|
||||||
GameQueueTypes,
|
|
||||||
} from '../schemas/game-queue.schema';
|
|
||||||
import {Model, Promise} from 'mongoose';
|
import {Model, Promise} from 'mongoose';
|
||||||
import {ProceedGameQueueCommand} from './commands/proceed-game-queue.command';
|
import {ProceedGameQueueCommand} from './commands/proceed-game-queue.command';
|
||||||
import {SharedService} from '../shared/shared.service';
|
import {SharedService} from '../shared/shared.service';
|
||||||
import { SocketEvents } from '../shared/events.consts';
|
|
||||||
import {ConfigService} from "@nestjs/config";
|
import {ConfigService} from "@nestjs/config";
|
||||||
import {gameCards} from "./entities/cards.entities";
|
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()
|
@Injectable()
|
||||||
export class GameService implements OnApplicationBootstrap{
|
export class GameService implements OnApplicationBootstrap{
|
||||||
|
|
@ -53,7 +52,26 @@ export class GameService implements OnApplicationBootstrap{
|
||||||
}
|
}
|
||||||
|
|
||||||
async getGameQueueItem() {
|
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) {
|
async markQueueAsCompleted(id: string| null) {
|
||||||
|
|
@ -63,35 +81,27 @@ export class GameService implements OnApplicationBootstrap{
|
||||||
} else {
|
} else {
|
||||||
qItem = await this.gameQueueModel.findById(id).exec();
|
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) {
|
if (!qItem) {
|
||||||
throw new InternalServerErrorException('no such item');
|
throw new InternalServerErrorException('no such item');
|
||||||
|
|
||||||
}
|
}
|
||||||
qItem.completed = true;
|
qItem.completed = true;
|
||||||
await qItem.save();
|
await qItem.save();
|
||||||
this.sharedService.sendSocketNotificationToAllClients(
|
this.sharedService.notifyAllClients<IEmptyNotification>(ClientNotificationType.QueueCompleted, {});
|
||||||
SocketEvents.QUEUE_COMPLETED,
|
|
||||||
{},
|
|
||||||
);
|
|
||||||
await this.cmdBus.execute(new ProceedGameQueueCommand());
|
await this.cmdBus.execute(new ProceedGameQueueCommand());
|
||||||
return qItem;
|
return qItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
async pauseGame() {
|
async pauseGame() {
|
||||||
await this.sharedService.setConfig('game_state', 'paused');
|
await this.sharedService.setConfig('game_state', 'paused');
|
||||||
await this.sharedService.sendSocketNotificationToAllClients(
|
this.sharedService.notifyAllClients<IEmptyNotification>(ClientNotificationType.GamePaused, {});
|
||||||
SocketEvents.GAME_PAUSED,
|
|
||||||
{},
|
|
||||||
);
|
|
||||||
return Promise.resolve({ result: true });
|
return Promise.resolve({ result: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
async resumeGame() {
|
async resumeGame() {
|
||||||
await this.sharedService.setConfig('game_state', 'running');
|
await this.sharedService.setConfig('game_state', 'running');
|
||||||
await this.sharedService.sendSocketNotificationToAllClients(
|
this.sharedService.notifyAllClients<IEmptyNotification>(ClientNotificationType.GameResumed,{});
|
||||||
SocketEvents.GAME_RESUMED,
|
|
||||||
{},
|
|
||||||
);
|
|
||||||
return Promise.resolve({ result: true });
|
return Promise.resolve({ result: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -112,4 +122,17 @@ export class GameService implements OnApplicationBootstrap{
|
||||||
cardInstance.setupHandlers(this.eventBus, this.commandBus, this.queryBus);
|
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 { Test, TestingModule } from '@nestjs/testing';
|
||||||
import { GiftsController } from './gifts.controller';
|
import { GiftsController } from './gifts.controller';
|
||||||
|
import {GiftsService} from "./gifts.service";
|
||||||
|
import {GiftServiceMock} from "../mocks/gift-service.mock";
|
||||||
|
|
||||||
describe('GiftsController', () => {
|
describe('GiftsController', () => {
|
||||||
let controller: GiftsController;
|
let controller: GiftsController;
|
||||||
|
|
@ -7,6 +9,9 @@ describe('GiftsController', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
controllers: [GiftsController],
|
controllers: [GiftsController],
|
||||||
|
providers: [
|
||||||
|
{ provide: GiftsService, useValue: GiftServiceMock },
|
||||||
|
]
|
||||||
}).compile();
|
}).compile();
|
||||||
|
|
||||||
controller = module.get<GiftsController>(GiftsController);
|
controller = module.get<GiftsController>(GiftsController);
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,18 @@
|
||||||
import { Test, TestingModule } from '@nestjs/testing';
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
import { GiftsService } from './gifts.service';
|
import { GiftsService } from './gifts.service';
|
||||||
|
import {getModelToken} from "@nestjs/mongoose";
|
||||||
|
import {Prize} from "../schemas/prize.schema";
|
||||||
|
import {Model} from "mongoose";
|
||||||
|
|
||||||
describe('GiftsService', () => {
|
describe('GiftsService', () => {
|
||||||
let service: GiftsService;
|
let service: GiftsService;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
providers: [GiftsService],
|
providers: [
|
||||||
|
GiftsService,
|
||||||
|
{ provide: getModelToken(Prize.name), useValue: Model },
|
||||||
|
],
|
||||||
}).compile();
|
}).compile();
|
||||||
|
|
||||||
service = module.get<GiftsService>(GiftsService);
|
service = module.get<GiftsService>(GiftsService);
|
||||||
|
|
|
||||||
|
|
@ -15,9 +15,8 @@ export class GetGuestPropertyHandler implements ICommandHandler<GetGuestProperty
|
||||||
async execute(command: GetGuestPropertyQuery): Promise<string> {
|
async execute(command: GetGuestPropertyQuery): Promise<string> {
|
||||||
this.logger.verbose(`entering`);
|
this.logger.verbose(`entering`);
|
||||||
const guest = await this.guestService.findById(command.user);
|
const guest = await this.guestService.findById(command.user);
|
||||||
console.log(command);
|
this.logger.verbose(command);
|
||||||
if(!command.property.startsWith('properties.')) {
|
if(!command.property.startsWith('properties.')) {
|
||||||
|
|
||||||
command.property = `properties.${command.property}`;
|
command.property = `properties.${command.property}`;
|
||||||
this.logger.warn(`update prop ${command.property}`);
|
this.logger.warn(`update prop ${command.property}`);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,13 +28,16 @@ export class TgPostCardsToUserCommandHandler implements ICommandHandler<PostCard
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
if (command.cards.length === 0) {
|
if (command.cards.length === 0) {
|
||||||
|
this.telegramService.emit({ cmd: CommandsConsts.SendMessage }, { chatId: command.chatId, message: "У вас нет карт которые можно сейчас использовать"});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
command.cards.forEach((card) => {
|
command.cards.forEach((card) => {
|
||||||
extra.reply_markup.keyboard.push([
|
extra.reply_markup.keyboard.push([
|
||||||
{text: Messages.EMOJI_CARD + ' ' + card},
|
{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}`,
|
await this.sharedService.setConfig(`buttons_${command.chatId}`,
|
||||||
JSON.stringify(extra),
|
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> {
|
async execute(command: RemoveCardFromUserCommand): Promise<any> {
|
||||||
const guest = await this.guestService.findById(command.telegramId);
|
// const guest = await this.guestService.findById(command.telegramId);
|
||||||
const data = await this.sharedService.getConfig(`buttons_${command.telegramId}`);
|
// const data = await this.sharedService.getConfig(`buttons_${command.telegramId}`);
|
||||||
const extra = {
|
// const extra = {
|
||||||
reply_markup: {
|
// reply_markup: {
|
||||||
remove_keyboard: false,
|
// remove_keyboard: false,
|
||||||
keyboard: [],
|
// keyboard: [],
|
||||||
},
|
// },
|
||||||
};
|
// };
|
||||||
const buttons = JSON.parse(data.value);
|
// const buttons = JSON.parse(data.value);
|
||||||
let found = false;
|
// let found = false;
|
||||||
buttons.reply_markup.keyboard.forEach((item) => {
|
// buttons.reply_markup.keyboard.forEach((item) => {
|
||||||
if (item[0].text.includes(command.card.description) && !found) {
|
// if (item[0].text.includes(command.card.description) && !found) {
|
||||||
found = true;
|
// found = true;
|
||||||
} else {
|
// } else {
|
||||||
extra.reply_markup.keyboard.push(
|
// extra.reply_markup.keyboard.push(
|
||||||
[ { ...item[0] } ]
|
// [ { ...item[0] } ]
|
||||||
)
|
// )
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
if (extra.reply_markup.keyboard.length === 0) {
|
// if (extra.reply_markup.keyboard.length === 0) {
|
||||||
extra.reply_markup.remove_keyboard = true;
|
// extra.reply_markup.remove_keyboard = true;
|
||||||
}
|
// }
|
||||||
await this.sharedService.setConfig(`buttons_${command.telegramId}`, JSON.stringify(extra));
|
// await this.sharedService.setConfig(`buttons_${command.telegramId}`, JSON.stringify(extra));
|
||||||
this.telegramService.emit<MqtMessageModel, ChatMessageRequestModel>({
|
// this.telegramService.emit<MqtMessageModel, ChatMessageRequestModel>({
|
||||||
cmd: CommandsConsts.SendMessage,
|
// cmd: CommandsConsts.SendMessage,
|
||||||
}, {
|
// }, {
|
||||||
chatId: guest.chatId,
|
// chatId: guest.chatId,
|
||||||
message: Messages.SELECT_CARD,
|
// message: Messages.SELECT_CARD,
|
||||||
extra: extra,
|
// extra: extra,
|
||||||
})
|
// })
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3,7 +3,7 @@ import {Promise} from "mongoose";
|
||||||
import {GuestsService} from "../guests.service";
|
import {GuestsService} from "../guests.service";
|
||||||
|
|
||||||
export class SendBetweenRoundsActionsCommand {
|
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) {
|
async handle(event: ValidAnswerReceivedEvent) {
|
||||||
await this.guestService.notifyAboutValidAnswer(event.tId);
|
await this.guestService.notifyAboutValidAnswer(event.tId);
|
||||||
await this.guestService.sendValidAnswerActions(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) {
|
if (event.extraDetails) {
|
||||||
await this.commandBus.execute(
|
await this.commandBus.execute(
|
||||||
new SendToastCommand(event.extraDetails, 4000),
|
new SendToastCommand(event.extraDetails, 4000),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const coef = +(await this.guestService.getCoefficient());
|
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);
|
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 { Test, TestingModule } from '@nestjs/testing';
|
||||||
import { GuestsController } from './guests.controller';
|
import { GuestsController } from './guests.controller';
|
||||||
|
import {GuestsService} from "./guests.service";
|
||||||
|
import {GuestsServiceMock} from "../mocks/guests-service.mock";
|
||||||
|
|
||||||
describe('GuestsController', () => {
|
describe('GuestsController', () => {
|
||||||
let controller: GuestsController;
|
let controller: GuestsController;
|
||||||
|
|
@ -7,6 +9,9 @@ describe('GuestsController', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
controllers: [GuestsController],
|
controllers: [GuestsController],
|
||||||
|
providers: [
|
||||||
|
{ provide: GuestsService, useValue: GuestsServiceMock },
|
||||||
|
]
|
||||||
}).compile();
|
}).compile();
|
||||||
|
|
||||||
controller = module.get<GuestsController>(GuestsController);
|
controller = module.get<GuestsController>(GuestsController);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import {
|
import {
|
||||||
Controller,
|
Controller,
|
||||||
Get, Param, Res
|
Get, Param, Post, Res
|
||||||
} from "@nestjs/common";
|
} from "@nestjs/common";
|
||||||
import { GuestsService } from './guests.service';
|
import { GuestsService } from './guests.service';
|
||||||
import { Response } from 'express';
|
import { Response } from 'express';
|
||||||
|
|
@ -42,4 +42,9 @@ export class GuestsController {
|
||||||
// this.echoService.enterQuiz(u.chatId);
|
// 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 { GuestsService } from './guests.service';
|
||||||
import { MongooseModule } from '@nestjs/mongoose';
|
import { MongooseModule } from '@nestjs/mongoose';
|
||||||
import { Guest, GuestSchema } from '../schemas/guest.schema';
|
import { Guest, GuestSchema } from '../schemas/guest.schema';
|
||||||
|
|
@ -23,8 +23,12 @@ import {GetGuestPropertyHandler} from "./command/get-guest-property.handler";
|
||||||
import {QueryHandlers } from "./queries";
|
import {QueryHandlers } from "./queries";
|
||||||
import {SetGuestPropertyCommandHandler} from "./command/handlers/set-guest-property.handler";
|
import {SetGuestPropertyCommandHandler} from "./command/handlers/set-guest-property.handler";
|
||||||
import {WrongAnswerReceivedGuestEventHandler} from "./event-handlers/wrong-answer-received-guest-event.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 {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 = [
|
const commandHandlers = [
|
||||||
GuestsRemoveKeyboardHandler,
|
GuestsRemoveKeyboardHandler,
|
||||||
|
|
@ -32,6 +36,7 @@ const commandHandlers = [
|
||||||
IncreasePlayerWinningRateCommandHandler,
|
IncreasePlayerWinningRateCommandHandler,
|
||||||
GetGuestPropertyHandler,
|
GetGuestPropertyHandler,
|
||||||
SetGuestPropertyCommandHandler,
|
SetGuestPropertyCommandHandler,
|
||||||
|
IncreasePlayerScoreCommandHandler,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -60,7 +65,7 @@ const eventHandlers = [
|
||||||
CardsModule,
|
CardsModule,
|
||||||
],
|
],
|
||||||
providers: [GuestsService, ConfigService, ...commandHandlers,...QueryHandlers, ...eventHandlers, ],
|
providers: [GuestsService, ConfigService, ...commandHandlers,...QueryHandlers, ...eventHandlers, ],
|
||||||
exports: [GuestsService],
|
exports: [GuestsService,],
|
||||||
controllers: [GuestsController],
|
controllers: [GuestsController],
|
||||||
})
|
})
|
||||||
export class GuestsModule {}
|
export class GuestsModule {}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,39 @@
|
||||||
import { Test, TestingModule } from '@nestjs/testing';
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
import { GuestsService } from './guests.service';
|
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', () => {
|
describe('GuestsService', () => {
|
||||||
let service: GuestsService;
|
let service: GuestsService;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
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();
|
}).compile();
|
||||||
|
|
||||||
service = module.get<GuestsService>(GuestsService);
|
service = module.get<GuestsService>(GuestsService);
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import {Inject, Injectable, Logger} from '@nestjs/common';
|
import {Inject, Injectable, Logger} from '@nestjs/common';
|
||||||
import {InjectModel} from '@nestjs/mongoose';
|
import {InjectModel} from '@nestjs/mongoose';
|
||||||
import {Guest, GuestDocument} from '../schemas/guest.schema';
|
import {Guest, GuestDocument} from '../schemas/guest.schema';
|
||||||
import {Model} from 'mongoose';
|
import {Document, Model} from 'mongoose';
|
||||||
import {CreateGuestDto} from './dto/create-guest.dto';
|
import {CreateGuestDto} from './dto/create-guest.dto';
|
||||||
import {QuestionDto} from '../quiz/dto/question.dto';
|
import {QuestionDto} from '../quiz/dto/question.dto';
|
||||||
import {Messages} from '../messaging/tg.text';
|
import {Messages} from '../messaging/tg.text';
|
||||||
|
|
@ -24,11 +24,11 @@ import {MqtMessageModel} from "../messaging/models/mqt-message.model";
|
||||||
import {ConfigService} from "@nestjs/config";
|
import {ConfigService} from "@nestjs/config";
|
||||||
import {StringHelper} from "../helpers/stringhelper";
|
import {StringHelper} from "../helpers/stringhelper";
|
||||||
import {DebuffsConsts} from "../game/entities/debuffs.consts";
|
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 {VoiceService} from "../voice/voice.service";
|
||||||
import {screpaDictManyInvalidAnswersDict} from "../voice/dicts/screpa-dict-many-invalid-answers.dict";
|
import {screpaDictManyInvalidAnswersDict} from "../voice/dicts/screpa-dict-many-invalid-answers.dict";
|
||||||
import {GuestPropertiesConsts} from "../schemas/properties.consts";
|
import {GuestPropertiesConsts} from "../schemas/properties.consts";
|
||||||
|
import {GuestNamesInCases} from "./guest.types";
|
||||||
|
import {QuizService} from "../quiz/quiz.service";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class GuestsService {
|
export class GuestsService {
|
||||||
|
|
@ -66,12 +66,21 @@ export class GuestsService {
|
||||||
return this.guestModel.find().exec();
|
return this.guestModel.find().exec();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getModel() {
|
||||||
|
return this.guestModel;
|
||||||
|
}
|
||||||
|
|
||||||
async filter(properties: object) {
|
async filter(properties: object) {
|
||||||
return this.guestModel.find(properties).exec();
|
return this.guestModel.find(properties).exec();
|
||||||
}
|
}
|
||||||
|
|
||||||
async findById(id: number) {
|
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) {
|
async hideKeyboard(text: string) {
|
||||||
|
|
@ -84,17 +93,26 @@ export class GuestsService {
|
||||||
this.logger.verbose(`Keyboard hidden`);
|
this.logger.verbose(`Keyboard hidden`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
async postQuestion(questionDto: QuestionDto, targetId = null) {
|
async postQuestion(questionDto: QuestionDto, targetId = null) {
|
||||||
const guests = await this.findAll();
|
const guests = await this.findAll();
|
||||||
const extra = {
|
const extra = {
|
||||||
reply_markup: {
|
reply_markup: {
|
||||||
keyboard: [],
|
keyboard: [],
|
||||||
|
inline_keyboard: [],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
questionDto.answers.forEach((item, index) => {
|
questionDto.answers.forEach((item, index) => {
|
||||||
extra.reply_markup.keyboard.push([
|
// extra.reply_markup.keyboard.push([
|
||||||
{ text: this.nums[index] + ' ' + item },
|
// { text: this.nums[index] + ' ' + item },
|
||||||
|
// ]);
|
||||||
|
if(item !== null){
|
||||||
|
extra.reply_markup.inline_keyboard.push(
|
||||||
|
[ { text: item, callback_data: `${item.substring(0,50)}` },
|
||||||
]);
|
]);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
if (!targetId) {
|
if (!targetId) {
|
||||||
guests.forEach((guest) => {
|
guests.forEach((guest) => {
|
||||||
|
|
@ -310,6 +328,15 @@ export class GuestsService {
|
||||||
await this.guestModel.updateMany({}, { prizeChance: 0 });
|
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) {
|
async incrementInvalidAnswersCount(tId: number) {
|
||||||
this.logger.verbose(`Increment invalid answers in the row for ${tId}`);
|
this.logger.verbose(`Increment invalid answers in the row for ${tId}`);
|
||||||
const guest = await this.findById(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`);
|
this.logger.error(`Can't find user ${tId} for incrementing invalid answers count`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
guest.invalidAnswers = +guest.invalidAnswers + 1;
|
guest.invalidAnswers = +guest.invalidAnswers + 1;
|
||||||
guest.invalidAnswersInRow = +guest.invalidAnswersInRow + 1;
|
guest.invalidAnswersInRow = +guest.invalidAnswersInRow + 1;
|
||||||
this.logger.verbose(`Invalid answers: ${guest.invalidAnswers}, inRow: ${guest.invalidAnswersInRow}`);
|
this.logger.verbose(`Invalid answers: ${guest.invalidAnswers}, inRow: ${guest.invalidAnswersInRow}`);
|
||||||
|
|
@ -327,8 +353,8 @@ export class GuestsService {
|
||||||
AccusativeCase: guest.get(StringHelper.getPropertyName(GuestPropertiesConsts.NameAccusativeCase)),
|
AccusativeCase: guest.get(StringHelper.getPropertyName(GuestPropertiesConsts.NameAccusativeCase)),
|
||||||
GenitiveCase: guest.get(StringHelper.getPropertyName(GuestPropertiesConsts.NameGenitiveCase))
|
GenitiveCase: guest.get(StringHelper.getPropertyName(GuestPropertiesConsts.NameGenitiveCase))
|
||||||
}, [...screpaDictManyInvalidAnswersDict])
|
}, [...screpaDictManyInvalidAnswersDict])
|
||||||
await this.commandBus.execute(new CreateNewQueueItemCommand(guest.telegramId, GameQueueTypes.screpaAnounce, text));
|
//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.penalty));
|
||||||
this.logger.verbose(`Reset invalidAnswerInRow, since user received penalty`);
|
this.logger.verbose(`Reset invalidAnswerInRow, since user received penalty`);
|
||||||
guest.invalidAnswersInRow = 0;
|
guest.invalidAnswersInRow = 0;
|
||||||
}
|
}
|
||||||
|
|
@ -341,4 +367,16 @@ export class GuestsService {
|
||||||
guest.invalidAnswersInRow = 0;
|
guest.invalidAnswersInRow = 0;
|
||||||
await guest.save();
|
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 {IQueryHandler, QueryHandler} from "@nestjs/cqrs";
|
||||||
import {GetGuestQuery} from "../getguest.query";
|
import {GetGuestQuery} from "../getguest.query";
|
||||||
import {Promise} from "mongoose";
|
|
||||||
import {GuestsService} from "../../guests.service";
|
import {GuestsService} from "../../guests.service";
|
||||||
|
|
||||||
@QueryHandler(GetGuestQuery)
|
@QueryHandler(GetGuestQuery)
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,21 @@
|
||||||
import {Controller, Logger} from "@nestjs/common";
|
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 {GetGuestInfoModel} from "./models/get-guest-info.model";
|
||||||
import {GuestsService} from "../guests/guests.service";
|
import {GuestsService} from "../guests/guests.service";
|
||||||
import {RegisterUserModel} from "./models/register-user.model";
|
import {RegisterUserModel} from "./models/register-user.model";
|
||||||
import {SharedService} from "../shared/shared.service";
|
import {SharedService} from "../shared/shared.service";
|
||||||
import {SocketEvents} from "../shared/events.consts";
|
|
||||||
import {CommandsConsts} from "../Consts/commands.consts";
|
import {CommandsConsts} from "../Consts/commands.consts";
|
||||||
import {EventBus} from "@nestjs/cqrs";
|
import {EventBus} from "@nestjs/cqrs";
|
||||||
import {PlayerCardSelectedEvent} from "../game/events/player-card-selected.event";
|
import {PlayerCardSelectedEvent} from "../game/events/player-card-selected.event";
|
||||||
import {getCard} from "../helpers/card-parser";
|
import {getCard} from "../helpers/card-parser";
|
||||||
|
import {QuizService} from "../quiz/quiz.service";
|
||||||
|
import {ClientNotificationType} from "../socket/socket.gateway";
|
||||||
|
|
||||||
@Controller()
|
@Controller()
|
||||||
export class GuestsMessageController {
|
export class GuestsMessageController {
|
||||||
private readonly logger = new Logger(GuestsMessageController.name);
|
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'} )
|
@MessagePattern({ cmd: 'GuestInfo'} )
|
||||||
async getGuestInformation(@Payload() data: GetGuestInfoModel, @Ctx() context: RmqContext) {
|
async getGuestInformation(@Payload() data: GetGuestInfoModel, @Ctx() context: RmqContext) {
|
||||||
|
|
@ -48,7 +49,7 @@ export class GuestsMessageController {
|
||||||
@MessagePattern({ cmd: CommandsConsts.PhotoUpdated })
|
@MessagePattern({ cmd: CommandsConsts.PhotoUpdated })
|
||||||
async photoUpdated(@Payload() data: { id: number}) {
|
async photoUpdated(@Payload() data: { id: number}) {
|
||||||
this.logger.verbose(`Photo updated event, send notification`);
|
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 })
|
@MessagePattern({ cmd: CommandsConsts.CardPlayed })
|
||||||
|
|
@ -58,4 +59,10 @@ export class GuestsMessageController {
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MessagePattern({ cmd: CommandsConsts.GetQuestion})
|
||||||
|
async getQuestion(@Payload() data: { user: number, inline: false}) {
|
||||||
|
await this.quizService.displayQuestionForUser(data.user);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -3,3 +3,9 @@ export interface ValidateAnswerModel {
|
||||||
user: number;
|
user: number;
|
||||||
name: string;
|
name: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ValidateAnswerInline {
|
||||||
|
answer: string;
|
||||||
|
user: number;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import {Controller, Logger} from "@nestjs/common";
|
import {Controller, Logger} from "@nestjs/common";
|
||||||
import {MessagePattern, Payload} from "@nestjs/microservices";
|
import {MessagePattern, Payload} from "@nestjs/microservices";
|
||||||
import {CommandsConsts} from "../Consts/commands.consts";
|
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 {QuizAnsweredEvent} from "../game/events/quiz.answered";
|
||||||
import {QuizService} from "../quiz/quiz.service";
|
import {QuizService} from "../quiz/quiz.service";
|
||||||
import {CommandBus, EventBus} from "@nestjs/cqrs";
|
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) {
|
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.logger.verbose(`Validate answer ${data}`);
|
||||||
this.eventBus.publish(new QuizAnsweredEvent(data.name));
|
this.eventBus.publish(new QuizAnsweredEvent(data.name));
|
||||||
const result = await this.quizService.validateAnswer(
|
const result = await this.quizService.validateAnswerInline(
|
||||||
data.answer,
|
data.answer,
|
||||||
data.user,
|
data.user,
|
||||||
);
|
);
|
||||||
|
|
@ -30,12 +31,13 @@ export class QuizMessagingController {
|
||||||
async completeQueueItem(@Payload() data: any) {
|
async completeQueueItem(@Payload() data: any) {
|
||||||
this.logger.verbose(`complete item`)
|
this.logger.verbose(`complete item`)
|
||||||
await this.gameService.markQueueAsCompleted(null);
|
await this.gameService.markQueueAsCompleted(null);
|
||||||
|
//await this.quizService.proceedWithGame();
|
||||||
}
|
}
|
||||||
|
|
||||||
@MessagePattern({ cmd: CommandsConsts.GetCards})
|
@MessagePattern({ cmd: CommandsConsts.GetCards})
|
||||||
async getCardsForUser(@Payload() data: { user: number, inline: boolean}) {
|
async getCardsForUser(@Payload() data: { user: number, inline: boolean}) {
|
||||||
this.logger.verbose(`getCardsForUser ${data}`);
|
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})
|
@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 { Test, TestingModule } from '@nestjs/testing';
|
||||||
import { PenaltyController } from './penalty.controller';
|
import { PenaltyController } from './penalty.controller';
|
||||||
|
import {PenaltyService} from "./penalty.service";
|
||||||
|
import {PenaltyServiceMock} from "../mocks/penalty-service.mock";
|
||||||
|
|
||||||
describe('PenaltyController', () => {
|
describe('PenaltyController', () => {
|
||||||
let controller: PenaltyController;
|
let controller: PenaltyController;
|
||||||
|
|
@ -7,6 +9,9 @@ describe('PenaltyController', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
controllers: [PenaltyController],
|
controllers: [PenaltyController],
|
||||||
|
providers: [
|
||||||
|
{ provide: PenaltyService, useValue: PenaltyServiceMock },
|
||||||
|
]
|
||||||
}).compile();
|
}).compile();
|
||||||
|
|
||||||
controller = module.get<PenaltyController>(PenaltyController);
|
controller = module.get<PenaltyController>(PenaltyController);
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,18 @@
|
||||||
import { Test, TestingModule } from '@nestjs/testing';
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
import { PenaltyService } from './penalty.service';
|
import { PenaltyService } from './penalty.service';
|
||||||
|
import {getModelToken} from "@nestjs/mongoose";
|
||||||
|
import {Penalty} from "../schemas/penalty.schema";
|
||||||
|
import {Model} from "mongoose";
|
||||||
|
|
||||||
describe('PenaltyService', () => {
|
describe('PenaltyService', () => {
|
||||||
let service: PenaltyService;
|
let service: PenaltyService;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
providers: [PenaltyService],
|
providers: [
|
||||||
|
PenaltyService,
|
||||||
|
{ provide: getModelToken(Penalty.name), useValue: Model },
|
||||||
|
],
|
||||||
}).compile();
|
}).compile();
|
||||||
|
|
||||||
service = module.get<PenaltyService>(PenaltyService);
|
service = module.get<PenaltyService>(PenaltyService);
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
export interface QuestionDto {
|
export interface QuestionDto {
|
||||||
|
id: string;
|
||||||
text: string;
|
text: string;
|
||||||
answers: string[];
|
answers: string[];
|
||||||
valid: string;
|
valid: string;
|
||||||
note: string | null;
|
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 { Test, TestingModule } from '@nestjs/testing';
|
||||||
import { QuizController } from './quiz.controller';
|
import { QuizController } from './quiz.controller';
|
||||||
|
import {QuizService} from "./quiz.service";
|
||||||
|
import {QuizServiceMock} from "../mocks/quiz-service.mock";
|
||||||
|
|
||||||
describe('QuizController', () => {
|
describe('QuizController', () => {
|
||||||
let controller: QuizController;
|
let controller: QuizController;
|
||||||
|
let quizService: QuizService;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
controllers: [QuizController],
|
controllers: [QuizController],
|
||||||
|
providers: [
|
||||||
|
{ provide: QuizService, useValue: QuizServiceMock },
|
||||||
|
]
|
||||||
}).compile();
|
}).compile();
|
||||||
|
|
||||||
controller = module.get<QuizController>(QuizController);
|
controller = module.get<QuizController>(QuizController);
|
||||||
|
quizService = module.get<QuizService>(QuizService);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be defined', () => {
|
it('should be defined', () => {
|
||||||
|
|
|
||||||
|
|
@ -16,11 +16,21 @@ export class QuizController {
|
||||||
return await this.quizService.setQuestion(qustionDto);
|
return await this.quizService.setQuestion(qustionDto);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Get('question-results')
|
||||||
|
async GetQuestionResults() {
|
||||||
|
return await this.quizService.getQuestionResults();
|
||||||
|
}
|
||||||
|
|
||||||
@Post('proceed')
|
@Post('proceed')
|
||||||
async Get() {
|
async Get() {
|
||||||
return this.quizService.proceedWithGame();
|
return this.quizService.proceedWithGame();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Post('timeout')
|
||||||
|
async Timeout() {
|
||||||
|
return await this.quizService.questionTimeout();
|
||||||
|
}
|
||||||
|
|
||||||
@Post('questions')
|
@Post('questions')
|
||||||
async postQuestion(@Body() questionDto: QuestionDto[]) {
|
async postQuestion(@Body() questionDto: QuestionDto[]) {
|
||||||
return await this.quizService.populateQuestions(questionDto);
|
return await this.quizService.populateQuestions(questionDto);
|
||||||
|
|
@ -45,4 +55,15 @@ export class QuizController {
|
||||||
async dealPrize() {
|
async dealPrize() {
|
||||||
return this.quizService.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 { MarkQuestionsAsUnansweredCommandHandler } from './command-handlers/mark-questions-as-unanswred-command.handler';
|
||||||
import { PenaltyModule } from '../penalty/penalty.module';
|
import { PenaltyModule } from '../penalty/penalty.module';
|
||||||
import {ConfigModule, ConfigService} from "@nestjs/config";
|
import {ConfigModule, ConfigService} from "@nestjs/config";
|
||||||
|
import {Config, ConfigSchema} from "../schemas/config.schema";
|
||||||
|
import {StateChangedEventHandler} from "./event-handlers/state-changed-event.handler";
|
||||||
|
|
||||||
const cmdHandlers = [
|
const cmdHandlers = [
|
||||||
GameNextQuestionCommandHandler,
|
GameNextQuestionCommandHandler,
|
||||||
MarkQuestionsAsUnansweredCommandHandler,
|
MarkQuestionsAsUnansweredCommandHandler,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const eventHandlers = [
|
||||||
|
StateChangedEventHandler
|
||||||
|
]
|
||||||
|
|
||||||
@Global()
|
@Global()
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
|
|
@ -32,6 +38,6 @@ const cmdHandlers = [
|
||||||
],
|
],
|
||||||
controllers: [QuizController],
|
controllers: [QuizController],
|
||||||
exports: [QuizService],
|
exports: [QuizService],
|
||||||
providers: [QuizService,ConfigService, ...cmdHandlers],
|
providers: [QuizService,ConfigService, ...cmdHandlers, ...eventHandlers],
|
||||||
})
|
})
|
||||||
export class QuizModule {}
|
export class QuizModule {}
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,260 @@
|
||||||
import {Test, TestingModule} from '@nestjs/testing';
|
import {Test, TestingModule} from '@nestjs/testing';
|
||||||
import {QuizService} from './quiz.service';
|
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', () => {
|
describe('QuizService', () => {
|
||||||
let service: QuizService;
|
let service: QuizService;
|
||||||
|
let cmdBus: CommandBus;
|
||||||
|
let guestService: GuestsService;
|
||||||
|
let featureFlagService: FeatureflagService;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
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();
|
}).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', () => {
|
it('should be defined', () => {
|
||||||
expect(service).toBeDefined();
|
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 {QuestionStorage, QuestionStorageDocument,} from '../schemas/question-storage.schema';
|
||||||
import {WrongAnswerReceivedEvent} from '../game/events/wrong-answer-received.event';
|
import {WrongAnswerReceivedEvent} from '../game/events/wrong-answer-received.event';
|
||||||
import {ProceedGameQueueCommand} from '../game/commands/proceed-game-queue.command';
|
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 {Messages} from "../messaging/tg.text";
|
||||||
import {CreateNewQueueItemCommand} from "../game/commands/create-new-queue-item.command";
|
import {CreateNewQueueItemCommand} from "../game/commands/create-new-queue-item.command";
|
||||||
import {GameQueueTypes} from "../schemas/game-queue.schema";
|
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 })
|
@Injectable({ scope: Scope.TRANSIENT })
|
||||||
export class QuizService {
|
export class QuizService {
|
||||||
private readonly answerNumbers = Messages.answerNumbers;
|
|
||||||
private readonly logger = new Logger(QuizService.name);
|
private readonly logger = new Logger(QuizService.name);
|
||||||
constructor(
|
constructor(
|
||||||
@InjectModel(Question.name) private questionModel: Model<QuestionDocument>,
|
@InjectModel(Question.name) private questionModel: Model<QuestionDocument>,
|
||||||
|
|
@ -27,54 +32,59 @@ export class QuizService {
|
||||||
private guestService: GuestsService,
|
private guestService: GuestsService,
|
||||||
private sharedService: SharedService,
|
private sharedService: SharedService,
|
||||||
private eventBus: EventBus,
|
private eventBus: EventBus,
|
||||||
private configService: ConfigService,
|
|
||||||
private commandBus: CommandBus,
|
private commandBus: CommandBus,
|
||||||
) {}
|
private featureFlagService: FeatureflagService,
|
||||||
|
) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
async get(): Promise<QuestionDocument> {
|
async get(): Promise<QuestionDocument> {
|
||||||
return this.questionModel.find().sort({ _id: -1 }).findOne();
|
return this.questionModel.find().sort({ _id: -1 }).findOne();
|
||||||
}
|
}
|
||||||
|
|
||||||
async setQuestion(questionDto: QuestionDto, target: number = null) {
|
async setQuestion(questionDto: QuestionDto, target: number = null) {
|
||||||
|
await this.sharedService.setConfig('currentQuestion', questionDto.id)
|
||||||
const item = new this.questionModel(questionDto);
|
const item = new this.questionModel(questionDto);
|
||||||
await item.save();
|
await item.save();
|
||||||
this.logger.verbose(`Question updated`);
|
this.logger.verbose(`Question updated`);
|
||||||
await this.guestService.postQuestion(questionDto, target);
|
await this.guestService.postQuestion(questionDto, target);
|
||||||
this.sharedService.sendSocketNotificationToAllClients(
|
this.sharedService.notifyAllClients<QuestionDto>(ClientNotificationType.QuestionChanged, questionDto);
|
||||||
'question_changed',
|
|
||||||
questionDto,
|
|
||||||
);
|
|
||||||
return item.save();
|
return item.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
async validateAnswer(answer: string, id: number) {
|
async validateAnswerInline(answer:string, id: number) {
|
||||||
this.logger.verbose(`enter validate answer ${answer} ${id}`);
|
this.logger.verbose(`[validateAnswer] enter ${answer} ${id}`);
|
||||||
const question = await this.get();
|
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(
|
this.logger.verbose(
|
||||||
`Validating answer for question: ${JSON.stringify(question.text)}`,
|
`Validating answer for question: ${JSON.stringify(question.text)}`,
|
||||||
);
|
);
|
||||||
const filtered = answer.replace(regexp, '').trim();
|
// check that answer exist
|
||||||
if (question.valid === filtered) {
|
const shortAnswers = question.answers.map((answer) => answer.substring(0,50));
|
||||||
question.answered = true;
|
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;
|
question.answeredBy = id;
|
||||||
this.logger.verbose(`extra ${question.note}`);
|
this.logger.verbose(`extra ${question.note}`);
|
||||||
this.eventBus.publish(
|
this.eventBus.publish(
|
||||||
new ValidAnswerReceivedEvent(id, filtered, question.note),
|
new ValidAnswerReceivedEvent(id, answer, question.note),
|
||||||
);
|
);
|
||||||
await question.save();
|
await question.save();
|
||||||
await this.markQuestionStorageAsAnsweredCorrectly(question.text);
|
await this.markQuestionStorageAsAnsweredCorrectly(question.text);
|
||||||
|
|
@ -107,16 +117,118 @@ export class QuizService {
|
||||||
|
|
||||||
async proceedWithGame() {
|
async proceedWithGame() {
|
||||||
this.logger.verbose(`[proceedWithGame] Executing proceed with game`);
|
this.logger.verbose(`[proceedWithGame] Executing proceed with game`);
|
||||||
|
await this.calculateScore();
|
||||||
await this.commandBus.execute(new ProceedGameQueueCommand());
|
await this.commandBus.execute(new ProceedGameQueueCommand());
|
||||||
return Promise.resolve(true);
|
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() {
|
private async getNextQuestion() {
|
||||||
let question = await this.questionStorageModel
|
let question = await this.questionStorageModel
|
||||||
.findOne({ isAnswered: false })
|
.findOne({ isAnswered: false })
|
||||||
.exec();
|
.exec();
|
||||||
if (!question) {
|
if (!question) {
|
||||||
const unanswered = await this.getRemainQuestionWithouValidAnswer();
|
const unanswered = await this.getRemainQuestionWithoutValidAnswer();
|
||||||
const skipRand = getRandomInt(0, unanswered);
|
const skipRand = getRandomInt(0, unanswered);
|
||||||
question = await this.questionStorageModel
|
question = await this.questionStorageModel
|
||||||
.findOne({ isAnsweredCorrectly: false })
|
.findOne({ isAnsweredCorrectly: false })
|
||||||
|
|
@ -131,6 +243,8 @@ export class QuizService {
|
||||||
const question = await this.getNextQuestion();
|
const question = await this.getNextQuestion();
|
||||||
question.isAnswered = true;
|
question.isAnswered = true;
|
||||||
await this.setQuestion({
|
await this.setQuestion({
|
||||||
|
qId: question.id,
|
||||||
|
id: question.id,
|
||||||
text: question.text,
|
text: question.text,
|
||||||
answers: question.answers,
|
answers: question.answers,
|
||||||
valid: question.valid,
|
valid: question.valid,
|
||||||
|
|
@ -143,7 +257,7 @@ export class QuizService {
|
||||||
const question = await this.getNextQuestion();
|
const question = await this.getNextQuestion();
|
||||||
this.logger.verbose(`playExtraQuestion: ${question.text}`);
|
this.logger.verbose(`playExtraQuestion: ${question.text}`);
|
||||||
await this.setQuestion(
|
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,
|
telegramId,
|
||||||
);
|
);
|
||||||
question.isAnswered = true;
|
question.isAnswered = true;
|
||||||
|
|
@ -166,7 +280,7 @@ export class QuizService {
|
||||||
return questions.length;
|
return questions.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getRemainQuestionWithouValidAnswer(): Promise<number> {
|
async getRemainQuestionWithoutValidAnswer(): Promise<number> {
|
||||||
const questions = await this.questionStorageModel.find({
|
const questions = await this.questionStorageModel.find({
|
||||||
isAnsweredCorrectly: false,
|
isAnsweredCorrectly: false,
|
||||||
});
|
});
|
||||||
|
|
@ -209,4 +323,34 @@ export class QuizService {
|
||||||
await newQuestion.save();
|
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 { Test, TestingModule } from '@nestjs/testing';
|
||||||
import { SchedulerService } from './scheduler.service';
|
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', () => {
|
describe('SchedulerService', () => {
|
||||||
let service: SchedulerService;
|
let service: SchedulerService;
|
||||||
|
let giftService: GiftsService;
|
||||||
|
let sharedService: SharedService;
|
||||||
|
let stateService: StateService;
|
||||||
|
let featureFlagService: FeatureflagService;
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
|
jest.clearAllMocks();
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
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();
|
}).compile();
|
||||||
|
|
||||||
service = module.get<SchedulerService>(SchedulerService);
|
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', () => {
|
it('should be defined', () => {
|
||||||
expect(service).toBeDefined();
|
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 {Injectable, Logger} from '@nestjs/common';
|
||||||
import {Cron} from '@nestjs/schedule';
|
import {Cron} from '@nestjs/schedule';
|
||||||
import {StateService} from '../state/state.service';
|
import {StateService} from '../state/state.service';
|
||||||
import {CommandBus, QueryBus} from '@nestjs/cqrs';
|
import {QuizService} from '../quiz/quiz.service';
|
||||||
import { GiftsService } from 'src/gifts/gifts.service';
|
import {GiftsService} from '../gifts/gifts.service';
|
||||||
import { QuizService } from 'src/quiz/quiz.service';
|
import {SharedService} from '../shared/shared.service';
|
||||||
import { SharedService } from 'src/shared/shared.service';
|
import {FeatureflagService} from "../featureflag/featureflag.service";
|
||||||
import {GetGuestPropertyQuery} from "../guests/command/get-guest-property.handler";
|
import {FeatureFlagsConsts} from "../Consts/FeatureFlags.consts";
|
||||||
import {GuestPropertiesConsts} from "../schemas/properties.consts";
|
import {CommandBus} from "@nestjs/cqrs";
|
||||||
import {GetGuestQuery} from "../guests/queries/getguest.query";
|
import {GameStateConsts} from "../Consts/game-state.consts";
|
||||||
import {StringHelper} from "../helpers/stringhelper";
|
import {IStateInfo} from "../Consts/types";
|
||||||
|
import {ClientNotificationType} from "../socket/socket.gateway";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SchedulerService {
|
export class SchedulerService {
|
||||||
private readonly logger = new Logger(SchedulerService.name);
|
private readonly logger = new Logger(SchedulerService.name);
|
||||||
|
|
@ -17,11 +19,11 @@ export class SchedulerService {
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private stateService: StateService,
|
private stateService: StateService,
|
||||||
private cmdBus: CommandBus,
|
|
||||||
private queryBus: QueryBus,
|
|
||||||
private giftsService: GiftsService,
|
private giftsService: GiftsService,
|
||||||
private quizService: QuizService,
|
private quizService: QuizService,
|
||||||
private sharedService: SharedService,
|
private sharedService: SharedService,
|
||||||
|
private featureFlagService: FeatureflagService,
|
||||||
|
private commandBus: CommandBus,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@Cron('* * * * *')
|
@Cron('* * * * *')
|
||||||
|
|
@ -29,6 +31,21 @@ export class SchedulerService {
|
||||||
await this.updateState();
|
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() {
|
private async updateState() {
|
||||||
this.state = (await this.stateService.getState('main')).value;
|
this.state = (await this.stateService.getState('main')).value;
|
||||||
this.logger.verbose(`Game state is: ${this.state}`);
|
this.logger.verbose(`Game state is: ${this.state}`);
|
||||||
|
|
@ -37,12 +54,7 @@ export class SchedulerService {
|
||||||
async gameStatus() {
|
async gameStatus() {
|
||||||
const giftsLeft = await this.giftsService.getRemainingPrizeCount();
|
const giftsLeft = await this.giftsService.getRemainingPrizeCount();
|
||||||
if (giftsLeft === 0) {
|
if (giftsLeft === 0) {
|
||||||
const state = await this.stateService.setState('main', 'finish');
|
await this.finishGame();
|
||||||
this.sharedService.sendSocketNotificationToAllClients(
|
|
||||||
'state_changed',
|
|
||||||
state,
|
|
||||||
);
|
|
||||||
this.logger.warn(`Gifts is ended, finishing game`);
|
|
||||||
}
|
}
|
||||||
const questionsLeft = await this.quizService.getRemainQuestionCount();
|
const questionsLeft = await this.quizService.getRemainQuestionCount();
|
||||||
this.logger.verbose(
|
this.logger.verbose(
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
|
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
|
||||||
import { Document } from 'mongoose';
|
import { Document } from 'mongoose';
|
||||||
|
|
||||||
export type ConfigDocument = Config & Document;
|
|
||||||
|
|
||||||
@Schema()
|
@Schema()
|
||||||
export class Config {
|
export class Config {
|
||||||
@Prop()
|
@Prop()
|
||||||
|
|
@ -12,3 +10,4 @@ export class Config {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ConfigSchema = SchemaFactory.createForClass(Config);
|
export const ConfigSchema = SchemaFactory.createForClass(Config);
|
||||||
|
export type ConfigDocument = Config & Document;
|
||||||
|
|
@ -7,6 +7,9 @@ export enum GameQueueTypes {
|
||||||
penalty = 'penalty',
|
penalty = 'penalty',
|
||||||
playExtraCard = 'play_extra_card',
|
playExtraCard = 'play_extra_card',
|
||||||
screpaAnounce = 'screpa',
|
screpaAnounce = 'screpa',
|
||||||
|
showresults = 'show_results',
|
||||||
|
extra_points = 'extra_points',
|
||||||
|
versus = 'versus',
|
||||||
}
|
}
|
||||||
|
|
||||||
export type GameQueueDocument = GameQueue & Document;
|
export type GameQueueDocument = GameQueue & Document;
|
||||||
|
|
|
||||||
|
|
@ -25,8 +25,6 @@ export class Guest {
|
||||||
@Prop({ default: 10 })
|
@Prop({ default: 10 })
|
||||||
prizeChance: number;
|
prizeChance: number;
|
||||||
@Prop({ default: 0 })
|
@Prop({ default: 0 })
|
||||||
prizesCount: number;
|
|
||||||
@Prop({ default: 0 })
|
|
||||||
validAnswers: number;
|
validAnswers: number;
|
||||||
@Prop({ default: 0 })
|
@Prop({ default: 0 })
|
||||||
invalidAnswers: number;
|
invalidAnswers: number;
|
||||||
|
|
@ -34,6 +32,8 @@ export class Guest {
|
||||||
invalidAnswersInRow: number;
|
invalidAnswersInRow: number;
|
||||||
@Prop({ default:0 })
|
@Prop({ default:0 })
|
||||||
rewardsReceived: number;
|
rewardsReceived: number;
|
||||||
|
@Prop({ default: 0})
|
||||||
|
penaltiesReceived: number;
|
||||||
@Prop({ type: Map })
|
@Prop({ type: Map })
|
||||||
properties: Record<string, string>;
|
properties: Record<string, string>;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,16 @@
|
||||||
import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose";
|
import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose";
|
||||||
import { Document } from 'mongoose';
|
import { Document } from 'mongoose';
|
||||||
|
|
||||||
|
|
||||||
|
export class QuestionAnswer {
|
||||||
|
user: number;
|
||||||
|
time: Date;
|
||||||
|
valid: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export type QuestionDocument = Question & Document;
|
export type QuestionDocument = Question & Document;
|
||||||
|
|
||||||
|
|
||||||
@Schema()
|
@Schema()
|
||||||
export class Question {
|
export class Question {
|
||||||
@Prop()
|
@Prop()
|
||||||
|
|
@ -15,8 +23,15 @@ export class Question {
|
||||||
answered: boolean;
|
answered: boolean;
|
||||||
@Prop()
|
@Prop()
|
||||||
answeredBy: number;
|
answeredBy: number;
|
||||||
|
|
||||||
@Prop()
|
@Prop()
|
||||||
note: string | null;
|
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);
|
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 * as process from "process";
|
||||||
import {ConfigModule} from "@nestjs/config";
|
import {ConfigModule} from "@nestjs/config";
|
||||||
import {CqrsModule} from "@nestjs/cqrs";
|
import {CqrsModule} from "@nestjs/cqrs";
|
||||||
|
import {FeatureflagService} from "../featureflag/featureflag.service";
|
||||||
@Global()
|
@Global()
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
|
|
@ -17,7 +18,7 @@ import {CqrsModule} from "@nestjs/cqrs";
|
||||||
GameModule,
|
GameModule,
|
||||||
MongooseModule.forFeature([{ name: Config.name, schema: ConfigSchema }]),
|
MongooseModule.forFeature([{ name: Config.name, schema: ConfigSchema }]),
|
||||||
],
|
],
|
||||||
providers: [SharedService, {
|
providers: [SharedService,FeatureflagService, {
|
||||||
provide: 'Telegram',
|
provide: 'Telegram',
|
||||||
useFactory: () =>
|
useFactory: () =>
|
||||||
ClientProxyFactory.create({
|
ClientProxyFactory.create({
|
||||||
|
|
@ -31,7 +32,7 @@ import {CqrsModule} from "@nestjs/cqrs";
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
}],
|
}],
|
||||||
exports: [SharedService, 'Telegram'],
|
exports: [SharedService, 'Telegram',FeatureflagService],
|
||||||
})
|
})
|
||||||
export class SharedModule {
|
export class SharedModule {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,21 @@
|
||||||
import { Test, TestingModule } from '@nestjs/testing';
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
import { SharedService } from './shared.service';
|
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', () => {
|
describe('SharedService', () => {
|
||||||
let service: SharedService;
|
let service: SharedService;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
providers: [SharedService],
|
providers: [
|
||||||
|
SharedService,
|
||||||
|
{ provide: SocketGateway, useValue: SocketGatewayMock },
|
||||||
|
{ provide: getModelToken(Config.name), useValue: Model },
|
||||||
|
],
|
||||||
}).compile();
|
}).compile();
|
||||||
|
|
||||||
service = module.get<SharedService>(SharedService);
|
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 { Injectable, Logger } from '@nestjs/common';
|
||||||
import { InjectModel } from '@nestjs/mongoose';
|
import { InjectModel } from '@nestjs/mongoose';
|
||||||
import { Config, ConfigDocument } from '../schemas/config.schema';
|
import { Config, ConfigDocument } from '../schemas/config.schema';
|
||||||
|
|
@ -9,18 +9,24 @@ export class SharedService {
|
||||||
private logger = new Logger(SharedService.name);
|
private logger = new Logger(SharedService.name);
|
||||||
constructor(
|
constructor(
|
||||||
private socketGateway: SocketGateway,
|
private socketGateway: SocketGateway,
|
||||||
private eventBus: EventBus,
|
|
||||||
@InjectModel(Config.name)
|
@InjectModel(Config.name)
|
||||||
private configModel: Model<ConfigDocument>,
|
private configModel: Model<ConfigDocument>,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async getConfig(key: string) {
|
async getConfig(key: string) {
|
||||||
return this.configModel
|
const res = await this.configModel
|
||||||
.findOne({
|
.findOne({
|
||||||
key,
|
key,
|
||||||
})
|
})
|
||||||
.exec();
|
.exec();
|
||||||
|
if(!res) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
key: res.key,
|
||||||
|
value: res.value,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async setConfig(key: string, value: string) {
|
async setConfig(key: string, value: string) {
|
||||||
|
|
@ -35,16 +41,33 @@ export class SharedService {
|
||||||
value,
|
value,
|
||||||
});
|
});
|
||||||
await record.save();
|
await record.save();
|
||||||
return record;
|
return {
|
||||||
|
key: record.key,
|
||||||
|
value: record.value,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
cfgItem.value = value;
|
cfgItem.value = value;
|
||||||
await cfgItem.save();
|
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.logger.verbose(`Sending notification to client: ${event}, ${JSON.stringify(payload)}`);
|
||||||
this.socketGateway.notifyAllClients(event, 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