feat(nestjs): migrate to NestJS

This commit is contained in:
Ahmed Bouhuolia
2025-04-07 11:51:24 +02:00
parent f068218a16
commit 55fcc908ef
3779 changed files with 631 additions and 195332 deletions

View File

@@ -0,0 +1,77 @@
import { get } from 'lodash';
import {
IModelMeta,
IModelMetaField,
IModelMetaDefaultSort,
} from '@/interfaces/Model';
const defaultModelMeta = {
fields: {},
fields2: {},
};
export const ModelSettings = (Model) =>
class extends Model {
/**
*
* @returns {IModelMeta}
*/
static get meta(): IModelMeta {
throw new Error('');
}
/**
* Parsed meta merged with default emta.
* @returns {IModelMeta}
*/
static get parsedMeta(): IModelMeta {
return {
...defaultModelMeta,
...this.meta,
};
}
/**
* Retrieve specific model field meta of the given field key.
* @param {string} key
* @returns {IModelMetaField}
*/
public static getField(key: string, attribute?: string): IModelMetaField {
const field = get(this.meta.fields, key);
return attribute ? get(field, attribute) : field;
}
/**
* Retrieves the specific model meta.
* @param {string} key
* @returns
*/
public static getMeta(key?: string) {
return key ? get(this.parsedMeta, key) : this.parsedMeta;
}
/**
* Retrieve the model meta fields.
* @return {{ [key: string]: IModelMetaField }}
*/
public static get fields(): { [key: string]: IModelMetaField } {
return this.getMeta('fields');
}
/**
* Retrieve the model default sort settings.
* @return {IModelMetaDefaultSort}
*/
public static get defaultSort(): IModelMetaDefaultSort {
return this.getMeta('defaultSort');
}
/**
* Retrieve the default filter field key.
* @return {string}
*/
public static get defaultFilterField(): string {
return this.getMeta('defaultFilterField');
}
};

View File

@@ -0,0 +1,24 @@
import { Body, Controller, Get, Post, Put } from '@nestjs/common';
import { SettingsApplicationService } from './SettingsApplication.service';
import { ISettingsDTO } from './Settings.types';
import { ApiOperation, ApiTags } from '@nestjs/swagger';
@Controller('settings')
@ApiTags('settings')
export class SettingsController {
constructor(
private readonly settingsApplicationService: SettingsApplicationService,
) {}
@Put('')
@ApiOperation({ summary: 'Save the given settings.' })
async saveSettings(@Body() settingsDTO: ISettingsDTO) {
return this.settingsApplicationService.saveSettings(settingsDTO);
}
@Get('')
@ApiOperation({ summary: 'Retrieves the settings.' })
async getSettings() {
return this.settingsApplicationService.getSettings();
}
}

View File

@@ -0,0 +1,39 @@
import { Global, Module } from '@nestjs/common';
import { SettingRepository } from './repositories/Setting.repository';
import { SettingsStore } from './SettingsStore';
import { SettingsApplicationService } from './SettingsApplication.service';
import { SaveSettingsService } from './commands/SaveSettings.service';
import { SettingsController } from './Settings.controller';
import { SETTINGS_PROVIDER } from './Settings.types';
import { GetSettingsService } from './queries/GetSettings.service';
import { ClsModule } from 'nestjs-cls';
@Global()
@Module({
imports: [
ClsModule.forFeatureAsync({
provide: SETTINGS_PROVIDER,
inject: [SettingRepository],
useFactory: (settingRepository: SettingRepository) => async () => {
const settings = new SettingsStore(settingRepository);
// Load settings from database.
await settings.load();
return settings;
},
global: true,
strict: true,
type: 'function',
}),
],
providers: [
SettingRepository,
GetSettingsService,
SettingsApplicationService,
SaveSettingsService,
],
exports: [SettingRepository],
controllers: [SettingsController],
})
export class SettingsModule {}

View File

@@ -0,0 +1,17 @@
export interface IOptionDTO {
key: string;
value: string;
group: string;
}
export interface ISettingsDTO {
options: IOptionDTO[];
}
export const SETTINGS_PROVIDER = 'SETTINGS';
export enum PreferencesAction {
Mutate = 'Mutate'
}

View File

