diff --git a/src/__tests__/shop/buy/ClanCoinsService/addCoins.test.ts b/src/__tests__/shop/buy/ClanCoinsService/addCoins.test.ts new file mode 100644 index 000000000..821f080c3 --- /dev/null +++ b/src/__tests__/shop/buy/ClanCoinsService/addCoins.test.ts @@ -0,0 +1,34 @@ +import ClanCoinsModule from '../../modules/clanCoins.module'; +import { ClanCoinsService } from '../../../../shop/buy/clanCoins.service'; +import { Coins } from '../../../../shop/enum/coins.enum.dto'; +import ClanCoinsBuilderFactory from '../../data/clanCoinsBuilderFactory'; +import { ClanCoinsDto } from '../../../../shop/buy/dto/clanCoins.dto'; + +describe('clanCoinsService.addCoins() test suite', () => { + + let clanCoinsService: ClanCoinsService; + let clanCoins: ClanCoinsDto; + + const clan_id = '681e534624e7710f1b5ccb80'; + const coins = Coins.FiveHundred; + + beforeEach(async () => { + clanCoinsService = await ClanCoinsModule.getClainCoinsService(); + + const clanCoinsBuilder = ClanCoinsBuilderFactory.getBuilder('ClanCoinsDto'); + + clanCoins = clanCoinsBuilder + .setAmount(coins) + .build(); + }); + + it('Should return with error if the clan does not exist', async () => { + + const [_, error] = await clanCoinsService.addCoins(clan_id, clanCoins.amount); + + expect(error).toBeDefined(); + expect(error[0]?.reason).toBe('NOT_FOUND'); + expect(error[0]?.field).toBe('_id'); + expect(error[0]?.value).toBe(clan_id); + }); +}); diff --git a/src/__tests__/shop/data/clanCoins/clanCoinsDtoBuilder.ts b/src/__tests__/shop/data/clanCoins/clanCoinsDtoBuilder.ts new file mode 100644 index 000000000..65de654a7 --- /dev/null +++ b/src/__tests__/shop/data/clanCoins/clanCoinsDtoBuilder.ts @@ -0,0 +1,19 @@ +import { ObjectId } from 'mongodb'; +import IDataBuilder from '../../../test_utils/interface/IDataBuilder'; +import { ClanCoinsDto } from '../../../../shop/buy/dto/clanCoins.dto'; +import { Coins } from '../../../../shop/enum/coins.enum.dto'; + +export default class ClanCoinsDtoBuilder implements IDataBuilder { + private readonly base: ClanCoinsDto = { + clan_id: 'clan-id', + amount: Coins.FiveHundred, // or another valid Coins enum value + }; + + build(): ClanCoinsDto { + return { ...this.base }; + } + setId(id: string | ObjectId) { + this.base.clan_id = id as any; + return this; + } +} diff --git a/src/__tests__/shop/data/clanCoins/createClanCoinsDtoBuilder.ts b/src/__tests__/shop/data/clanCoins/createClanCoinsDtoBuilder.ts new file mode 100644 index 000000000..b9aeca8e7 --- /dev/null +++ b/src/__tests__/shop/data/clanCoins/createClanCoinsDtoBuilder.ts @@ -0,0 +1,20 @@ +import IDataBuilder from '../../../test_utils/interface/IDataBuilder'; +import { ClanCoinsDto } from '../../../../shop/buy/dto/clanCoins.dto'; +import { Coins } from '../../../../shop/enum/coins.enum.dto'; + +export default class ClanCoinsDtoBuilder + implements IDataBuilder +{ + private readonly base: ClanCoinsDto = { + amount: Coins.OneHundred + }; + + build(): ClanCoinsDto { + return { ...this.base }; + } + + setAmount(amount: Coins) { + this.base.amount = amount; + return this; + } +} diff --git a/src/__tests__/shop/data/clanCoinsBuilderFactory.ts b/src/__tests__/shop/data/clanCoinsBuilderFactory.ts new file mode 100644 index 000000000..b53545617 --- /dev/null +++ b/src/__tests__/shop/data/clanCoinsBuilderFactory.ts @@ -0,0 +1,19 @@ +import ClanCoinsDtoBuilder from "./clanCoins/createClanCoinsDtoBuilder"; + +type BuilderName = + | 'ClanCoinsDto'; + +type BuilderMap = { + ClanCoinsDto: ClanCoinsDtoBuilder; +}; + +export default class PlayerBuilderFactory { + static getBuilder(builderName: T): BuilderMap[T] { + switch (builderName) { + case 'ClanCoinsDto': + return new ClanCoinsDtoBuilder() as BuilderMap[T]; + default: + throw new Error(`Unknown builder name: ${builderName}`); + } + } +} diff --git a/src/__tests__/shop/modules/clanCoins.module.ts b/src/__tests__/shop/modules/clanCoins.module.ts new file mode 100644 index 000000000..013935e6c --- /dev/null +++ b/src/__tests__/shop/modules/clanCoins.module.ts @@ -0,0 +1,10 @@ +import ClanCoinsCommonModule from './clanCoinsCommon'; +import { ClanCoinsService } from '../../../shop/buy/clanCoins.service'; + +export default class ClanCoinsModule { + private constructor() {} + static async getClainCoinsService() { + const module = await ClanCoinsCommonModule.getModule(); + return module.resolve(ClanCoinsService); + } +} diff --git a/src/__tests__/shop/modules/clanCoinsCommon.ts b/src/__tests__/shop/modules/clanCoinsCommon.ts new file mode 100644 index 000000000..63cd607f8 --- /dev/null +++ b/src/__tests__/shop/modules/clanCoinsCommon.ts @@ -0,0 +1,33 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { MongooseModule } from '@nestjs/mongoose'; +import { ModelName } from '../../../common/enum/modelName.enum'; +import { mongooseOptions, mongoString } from '../../test_utils/const/db'; +import { ClanSchema } from '../../../clan/clan.schema'; +import { ClanModule } from '../../../clan/clan.module'; +import { ClanCoinsService } from '../../..//shop/buy/clanCoins.service'; +import { ShopModule } from '../../../shop/shop.module'; + +export default class ClanCoinsCommonModule { + private constructor() {} + + private static module: TestingModule; + + static async getModule() { + if (!ClanCoinsCommonModule.module) + ClanCoinsCommonModule.module = await Test.createTestingModule({ + imports: [ + MongooseModule.forRoot(mongoString, mongooseOptions), + MongooseModule.forFeature([ + { name: ModelName.CLAN, schema: ClanSchema }, + ]), + ClanModule, + ShopModule + ], + providers: [ + ClanCoinsService + ], + }).compile(); + + return ClanCoinsCommonModule.module; + } +} diff --git a/src/app.module.ts b/src/app.module.ts index ea7bf1ce1..de111649d 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -31,6 +31,7 @@ import isTestingSession from './box/util/isTestingSession'; import { ScheduleModule } from '@nestjs/schedule'; import { OnlinePlayersModule } from './onlinePlayers/onlinePlayers.module'; import { ClanShopModule } from './clanShop/clanShop.module'; +import { ShopModule } from './shop/shop.module'; // Set up database connection const mongoUser = envVars.MONGO_USERNAME; @@ -86,6 +87,7 @@ const authGuardClassToUse = isTestingSession() ? BoxAuthGuard : AuthGuard; BoxModule, OnlinePlayersModule, ClanShopModule, + ShopModule, ], controllers: [AppController], providers: [ diff --git a/src/shop/buy/clanCoins.controller.ts b/src/shop/buy/clanCoins.controller.ts new file mode 100644 index 000000000..46088a43f --- /dev/null +++ b/src/shop/buy/clanCoins.controller.ts @@ -0,0 +1,26 @@ +import { + Body, + Controller, + HttpCode, + Post, + } from '@nestjs/common'; + +import { LoggedUser } from '../../common/decorator/param/LoggedUser.decorator'; +import { ClanCoinsDto } from './dto/clanCoins.dto'; +import { User } from '../../auth/user'; +import DetermineClanId from '../../common/guard/clanId.guard'; +import { ClanCoinsService } from './clanCoins.service'; + +@Controller('clanCoins') +export class ClanCoinsController { + public constructor(private readonly service: ClanCoinsService,) {} + + @Post() + @HttpCode(204) + @DetermineClanId() + public async addCoins(@Body() body: ClanCoinsDto, @LoggedUser() user: User) { + + const [, errors] = await this.service.addCoins(user.clan_id, body.amount); + if (errors) return [null, errors]; + } + } \ No newline at end of file diff --git a/src/shop/buy/clanCoins.service.ts b/src/shop/buy/clanCoins.service.ts new file mode 100644 index 000000000..75e9658db --- /dev/null +++ b/src/shop/buy/clanCoins.service.ts @@ -0,0 +1,15 @@ +import { Injectable } from '@nestjs/common'; +import { ClanService } from '../../clan/clan.service'; + +@Injectable() +export class ClanCoinsService { + public constructor( + private readonly clanService: ClanService, + ) {} + + async addCoins(clan_id: string, amount: number) { + return await this.clanService.basicService.updateOneById(clan_id, { + $inc: { gameCoins: amount }, + }); + } +} diff --git a/src/shop/buy/dto/clanCoins.dto.ts b/src/shop/buy/dto/clanCoins.dto.ts new file mode 100644 index 000000000..420c14201 --- /dev/null +++ b/src/shop/buy/dto/clanCoins.dto.ts @@ -0,0 +1,12 @@ +import { + IsEnum, +} from 'class-validator'; +import AddType from '../../../common/base/decorator/AddType.decorator'; +import { Coins } from '../../../shop/enum/coins.enum.dto'; + +@AddType('ClanCoinsDto') +export class ClanCoinsDto { + + @IsEnum(Coins) + amount: Coins; +} diff --git a/src/shop/enum/coins.enum.dto.ts b/src/shop/enum/coins.enum.dto.ts new file mode 100644 index 000000000..4ead019c8 --- /dev/null +++ b/src/shop/enum/coins.enum.dto.ts @@ -0,0 +1,8 @@ +export enum Coins { + OneHundred = 100, + TwoHundredFifty = 250, + FiveHundred = 500, + OneThousand = 1000, + TwoThousands = 2000, + FiveThousands = 5000, +} \ No newline at end of file diff --git a/src/shop/shop.module.ts b/src/shop/shop.module.ts new file mode 100644 index 000000000..b2eff126e --- /dev/null +++ b/src/shop/shop.module.ts @@ -0,0 +1,36 @@ +import { Module } from '@nestjs/common'; +import { MongooseModule } from '@nestjs/mongoose'; +import { ClanCoinsController } from './buy/clanCoins.controller'; +import { ModelName } from '../common/enum/modelName.enum'; +import { ClanSchema } from '../clan/clan.schema'; +import { joinSchema } from '../clan/join/join.schema'; +import { PlayerSchema } from '../player/schemas/player.schema'; +import { ClanInventoryModule } from '../clanInventory/clanInventory.module'; +import { RequestHelperModule } from '../requestHelper/requestHelper.module'; +import { PlayerModule } from '../player/player.module'; +import { GameEventsEmitterModule } from '../gameEventsEmitter/gameEventsEmitter.module'; +import ClanHelperService from '../clan/utils/clanHelper.service'; +import { ClanCoinsService } from './buy/clanCoins.service'; +import { ClanService } from '../clan/clan.service'; + +@Module({ + imports: [ + MongooseModule.forFeature([ + { name: ModelName.CLAN, schema: ClanSchema }, + { name: ModelName.JOIN, schema: joinSchema }, + { name: ModelName.PLAYER, schema: PlayerSchema }, + ]), + ClanInventoryModule, + RequestHelperModule, + PlayerModule, + GameEventsEmitterModule, + ], + controllers: [ClanCoinsController], + providers: [ + ClanService, + ClanCoinsService, + ClanHelperService, + ], + exports: [], +}) +export class ShopModule {}