featureflag for DisableVoice & DontMarkQuestsion

This commit is contained in:
Kirill Ivlev 2024-11-11 18:16:33 +04:00
parent 9e4f5e13c0
commit 59f32b94d9
9 changed files with 55 additions and 18 deletions

View file

@ -1,3 +1,5 @@
export class FeatureFlagsConsts { export class FeatureFlagsConsts {
static EnableEndgamePoints = 'EnableEndgamePoints'; static EnableEndgamePoints = 'EnableEndgamePoints';
static DontMarkQuestionsAsCompleted = 'DontMarkQuestionsAsCompleted';
static DisableVoice = 'DisableVoice';
} }

View file

@ -45,6 +45,6 @@ import { FeatureflagService } from './featureflag/featureflag.service';
], ],
controllers: [AppController, FeatureflagController], controllers: [AppController, FeatureflagController],
providers: [AppService, SocketGateway, SchedulerService, FeatureflagService], providers: [AppService, SocketGateway, SchedulerService, FeatureflagService],
exports: [AppService, SocketGateway], exports: [AppService, SocketGateway, FeatureflagService],
}) })
export class AppModule {} export class AppModule {}

View file

@ -1,5 +1,6 @@
import {Injectable, Logger} from '@nestjs/common'; import {Injectable, Logger} from '@nestjs/common';
import {SharedService} from "../shared/shared.service"; import {SharedService} from "../shared/shared.service";
import {SocketEvents} from "../shared/events.consts";
export interface IFeatureFlagStatus { export interface IFeatureFlagStatus {
name: string; name: string;
@ -13,17 +14,25 @@ export class FeatureflagService {
} }
async getFeatureFlag(id: string): Promise<IFeatureFlagStatus> { async getFeatureFlag(id: string): Promise<IFeatureFlagStatus> {
this.logger.verbose(`Getting feature flag status for ${id}`); this.logger.verbose(`[getFeatureFlag] Getting feature flag status for ${id}`);
const state = await this.sharedService.getConfig(`featureflag/${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 { return {
name: id, name: id,
state: state.value !== 'false', state: ffState
} }
} }
async setFeatureFlag(id: string, status: boolean) : Promise<IFeatureFlagStatus> { async setFeatureFlag(id: string, status: boolean) : Promise<IFeatureFlagStatus> {
this.logger.verbose(`Setting feature flag status for ${id} to ${status} `); this.logger.verbose(`Setting feature flag status for ${id} to ${status} `);
const result = await this.sharedService.setConfig(`featureflag/${id}`, status.toString()); const result = await this.sharedService.setConfig(`featureflag/${id}`, status.toString());
this.sharedService.sendSocketNotificationToAllClients(SocketEvents.FEATURE_FLAG_CHANGED, {});
return { return {
name: id, name: id,
state: result.value !== 'false', state: result.value !== 'false',

View file

@ -16,6 +16,8 @@ import {CreateNewQueueItemCommand} from "../game/commands/create-new-queue-item.
import {GameQueueTypes} from "../schemas/game-queue.schema"; import {GameQueueTypes} from "../schemas/game-queue.schema";
import {IncreasePlayerWinningRateCommand} from "../game/commands/increase-player-winning-rate.command"; import {IncreasePlayerWinningRateCommand} from "../game/commands/increase-player-winning-rate.command";
import {IncreasePlayerScoreCommand} from "../guests/command/increase-player-score.command"; import {IncreasePlayerScoreCommand} from "../guests/command/increase-player-score.command";
import {FeatureflagService} from "../featureflag/featureflag.service";
import {FeatureFlagsConsts} from "../Consts/FeatureFlags.consts";
@Injectable({ scope: Scope.TRANSIENT }) @Injectable({ scope: Scope.TRANSIENT })
export class QuizService { export class QuizService {
@ -30,6 +32,7 @@ export class QuizService {
private sharedService: SharedService, private sharedService: SharedService,
private eventBus: EventBus, private eventBus: EventBus,
private commandBus: CommandBus, private commandBus: CommandBus,
private featureFlagService: FeatureflagService,
) { ) {
} }
@ -54,7 +57,6 @@ export class QuizService {
async validateAnswer(answer: string, id: number) { async validateAnswer(answer: string, id: number) {
this.logger.verbose(`enter validate answer ${answer} ${id}`); this.logger.verbose(`enter validate answer ${answer} ${id}`);
const question = await this.get(); const question = await this.get();
//question.answered = true;
await question.save(); await question.save();
const regexp = new RegExp( const regexp = new RegExp(
Object.keys(this.answerNumbers) Object.keys(this.answerNumbers)
@ -82,7 +84,6 @@ export class QuizService {
await question.save(); await question.save();
this.logger.verbose("question saved with user details") this.logger.verbose("question saved with user details")
if (question.valid === filtered) { if (question.valid === filtered) {
//question.answered = true;
question.answeredBy = id; question.answeredBy = id;
this.logger.verbose(`extra ${question.note}`); this.logger.verbose(`extra ${question.note}`);
this.eventBus.publish( this.eventBus.publish(
@ -126,6 +127,11 @@ export class QuizService {
private async calculateScore() { private async calculateScore() {
const question = await this.get(); const question = await this.get();
if(!await this.featureFlagService.getFeatureFlag(FeatureFlagsConsts.DontMarkQuestionsAsCompleted)) {
this.logger.verbose(`[proceedWithGame]: DontMarkQuestionsAsCompleted disabled, marking as complete`);
question.answered = true;
await question.save();
}
this.logger.verbose(`[calculateScore] enter `); this.logger.verbose(`[calculateScore] enter `);
const playerAnswers = question.userAnswers.map((answer) => { const playerAnswers = question.userAnswers.map((answer) => {
return { return {

View file

@ -12,4 +12,5 @@ export enum SocketEvents {
GAME_PAUSED = 'game_paused', GAME_PAUSED = 'game_paused',
GAME_RESUMED = 'game_resumed', GAME_RESUMED = 'game_resumed',
NOTIFICATION = 'notification', NOTIFICATION = 'notification',
FEATURE_FLAG_CHANGED = 'feature_flag_changed',
} }

View file

@ -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() {

View file

@ -20,6 +20,9 @@ export class SharedService {
key, key,
}) })
.exec(); .exec();
if(!res) {
return null;
}
return { return {
key: res.key, key: res.key,
value: res.value, value: res.value,

View file

@ -1,24 +1,39 @@
import { Controller, Get, Header, NotFoundException, Query, StreamableFile } from '@nestjs/common'; import {Controller, Get, Header, Logger, NotFoundException, Query, StreamableFile} from '@nestjs/common';
import { VoiceService } from './voice.service'; import { VoiceService } from './voice.service';
import { TtsRequestDto, TtsRequestWithVars } from './models/TtsRequestDto'; import { TtsRequestDto, TtsRequestWithVars } from './models/TtsRequestDto';
import { invalidPrefixDict } from './dicts/invalid-prefix.dict'; import { invalidPrefixDict } from './dicts/invalid-prefix.dict';
import { validPrefixDict } from './dicts/valid-prefix.dict'; import { validPrefixDict } from './dicts/valid-prefix.dict';
import * as translit from 'latin-to-cyrillic'; import * as translit from 'latin-to-cyrillic';
import {ConfigService} from "@nestjs/config"; import {ConfigService} from "@nestjs/config";
import {FeatureflagService} from "../featureflag/featureflag.service";
import {FeatureFlagsConsts} from "../Consts/FeatureFlags.consts";
@Controller('voice') @Controller('voice')
export class VoiceController { export class VoiceController {
constructor(private voiceService: VoiceService, private configService: ConfigService) {} private voiceEnabled = false;
private logger = new Logger(VoiceController.name)
constructor(private voiceService: VoiceService, private configService: ConfigService,private featureFlagService: FeatureflagService) {
setInterval(() => {
this.featureFlagService.getFeatureFlag(FeatureFlagsConsts.DisableVoice).then(r => this.voiceEnabled = !r.state);
}, 30000);
this.featureFlagService.getFeatureFlag(FeatureFlagsConsts.DisableVoice).then(r => {
this.voiceEnabled = !r.state;
console.log(`Voice enabled: ${this.voiceEnabled}`);
});
}
@Get('ssml') @Get('ssml')
@Header('content-type', 'audio/opus') @Header('content-type', 'audio/opus')
@Header('content-disposition', 'inline') @Header('content-disposition', 'inline')
async textToSpeechSSML(@Query() dto: TtsRequestDto) { async textToSpeechSSML(@Query() dto: TtsRequestDto) {
if (Boolean(this.configService.get<boolean>('ENABLE_VOICE')) === true) { this.logger.verbose(`[textToSpeechSSML] enter, FF state is: ${this.voiceEnabled}`);
//return new StreamableFile(await this.voiceService.textToFile(dto, true)); if (this.voiceEnabled) {
return new StreamableFile(await this.voiceService.textToFile(dto, true));
} else { } else {
return new NotFoundException('Voice disabled'); return new NotFoundException('Voice disabled');
} }
} }
@Get('tts') @Get('tts')
@ -26,8 +41,8 @@ export class VoiceController {
@Header('content-disposition', 'inline') @Header('content-disposition', 'inline')
async getText(@Query() dto: TtsRequestDto) { async getText(@Query() dto: TtsRequestDto) {
dto.text = translit(dto.text); dto.text = translit(dto.text);
if (Boolean(this.configService.get<boolean>('ENABLE_VOICE')) === true) { if (this.voiceEnabled) {
//return new StreamableFile(await this.voiceService.textToFile(dto)); return new StreamableFile(await this.voiceService.textToFile(dto));
} else { } else {
return new NotFoundException('Voice disabled'); return new NotFoundException('Voice disabled');
} }
@ -36,8 +51,7 @@ export class VoiceController {
@Header('content-type', 'audio/opus') @Header('content-type', 'audio/opus')
@Header('content-disposition', 'inline') @Header('content-disposition', 'inline')
async announceValid(@Query() dto: TtsRequestWithVars) { async announceValid(@Query() dto: TtsRequestWithVars) {
console.log(this.configService.get<boolean>('ENABLE_VOICE')); if (this.voiceEnabled) {
if (Boolean(this.configService.get<boolean>('ENABLE_VOICE')) === true) {
const vars = JSON.parse(dto.vars); const vars = JSON.parse(dto.vars);
dto.text = this.voiceService.buildTemplate(dto, vars, validPrefixDict); dto.text = this.voiceService.buildTemplate(dto, vars, validPrefixDict);
return new StreamableFile(await this.voiceService.textToFile(dto)); return new StreamableFile(await this.voiceService.textToFile(dto));
@ -50,7 +64,7 @@ export class VoiceController {
@Header('content-type', 'audio/opus') @Header('content-type', 'audio/opus')
@Header('content-disposition', 'inline') @Header('content-disposition', 'inline')
async announceInvalid(@Query() dto: TtsRequestWithVars) { async announceInvalid(@Query() dto: TtsRequestWithVars) {
if (Boolean(this.configService.get<boolean>('ENABLE_VOICE')) === true) { if (this.voiceEnabled) {
const vars = JSON.parse(dto.vars); const vars = JSON.parse(dto.vars);
dto.text = this.voiceService.buildTemplate(dto, vars, invalidPrefixDict); dto.text = this.voiceService.buildTemplate(dto, vars, invalidPrefixDict);
return new StreamableFile(await this.voiceService.textToFile(dto)); return new StreamableFile(await this.voiceService.textToFile(dto));

View file

@ -7,12 +7,13 @@ import { AxiosRequestConfig } from 'axios';
import * as translit from 'latin-to-cyrillic'; import * as translit from 'latin-to-cyrillic';
import { TGD_Config } from 'app.config'; import { TGD_Config } from 'app.config';
import {ConfigService} from "@nestjs/config"; import {ConfigService} from "@nestjs/config";
import {FeatureflagService} from "../featureflag/featureflag.service";
@Injectable() @Injectable()
export class VoiceService { export class VoiceService {
private apiUrl = 'https://tts.api.cloud.yandex.net/speech/v1/tts:synthesize'; private apiUrl = 'https://tts.api.cloud.yandex.net/speech/v1/tts:synthesize';
private apiKey: string; private apiKey: string;
private readonly logger = new Logger(VoiceService.name); private readonly logger = new Logger(VoiceService.name);
constructor(private httpService: HttpService, private configService: ConfigService) { constructor(private httpService: HttpService, private configService: ConfigService, private featureFlagService: FeatureflagService) {
this.apiKey = this.configService.get<string>("VOICE_APIKEY"); this.apiKey = this.configService.get<string>("VOICE_APIKEY");
} }