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,40 @@
import { get } from 'lodash';
export class MetableConfig {
public config: any;
constructor(config: any) {
this.setConfig(config);
}
/**
* Sets the config.
*/
setConfig(config: any) {
this.config = config;
}
/**
* Gets the meta config.
* @param {string} key
* @param {string} group
* @param {string} accessor
* @returns {object|string}
*/
getMetaConfig(key: string, group?: string, accessor?: string) {
const configGroup = get(this.config, group);
const config = get(configGroup, key);
return accessor ? get(config, accessor) : config;
}
/**
* Gets the meta type.
* @param {string} key
* @param {string} group
* @returns {string}
*/
getMetaType(key: string, group?: string) {
return this.getMetaConfig(key, group, 'type');
}
}

View File

@@ -0,0 +1,9 @@
export class Metable {
static get modifiers() {
return {
whereKey(builder, key) {
builder.where('key', key);
},
};
}
}

View File

@@ -0,0 +1,215 @@
import { Model } from 'objection';
import { omit, isEmpty } from 'lodash';
import { IMetadata, IMetaQuery, IMetableStore } from './types';
import { itemsStartWith } from '@/utils/items-start-with';
export class MetableStore implements IMetableStore {
metadata: IMetadata[];
model: Model;
extraColumns: string[];
/**
* Constructor method.
*/
constructor() {
this.metadata = [];
this.model = null;
this.extraColumns = [];
}
/**
* Sets a extra columns.
* @param {Array} columns -
*/
setExtraColumns(columns: string[]): void {
this.extraColumns = columns;
}
/**
* Find the given metadata key.
* @param {string|IMetaQuery} query -
* @returns {IMetadata} - Metadata object.
*/
find(query: string | IMetaQuery): IMetadata {
const { key, value, ...extraColumns } = this.parseQuery(query);
return this.metadata.find((meta: IMetadata) => {
const isSameKey = meta.key === key;
const sameExtraColumns = this.extraColumns.some(
(extraColumn: string) => extraColumns[extraColumn] === meta[extraColumn]
);
const isSameExtraColumns = sameExtraColumns || isEmpty(extraColumns);
return isSameKey && isSameExtraColumns;
});
}
/**
* Retrieve all metadata.
* @returns {IMetadata[]}
*/
all(): IMetadata[] {
return this.metadata
.filter((meta: IMetadata) => !meta._markAsDeleted)
.map((meta: IMetadata) =>
omit(meta, itemsStartWith(Object.keys(meta), '_'))
);
}
/**
* Retrieve metadata of the given key.
* @param {String} key -
* @param {Mixied} defaultValue -
*/
get(query: string | IMetaQuery, defaultValue?: any): any | null {
const metadata = this.find(query);
return metadata
? metadata.value
: typeof defaultValue !== 'undefined'
? defaultValue
: null;
}
/**
* Markes the metadata to should be deleted.
* @param {String} key -
*/
remove(query: string | IMetaQuery): void {
const metadata: IMetadata = this.find(query);
if (metadata) {
metadata._markAsDeleted = true;
}
}
/**
* Remove all meta data of the given group.
* @param {string} group
*/
removeAll(group: string = 'default'): void {
this.metadata = this.metadata.map((meta) => ({
...meta,
_markAsDeleted: true,
}));
}
/**
* Set the meta data to the stack.
* @param {String} key -
* @param {String} value -
*/
set(query: IMetaQuery | IMetadata[] | string, metaValue?: any): void {
if (Array.isArray(query)) {
const metadata = query;
metadata.forEach((meta: IMetadata) => {
this.set(meta);
});
return;
}
const { key, value, ...extraColumns } = this.parseQuery(query);
const metadata = this.find(query);
const newValue = metaValue || value;
if (metadata) {
metadata.value = newValue;
metadata._markAsUpdated = true;
} else {
this.metadata.push({
value: newValue,
key,
...extraColumns,
_markAsInserted: true,
group: 'default',
});
}
}
/**
* Parses query query.
* @param query
* @param value
*/
parseQuery(query: string | IMetaQuery): IMetaQuery {
return typeof query !== 'object' ? { key: query } : { ...query };
}
/**
* Format the metadata before saving to the database.
* @param {string|number|boolean} value -
* @param {string} valueType -
* @return {string|number|boolean} -
*/
static formatMetaValue(
value: string | boolean | number,
valueType: string
): string | number | boolean {
let parsedValue;
switch (valueType) {
case 'number':
parsedValue = `${value}`;
break;
case 'boolean':
parsedValue = value ? '1' : '0';
break;
case 'json':
parsedValue = JSON.stringify(parsedValue);
break;
default:
parsedValue = value;
break;
}
return parsedValue;
}
/**
* Parse the metadata to the collection.
* @param {Array} collection -
*/
// mapMetadataToCollection(metadata: IMetadata[], parseType: string = 'parse') {
// return metadata.map((model) =>
// this.mapMetadataToCollection(model, parseType)
// );
// }
/**
* Load metadata to the metable collection.
* @param {Array} meta -
*/
from(meta: []) {
if (Array.isArray(meta)) {
meta.forEach((m) => {
this.from(m);
});
return;
}
this.metadata.push(meta);
}
/**
*
* @returns {array}
*/
toArray(): IMetadata[] {
return this.metadata;
}
/**
* Static method to load metadata to the collection.
* @param {Array} meta
*/
// static from(meta) {
// const collection = new MetableCollection();
// collection.from(meta);
// return collection;
// }
/**
* Reset the momerized metadata.
*/
resetMetadata() {
this.metadata = [];
}
}

