feat: add plugins settings i18n + typing

This commit is contained in:
Mohamed Marrouchi
2024-10-19 13:56:09 +01:00
parent 8d846186cc
commit 92bb4978c3
20 changed files with 107 additions and 67 deletions

View File

@@ -24,7 +24,7 @@ export interface ChannelModuleOptions {
folder: string;
}
@InjectDynamicProviders('dist/**/*.channel.js')
@InjectDynamicProviders('dist/extensions/**/*.channel.js')
@Module({
controllers: [WebhookController, ChannelController],
providers: [ChannelService],

View File

@@ -19,6 +19,7 @@ import { LoggerService } from '@/logger/logger.service';
import BaseNlpHelper from '@/nlp/lib/BaseNlpHelper';
import { NlpService } from '@/nlp/services/nlp.service';
import { SettingService } from '@/setting/services/setting.service';
import { hyphenToUnderscore } from '@/utils/helpers/misc';
import { SocketRequest } from '@/websocket/utils/socket-request';
import { SocketResponse } from '@/websocket/utils/socket-response';
@@ -56,7 +57,7 @@ export default abstract class ChannelHandler<N extends string = string> {
}
protected getGroup() {
return this.getChannel().replaceAll('-', '_') as ChannelSetting<N>['group'];
return hyphenToUnderscore(this.getChannel()) as ChannelSetting<N>['group'];
}
async setup() {

View File

@@ -59,7 +59,7 @@ export class BlockController extends BaseController<
private readonly categoryService: CategoryService,
private readonly labelService: LabelService,
private readonly userService: UserService,
private pluginsService: PluginService<BaseBlockPlugin>,
private pluginsService: PluginService<BaseBlockPlugin<any>>,
) {
super(blockService);
}
@@ -122,8 +122,7 @@ export class BlockController extends BaseController<
const plugins = this.pluginsService
.getAllByType(PluginType.block)
.map((p) => ({
title: p.title,
name: p.id,
id: p.id,
template: {
...p.template,
message: {

View File

@@ -116,4 +116,23 @@ export class MessageService extends BaseService<
limit,
);
}
/**
* Retrieves the latest messages for a given subscriber
*
* @param subscriber - The subscriber whose message history is being retrieved.
* @param limit - The maximum number of messages to return (defaults to 5).
*
* @returns The message history since the specified date.
*/
async findLastMessages(subscriber: Subscriber, limit: number = 5) {
const lastMessages = await this.findPage(
{
$or: [{ sender: subscriber.id }, { recipient: subscriber.id }],
},
{ sort: ['createdAt', 'desc'], skip: 0, limit },
);
return lastMessages.reverse();
}
}

View File

View File

@@ -22,6 +22,7 @@ import { IfAnyOrNever } from 'nestjs-i18n/dist/types';
import { config } from '@/config';
import { Translation } from '@/i18n/schemas/translation.schema';
import { hyphenToUnderscore } from '@/utils/helpers/misc';
@Injectable()
export class I18nService<K = Record<string, unknown>>
@@ -84,41 +85,40 @@ export class I18nService<K = Record<string, unknown>>
}
async loadExtensionI18nTranslations() {
const extensionsDir = path.join(
__dirname,
'..',
'..',
'extensions',
'channels',
);
const baseDir = path.join(__dirname, '..', '..', 'extensions');
const extensionTypes = ['channels', 'plugins'];
try {
const extensionFolders = await fs.readdir(extensionsDir, {
withFileTypes: true,
});
for (const type of extensionTypes) {
const extensionsDir = path.join(baseDir, type);
const extensionFolders = await fs.readdir(extensionsDir, {
withFileTypes: true,
});
for (const folder of extensionFolders) {
if (folder.isDirectory()) {
const i18nPath = path.join(extensionsDir, folder.name, 'i18n');
const extensionName = folder.name.replaceAll('-', '_');
try {
// Check if the i18n directory exists
await fs.access(i18nPath);
for (const folder of extensionFolders) {
if (folder.isDirectory()) {
const i18nPath = path.join(extensionsDir, folder.name, 'i18n');
const namespace = hyphenToUnderscore(folder.name);
try {
// Check if the i18n directory exists
await fs.access(i18nPath);
// Load and merge translations
const i18nLoader = new I18nJsonLoader({ path: i18nPath });
const translations = await i18nLoader.load();
for (const lang in translations) {
if (!this.extensionTranslations[lang]) {
this.extensionTranslations[lang] = {
[extensionName]: translations[lang],
};
} else {
this.extensionTranslations[lang][extensionName] =
translations[lang];
// Load and merge translations
const i18nLoader = new I18nJsonLoader({ path: i18nPath });
const translations = await i18nLoader.load();
for (const lang in translations) {
if (!this.extensionTranslations[lang]) {
this.extensionTranslations[lang] = {
[namespace]: translations[lang],
};
} else {
this.extensionTranslations[lang][namespace] =
translations[lang];
}
}
} catch (error) {
// If the i18n folder does not exist or error in reading, skip this folder
}
} catch (error) {
// If the i18n folder does not exist or error in reading, skip this folder
}
}
}

View File

@@ -33,7 +33,7 @@ import { NlpSampleService } from './services/nlp-sample.service';
import { NlpValueService } from './services/nlp-value.service';
import { NlpService } from './services/nlp.service';
@InjectDynamicProviders('dist/**/*.nlp.helper.js')
@InjectDynamicProviders('dist/extensions/**/*.nlp.helper.js')
@Module({
imports: [
MongooseModule.forFeature([

View File

@@ -22,17 +22,22 @@ import {
} from './types';
@Injectable()
export abstract class BaseBlockPlugin extends BasePlugin {
export abstract class BaseBlockPlugin<
T extends PluginSetting[],
> extends BasePlugin {
public readonly type: PluginType = PluginType.block;
constructor(id: string, pluginService: PluginService<BasePlugin>) {
public readonly settings: T;
constructor(
id: string,
settings: T,
pluginService: PluginService<BasePlugin>,
) {
super(id, pluginService);
this.settings = settings;
}
title: string;
settings: PluginSetting[];
template: PluginBlockTemplate;
effects?: PluginEffects;
@@ -42,4 +47,11 @@ export abstract class BaseBlockPlugin extends BasePlugin {
context: Context,
convId?: string,
): Promise<StdOutgoingEnvelope>;
protected getArguments(block: Block) {
if ('args' in block.message) {
return block.message.args as SettingObject<T>;
}
throw new Error(`Block "${block.name}" does not have any arguments.`);
}
}

View File

@@ -20,7 +20,7 @@ import { ContentModel } from '@/cms/schemas/content.schema';
import { PluginService } from './plugins.service';
@InjectDynamicProviders('dist/**/*.plugin.js')
@InjectDynamicProviders('dist/extensions/**/*.plugin.js')
@Global()
@Module({
imports: [

View File

@@ -8,8 +8,8 @@
import { Test } from '@nestjs/testing';
import { DummyPlugin } from '@/extensions/plugins/dummy.plugin';
import { LoggerModule } from '@/logger/logger.module';
import { DummyPlugin } from '@/utils/test/dummy/dummy.plugin';
import { BaseBlockPlugin } from './base-block-plugin';
import { PluginService } from './plugins.service';

View File

@@ -22,7 +22,7 @@ export interface CustomBlocks {}
type ChannelEvent = any;
type BlockAttrs = Partial<BlockCreateDto> & { name: string };
export type PluginSetting = SettingCreateDto;
export type PluginSetting = Omit<SettingCreateDto, 'weight'>;
export type PluginBlockTemplate = Omit<
BlockAttrs,

View File

@@ -9,3 +9,7 @@
export const isEmpty = (value: string): boolean => {
return value === undefined || value === null || value === '';
};
export const hyphenToUnderscore = (str: string) => {
return str.replaceAll('-', '_');
};

View File

@@ -15,18 +15,15 @@ import {
import { LoggerService } from '@/logger/logger.service';
import { BaseBlockPlugin } from '@/plugins/base-block-plugin';
import { PluginService } from '@/plugins/plugins.service';
import { PluginSetting } from '@/plugins/types';
@Injectable()
export class DummyPlugin extends BaseBlockPlugin {
export class DummyPlugin extends BaseBlockPlugin<PluginSetting[]> {
constructor(
pluginService: PluginService,
private logger: LoggerService,
) {
super('dummy', pluginService);
this.title = 'Dummy';
this.settings = [];
super('dummy', [], pluginService);
this.template = { name: 'Dummy Plugin' };