diff --git a/sample.env b/sample.env index c72e487bb..6303ee9d7 100644 --- a/sample.env +++ b/sample.env @@ -4,6 +4,9 @@ API_URL=http://localhost:3000 # An URL to where the client is hosted CLIENT_URL=http://localhost:4200 +# The bot name; it can be changed via the web client afterwards +BOT_NAME=tf2pickup.pl + # MongoDB MONGODB_HOST=localhost MONGODB_PORT=27017 diff --git a/src/environment/environment.spec.ts b/src/environment/environment.spec.ts index 8f832d2d4..e69b0e123 100644 --- a/src/environment/environment.spec.ts +++ b/src/environment/environment.spec.ts @@ -29,6 +29,7 @@ describe('Environment', () => { [ 'API_URL', 'CLIENT_URL', + 'BOT_NAME', 'MONGODB_HOST', 'MONGODB_PORT', 'MONGODB_DB', diff --git a/src/environment/environment.ts b/src/environment/environment.ts index 67d05d971..db319ffaa 100644 --- a/src/environment/environment.ts +++ b/src/environment/environment.ts @@ -16,6 +16,10 @@ export class Environment { return this.configService.get('CLIENT_URL'); } + get botName() { + return this.configService.get('BOT_NAME'); + } + get mongoDbHost() { return this.configService.get('MONGODB_HOST'); } diff --git a/src/players/models/player-role.ts b/src/players/models/player-role.ts index 981833f5f..e57ad788c 100644 --- a/src/players/models/player-role.ts +++ b/src/players/models/player-role.ts @@ -1 +1 @@ -export type PlayerRole = 'admin' | 'super-user'; +export type PlayerRole = 'admin' | 'super-user' | 'bot'; diff --git a/src/players/models/player.ts b/src/players/models/player.ts index eac6f5208..d00117b0f 100644 --- a/src/players/models/player.ts +++ b/src/players/models/player.ts @@ -3,15 +3,14 @@ import { PlayerRole } from './player-role'; import { TwitchTvUser } from './twitch-tv-user'; @index({ steamId: 'hashed' }) -@index({ etf2lProfileId: 1 }) export class Player { id: string; @prop({ required: true, unique: true, trim: true }) name!: string; - @prop({ required: true, unique: true }) - steamId!: string; + @prop({ unique: true }) + steamId?: string; @prop({ default: () => new Date() }) joinedAt?: Date; @@ -23,9 +22,9 @@ export class Player { role?: PlayerRole; @prop({ default: false }) - hasAcceptedRules!: boolean; + hasAcceptedRules?: boolean; - @prop() + @prop({ index: true }) etf2lProfileId?: number; @prop({ type: TwitchTvUser }) diff --git a/src/players/services/players.service.spec.ts b/src/players/services/players.service.spec.ts index 590f26b93..834766326 100644 --- a/src/players/services/players.service.spec.ts +++ b/src/players/services/players.service.spec.ts @@ -25,6 +25,7 @@ jest.mock('@/discord/services/discord.service'); class EnvironmentStub { superUser = null; + botName = 'FAKE_BOT_NAME'; } class Etf2lProfileServiceStub { @@ -120,12 +121,43 @@ describe('PlayersService', () => { expect(service).toBeDefined(); }); + describe('#onModuleInit()', () => { + describe('when the bot user is not yet created', () => { + it('should create the bot user', async () => { + await service.onModuleInit(); + expect(await playerModel.findOne({ name: 'FAKE_BOT_NAME' })).toBeTruthy(); + }); + }); + + describe('when the bot user is already created', () => { + beforeEach(async () => { + await service.onModuleInit(); + }); + + it('should not create any users', async () => { + await service.onModuleInit(); + expect((await playerModel.find({ name: 'FAKE_BOT_NAME' })).length).toEqual(1); + }); + }); + }); + describe('#getAll()', () => { it('should retrieve all players from the database', async () => { const ret = await service.getAll(); expect(ret.length).toEqual(1); expect(ret[0].toObject()).toEqual(mockPlayer.toObject()); }); + + describe('when the bot user is created', () => { + beforeEach(async () => { + await service.onModuleInit(); + }); + + it('should exclude the bot from the player list', async () => { + const ret = await service.getAll(); + expect(ret.length).toEqual(1); + }); + }); }); describe('#getById()', () => { @@ -169,6 +201,22 @@ describe('PlayersService', () => { }); }); + describe('#findBot()', () => { + beforeEach(async () => { + await playerModel.create({ + name: 'FAKE_BOT_NAME', + role: 'bot', + }) + }); + + it('should find the bot', async () => { + expect(await service.findBot()).toMatchObject({ + name: 'FAKE_BOT_NAME', + role: 'bot', + }); + }); + }); + describe('#createPlayer()', () => { const mockSteamProfile: SteamProfile = { provider: 'steam', diff --git a/src/players/services/players.service.ts b/src/players/services/players.service.ts index 3b7c8fe3b..63890dbaa 100644 --- a/src/players/services/players.service.ts +++ b/src/players/services/players.service.ts @@ -1,4 +1,4 @@ -import { Injectable, Logger, Inject, forwardRef } from '@nestjs/common'; +import { Injectable, Logger, Inject, forwardRef, OnModuleInit } from '@nestjs/common'; import { Environment } from '@/environment/environment'; import { Player } from '../models/player'; import { DocumentType, ReturnModelType } from '@typegoose/typegoose'; @@ -18,7 +18,7 @@ import { ObjectId } from 'mongodb'; import { minimumTf2InGameHours, requireEtf2lAccount } from '@configs/players'; @Injectable() -export class PlayersService { +export class PlayersService implements OnModuleInit { private logger = new Logger(PlayersService.name); private _playerRegistered = new Subject(); @@ -37,8 +37,18 @@ export class PlayersService { private discordService: DiscordService, ) { } + async onModuleInit() { + const bot = await this.findBot(); + if (bot === null) { + await this.playerModel.create({ + name: this.environment.botName, + role: 'bot', + }) + } + } + async getAll(): Promise[]> { - return await this.playerModel.find(); + return await this.playerModel.find({ role: { $ne: 'bot' } }); } async getById(id: string | ObjectId): Promise> { @@ -57,6 +67,10 @@ export class PlayersService { return await this.playerModel.findOne({ 'twitchTvUser.userId': twitchTvUserId }); } + async findBot() { + return this.playerModel.findOne({ name: this.environment.botName }); + } + async createPlayer(steamProfile: SteamProfile): Promise> { await this.verifyTf2InGameHours(steamProfile.id);