@@ -0,0 +1,24 @@
import { Injectable } from '@nestjs/common';
import { SaveSettingsService } from './commands/SaveSettings.service';
import { ISettingsDTO } from './Settings.types';
import { GetSettingsService } from './queries/GetSettings.service';
@Injectable()
export class SettingsApplicationService {
constructor(
private readonly saveSettingsService: SaveSettingsService,
private readonly getSettingsService: GetSettingsService,
) {}
/**
* Saves the given settings.
* @param {ISettingsDTO} settingsDTO
*/
public async saveSettings(settingsDTO: ISettingsDTO) {
return this.saveSettingsService.saveSettings(settingsDTO);
}
public async getSettings() {
return this.getSettingsService.execute();
}
}

View File

@@ -0,0 +1,17 @@
import { EntityRepository } from '@/common/repository/EntityRepository';
import { SettingsOptions } from '@/constants/metable-options';
import { MetableDBStore } from '../Metable/MetableStoreDB';
export class SettingsStore extends MetableDBStore {
/**
* Constructor method.
* @param {number} tenantId
*/
constructor(repository: EntityRepository, config: any = SettingsOptions) {
super(config);
this.setExtraColumns(['group']);
this.setRepository(repository);
}
}

View File

@@ -0,0 +1,60 @@
import { Inject, Injectable } from '@nestjs/common';
import { pick } from 'lodash';
import { SettingsStore } from '../SettingsStore';
import { IOptionDTO, ISettingsDTO, SETTINGS_PROVIDER } from '../Settings.types';
@Injectable()
export class SaveSettingsService {
constructor(
@Inject(SETTINGS_PROVIDER)
private readonly settingsStore: () => SettingsStore,
) {}
/**
* Saves the given settings.
* @param {ISettingsDTO} settingsDTO
*/
public async saveSettings(settingsDTO: ISettingsDTO) {
const settingsStore = await this.settingsStore();
const notDefinedOptions = await this.validateNotDefinedSettings(
settingsDTO.options,
);
const errorReasons: { type: string; code: number; keys: any[] }[] = [];
if (notDefinedOptions.length) {
errorReasons.push({
type: 'OPTIONS.KEY.NOT.DEFINED',
code: 200,
keys: notDefinedOptions.map((o) => ({ ...pick(o, ['key', 'group']) })),
});
}
if (errorReasons.length) {
throw new Error(JSON.stringify(errorReasons));
}
settingsDTO.options.forEach((option: IOptionDTO) => {
settingsStore.set({ ...option });
});
await settingsStore.save();
}
/**
* Validates the given options is defined or either not.
* @param {Array} options
* @return {Boolean}
*/
private async validateNotDefinedSettings(options) {
const notDefined = [];
const settingStore = await this.settingsStore();
options.forEach((option) => {
const setting = settingStore.config.getMetaConfig(
option.key,
option.group,
);
if (!setting) {
notDefined.push(option);
}
});
return notDefined;
}
}

View File

@@ -0,0 +1,22 @@
import { BaseModel } from '@/models/Model';
// import TenantModel from 'models/TenantModel';
// import Auth from './Auth';
export class Setting extends BaseModel {
/**
* Table name
*/
static get tableName() {
return 'settings';
}
/**
* Extra metadata query to query with the current authenticate user.
* @param {Object} query
*/
// static extraMetadataQuery(query) {
// if (Auth.isLogged()) {
// query.where('user_id', Auth.userId());
// }
// }
}

View File

@@ -0,0 +1,15 @@
import { Inject, Injectable } from '@nestjs/common';
import { SettingsStore } from '../SettingsStore';
import { SETTINGS_PROVIDER } from '../Settings.types';
@Injectable()
export class GetSettingsService {
constructor(
@Inject(SETTINGS_PROVIDER)
private readonly settingsStore: () => SettingsStore,
) {}
public async execute() {
return (await this.settingsStore()).all();
}
}

View File

@@ -0,0 +1,22 @@
import { Knex } from 'knex';
import { Inject, Injectable } from '@nestjs/common';
import { TenantRepository } from '@/common/repository/TenantRepository';
import { TENANCY_DB_CONNECTION } from '@/modules/Tenancy/TenancyDB/TenancyDB.constants';
import { Setting } from '../models/Setting';
@Injectable()
export class SettingRepository extends TenantRepository {
constructor(
@Inject(TENANCY_DB_CONNECTION)
private readonly tenantKnex: () => Knex,
) {
super();
}
/**
* Gets the repository's model.
*/
get model(): typeof Setting {
return Setting.bindKnex(this.tenantKnex());
}
}