mirror of
https://github.com/hexastack/hexabot
synced 2025-06-26 18:27:28 +00:00
feat: add cleanup module
This commit is contained in:
@@ -11,7 +11,7 @@ import path from 'path';
|
|||||||
import { CacheModule } from '@nestjs/cache-manager';
|
import { CacheModule } from '@nestjs/cache-manager';
|
||||||
// eslint-disable-next-line import/order
|
// eslint-disable-next-line import/order
|
||||||
import { MailerModule } from '@nestjs-modules/mailer';
|
import { MailerModule } from '@nestjs-modules/mailer';
|
||||||
import { Module } from '@nestjs/common';
|
import { Module, OnApplicationBootstrap } from '@nestjs/common';
|
||||||
import { APP_GUARD } from '@nestjs/core';
|
import { APP_GUARD } from '@nestjs/core';
|
||||||
import { EventEmitterModule } from '@nestjs/event-emitter';
|
import { EventEmitterModule } from '@nestjs/event-emitter';
|
||||||
import { MongooseModule } from '@nestjs/mongoose';
|
import { MongooseModule } from '@nestjs/mongoose';
|
||||||
@@ -33,12 +33,15 @@ import { AppService } from './app.service';
|
|||||||
import { AttachmentModule } from './attachment/attachment.module';
|
import { AttachmentModule } from './attachment/attachment.module';
|
||||||
import { ChannelModule } from './channel/channel.module';
|
import { ChannelModule } from './channel/channel.module';
|
||||||
import { ChatModule } from './chat/chat.module';
|
import { ChatModule } from './chat/chat.module';
|
||||||
|
import { CleanupModule } from './cleanup/cleanup.module';
|
||||||
|
import { CleanupService } from './cleanup/cleanup.service';
|
||||||
import { CmsModule } from './cms/cms.module';
|
import { CmsModule } from './cms/cms.module';
|
||||||
import { config } from './config';
|
import { config } from './config';
|
||||||
import extraModules from './extra';
|
import extraModules from './extra';
|
||||||
import { HelperModule } from './helper/helper.module';
|
import { HelperModule } from './helper/helper.module';
|
||||||
import { I18nModule } from './i18n/i18n.module';
|
import { I18nModule } from './i18n/i18n.module';
|
||||||
import { LoggerModule } from './logger/logger.module';
|
import { LoggerModule } from './logger/logger.module';
|
||||||
|
import { LoggerService } from './logger/logger.service';
|
||||||
import { MigrationModule } from './migration/migration.module';
|
import { MigrationModule } from './migration/migration.module';
|
||||||
import { NlpModule } from './nlp/nlp.module';
|
import { NlpModule } from './nlp/nlp.module';
|
||||||
import { PluginsModule } from './plugins/plugins.module';
|
import { PluginsModule } from './plugins/plugins.module';
|
||||||
@@ -152,6 +155,7 @@ const i18nOptions: I18nOptions = {
|
|||||||
max: config.cache.max,
|
max: config.cache.max,
|
||||||
}),
|
}),
|
||||||
MigrationModule,
|
MigrationModule,
|
||||||
|
CleanupModule,
|
||||||
...extraModules,
|
...extraModules,
|
||||||
],
|
],
|
||||||
controllers: [AppController],
|
controllers: [AppController],
|
||||||
@@ -161,4 +165,17 @@ const i18nOptions: I18nOptions = {
|
|||||||
AppService,
|
AppService,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class HexabotModule {}
|
export class HexabotModule implements OnApplicationBootstrap {
|
||||||
|
constructor(
|
||||||
|
private readonly loggerService: LoggerService,
|
||||||
|
private readonly cleanupService: CleanupService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async onApplicationBootstrap() {
|
||||||
|
try {
|
||||||
|
await this.cleanupService.deleteUnusedSettings();
|
||||||
|
} catch (error) {
|
||||||
|
this.loggerService.error('Unable to delete unused settings', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
18
api/src/cleanup/cleanup.module.ts
Normal file
18
api/src/cleanup/cleanup.module.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2025 Hexastack. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
|
||||||
|
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
|
||||||
|
* 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 { Global, Module } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { CleanupService } from './cleanup.service';
|
||||||
|
|
||||||
|
@Global()
|
||||||
|
@Module({
|
||||||
|
providers: [CleanupService],
|
||||||
|
exports: [CleanupService],
|
||||||
|
})
|
||||||
|
export class CleanupModule {}
|
||||||
62
api/src/cleanup/cleanup.service.ts
Normal file
62
api/src/cleanup/cleanup.service.ts
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2025 Hexastack. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
|
||||||
|
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
|
||||||
|
* 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 } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { ChannelService } from '@/channel/channel.service';
|
||||||
|
import { HelperService } from '@/helper/helper.service';
|
||||||
|
import { LoggerService } from '@/logger/logger.service';
|
||||||
|
import { SettingService } from '@/setting/services/setting.service';
|
||||||
|
import { DeleteResult } from '@/utils/generics/base-repository';
|
||||||
|
|
||||||
|
import { TCriteria, TExtractExtension, TExtractNamespace } from './types';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class CleanupService {
|
||||||
|
constructor(
|
||||||
|
private readonly helperService: HelperService,
|
||||||
|
private readonly loggerService: LoggerService,
|
||||||
|
private readonly settingService: SettingService,
|
||||||
|
private readonly channelService: ChannelService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
private async deleteMany(criteria: TCriteria[]): Promise<DeleteResult> {
|
||||||
|
return await this.settingService.deleteMany({
|
||||||
|
$or: criteria.map(({ suffix, namespaces }) => ({
|
||||||
|
group: { $regex: suffix, $nin: namespaces },
|
||||||
|
})),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public getChannelNamespaces(): TExtractNamespace<'channel'>[] {
|
||||||
|
return this.channelService
|
||||||
|
.getAll()
|
||||||
|
.map((channel) => channel.getNamespace<TExtractExtension<'channel'>>());
|
||||||
|
}
|
||||||
|
|
||||||
|
public getHelperNamespaces(): TExtractNamespace<'helper'>[] {
|
||||||
|
return this.helperService
|
||||||
|
.getAll()
|
||||||
|
.map((helper) => helper.getNamespace<TExtractExtension<'helper'>>());
|
||||||
|
}
|
||||||
|
|
||||||
|
public async deleteUnusedSettings() {
|
||||||
|
const channels = this.getChannelNamespaces();
|
||||||
|
const helpers = this.getHelperNamespaces();
|
||||||
|
const { deletedCount } = await this.deleteMany([
|
||||||
|
{ suffix: '_channel', namespaces: channels },
|
||||||
|
{ suffix: '_helper', namespaces: helpers },
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (deletedCount > 0) {
|
||||||
|
this.loggerService.log(
|
||||||
|
`${deletedCount} unused setting${deletedCount === 1 ? '' : 's'} are successfully deleted!`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
41
api/src/cleanup/types.ts
Normal file
41
api/src/cleanup/types.ts
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2025 Hexastack. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
|
||||||
|
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
|
||||||
|
* 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 { ExtensionName } from '@/utils/types/extension';
|
||||||
|
|
||||||
|
type TExcludedExtension = 'plugin';
|
||||||
|
|
||||||
|
type TExcludeSuffix<
|
||||||
|
T,
|
||||||
|
S extends string = '_',
|
||||||
|
Suffix extends string = `${S}${TExcludedExtension}`,
|
||||||
|
> = T extends `${infer _Base}${Suffix}` ? never : T;
|
||||||
|
|
||||||
|
export type TExtensionName = TExcludeSuffix<ExtensionName, '-'>;
|
||||||
|
|
||||||
|
export type TExtension =
|
||||||
|
Extract<TExtensionName, `${string}-${string}`> extends `${string}-${infer S}`
|
||||||
|
? `${S}`
|
||||||
|
: never;
|
||||||
|
|
||||||
|
export type TNamespace = HyphenToUnderscore<TExtensionName>;
|
||||||
|
|
||||||
|
export type TExtractNamespace<
|
||||||
|
T extends TExtension = TExtension,
|
||||||
|
M extends TExtensionName = TExtensionName,
|
||||||
|
> = M extends `${string}${T}` ? HyphenToUnderscore<M> : never;
|
||||||
|
|
||||||
|
export type TExtractExtension<
|
||||||
|
T extends TExtension = TExtension,
|
||||||
|
M extends TExtensionName = TExtensionName,
|
||||||
|
> = M extends `${string}${T}` ? M : never;
|
||||||
|
|
||||||
|
export type TCriteria = {
|
||||||
|
suffix: `_${TExtension}`;
|
||||||
|
namespaces: TNamespace[];
|
||||||
|
};
|
||||||
36
api/src/utils/test/fixtures/setting.ts
vendored
36
api/src/utils/test/fixtures/setting.ts
vendored
@@ -11,6 +11,7 @@ import mongoose from 'mongoose';
|
|||||||
import { SettingCreateDto } from '@/setting/dto/setting.dto';
|
import { SettingCreateDto } from '@/setting/dto/setting.dto';
|
||||||
import { SettingModel } from '@/setting/schemas/setting.schema';
|
import { SettingModel } from '@/setting/schemas/setting.schema';
|
||||||
import { SettingType } from '@/setting/schemas/types';
|
import { SettingType } from '@/setting/schemas/types';
|
||||||
|
import { getRandom } from '@/utils/helpers/safeRandom';
|
||||||
|
|
||||||
export const settingFixtures: SettingCreateDto[] = [
|
export const settingFixtures: SettingCreateDto[] = [
|
||||||
{
|
{
|
||||||
@@ -90,6 +91,41 @@ export const settingFixtures: SettingCreateDto[] = [
|
|||||||
type: SettingType.text,
|
type: SettingType.text,
|
||||||
weight: 10,
|
weight: 10,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
group: `${getRandom()}_channel`,
|
||||||
|
label: `${getRandom()}`,
|
||||||
|
value: '',
|
||||||
|
type: SettingType.text,
|
||||||
|
weight: 11,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
group: `${getRandom()}_helper`,
|
||||||
|
label: `${getRandom()}`,
|
||||||
|
value: '',
|
||||||
|
type: SettingType.text,
|
||||||
|
weight: 12,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
group: `${getRandom()}_channel`,
|
||||||
|
label: `${getRandom()}`,
|
||||||
|
value: '',
|
||||||
|
type: SettingType.text,
|
||||||
|
weight: 13,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
group: `${getRandom()}_helper`,
|
||||||
|
label: `${getRandom()}`,
|
||||||
|
value: '',
|
||||||
|
type: SettingType.text,
|
||||||
|
weight: 14,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
group: 'local_storage_helper',
|
||||||
|
label: 'default storage helper label',
|
||||||
|
value: 'local-storage-helper',
|
||||||
|
type: SettingType.text,
|
||||||
|
weight: 15,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const installSettingFixtures = async () => {
|
export const installSettingFixtures = async () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user