@cmmv/repository
Version:
Repository module using TypeORM for CMMV
1,301 lines (1,300 loc) • 49.6 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.RepositorySchema = exports.Repository = void 0;
const tslib_1 = require("tslib");
const path = require("node:path");
const node_process_1 = require("node:process");
const fg = require("fast-glob");
const typeorm_1 = require("typeorm");
const core_1 = require("@cmmv/core");
const mongodb_1 = require("mongodb");
class Repository extends core_1.Singleton {
/**
* Load the repository configuration and initialize the database connection
* @returns Promise<void>
*/
static async loadConfig() {
const instance = Repository.getInstance();
const config = core_1.Config.get('repository');
const sourceDir = core_1.Config.get('app.sourceDir', 'src');
const migrations = core_1.Config.get('repository.migrations', true);
const migrationsDir = core_1.Config.get('repository.migrationsDir', path.join((0, node_process_1.cwd)(), 'src', 'migrations'));
const entitiesDir = path.join((0, node_process_1.cwd)(), sourceDir, 'entities');
const entitiesGeneratedDir = path.join((0, node_process_1.cwd)(), '.generated', 'entities');
const migrationsGeneratedDir = path.join((0, node_process_1.cwd)(), '.generated', 'migrations');
try {
const AppDataSource = new typeorm_1.DataSource({
...config,
entities: [
`${entitiesDir}/**/*.entity.ts`,
`${entitiesGeneratedDir}/**/*.entity.ts`,
],
migrations: migrations
? [
`${migrationsDir}/**/*.ts`,
`${migrationsGeneratedDir}/**/*.ts`,
]
: [],
});
instance.dataSource = await AppDataSource.initialize();
}
catch (error) {
this.logger.error('Database connection error:', error);
const AppDataSource = new typeorm_1.DataSource({
...config,
entities: [
`${entitiesDir}/**/*.entity.ts`,
`${entitiesGeneratedDir}/**/*.entity.ts`,
],
migrations: migrations
? [
`${migrationsDir}/**/*.ts`,
`${migrationsGeneratedDir}/**/*.ts`,
]
: [],
synchronize: false,
});
instance.dataSource = await AppDataSource.initialize();
}
if (instance.dataSource) {
instance.dataSource.entityMetadatas?.forEach((entity) => {
Repository.entities.set(entity.name, entity.target);
if (entity.name === 'LogsEntity')
Repository.logEntity = entity.target;
});
}
// Load settings
const SettingsEntity = this.getEntity('SettingsEntity');
const settings = await this.findAll(SettingsEntity, {
limit: 1000,
}, [], { select: ['key', 'value', 'type', 'flags', 'group'] });
if (settings.data && settings.data.length > 0) {
settings.data.forEach((setting) => {
let value = setting.value;
switch (setting.type) {
case 'json':
value = JSON.parse(value);
break;
case 'array':
value = value.split(',');
break;
case 'boolean':
value = value === 'true';
break;
case 'number':
value = parseInt(value);
break;
case 'float':
value = parseFloat(value);
break;
}
core_1.Config.set(setting.key, value);
});
}
}
static async log(message) {
if (Repository.logEntity !== null) {
const repository = this.getRepository(Repository.logEntity);
try {
switch (message.context) {
case 'HTTP':
const split = message.message.split(' ');
const method = split[0];
const url = split[1];
const processTime = split[2]
.replace('(', '')
.replace(')', '');
const status = split[3];
const ip = split[4];
let messageLog = '';
switch (status) {
case '200':
messageLog = `connection authorized: method="${method}" url="${url}" ip="${ip}" process_time="${processTime}" status="${status}"`;
break;
case '401':
messageLog = `connection unauthorized: method="${method}" url="${url}" ip="${ip}" process_time="${processTime}" status="${status}"`;
break;
case '403':
messageLog = `connection forbidden: method="${method}" url="${url}" ip="${ip}" process_time="${processTime}" status="${status}"`;
break;
case '404':
messageLog = `connection not found: method="${method}" url="${url}" ip="${ip}" process_time="${processTime}" status="${status}"`;
break;
case '500':
messageLog = `connection error: method="${method}" url="${url}" ip="${ip}" process_time="${processTime}" status="${status}"`;
break;
}
if (messageLog) {
const newEntity = repository.create({
message: messageLog,
timestamp: message.timestamp,
source: message.context,
level: message.level,
event: JSON.stringify({
process_id: process.pid,
method: method,
url: url,
process_time: processTime,
status: status,
connection_from: ip,
}),
});
await repository.save(newEntity);
}
break;
case 'DATABASE':
const config = core_1.Config.get('repository');
const newEntityDatabase = repository.create({
message: `query: database="${config.database}" entity="${message.metadata?.entity}" total="${message.metadata?.total}"`,
timestamp: message.timestamp,
source: message.context,
level: message.level,
event: JSON.stringify({
process_id: process.pid,
process_time: message.metadata?.process_time,
}),
metadata: JSON.stringify(message.metadata ?? {}),
});
await repository.save(newEntityDatabase);
break;
case 'AUTH':
if (message.message) {
const newEntityAuth = repository.create({
message: message.message,
timestamp: message.timestamp,
source: message.context,
level: message.level,
event: JSON.stringify(message.event ?? {}),
metadata: JSON.stringify(message.metadata ?? {}),
});
await repository.save(newEntityAuth);
}
break;
case 'EVENT':
if (message.message) {
const newEntityDefault = repository.create({
message: message.message,
timestamp: message.timestamp,
source: message.context,
level: message.level,
metadata: JSON.stringify(message.metadata ?? {}),
});
await repository.save(newEntityDefault);
}
break;
}
}
catch (e) {
Repository.logger.error('Error saving log:', e);
}
}
}
/**
* Get the data source instance
* @returns DataSource
*/
static getDataSource() {
core_1.Config.loadConfig();
const config = core_1.Config.get('repository');
const sourceDir = core_1.Config.get('app.sourceDir', 'src');
const entitiesDir = path.join((0, node_process_1.cwd)(), sourceDir, 'entities');
const entitiesGeneratedDir = path.join((0, node_process_1.cwd)(), '.generated', 'entities');
const migrationsDir = path.join((0, node_process_1.cwd)(), sourceDir, 'migrations');
const migrationsGeneratedDir = path.join((0, node_process_1.cwd)(), '.generated', 'migrations');
const entityFiles = fg.sync([
`${entitiesDir}/**/*.entity.ts`,
`${entitiesGeneratedDir}/**/*.entity.ts`,
]);
const migrationFiles = fg.sync([
`${migrationsDir}/**/*.ts`,
`${migrationsGeneratedDir}/**/*.ts`,
]);
const AppDataSource = new typeorm_1.DataSource({
...config,
entities: entityFiles,
migrations: migrationFiles,
synchronize: false,
});
return AppDataSource;
}
/**
* Generate a MongoDB connection URL
* @returns string
*/
static generateMongoUrl() {
const config = core_1.Config.get('repository');
const protocol = 'mongodb';
const username = config.username
? encodeURIComponent(config.username)
: '';
const password = config.password
? encodeURIComponent(config.password)
: '';
const authSource = config.authSource
? `?authSource=${config.authSource}`
: '';
const replicaSet = config.replicaSet
? `&replicaSet=${config.replicaSet}`
: '';
const host = Array.isArray(config.host)
? config.host.join(',')
: config.host;
const port = config.port ? `:${config.port}` : '';
const database = config.database ? `/${config.database}` : '';
if (username && password)
return `${protocol}://${username}:${password}@${host}${port}${database}${authSource}${replicaSet}`;
return `${protocol}://${host}${port}${database}${authSource}${replicaSet}`;
}
/**
* Get a repository instance for a given entity
* @param entity - The entity type
* @returns TypeORMRepository<Entity>
*/
static getRepository(entity) {
const instance = Repository.getInstance();
return instance.dataSource.getRepository(entity);
}
/**
* Get the ID field for the repository
* @returns string
*/
static getIdField() {
return core_1.Config.get('repository.type') === 'mongodb' ? '_id' : 'id';
}
/**
* Fix the ID for the repository
* @param id - The ID to fix
* @returns ObjectId | string
*/
static fixId(id) {
if (typeof id === 'string')
return core_1.Config.get('repository.type') === 'mongodb'
? new mongodb_1.ObjectId(id)
: id;
else
return core_1.Config.get('repository.type') === 'mongodb'
? id
: id.toString();
}
/**
* Fix the ID for the repository
* @param value - The value to fix
* @returns ObjectId | string
*/
static fixObjectIds(value) {
const isMongoDB = core_1.Config.get('repository.type') === 'mongodb';
if (typeof value === 'string') {
value = this.fixId(value);
}
else if (typeof value === 'object' && value !== null) {
if (value.$in && Array.isArray(value.$in)) {
value = isMongoDB
? { $in: value.$in.map((id) => this.fixId(id)) }
: (0, typeorm_1.In)(value.$in.map((id) => this.fixId(id)));
}
else {
value = value;
}
}
return value;
}
/**
* Escape a string for the repository
* @param str - The string to escape
* @returns string
*/
static escape(str) {
if (typeof str !== 'string')
return str;
return str
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''')
.replace(/\\/g, '\\\\')
.replace(/\$/g, '\\$')
.replace(/\//g, '\\/');
}
/**
* Get an entity for the repository
* @param name - The name of the entity
* @returns new () => any | null
*/
static getEntity(name) {
if (Repository.entities.has(name))
return Repository.entities.get(name);
throw new Error(`Could not load entity '${name}'`);
}
/**
* Query builder for the repository
* @param payload - The payload to query
* @returns FindOptionsWhere<Entity>
*/
static queryBuilder(payload) {
let query = {};
for (let key in payload) {
if ((key === 'id' || key === '_id') &&
typeof payload[key] === 'string')
query[this.getIdField()] = this.fixId(payload[key]);
else if ((key === 'id' || key === '_id') &&
typeof payload[key] === 'object')
query[this.getIdField()] = this.fixObjectIds(payload[key]);
else
query[key] = payload[key];
}
return query;
}
/**
* Validate the criteria for the repository
* @param entity - The entity type
* @param criteria - The criteria to validate
* @returns boolean
*/
static validateCriteria(entity, criteria) {
if (!criteria || Object.keys(criteria).length === 0)
return true;
const instance = Repository.getInstance();
try {
const metadata = instance.dataSource.getMetadata(entity);
const entityColumns = metadata.columns.map((col) => col.propertyName);
const entityRelations = metadata.relations.map((rel) => rel.propertyName);
const validFields = [
...entityColumns,
...entityRelations,
'_id',
'id',
];
if (Array.isArray(criteria)) {
for (const criteriaItem of criteria) {
for (const field in criteriaItem) {
if (field.startsWith('$'))
continue;
if (!validFields.includes(field))
throw new Error(`Invalid field in search criteria: '${field}' does not exist in entity ${metadata.name}`);
}
}
return true;
}
for (const field in criteria) {
if (field.startsWith('$'))
continue;
if (!validFields.includes(field))
throw new Error(`Invalid field in search criteria: '${field}' does not exist in entity ${metadata.name}`);
}
return true;
}
catch (error) {
if (error.message.includes('Invalid field'))
throw error;
Repository.logger.error(`Error validating criteria: ${error.message}`);
throw new Error('Error validating search criteria');
}
}
/**
* Validate the query options for the repository
* @param entity - The entity type
* @param options - The options to validate
* @returns boolean
*/
static validateQueryOptions(entity, options) {
if (!options)
return true;
const instance = Repository.getInstance();
try {
const metadata = instance.dataSource.getMetadata(entity);
const entityColumns = metadata.columns.map((col) => col.propertyName);
const entityRelations = metadata.relations.map((rel) => rel.propertyName);
const validFields = [
...entityColumns,
...entityRelations,
'_id',
'id',
];
if (options.take !== undefined) {
if (typeof options.take !== 'number' || options.take <= 0)
throw new Error('The limit parameter must be a positive number');
if (options.take > 1000)
throw new Error('The maximum limit for results per query is 1000');
}
if (options.skip !== undefined) {
if (typeof options.skip !== 'number' || options.skip < 0)
throw new Error('The offset parameter must be a non-negative number');
}
if (options.order) {
const orderFields = Object.keys(options.order);
for (const field of orderFields) {
if (!validFields.includes(field))
throw new Error(`Invalid field for sorting: '${field}' does not exist in entity ${metadata.name}`);
const hasIndex = metadata.indices.some((index) => index.columns.some((col) => col.databaseName === field ||
(field === 'id' && col.databaseName === '_id')));
if (!hasIndex && field !== 'id' && field !== '_id')
throw new Error(`Sorting by non-indexed field is not allowed: '${field}'. Create an index for this field.`);
}
}
if (options.select) {
const selectFields = Object.values(options.select);
for (const field of selectFields) {
if (!validFields.includes(field))
throw new Error(`Invalid field in select: '${field}' does not exist in entity ${metadata.name}`);
}
}
if (options.where)
return this.validateCriteria(entity, options.where);
return true;
}
catch (error) {
if (error.message.includes('Invalid field') ||
error.message.includes('maximum limit') ||
error.message.includes('Sorting by non-indexed field')) {
throw error;
}
Repository.logger.error(`Error validating query options: ${error.message}`);
throw new Error('Error validating query options');
}
}
/**
* Find a single entity by criteria
* @param entity - The entity type
* @param criteria - The criteria to find the entity by
* @param options - The options to find the entity by
* @returns Entity | null
*/
static async findOne(entity, criteria, options = {}) {
if (!this.validateCriteria(entity, criteria))
throw new Error('Invalid criteria');
return await this.findBy(entity, criteria, options);
}
/**
* Find an entity by criteria
* @param entity - The entity type
* @param criteria - The criteria to find the entity by
* @param options - The options to find the entity by
* @returns Entity | null
*/
static async findBy(entity, criteria, options = {}) {
try {
if (!this.validateCriteria(entity, criteria))
throw new Error('Invalid criteria');
const repository = this.getRepository(entity);
const registry = await repository.findOne({
where: criteria,
...options,
});
return registry ? registry : null;
}
catch (e) {
if (process.env.NODE_ENV === 'dev')
Repository.logger.error(e.message);
return null;
}
}
/**
* Find all entities by criteria
* @param entity - The entity type
* @param queries - The queries to find the entities by
* @param relations - The relations to find the entities by
* @param options - The options to find the entities by
* @returns IFindResponse | null
*/
static async findAll(entity, queries, relations, options) {
try {
const isMongoDB = core_1.Config.get('repository.type') === 'mongodb';
const repository = this.getRepository(entity);
const limit = Math.max(1, Math.min(1000, parseInt(queries?.limit) || 10));
const offset = Math.max(0, parseInt(queries?.offset) || 0);
const sortBy = this.escape(queries?.sortBy || 'id');
const sort = queries?.sort?.toUpperCase() === 'DESC' ? 'DESC' : 'ASC';
const search = this.escape(queries?.search || '');
const searchField = this.escape(queries?.searchField || '');
const filters = queries ? { ...queries } : {};
delete filters.limit;
delete filters.offset;
delete filters.sortBy;
delete filters.sort;
delete filters.search;
delete filters.searchField;
const where = {};
if (search && searchField) {
if (isMongoDB)
where[searchField] = { $regex: new RegExp(search, 'i') };
else
where[searchField] = (0, typeorm_1.Like)(`%${search}%`);
}
Object.entries(filters).forEach(([key, value]) => {
where[key] = this.escape(value);
});
if (!options)
options = {};
const order = {
[sortBy]: sort,
};
const queryOptions = {
where,
relations,
take: limit,
skip: offset,
order,
...options,
};
const start = Date.now();
if (!this.validateQueryOptions(entity, queryOptions))
throw new Error('Invalid query options');
const total = await repository.count({
where: queryOptions.where,
...options,
});
const results = await repository.find(queryOptions);
const end = Date.now();
if (entity.name !== 'LogsEntity') {
core_1.Hooks.execute(core_1.HooksType.Log, {
context: 'DATABASE',
message: `findAll: ${entity.name} (${end - start}ms)`,
level: 'INFO',
timestamp: Date.now(),
metadata: {
total,
entity: entity.name,
process_time: end - start,
...queryOptions,
},
});
}
return {
data: results,
count: total,
pagination: {
limit,
offset,
sortBy,
sort,
search,
searchField,
filters,
},
};
}
catch (error) {
console.error('Database findAll error:', error);
return null;
}
}
/**
* Count an entity by criteria
* @param entity - The entity type
* @param criteria - The criteria to count the entity by
* @param options - The options to count the entity by
* @returns number
*/
static async count(entity, criteria, options = {}) {
const repository = this.getRepository(entity);
return await repository.count({
where: criteria,
...options,
});
}
/**
* Insert an entity into the repository
* @param entity - The entity type
* @param data - The data to insert
* @returns IInsertResponse
*/
static async insert(entity, data) {
try {
const repository = this.getRepository(entity);
const newEntity = repository.create(data);
return { data: await repository.save(newEntity), success: true };
}
catch (e) {
return { success: false, message: e.message };
}
}
/**
* Insert an entity into the repository if it does not exist
* @param entity - The entity type
* @param data - The data to insert
* @param fieldFilter - The field to filter by
* @returns boolean
*/
static async insertIfNotExists(entity, data, fieldFilter) {
try {
let criteria = {};
criteria[fieldFilter] = data[fieldFilter];
const repository = this.getRepository(entity);
const exists = await repository.findOne({ where: criteria });
if (!exists) {
const newEntity = repository.create(data);
await repository.save(newEntity);
}
}
catch (e) {
return false;
}
}
/**
* Update an entity in the repository
* @param entity - The entity type
* @param id - The ID of the entity
* @param data - The data to update
* @returns number
*/
static async update(entity, id, data) {
try {
const repository = this.getRepository(entity);
const result = await repository.update(id, data);
return result.affected;
}
catch (e) {
console.log(e);
return 0;
}
}
/**
* Update an entity in the repository by criteria
* @param entity - The entity type
* @param criteria - The criteria to update the entity by
* @param data - The data to update
* @returns boolean
*/
static async updateOne(entity, criteria, data) {
try {
const repository = this.getRepository(entity);
const existingRecord = await repository.findOne({
where: criteria,
});
if (!existingRecord)
return false;
Object.assign(existingRecord, data);
await repository.save(existingRecord);
return true;
}
catch (e) {
return false;
}
}
/**
* Update an entity in the repository by ID
* @param entity - The entity type
* @param id - The ID of the entity
* @param data - The data to update
* @returns boolean
*/
static async updateById(entity, id, data) {
try {
const repository = this.getRepository(entity);
const query = {
[this.getIdField()]: this.fixId(id),
};
const existingRecord = await repository.findOne({
where: query,
select: {
[this.getIdField()]: true,
},
});
if (!existingRecord)
return false;
Object.assign(existingRecord, data);
await repository.save(existingRecord);
return true;
}
catch (e) {
return false;
}
}
/**
* Upsert an entity into the repository
* @param entity - The entity type
* @param criteria - The criteria to upsert the entity by
* @param data - The data to upsert
* @returns boolean
*/
static async upsert(entity, criteria, data) {
const repository = this.getRepository(entity);
const existingRecord = await this.findOne(entity, criteria);
if (existingRecord)
return await this.update(entity, existingRecord[this.getIdField()], data);
return await this.insert(entity, data);
}
/**
* Delete an entity from the repository
* @param entity - The entity type
* @param id - The ID of the entity
* @returns number
*/
static async delete(entity, id) {
try {
const repository = this.getRepository(entity);
const result = await repository.delete(id);
return result.affected;
}
catch (e) {
return 0;
}
}
/**
* Check if an entity exists in the repository
* @param entity - The entity type
* @param criteria - The criteria to check if the entity exists by
* @returns boolean
*/
static async exists(entity, criteria) {
try {
const repository = this.getRepository(entity);
const result = await repository.findOne({
where: criteria,
select: {
[this.getIdField()]: true,
},
});
return result !== null;
}
catch (e) {
return false;
}
}
/**
* List all databases
* @returns { databases: string[] }
*/
static async listDatabases() {
const instance = this.getInstance();
const type = core_1.Config.get('repository.type');
if (type === 'mongodb') {
const client = new mongodb_1.MongoClient(Repository.generateMongoUrl());
const conn = await client.connect();
const result = await conn.db().admin().listDatabases();
return { databases: result.databases.map((db) => db.name) };
}
if (type === 'sqlite') {
throw new Error(`SQLite does not support database listing`);
}
else {
const queryRunner = instance.dataSource.createQueryRunner();
const databases = await queryRunner.query('SHOW DATABASES');
await queryRunner.release();
return {
databases: databases.map((db) => Object.values(db)[0]),
};
}
}
/**
* List all tables in a database
* @param database - The database to list the tables from
* @returns { tables: string[] }
*/
static async listTables(database) {
const instance = this.getInstance();
const type = core_1.Config.get('repository.type');
if (type === 'mongodb') {
const client = new mongodb_1.MongoClient(Repository.generateMongoUrl());
const conn = await client.connect();
const result = await conn
.db(database)
.listCollections()
.toArray();
return { tables: result.map((collection) => collection.name) };
}
else {
const queryRunner = instance.dataSource.createQueryRunner();
let query = '';
switch (type) {
case 'mysql':
query = `SHOW TABLES FROM \`${database}\``;
break;
case 'postgres':
query = `SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_catalog = '${database}'`;
break;
case 'mssql':
query = `SELECT name FROM ${database}.sys.tables`;
break;
case 'sqlite':
query = `SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'`;
break;
default:
throw new Error(`Database type '${type}' is not supported for table listing.`);
}
const result = await queryRunner.query(query);
await queryRunner.release();
return { tables: result.map((row) => Object.values(row)[0]) };
}
}
/**
* List all indexes for a table
* @param table - The table to list the indexes from
* @returns any[]
*/
static async listIndexes(table) {
try {
const queryRunner = this.getInstance().dataSource.createQueryRunner();
const indexes = await queryRunner.getTable(table);
await queryRunner.release();
return indexes ? indexes.indices : [];
}
catch (error) {
Repository.logger.error(`Error listing indexes for ${table}: ${error.message}`);
return [];
}
}
/**
* Create a table in the repository
* @param tableName - The name of the table to create
* @param columns - The columns to create in the table
* @returns boolean
*/
static async createTable(tableName, columns) {
try {
const queryRunner = this.getInstance().dataSource.createQueryRunner();
await queryRunner.createTable(new typeorm_1.Table({
name: tableName,
columns: columns,
}));
await queryRunner.release();
return true;
}
catch (error) {
Repository.logger.error(`Error creating table ${tableName}: ${error.message}`);
return false;
}
}
/**
* Update an index in the repository
* @param table - The table to update the index on
* @param indexName - The name of the index to update
* @param newIndexDefinition - The new index definition
* @returns boolean
*/
static async updateIndex(table, indexName, newIndexDefinition) {
try {
const queryRunner = this.getInstance().dataSource.createQueryRunner();
const tableSchema = await queryRunner.getTable(table);
if (!tableSchema)
return false;
const existingIndex = tableSchema.indices.find((index) => index.name === indexName);
if (existingIndex)
await queryRunner.dropIndex(table, indexName);
await queryRunner.createIndex(table, new typeorm_1.TableIndex(newIndexDefinition));
await queryRunner.release();
return true;
}
catch (error) {
Repository.logger.error(`Error updating index ${indexName} on table ${table}: ${error.message}`);
return false;
}
}
/**
* Remove an index from a table
* @param table - The table to remove the index from
* @param indexName - The name of the index to remove
* @returns boolean
*/
static async removeIndex(table, indexName) {
try {
const queryRunner = this.getInstance().dataSource.createQueryRunner();
await queryRunner.dropIndex(table, indexName);
await queryRunner.release();
return true;
}
catch (error) {
Repository.logger.error(`Error removing index ${indexName} from table ${table}: ${error.message}`);
return false;
}
}
/**
* List all fields for a table
* @param table - The table to list the fields from
* @returns any[]
*/
static async listFields(table) {
try {
const queryRunner = this.getInstance().dataSource.createQueryRunner();
const tableSchema = await queryRunner.getTable(table);
await queryRunner.release();
return tableSchema ? tableSchema.columns : [];
}
catch (error) {
Repository.logger.error(`Error listing fields for ${table}: ${error.message}`);
return [];
}
}
/**
* Set a setting in the repository
* @param group - The group of the setting
* @param key - The key of the setting
* @param value - The value of the setting
* @param type - The type of the setting
* @param flags - The flags of the setting
* @returns boolean
*/
static async setSetting(group, key, value, type, flags) {
const SettingsEntity = this.getEntity('SettingsEntity');
const settingsRepository = this.getRepository(SettingsEntity);
const setting = await settingsRepository.findOne({ where: { key } });
core_1.Hooks.execute(core_1.HooksType.onSettingChange, {
key,
value,
type,
flags,
group,
});
if (setting) {
setting.group = group;
setting.value = value;
setting.type = type;
setting.flags = flags;
await settingsRepository.save(setting);
return true;
}
else {
await settingsRepository.insert({ key, value, type, flags, group });
return true;
}
}
/**
* Get a setting from the repository
* @param key - The key of the setting
* @returns any
*/
static async getSetting(key) {
const SettingsEntity = this.getEntity('SettingsEntity');
const settingsRepository = this.getRepository(SettingsEntity);
const setting = await settingsRepository.findOne({ where: { key } });
return setting || null;
}
/**
* Delete a setting from the repository
* @param key - The key of the setting
* @returns boolean
*/
static async deleteSetting(key) {
const SettingsEntity = this.getEntity('SettingsEntity');
const settingsRepository = this.getRepository(SettingsEntity);
const setting = await settingsRepository.findOne({ where: { key } });
if (setting) {
await settingsRepository.delete(setting);
return true;
}
return false;
}
}
exports.Repository = Repository;
Repository.logger = new core_1.Logger('Repository');
Repository.entities = new Map();
Repository.logEntity = null;
tslib_1.__decorate([
(0, core_1.Hook)(core_1.HooksType.Log),
tslib_1.__metadata("design:type", Function),
tslib_1.__metadata("design:paramtypes", [Object]),
tslib_1.__metadata("design:returntype", Promise)
], Repository, "log", null);
class RepositorySchema {
/**
* Constructor for the RepositorySchema class
* @param entity - The entity type
* @param model - The model type
* @param fakeDelete - Whether to fake delete entities
* @param timestamps - Whether to include timestamps in entities
* @param userAction - Whether to include user action in entities
*/
constructor(entity, model, fakeDelete = false, timestamps = false, userAction = false) {
this.entity = entity;
this.model = model;
this.fakeDelete = fakeDelete;
this.timestamps = timestamps;
this.userAction = userAction;
}
/**
* Get all entities
* @param queries - The queries to get the entities by
* @param req - The request object
* @param options - The options to get the entities by
* @returns IFindResponse
*/
async getAll(queries, req, options) {
if (this.fakeDelete)
queries.deleted = false;
let result = await Repository.findAll(this.entity, queries);
if (core_1.Config.get('repository.type') === 'mongodb')
result = this.fixIds(result);
if (!result)
throw new Error('Unable to return a valid result.');
let resultModels = result && result.data.length > 0
? result.data //@ts-ignore
.map((item) => this.model.fromEntity(item))
: [];
if (options && options.resolvers) {
const resolvers = Array.isArray(options.resolvers)
? options.resolvers
: [options.resolvers];
for (let keyResolver in resolvers) {
if (core_1.Resolvers.has(resolvers[keyResolver])) {
for (let key in resultModels) {
resultModels[key] = await core_1.Resolvers.execute(resolvers[keyResolver], resultModels[key]);
}
}
}
}
return {
count: result.count,
pagination: result.pagination,
data: resultModels,
};
}
/**
* Get entities by in array
* @param inArr - The in array
* @param options - The options to get the entities by
* @returns IFindResponse
*/
async getIn(inArr, options) {
const inToAssign = Array.isArray(inArr) ? inArr : [inArr];
let query = {};
if (this.fakeDelete) {
query = Repository.queryBuilder({
id: { $in: inToAssign },
deleted: false,
});
}
else {
query = Repository.queryBuilder({
id: { $in: inToAssign },
});
}
const result = await Repository.findAll(this.entity, query);
let resultModels = result && result.data.length > 0
? result.data //@ts-ignore
.map((item) => this.model.fromEntity(item))
: [];
if (options && options.resolvers) {
const resolvers = Array.isArray(options.resolvers)
? options.resolvers
: [options.resolvers];
for (let keyResolver in resolvers) {
if (core_1.Resolvers.has(resolvers[keyResolver])) {
for (let key in resultModels) {
resultModels[key] = await core_1.Resolvers.execute(resolvers[keyResolver], resultModels[key]);
}
}
}
}
return {
count: result.count,
pagination: result.pagination,
data: resultModels,
};
}
/**
* Get an entity by ID
* @param id - The ID of the entity
* @param options - The options to get the entity by
* @returns IFindResponse
*/
async getById(id, options) {
let query = {};
if (this.fakeDelete)
query = Repository.queryBuilder({ id, deleted: false });
else
query = Repository.queryBuilder({ id });
let result = await Repository.findBy(this.entity, query);
if (core_1.Config.get('repository.type') === 'mongodb')
result = this.fixIds(result);
if (!result)
throw new Error('Unable to return a valid result.');
//@ts-ignore
let resultModel = this.model.fromEntity(result);
if (options && options.resolvers) {
const resolvers = Array.isArray(options.resolvers)
? options.resolvers
: [options.resolvers];
for (let keyResolver in resolvers) {
if (core_1.Resolvers.has(resolvers[keyResolver])) {
resultModel = await core_1.Resolvers.execute(resolvers[keyResolver], resultModel);
}
}
}
return {
count: 1,
pagination: {
limit: 1,
offset: 0,
search: id,
searchField: 'id',
sortBy: 'id',
sort: 'asc',
filters: {},
},
data: resultModel,
};
}
/**
* Count an entity by criteria
* @param criteria - The criteria to count the entity by
* @param options - The options to count the entity by
* @returns number
*/
async count(criteria, options = {}) {
return await Repository.count(this.entity, criteria, options);
}
/**
* Check if an entity exists by criteria
* @param criteria - The criteria to check if the entity exists by
* @returns boolean
*/
async exists(criteria) {
return await Repository.exists(this.entity, criteria);
}
/**
* Insert an entity into the repository
* @param data - The data to insert
* @returns any
*/
async insert(data) {
const result = await Repository.insert(this.entity, data);
if (!result.success)
throw new Error(result.message || 'Insert operation failed');
return { data: this.toModel(this.model, result.data) };
}
/**
* Update an entity in the repository
* @param id - The ID of the entity
* @param data - The data to update
* @returns number
*/
async update(id, data) {
if (data.deleted)
delete data.deleted;
let result = 0;
let dataToUpdate = this.timestamps
? {
...data,
updatedAt: new Date(),
}
: data;
if (this.fakeDelete) {
result = await Repository.update(this.entity, Repository.queryBuilder({ id, deleted: false }), dataToUpdate);
}
else {
result = await Repository.update(this.entity, Repository.queryBuilder({ id }), dataToUpdate);
}
return { success: result > 0, affected: result };
}
/**
* Update an entity in the repository by criteria
* @param criteria - The criteria to update the entity by
* @param data - The data to update
* @returns number
*/
async updateOne(criteria, data) {
return await Repository.updateOne(this.entity, criteria, data);
}
/**
* Update an entity in the repository by ID
* @param id - The ID of the entity
* @param data - The data to update
* @returns number
*/
async updateById(id, data) {
return await Repository.updateById(this.entity, id, data);
}
/**
* Upsert an entity into the repository
* @param criteria - The criteria to upsert the entity by
* @param data - The data to upsert
* @returns boolean
*/
async upsert(criteria, data) {
return await Repository.upsert(this.entity, criteria, data);
}
/**
* Delete an entity from the repository
* @param id - The ID of the entity
* @returns { success: boolean, affected: number }
*/
async delete(id) {
let result = 0;
if (this.fakeDelete) {
result = await Repository.update(this.entity, Repository.queryBuilder({ id }), {
deleted: true,
deletedAt: new Date(),
});
}
else {
result = await Repository.delete(this.entity, Repository.queryBuilder({ id }));
}
return { success: result > 0, affected: result };
}
/**
* Fix the IDs for the repository
* @param item - The item to fix the IDs for
* @param subtree - Whether to fix the IDs for a subtree
* @returns any
*/
fixIds(item, subtree = false) {
if (item && typeof item === 'object') {
if (item._id) {
item.id = item._id.toString();
delete item._id;
}
for (const key in item) {
if (Array.isArray(item[key])) {
item[key] = item[key].map((element) => this.fixIds(element));
}
else if (item[key] instanceof mongodb_1.ObjectId) {
item[key] = item[key].toString();
}
else if (typeof item[key] === 'object' && !subtree) {
item[key] = this.fixIds(item[key], true);
}
}
}
return item;
}
/**
* Convert a partial model to a full model
* @param model - The model to convert
* @param data - The data to convert
* @param req - The request object
* @returns any
*/
fromPartial(model, data, req) {
if (model && model.fromPartial)
return this.extraData(model?.fromPartial(data), req);
else
return data;
}
/**
* Convert a data object to a model
* @param model - The model to convert
* @param data - The data to convert
* @returns any
*/
toModel(model, data) {
const dataFixed = core_1.Config.get('repository.type') === 'mongodb'
? this.fixIds(data)
: data;
return model && model.fromEntity
? model.fromEntity(dataFixed)
: dataFixed;
}
/**
* Extra data from the request
* @param newItem - The new item
* @param req - The request object
* @returns any
*/
extraData(newItem, req) {
const userId = req?.user?.id;
if (typeof userId === 'string') {
try {
newItem.userCreator =
core_1.Config.get('repository.type') === 'mongodb'
? new mongodb_1.ObjectId(userId)
: userId;
}
catch (error) {
console.warn('Error assigning userCreator:', error);
}
}
return newItem;
}
}
exports.RepositorySchema = RepositorySchema;