View File

@@ -0,0 +1,247 @@
import { IMetadata, IMetableStoreStorage } from './types';
import { MetableStore } from './MetableStore';
import { MetableConfig } from './MetableConfig';
import { EntityRepository } from '@/common/repository/EntityRepository';
import { isBlank } from '@/utils/is-blank';
import { parseBoolean } from '@/utils/parse-boolean';
export class MetableDBStore
extends MetableStore
implements IMetableStoreStorage
{
repository: any;
KEY_COLUMN: string;
VALUE_COLUMN: string;
TYPE_COLUMN: string;
extraQuery: Function;
loaded: Boolean;
config: MetableConfig;
extraColumns: Array<string>;
/**
* Constructor method.
*/
constructor(config: any) {
super();
this.loaded = false;
this.KEY_COLUMN = 'key';
this.VALUE_COLUMN = 'value';
this.TYPE_COLUMN = 'type';
this.repository = null;
this.extraQuery = (meta) => {
return {
key: meta[this.KEY_COLUMN],
...this.transfromMetaExtraColumns(meta),
};
};
this.config = new MetableConfig(config);
}
/**
* Transformes meta query.
* @param {IMetadata} meta
*/
private transfromMetaExtraColumns(meta: IMetadata) {
return this.extraColumns.reduce((obj, column) => {
const metaValue = meta[column];
if (!isBlank(metaValue)) {
obj[column] = metaValue;
}
return obj;
}, {});
}
/**
* Set repository entity of this metadata collection.
* @param {Object} repository -
*/
setRepository(repository: EntityRepository) {
this.repository = repository;
}
/**
* Sets a extra query callback.
* @param callback
*/
setExtraQuery(callback) {
this.extraQuery = callback;
}
/**
* Saves the modified, deleted and insert metadata.
*/
async save() {
this.validateStoreIsLoaded();
await Promise.all([
this.saveUpdated(this.metadata),
this.saveDeleted(this.metadata),
this.saveInserted(this.metadata),
]);
}
/**
* Saves the updated metadata.
* @param {IMetadata[]} metadata -
* @returns {Promise}
*/
saveUpdated(metadata: IMetadata[]) {
const updated = metadata.filter((m) => m._markAsUpdated === true);
const opers = [];
updated.forEach((meta) => {
const updateOper = this.repository
.update(
{ [this.VALUE_COLUMN]: meta.value },
{ ...this.extraQuery(meta) },
)
.then(() => {
meta._markAsUpdated = false;
});
opers.push(updateOper);
});
return Promise.all(opers);
}
/**
* Saves the deleted metadata.
* @param {IMetadata[]} metadata -
* @returns {Promise}
*/
saveDeleted(metadata: IMetadata[]) {
const deleted = metadata.filter(
(m: IMetadata) => m._markAsDeleted === true,
);
const opers: Array<Promise<void>> = [];
if (deleted.length > 0) {
deleted.forEach((meta) => {
const deleteOper = this.repository
.deleteBy({
...this.extraQuery(meta),
})
.then(() => {
meta._markAsDeleted = false;
});
opers.push(deleteOper);
});
}
return Promise.all(opers);
}
/**
* Saves the inserted metadata.
* @param {IMetadata[]} metadata -
* @returns {Promise}
*/
saveInserted(metadata: IMetadata[]) {
const inserted = metadata.filter(
(m: IMetadata) => m._markAsInserted === true,
);
const opers: Array<Promise<void>> = [];
inserted.forEach((meta) => {
const insertData = {
[this.KEY_COLUMN]: meta.key,
[this.VALUE_COLUMN]: meta.value,
...this.transfromMetaExtraColumns(meta),
};
const insertOper = this.repository.create(insertData).then(() => {
meta._markAsInserted = false;
});
opers.push(insertOper);
});
return Promise.all(opers);
}
/**
* Loads the metadata from the storage.
* @param {String|Array} key -
* @param {Boolean} force -
*/
async load() {
const metadata = await this.repository.all();
const mappedMetadata = this.mapMetadataCollection(metadata);
this.resetMetadata();
mappedMetadata.forEach((meta: IMetadata) => {
this.metadata.push(meta);
});
this.loaded = true;
}
/**
* Parse the metadata values after fetching it from the storage.
* @param {String|Number|Boolean} value -
* @param {String} valueType -
* @return {String|Number|Boolean} -
*/
static parseMetaValue(
value: string,
valueType: string | false,
): string | boolean | number {
let parsedValue: string | number | boolean;
switch (valueType) {
case 'number':
parsedValue = parseFloat(value);
break;
case 'boolean':
parsedValue = parseBoolean(value, false);
break;
case 'json':
parsedValue = JSON.stringify(parsedValue);
break;
default:
parsedValue = value;
break;
}
return parsedValue;
}
/**
* Mapping and parse metadata to collection entries.
* @param {Meta} attr -
* @param {String} parseType -
*/
mapMetadata(metadata: IMetadata) {
const metaType = this.config.getMetaType(
metadata[this.KEY_COLUMN],
metadata['group'],
);
return {
key: metadata[this.KEY_COLUMN],
value: MetableDBStore.parseMetaValue(
metadata[this.VALUE_COLUMN],
metaType,
),
...this.extraColumns.reduce((obj, extraCol: string) => {
obj[extraCol] = metadata[extraCol] || null;
return obj;
}, {}),
};
}
/**
* Parse the metadata to the collection.
* @param {Array} collection -
*/
mapMetadataCollection(metadata: IMetadata[]) {
return metadata.map((model) => this.mapMetadata(model));
}
/**
* Throw error in case the store is not loaded yet.
*/
private validateStoreIsLoaded() {
if (!this.loaded) {
throw new Error(
'You could not save the store before loaded from the storage.',
);
}
}
}

View File

@@ -0,0 +1,27 @@
export interface IMetadata {
key: string;
value: string | boolean | number;
group: string;
_markAsDeleted?: boolean;
_markAsInserted?: boolean;
_markAsUpdated?: boolean;
}
export interface IMetaQuery {
key: string;
group?: string;
value?: any;
}
export interface IMetableStore {
find(query: string | IMetaQuery): IMetadata;
all(): IMetadata[];
get(query: string | IMetaQuery, defaultValue: any): string | number | boolean;
remove(query: string | IMetaQuery): void;
removeAll(): void;
toArray(): IMetadata[];
}
export interface IMetableStoreStorage {
save(): Promise<void>;
}