Skip to content

Commit

Permalink
feat(discord): add admin responsible field for discord admin notifica…
Browse files Browse the repository at this point in the history
…tions (#427)

* add player ban revoked admin responsible

* add player name change admin responsible

* admin responsible for player skill change
  • Loading branch information
garrappachc authored Jun 12, 2020
1 parent acc4d1c commit dc7634f
Show file tree
Hide file tree
Showing 11 changed files with 103 additions and 30 deletions.
3 changes: 2 additions & 1 deletion src/discord/notifications/player-ban-revoked.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@ interface PlayerBanRevokedFields {
player: string;
reason: string;
playerProfileUrl: string;
adminResponsible: string;
}

export function playerBanRevoked(fields: PlayerBanRevokedFields): MessageEmbedOptions {
return {
color: Colors.PlayerBanRevoked,
title: 'Ban revoked',
title: `Ban revoked by ${fields.adminResponsible}`,
fields: [
{
name: 'Player',
Expand Down
3 changes: 2 additions & 1 deletion src/discord/notifications/player-name-changed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@ interface PlayerNameChangedFields {
oldName: string;
newName: string;
profileUrl: string;
adminResponsible: string;
}

export function playerNameChanged(fields: PlayerNameChangedFields): MessageEmbedOptions {
return {
color: Colors.PlayerNameChanged,
title: 'Player name changed',
title: `Player name changed by ${fields.adminResponsible}`,
fields: [
{
name: 'Old name',
Expand Down
5 changes: 3 additions & 2 deletions src/discord/notifications/skill-changed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,16 @@ interface SkillChangedFields {
oldSkill: Map<string, number>;
newSkill: Map<string, number>;
playerProfileUrl: string;
adminResponsible: string;
}

export function skillChanged(fields: SkillChangedFields): MessageEmbedOptions {
const embed: MessageEmbedOptions = {
color: Colors.SkillChanged,
title: 'Player\s skill has been updated',
title: `Player\'s skill has been updated by ${fields.adminResponsible}`,
fields: [
{
name: 'Player name',
name: 'Player',
value: `[${fields.playerName}](${fields.playerProfileUrl})`,
},
],
Expand Down
13 changes: 7 additions & 6 deletions src/players/controllers/players.controller.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,8 +149,8 @@ describe('Players Controller', () => {
describe('#updatePlayer()', () => {
it('should update the player', async () => {
const spy = jest.spyOn(playersService, 'updatePlayer');
const ret = await controller.updatePlayer('FAKE_ID', { name: 'FAKE_NEW_NAME' });
expect(spy).toHaveBeenCalledWith('FAKE_ID', { name: 'FAKE_NEW_NAME' });
const ret = await controller.updatePlayer('FAKE_ID', { name: 'FAKE_NEW_NAME' }, { id: 'FAKE_ADMIN_ID' } as any);
expect(spy).toHaveBeenCalledWith('FAKE_ID', { name: 'FAKE_NEW_NAME' }, 'FAKE_ADMIN_ID');
expect(ret).toEqual(playersService.player as any);
});
});
Expand Down Expand Up @@ -210,8 +210,8 @@ describe('Players Controller', () => {
it('should set player skill', async () => {
const skill = { soldier: 1, medic: 2 };
const spy = jest.spyOn(playerSkillService, 'setPlayerSkill');
const ret = await controller.setPlayerSkill('FAKE_ID', skill);
expect(spy).toHaveBeenCalledWith('FAKE_ID', new Map([['soldier', 1], ['medic', 2]]));
const ret = await controller.setPlayerSkill('FAKE_ID', skill, { id: 'FAKE_ADMIN_ID' } as any);
expect(spy).toHaveBeenCalledWith('FAKE_ID', new Map([['soldier', 1], ['medic', 2]]), 'FAKE_ADMIN_ID');
expect(ret).toEqual(playerSkillService.skill.skill);
});
});
Expand All @@ -237,13 +237,14 @@ describe('Players Controller', () => {

it('should add player ban', async () => {
const spy = jest.spyOn(playerBansService, 'addPlayerBan');
const ret = await controller.addPlayerBan('FAKE_ID', ban as any, { id: '5d448875b963ff7e00c6b6b3' });
const ret = await controller.addPlayerBan('FAKE_ID', ban as any, { id: '5d448875b963ff7e00c6b6b3' } as any);
expect(spy).toHaveBeenCalledWith(ban);
expect(ret).toEqual(ban as any);
});

it('should fail if the authorized user id is not the same as admin\'s', async () => {
await expect(controller.addPlayerBan('FAKE_ID', ban as any, { id: 'SOME_ID' })).rejects.toThrow(BadRequestException);
await expect(controller.addPlayerBan('FAKE_ID', ban as any, { id: 'SOME_ID' } as any))
.rejects.toThrow(BadRequestException);
});
});
});
18 changes: 11 additions & 7 deletions src/players/controllers/players.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ export class PlayersController {

@Patch(':id')
@Auth('admin', 'super-user')
async updatePlayer(@Param('id', ObjectIdValidationPipe) playerId: string, @Body() player: Partial<Player>) {
return await this.playersService.updatePlayer(playerId, player);
async updatePlayer(@Param('id', ObjectIdValidationPipe) playerId: string, @Body() player: Partial<Player>, @User() user: Player) {
return await this.playersService.updatePlayer(playerId, player, user.id);
}

@Get(':id/games')
Expand Down Expand Up @@ -93,8 +93,12 @@ export class PlayersController {
@Put(':id/skill')
@Auth('admin', 'super-user')
// todo validate skill
async setPlayerSkill(@Param('id', ObjectIdValidationPipe) playerId: string, @Body() newSkill: { [className: string]: number }) {
return (await this.playerSkillService.setPlayerSkill(playerId, new Map(Object.entries(newSkill)))).skill;
async setPlayerSkill(
@Param('id', ObjectIdValidationPipe) playerId: string,
@Body() newSkill: Record<string, number>,
@User() user: Player,
) {
return (await this.playerSkillService.setPlayerSkill(playerId, new Map(Object.entries(newSkill)), user.id))?.skill;
}

@Get(':id/bans')
Expand All @@ -106,7 +110,7 @@ export class PlayersController {
@Post(':id/bans')
@Auth('admin', 'super-user')
@UsePipes(ValidationPipe)
async addPlayerBan(@Param('id', ObjectIdValidationPipe) playerId: string, @Body() playerBan: PlayerBan, @User() user: any) {
async addPlayerBan(@Param('id', ObjectIdValidationPipe) playerId: string, @Body() playerBan: PlayerBan, @User() user: Player) {
if (playerBan.admin.toString() !== user.id) {
throw new BadRequestException('the admin field must be the same as authorized user\'s id');
}
Expand All @@ -117,7 +121,7 @@ export class PlayersController {
@Auth('admin', 'super-user')
@HttpCode(200)
async updatePlayerBan(@Param('playerId', ObjectIdValidationPipe) playerId: string, @Param('banId', ObjectIdValidationPipe) banId: string,
@Query('revoke') revoke: any) {
@Query('revoke') revoke: any, @User() user: Player) {
const player = await this.playersService.getById(playerId);
if (!player) {
throw new NotFoundException('player not found');
Expand All @@ -133,7 +137,7 @@ export class PlayersController {
}

if (revoke !== undefined) {
return this.playerBansService.revokeBan(banId);
return this.playerBansService.revokeBan(banId, user.id);
}
}

Expand Down
23 changes: 20 additions & 3 deletions src/players/services/player-bans.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ describe('PlayerBansService', () => {

describe('#revokeBan()', () => {
it('should revoke the ban', async () => {
const ban = await service.revokeBan(mockPlayerBan.id);
const ban = await service.revokeBan(mockPlayerBan.id, admin.id);
expect(ban.end.getTime()).toBeLessThanOrEqual(new Date().getTime());
});

Expand All @@ -193,13 +193,30 @@ describe('PlayerBansService', () => {
done();
});

await service.revokeBan(mockPlayerBan.id);
await service.revokeBan(mockPlayerBan.id, admin.id);
});

it('should send discord notification', async () => {
const spy = jest.spyOn(discordService.getAdminsChannel(), 'send');
const ban = await service.revokeBan(mockPlayerBan.id);
const ban = await service.revokeBan(mockPlayerBan.id, admin.id);
expect(spy).toHaveBeenCalled();
});

describe('when attempting to revoke an already expired ban', () => {
beforeEach(async () => {
mockPlayerBan.end = new Date();
await mockPlayerBan.save();
});

it('should reject', async () => {
await expect(service.revokeBan(mockPlayerBan.id, admin.id)).rejects.toThrowError();
});
});

describe('when the provided admin does not exist', () => {
it('should reject', async () => {
await expect(service.revokeBan(mockPlayerBan.id, new ObjectId().toString())).rejects.toThrowError();
});
});
});
});
8 changes: 7 additions & 1 deletion src/players/services/player-bans.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,12 @@ export class PlayerBansService implements OnModuleInit {
return addedBan;
}

async revokeBan(banId: string): Promise<DocumentType<PlayerBan>> {
async revokeBan(banId: string, adminId: string): Promise<DocumentType<PlayerBan>> {
const admin = await this.playersService.getById(adminId);
if (!admin) {
throw new Error('this admin does not exist');
}

const ban = await this.playerBanModel.findById(banId);

if (ban.end < new Date()) {
Expand All @@ -109,6 +114,7 @@ export class PlayerBansService implements OnModuleInit {
player: player.name,
reason: ban.reason,
playerProfileUrl: `${this.environment.clientUrl}/player/${player.id}`,
adminResponsible: admin.name,
}));

return ban;
Expand Down
9 changes: 9 additions & 0 deletions src/players/services/player-skill.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,15 @@ describe('PlayerSkillService', () => {
await expect(service.setPlayerSkill(new ObjectId().toString(), new Map([['scout', 1]]))).rejects.toThrowError('no such player');
});
});

describe('when the admin id is provided', () => {
describe('and the provided admin does not exist', () => {
it('should reject', async () => {
await expect(service.setPlayerSkill(mockPlayer.id, new Map([['soldier', 4]]), new ObjectId().toString()))
.rejects.toThrowError();
});
});
});
});

describe('#exportPlayerSkills()', () => {
Expand Down
12 changes: 11 additions & 1 deletion src/players/services/player-skill.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { Etf2lProfileService } from './etf2l-profile.service';
import { DiscordService } from '@/discord/services/discord.service';
import { skillChanged } from '@/discord/notifications';
import { Environment } from '@/environment/environment';
import { Player } from '../models/player';

@Injectable()
@Console()
Expand Down Expand Up @@ -46,7 +47,15 @@ export class PlayerSkillService implements OnModuleInit {
return await this.playerSkillModel.findOne({ player: playerId });
}

async setPlayerSkill(playerId: string, skill: Map<string, number>): Promise<DocumentType<PlayerSkill>> {
async setPlayerSkill(playerId: string, skill: Map<string, number>, adminId?: string): Promise<DocumentType<PlayerSkill>> {
let admin: DocumentType<Player>;
if (adminId) {
admin = await this.playersService.getById(adminId);
if (!admin) {
throw new Error('invalid admin');
}
}

const player = await this.playersService.getById(playerId);
if (!player) {
throw new Error('no such player');
Expand All @@ -63,6 +72,7 @@ export class PlayerSkillService implements OnModuleInit {
oldSkill: oldSkill.skill,
newSkill: skill,
playerProfileUrl: `${this.environment.clientUrl}/player/${player.id}`,
adminResponsible: admin?.name,
}));
break;
}
Expand Down
31 changes: 24 additions & 7 deletions src/players/services/players.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { typegooseTestingModule } from '@/utils/testing-typegoose-module';
import { MongoMemoryServer } from 'mongodb-memory-server';
import { SteamApiService } from './steam-api.service';
import { DiscordService } from '@/discord/services/discord.service';
import { ObjectId } from 'mongodb';

jest.mock('@/discord/services/discord.service');

Expand Down Expand Up @@ -326,16 +327,27 @@ describe('PlayersService', () => {
});

describe('#updatePlayer()', () => {
let admin: DocumentType<Player>;

beforeEach(async () => {
admin = await playerModel.create({
name: 'FAKE_ADMIN_NAME',
steamId: 'FAKE_ADMIN_STEAM_ID',
etf2lProfileId: 1,
hasAcceptedRules: true,
});
});

it('should update player name', async () => {
const ret = await service.updatePlayer(mockPlayer.id, { name: 'NEW_NAME' });
const ret = await service.updatePlayer(mockPlayer.id, { name: 'NEW_NAME' }, admin.id);
expect(ret.name).toEqual('NEW_NAME');
});

it('should update player role', async () => {
const ret1 = await service.updatePlayer(mockPlayer.id, { role: 'admin' });
const ret1 = await service.updatePlayer(mockPlayer.id, { role: 'admin' }, admin.id);
expect(ret1.role).toEqual('admin');

const ret2 = await service.updatePlayer(mockPlayer.id, { role: null });
const ret2 = await service.updatePlayer(mockPlayer.id, { role: null }, admin.id);
expect(ret2.role).toBe(null);
});

Expand All @@ -344,20 +356,25 @@ describe('PlayersService', () => {
jest.spyOn(onlinePlayersService, 'getSocketsForPlayer').mockReturnValue([ socket ] as any);
const spy = jest.spyOn(socket, 'emit');

await service.updatePlayer(mockPlayer.id, { name: 'NEW_NAME' });
await service.updatePlayer(mockPlayer.id, { name: 'NEW_NAME' }, admin.id);
expect(spy).toHaveBeenCalledWith('profile update', { name: 'NEW_NAME' });
});

it('should return null if the given player does not exist', async () => {
jest.spyOn(service, 'getById').mockResolvedValue(null);
expect(await service.updatePlayer('FAKE_ID', { })).toBeNull();
expect(await service.updatePlayer(new ObjectId().toString(), { }, admin.id)).toBeNull();
});

it('should notify admins on Discord', async () => {
const spy = jest.spyOn(discordService.getAdminsChannel(), 'send');
await service.updatePlayer(mockPlayer.id, { name: 'NEW_NAME' });
await service.updatePlayer(mockPlayer.id, { name: 'NEW_NAME' }, admin.id);
expect(spy).toHaveBeenCalled();
});

describe('when the admin does not exist', () => {
it('should reject', async () => {
await expect(service.updatePlayer(mockPlayer.id, { name: 'NEW_NAME' }, new ObjectId().toString())).rejects.toThrowError();
});
});
});

describe('#acceptTerms', () => {
Expand Down
8 changes: 7 additions & 1 deletion src/players/services/players.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,12 @@ export class PlayersService {
return await this.playerModel.find({ twitchTvUser: { $exists: true } });
}

async updatePlayer(playerId: string, update: Partial<Player>): Promise<DocumentType<Player>> {
async updatePlayer(playerId: string, update: Partial<Player>, adminId: string): Promise<DocumentType<Player>> {
const admin = await this.getById(adminId);
if (!admin) {
throw new Error('admin does not exist');
}

const player = await this.getById(playerId);
if (player) {
if (update.name) {
Expand All @@ -133,6 +138,7 @@ export class PlayersService {
oldName,
newName: player.name,
profileUrl: `${this.environment.clientUrl}/player/${player.id}`,
adminResponsible: admin.name,
}));
}

Expand Down

0 comments on commit dc7634f

Please sign in to comment.