Skip to content

Commit

Permalink
Enable-disable module and items (#1074)
Browse files Browse the repository at this point in the history
  • Loading branch information
niekcandaele authored Jul 18, 2024
1 parent 8163823 commit c6151a2
Show file tree
Hide file tree
Showing 30 changed files with 488 additions and 267 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ const tests = [
{
systemConfig: JSON.stringify({
cronJobs: {
[this.setupData.cronJobsModule.cronJobs[0].name]: '5 * * * *',
[this.setupData.cronJobsModule.cronJobs[0].name]: { temporalValue: '5 * * * *' },
},
}),
}
Expand Down Expand Up @@ -229,8 +229,8 @@ const tests = [
{
systemConfig: JSON.stringify({
cronJobs: {
[updatedModuleRes.data.data.cronJobs[0].name]: '5 * * * *',
[updatedModuleRes.data.data.cronJobs[1].name]: '13 * * * *',
[updatedModuleRes.data.data.cronJobs[0].name]: { temporalValue: '5 * * * *' },
[updatedModuleRes.data.data.cronJobs[1].name]: { temporalValue: '13 * * * *' },
},
}),
}
Expand All @@ -250,8 +250,12 @@ const tests = [
);

expect(installRes.data.data.systemConfig).to.deep.equal({
enabled: true,
cronJobs: {
[this.setupData.cronJobsModule.cronJobs[0].name]: this.setupData.cronJobsModule.cronJobs[0].temporalValue,
[this.setupData.cronJobsModule.cronJobs[0].name]: {
enabled: true,
temporalValue: this.setupData.cronJobsModule.cronJobs[0].temporalValue,
},
},
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,13 @@ const tests = [
// Should be a valid schema which compiles
ajv.compile(systemConfig);

expect(systemConfig.properties).to.be.eql({});
expect(systemConfig.properties).to.be.eql({
enabled: {
default: true,
description: 'Enable/disable the module without having to uninstall it.',
type: 'boolean',
},
});
},
}),
new IntegrationTest<ModuleOutputDTO>({
Expand Down Expand Up @@ -143,7 +149,7 @@ const tests = [
ajv.compile(systemConfig);

expect(systemConfig.properties.hooks).to.not.be.undefined;
expect(systemConfig.properties.hooks.properties['Test hook Discord channel ID']).to.not.be.undefined;
expect(systemConfig.properties.hooks.properties['Test hook'].properties.discordChannelId).to.not.be.undefined;
},
}),
new IntegrationTest<ModuleOutputDTO>({
Expand Down Expand Up @@ -171,8 +177,8 @@ const tests = [

ajv.compile(systemConfig);

expect(systemConfig.properties.hooks.properties).to.have.property('Test hook 1 Discord channel ID');
expect(systemConfig.properties.hooks.properties).to.have.property('Test hook 2 Discord channel ID');
expect(systemConfig.properties.hooks.properties['Test hook 1'].properties).to.have.property('discordChannelId');
expect(systemConfig.properties.hooks.properties['Test hook 2'].properties).to.have.property('discordChannelId');
},
}),
new IntegrationTest<ModuleOutputDTO>({
Expand All @@ -194,7 +200,8 @@ const tests = [

ajv.compile(systemConfig);

expect(systemConfig.properties.hooks).to.be.undefined;
expect(systemConfig.properties.hooks.properties['Test non-discord hook'].properties.discordChannelId).to.be
.undefined;
},
}),
];
Expand Down
69 changes: 54 additions & 15 deletions packages/app-api/src/lib/systemConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ export function getEmptyConfigSchema(): Ajv.AnySchemaObject {
export function getSystemConfigSchema(mod: ModuleOutputDTO | ModuleOutputDTOApi): string {
const systemConfigSchema = getEmptyConfigSchema();

systemConfigSchema.properties.enabled = {
type: 'boolean',
default: true,
description: 'Enable/disable the module without having to uninstall it.',
};

if (mod.cronJobs.length) {
systemConfigSchema.properties.cronJobs = {
type: 'object',
Expand All @@ -29,35 +35,56 @@ export function getSystemConfigSchema(mod: ModuleOutputDTO | ModuleOutputDTOApi)

for (const cronJob of mod.cronJobs) {
systemConfigSchema.properties.cronJobs.properties[cronJob.name] = {
type: 'string',
default: cronJob.temporalValue,
type: 'object',
required: [],
default: {},
properties: {
enabled: {
type: 'boolean',
default: true,
description: `Enable the ${cronJob.name} cron job.`,
},
temporalValue: {
type: 'string',
description: 'Temporal value for the cron job. Controls when it runs',
default: cronJob.temporalValue,
},
},
};
}
}

if (mod.hooks.length) {
systemConfigSchema.properties.hooks = {
type: 'object',
properties: {},
required: [],
default: {},
};

for (const hook of mod.hooks) {
if (hook.eventType === DiscordEvents.DISCORD_MESSAGE) {
if (!systemConfigSchema.properties.hooks) {
systemConfigSchema.properties.hooks = {
type: 'object',
properties: {},
required: [],
default: {},
};
}
systemConfigSchema.properties.hooks.properties[hook.name] = {
type: 'object',
properties: {
enabled: {
type: 'boolean',
default: true,
description: `Enable the ${hook.name} hook.`,
},
},
required: [],
default: {},
};

if (hook.eventType === DiscordEvents.DISCORD_MESSAGE) {
if (!systemConfigSchema.required.includes('hooks')) {
systemConfigSchema.required.push('hooks');
}

const configKey = `${hook.name} Discord channel ID`;

systemConfigSchema.properties.hooks.properties[configKey] = {
systemConfigSchema.properties.hooks.properties[hook.name].properties.discordChannelId = {
type: 'string',
description: 'Discord channel ID where Takaro will listen for messages.',
};
systemConfigSchema.properties.hooks.required.push(configKey);
}
}
}
Expand All @@ -75,9 +102,21 @@ export function getSystemConfigSchema(mod: ModuleOutputDTO | ModuleOutputDTOApi)
for (const command of mod.commands) {
const configKey = command.name;

systemConfigSchema.properties.commands.properties[configKey] = {
type: 'object',
properties: {},
required: [],
default: {},
};

systemConfigSchema.properties.commands.properties[configKey] = {
type: 'object',
properties: {
enabled: {
type: 'boolean',
default: true,
description: `Enable the ${configKey} command.`,
},
delay: {
type: 'number',
default: 0,
Expand Down
7 changes: 7 additions & 0 deletions packages/app-api/src/service/CommandService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { parseCommand } from '../lib/commandParser.js';
import { GameServerService } from './GameServerService.js';
import { PlayerService } from './PlayerService.js';
import { PlayerOnGameServerService } from './PlayerOnGameserverService.js';
import { ModuleService } from './ModuleService.js';

export class CommandOutputDTO extends TakaroModelDTO<CommandOutputDTO> {
@IsString()
Expand Down Expand Up @@ -204,6 +205,9 @@ export class CommandService extends TakaroService<CommandModel, CommandOutputDTO
);
}

const moduleService = new ModuleService(this.domainId);
await moduleService.refreshInstallations(item.moduleId);

return created;
}

Expand Down Expand Up @@ -318,6 +322,9 @@ export class CommandService extends TakaroService<CommandModel, CommandOutputDTO
const { data, db } = command;

const commandConfig = data.module.systemConfig.commands[db.name];
if (!data.module.systemConfig.enabled) return;
if (!commandConfig.enabled) return;

const delay = commandConfig ? commandConfig.delay * 1000 : 0;

if (delay) {
Expand Down
10 changes: 9 additions & 1 deletion packages/app-api/src/service/CronJobService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,9 @@ export class CronJobService extends TakaroService<CronJobModel, CronJobOutputDTO

const created = await this.repo.create(new CronJobCreateDTO({ ...item, function: fnIdToAdd }));

const moduleService = new ModuleService(this.domainId);
await moduleService.refreshInstallations(item.moduleId);

const gameServerService = new GameServerService(this.domainId);
const installedModules = await gameServerService.getInstalledModules({ moduleId: item.moduleId });
await Promise.all(installedModules.map((mod) => this.addCronjobToQueue(created, mod)));
Expand Down Expand Up @@ -195,6 +198,9 @@ export class CronJobService extends TakaroService<CronJobModel, CronJobOutputDTO
const gameServerService = new GameServerService(this.domainId);
const mod = await gameServerService.getModuleInstallation(modInstallation.gameserverId, modInstallation.moduleId);

if (!mod.systemConfig.enabled) return;
if (!mod.systemConfig.cronJobs[cronJob.name].enabled) return;

await queueService.queues.cronjobs.queue.add(
{
functionId: cronJob.function.id,
Expand All @@ -206,7 +212,9 @@ export class CronJobService extends TakaroService<CronJobModel, CronJobOutputDTO
{
jobId,
repeat: {
pattern: systemConfig ? modInstallation.systemConfig.cronJobs[cronJob.name] : cronJob.temporalValue,
pattern: systemConfig
? modInstallation.systemConfig.cronJobs[cronJob.name].temporalValue
: cronJob.temporalValue,
jobId,
},
}
Expand Down
7 changes: 7 additions & 0 deletions packages/app-api/src/service/HookService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { GameServerService } from './GameServerService.js';
import { HookEvents, isDiscordMessageEvent, EventPayload, EventTypes, EventMapping } from '@takaro/modules';
import { PlayerOnGameServerService } from './PlayerOnGameserverService.js';
import { PlayerService } from './PlayerService.js';
import { ModuleService } from './ModuleService.js';

interface IHandleHookOptions {
eventType: EventTypes;
Expand Down Expand Up @@ -160,6 +161,10 @@ export class HookService extends TakaroService<HookModel, HookOutputDTO, HookCre
}

const created = await this.repo.create(new HookCreateDTO({ ...item, function: fnIdToAdd }));

const moduleService = new ModuleService(this.domainId);
await moduleService.refreshInstallations(item.moduleId);

return created;
}
async update(id: string, item: HookUpdateDTO) {
Expand Down Expand Up @@ -237,6 +242,8 @@ export class HookService extends TakaroService<HookModel, HookOutputDTO, HookCre
const copiedHookData = { ...hookData };

const moduleInstallation = await gameServerService.getModuleInstallation(gameServerId, hook.moduleId);
if (!moduleInstallation.systemConfig.enabled) return;
if (!moduleInstallation.systemConfig.hooks[hook.name].enabled) return;

if (isDiscordMessageEvent(eventData)) {
const configuredChannel = moduleInstallation.systemConfig.hooks[`${hook.name} Discord channel ID`];
Expand Down
22 changes: 21 additions & 1 deletion packages/app-api/src/service/ModuleService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
TakaroEventModuleUpdated,
TakaroEventModuleDeleted,
} from '@takaro/modules';
import { GameServerService } from './GameServerService.js';
import { GameServerService, ModuleInstallDTO } from './GameServerService.js';
import { PermissionCreateDTO, PermissionOutputDTO } from './RoleService.js';

// Curse you ESM... :(
Expand Down Expand Up @@ -384,4 +384,24 @@ export class ModuleService extends TakaroService<ModuleModel, ModuleOutputDTO, M
return await this.repo.findByFunction(itemId);
}
}

/**
* After creating a hook, command, cronjob or function, the systemConfig may be outdated
* @param moduleId
*/
async refreshInstallations(moduleId: string) {
const gameserverService = new GameServerService(this.domainId);
const installations = await gameserverService.getInstalledModules({ moduleId });

for (const installation of installations) {
await gameserverService.installModule(
installation.gameserverId,
moduleId,
new ModuleInstallDTO({
systemConfig: JSON.stringify(installation.systemConfig),
userConfig: JSON.stringify(installation.userConfig),
})
);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ import { Mock } from '@takaro/gameserver';
import { IGamePlayer, EventChatMessage, HookEvents, ChatChannel } from '@takaro/modules';
import Sinon from 'sinon';
import { EventService } from '../EventService.js';
import { faker } from '@faker-js/faker';

export async function getMockPlayer(extra: Partial<IGamePlayer> = {}): Promise<IGamePlayer> {
const data: Partial<IGamePlayer> = {
gameId: '1',
name: 'mock-player',
steamId: faker.random.alphaNumeric(17),
...extra,
};

Expand Down Expand Up @@ -138,7 +140,7 @@ const tests = [
new IntegrationTest<IStandardSetupData>({
group,
snapshot: false,
name: 'Doesnt trigger when module is disabled',
name: 'Doesnt trigger when module is uninstalled',
setup,
test: async function () {
const addStub = sandbox.stub(queueService.queues.commands.queue, 'add');
Expand Down Expand Up @@ -258,6 +260,62 @@ const tests = [
this.setupData.gameserver.id
);

expect(addStub).to.have.been.calledOnce;
},
}),
new IntegrationTest<IStandardSetupData>({
group,
snapshot: false,
name: 'Doesnt trigger when module is disabled',
setup,
test: async function () {
const addStub = sandbox.stub(queueService.queues.commands.queue, 'add');
sandbox.stub(Mock.prototype, 'getPlayerLocation').resolves({
x: 0,
y: 0,
z: 0,
});

await this.client.gameserver.gameServerControllerInstallModule(
this.setupData.gameserver.id,
this.setupData.mod.id,
{
systemConfig: JSON.stringify({
commands: {
[this.setupData.normalCommand.name]: {
enabled: false,
},
},
}),
}
);

await this.setupData.service.handleChatMessage(
new EventChatMessage({
msg: '/test',
channel: ChatChannel.GLOBAL,
player: await getMockPlayer(),
}),

this.setupData.gameserver.id
);

expect(addStub).to.not.have.been.calledOnce;

await this.client.gameserver.gameServerControllerInstallModule(
this.setupData.gameserver.id,
this.setupData.mod.id
);

await this.setupData.service.handleChatMessage(
new EventChatMessage({
msg: '/test',
channel: ChatChannel.GLOBAL,
player: await getMockPlayer(),
}),
this.setupData.gameserver.id
);

expect(addStub).to.have.been.calledOnce;
},
}),
Expand Down
Loading

0 comments on commit c6151a2

Please sign in to comment.