Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Cleanup orphan extensions' settings #646

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 35 additions & 2 deletions api/src/channel/channel.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,18 @@
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
*/

import { Injectable, UnauthorizedException } from '@nestjs/common';
import {
Injectable,
OnApplicationBootstrap,
UnauthorizedException,
} from '@nestjs/common';
import { Request, Response } from 'express';

import { SubscriberService } from '@/chat/services/subscriber.service';
import { CONSOLE_CHANNEL_NAME } from '@/extensions/channels/console/settings';
import { WEB_CHANNEL_NAME } from '@/extensions/channels/web/settings';
import { LoggerService } from '@/logger/logger.service';
import { SettingService } from '@/setting/services/setting.service';
import { getSessionStore } from '@/utils/constants/session-store';
import {
SocketGet,
Expand All @@ -27,14 +32,42 @@ import ChannelHandler from './lib/Handler';
import { ChannelName } from './types';

@Injectable()
export class ChannelService {
export class ChannelService implements OnApplicationBootstrap {
private registry: Map<string, ChannelHandler<ChannelName>> = new Map();

constructor(
private readonly logger: LoggerService,
private readonly settingService: SettingService,
private readonly subscriberService: SubscriberService,
) {}

async onApplicationBootstrap(): Promise<void> {
await this.cleanup();
}

/**
* Cleanups the unregisterd channels from settings.
*
*/
async cleanup(): Promise<void> {
const activePlugins = this.getAll().map((handler) =>
handler.getNamespace(),
);

const channelSettings = (await this.settingService.getAllGroups()).filter(
(group) => group.split('_').pop() === 'channel',
) as HyphenToUnderscore<ChannelName>[];

const orphanSettings = channelSettings.filter(
(group) => !activePlugins.includes(group),
);

for (const group of orphanSettings) {
this.logger.log(`Deleting orphaned settings for ${group}...`);
await this.settingService.deleteGroup(group);
}
}

/**
* Registers a channel with a specific handler.
*
Expand Down
35 changes: 33 additions & 2 deletions api/src/helper/helper.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
*/

import { Injectable, InternalServerErrorException } from '@nestjs/common';
import {
Injectable,
InternalServerErrorException,
OnApplicationBootstrap,
} from '@nestjs/common';

import { LoggerService } from '@/logger/logger.service';
import { SettingService } from '@/setting/services/setting.service';
Expand All @@ -15,7 +19,7 @@ import BaseHelper from './lib/base-helper';
import { HelperName, HelperRegistry, HelperType, TypeOfHelper } from './types';

@Injectable()
export class HelperService {
export class HelperService implements OnApplicationBootstrap {
private registry: HelperRegistry = new Map();

constructor(
Expand All @@ -28,6 +32,33 @@ export class HelperService {
});
}

async onApplicationBootstrap(): Promise<void> {
await this.cleanup();
}

/**
* Cleanups the unregisterd helpers from settings.
*
*/
async cleanup(): Promise<void> {
const activeHelpers = this.getAll().map((handler) =>
handler.getNamespace(),
);

const helperSettings = (await this.settingService.getAllGroups()).filter(
(group) => group.split('_').pop() === 'helper',
) as HyphenToUnderscore<HelperName>[];

const orphanSettings = helperSettings.filter(
(group) => !activeHelpers.includes(group),
);

for (const group of orphanSettings) {
this.logger.log(`Deleting orphaned settings for ${group}...`);
await this.settingService.deleteGroup(group);
}
}

/**
* Registers a helper.
*
Expand Down
12 changes: 11 additions & 1 deletion api/src/plugins/plugins.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,27 @@
import { Test } from '@nestjs/testing';

import { LoggerModule } from '@/logger/logger.module';
import { SettingService } from '@/setting/services/setting.service';
import { DummyPlugin } from '@/utils/test/dummy/dummy.plugin';

import { BaseBlockPlugin } from './base-block-plugin';
import { PluginService } from './plugins.service';
import { PluginType } from './types';

// Mock services
const mockSettingService = {
get: jest.fn(),
} as unknown as SettingService;

describe('PluginsService', () => {
let pluginsService: PluginService;
beforeAll(async () => {
const module = await Test.createTestingModule({
providers: [PluginService, DummyPlugin],
providers: [
PluginService,
DummyPlugin,
{ provide: SettingService, useValue: mockSettingService },
],
imports: [LoggerModule],
}).compile();
pluginsService = module.get<PluginService>(PluginService);
Expand Down
45 changes: 42 additions & 3 deletions api/src/plugins/plugins.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,14 @@
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
*/

import { Injectable, InternalServerErrorException } from '@nestjs/common';
import {
Injectable,
InternalServerErrorException,
OnApplicationBootstrap,
} from '@nestjs/common';

import { LoggerService } from '@/logger/logger.service';
import { SettingService } from '@/setting/services/setting.service';

import { BasePlugin } from './base-plugin.service';
import { PluginInstance } from './map-types';
Expand All @@ -26,7 +33,9 @@ import { PluginName, PluginType } from './types';
* @typeparam T - The plugin type, which extends from `BasePlugin`. By default, it uses `BaseBlockPlugin`.
*/
@Injectable()
export class PluginService<T extends BasePlugin = BasePlugin> {
export class PluginService<T extends BasePlugin = BasePlugin>
implements OnApplicationBootstrap
{
/**
* The registry of plugins, stored as a map where the first key is the type of plugin,
* the second key is the name of the plugin and the value is a plugin of type `T`.
Expand All @@ -35,7 +44,37 @@ export class PluginService<T extends BasePlugin = BasePlugin> {
Object.keys(PluginType).map((t) => [t as PluginType, new Map()]),
);

constructor() {}
constructor(
private readonly settingService: SettingService,
private readonly logger: LoggerService,
) {}

async onApplicationBootstrap(): Promise<void> {
await this.cleanup();
}

/**
* Cleanups the unregisterd plugins from settings.
*
*/
async cleanup(): Promise<void> {
const activePlugins = this.getAll().map((handler) =>
handler.getNamespace(),
);

const pluginSettings = (await this.settingService.getAllGroups()).filter(
(group) => group.split('_').pop() === 'plugin',
) as HyphenToUnderscore<PluginName>[];

const orphanSettings = pluginSettings.filter(
(group) => !activePlugins.includes(group),
);

for (const group of orphanSettings) {
this.logger.log(`Deleting orphaned settings for ${group}...`);
await this.settingService.deleteGroup(group);
}
}
yassine-sallemi marked this conversation as resolved.
Show resolved Hide resolved

/**
* Registers a plugin with a given name.
Expand Down
25 changes: 25 additions & 0 deletions api/src/setting/services/setting.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,15 @@ export class SettingService extends BaseService<Setting> {
}
}

/**
* Removes all settings that belong to the provided group.
*
* @param group - The group of settings to remove.
*/
async deleteGroup(group: string) {
await this.repository.deleteMany({ group });
}

/**
* Loads all settings and returns them grouped in ascending order by weight.
*
Expand Down Expand Up @@ -100,6 +109,22 @@ export class SettingService extends BaseService<Setting> {
);
}

/**
* Retrieves a list of all setting groups.
*
* @returns A promise that resolves to a list of setting groups.
*/
async getAllGroups(): Promise<string[]> {
const settings = await this.findAll();
const groups = settings.reduce<string[]>((acc, setting) => {
if (!acc.includes(setting.group)) {
acc.push(setting.group);
}
return acc;
}, []);
return groups;
}

/**
* Retrieves the application configuration object.
*
Expand Down