391 lines
10 KiB
JavaScript
391 lines
10 KiB
JavaScript
const GenericDBApi = require('./base.api');
|
|
const db = require('../models');
|
|
const Utils = require('../utils');
|
|
|
|
const Sequelize = db.Sequelize;
|
|
const Op = Sequelize.Op;
|
|
|
|
class Project_element_defaultsDBApi extends GenericDBApi {
|
|
static get MODEL() {
|
|
return db.project_element_defaults;
|
|
}
|
|
|
|
static get TABLE_NAME() {
|
|
return 'project_element_defaults';
|
|
}
|
|
|
|
static get SEARCHABLE_FIELDS() {
|
|
return ['name', 'element_type'];
|
|
}
|
|
|
|
static get RANGE_FIELDS() {
|
|
return ['sort_order', 'snapshot_version'];
|
|
}
|
|
|
|
static get ENUM_FIELDS() {
|
|
return [];
|
|
}
|
|
|
|
static get ASSOCIATIONS() {
|
|
return [{ field: 'project', setter: 'setProject', isArray: false }];
|
|
}
|
|
|
|
static get RELATION_FILTERS() {
|
|
return [
|
|
{
|
|
filterKey: 'project',
|
|
model: db.projects,
|
|
as: 'project',
|
|
searchField: 'name',
|
|
},
|
|
];
|
|
}
|
|
|
|
static get FIND_ALL_INCLUDES() {
|
|
return [{ association: 'project' }, { association: 'source_element' }];
|
|
}
|
|
|
|
static get CSV_FIELDS() {
|
|
return [
|
|
'id',
|
|
'element_type',
|
|
'name',
|
|
'sort_order',
|
|
'projectId',
|
|
'snapshot_version',
|
|
'createdAt',
|
|
];
|
|
}
|
|
|
|
static get AUTOCOMPLETE_FIELD() {
|
|
return 'name';
|
|
}
|
|
|
|
// Declarative field configuration using base class patterns
|
|
static get JSON_FIELDS() {
|
|
return ['settings_json'];
|
|
}
|
|
|
|
static get FIELD_DEFAULTS() {
|
|
return {
|
|
element_type: { default: null },
|
|
name: { default: null },
|
|
sort_order: { default: 0 },
|
|
source_element_id: { default: null },
|
|
snapshot_version: { default: 1 },
|
|
};
|
|
}
|
|
|
|
static getFieldMapping(data) {
|
|
// Apply base class transformations (JSON fields, defaults, transformers)
|
|
const mapped = super.getFieldMapping(data);
|
|
|
|
// Custom mapping for projectId field (accepts both projectId and project)
|
|
if (mapped.project && !mapped.projectId) {
|
|
mapped.projectId = mapped.project;
|
|
}
|
|
|
|
return {
|
|
id: mapped.id || undefined,
|
|
element_type: mapped.element_type,
|
|
name: mapped.name,
|
|
sort_order: mapped.sort_order,
|
|
settings_json: mapped.settings_json,
|
|
source_element_id: mapped.source_element_id,
|
|
snapshot_version: mapped.snapshot_version,
|
|
projectId: mapped.projectId,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Custom findAll with project filtering
|
|
* Supports both 'project' and 'projectId' query params for consistency
|
|
*/
|
|
static async findAll(filter = {}, options = {}) {
|
|
filter = filter || {};
|
|
const limit = filter.limit || 0;
|
|
const currentPage = +filter.page || 0;
|
|
const offset = currentPage * limit;
|
|
|
|
let where = {};
|
|
|
|
// Support both 'project' and 'projectId' query params
|
|
const projectFilter = filter.project || filter.projectId;
|
|
const terms = projectFilter ? projectFilter.split('|') : [];
|
|
const validUuids = Utils.filterValidUuids(terms);
|
|
|
|
let include = [
|
|
{
|
|
model: db.projects,
|
|
as: 'project',
|
|
where: projectFilter
|
|
? {
|
|
[Op.or]: [
|
|
...(validUuids.length > 0
|
|
? [{ id: { [Op.in]: validUuids } }]
|
|
: []),
|
|
{
|
|
name: {
|
|
[Op.or]: terms.map((term) => ({ [Op.iLike]: `%${term}%` })),
|
|
},
|
|
},
|
|
],
|
|
}
|
|
: {},
|
|
},
|
|
{
|
|
model: db.element_type_defaults,
|
|
as: 'source_element',
|
|
required: false,
|
|
},
|
|
];
|
|
|
|
if (filter.id) {
|
|
if (!Utils.isValidUuid(filter.id)) {
|
|
return { rows: [], count: 0 };
|
|
}
|
|
where.id = filter.id;
|
|
}
|
|
|
|
for (const field of this.SEARCHABLE_FIELDS) {
|
|
if (filter[field]) {
|
|
where[Op.and] = Utils.ilike(this.TABLE_NAME, field, filter[field]);
|
|
}
|
|
}
|
|
|
|
for (const field of this.RANGE_FIELDS) {
|
|
const rangeKey = `${field}Range`;
|
|
if (filter[rangeKey]) {
|
|
const [start, end] = filter[rangeKey];
|
|
if (start !== undefined && start !== null && start !== '') {
|
|
where[field] = { ...where[field], [Op.gte]: start };
|
|
}
|
|
if (end !== undefined && end !== null && end !== '') {
|
|
where[field] = { ...where[field], [Op.lte]: end };
|
|
}
|
|
}
|
|
}
|
|
|
|
for (const field of this.ENUM_FIELDS) {
|
|
if (filter[field] !== undefined) {
|
|
where[field] = filter[field];
|
|
}
|
|
}
|
|
|
|
if (filter.createdAtRange) {
|
|
const [start, end] = filter.createdAtRange;
|
|
if (start !== undefined && start !== null && start !== '') {
|
|
where.createdAt = { ...where.createdAt, [Op.gte]: start };
|
|
}
|
|
if (end !== undefined && end !== null && end !== '') {
|
|
where.createdAt = { ...where.createdAt, [Op.lte]: end };
|
|
}
|
|
}
|
|
|
|
const queryOptions = {
|
|
where,
|
|
include,
|
|
distinct: true,
|
|
order:
|
|
filter.field && filter.sort
|
|
? [[filter.field, filter.sort]]
|
|
: [['sort_order', 'asc']],
|
|
transaction: options.transaction,
|
|
};
|
|
|
|
if (!options.countOnly) {
|
|
queryOptions.limit = limit ? Number(limit) : undefined;
|
|
queryOptions.offset = offset ? Number(offset) : undefined;
|
|
}
|
|
|
|
try {
|
|
const { rows, count } = await this.MODEL.findAndCountAll(queryOptions);
|
|
return {
|
|
rows: options.countOnly ? [] : rows,
|
|
count,
|
|
};
|
|
} catch (error) {
|
|
console.error('Error executing query:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Find project element default by element type for a specific project
|
|
*/
|
|
static async findByElementType(projectId, elementType, options = {}) {
|
|
return this.MODEL.findOne({
|
|
where: {
|
|
projectId,
|
|
element_type: elementType,
|
|
deletedAt: null,
|
|
},
|
|
...options,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Snapshot all global element defaults to a project
|
|
* Used when creating a new project
|
|
*/
|
|
static async snapshotGlobalDefaults(projectId, options = {}) {
|
|
const Element_type_defaultsDBApi = require('./element_type_defaults');
|
|
|
|
// Get all global defaults
|
|
const globalDefaults = await Element_type_defaultsDBApi.findAll({});
|
|
|
|
if (!globalDefaults?.rows?.length) {
|
|
return [];
|
|
}
|
|
|
|
// Dedupe by element_type (keep first occurrence)
|
|
// Prevents unique constraint violations if global defaults have duplicates
|
|
const seenTypes = new Set();
|
|
const dedupedDefaults = globalDefaults.rows.filter((row) => {
|
|
if (seenTypes.has(row.element_type)) {
|
|
console.warn(
|
|
`Duplicate element_type in global defaults: ${row.element_type} (skipping)`,
|
|
);
|
|
return false;
|
|
}
|
|
seenTypes.add(row.element_type);
|
|
return true;
|
|
});
|
|
|
|
const now = new Date();
|
|
const currentUserId = options.currentUser?.id || null;
|
|
|
|
// Create project defaults from global defaults
|
|
const projectDefaults = await this.MODEL.bulkCreate(
|
|
dedupedDefaults.map((globalDefault) => ({
|
|
projectId,
|
|
element_type: globalDefault.element_type,
|
|
name: globalDefault.name,
|
|
sort_order: globalDefault.sort_order,
|
|
settings_json: globalDefault.default_settings_json,
|
|
source_element_id: globalDefault.id,
|
|
snapshot_version: 1,
|
|
createdById: currentUserId,
|
|
updatedById: currentUserId,
|
|
createdAt: now,
|
|
updatedAt: now,
|
|
})),
|
|
{
|
|
transaction: options.transaction,
|
|
returning: true,
|
|
},
|
|
);
|
|
|
|
return projectDefaults;
|
|
}
|
|
|
|
/**
|
|
* Reset a project element default to the current global default
|
|
*/
|
|
static async resetToGlobal(id, options = {}) {
|
|
const Element_type_defaultsDBApi = require('./element_type_defaults');
|
|
|
|
// Ensure global defaults are initialized
|
|
await Element_type_defaultsDBApi.ensureInitialized();
|
|
|
|
// Find the project default
|
|
const projectDefault = await this.MODEL.findByPk(id);
|
|
if (!projectDefault) {
|
|
throw new Error('Project element default not found');
|
|
}
|
|
|
|
// Find the matching global default
|
|
const globalDefault = await Element_type_defaultsDBApi.MODEL.findOne({
|
|
where: {
|
|
element_type: projectDefault.element_type,
|
|
deletedAt: null,
|
|
},
|
|
});
|
|
|
|
if (!globalDefault) {
|
|
throw new Error(
|
|
`No global default found for element type: ${projectDefault.element_type}`,
|
|
);
|
|
}
|
|
|
|
// Update with global settings and increment version
|
|
const now = new Date();
|
|
await projectDefault.update(
|
|
{
|
|
name: globalDefault.name,
|
|
sort_order: globalDefault.sort_order,
|
|
settings_json: globalDefault.default_settings_json,
|
|
source_element_id: globalDefault.id,
|
|
snapshot_version: projectDefault.snapshot_version + 1,
|
|
updatedById: options.currentUser?.id || null,
|
|
updatedAt: now,
|
|
},
|
|
{
|
|
transaction: options.transaction,
|
|
},
|
|
);
|
|
|
|
return projectDefault.reload();
|
|
}
|
|
|
|
/**
|
|
* Get diff between project default and current global default
|
|
*/
|
|
static async getDiffFromGlobal(id) {
|
|
const Element_type_defaultsDBApi = require('./element_type_defaults');
|
|
|
|
// Ensure global defaults are initialized
|
|
await Element_type_defaultsDBApi.ensureInitialized();
|
|
|
|
// Find the project default
|
|
const projectDefault = await this.MODEL.findByPk(id);
|
|
if (!projectDefault) {
|
|
throw new Error('Project element default not found');
|
|
}
|
|
|
|
// Find the matching global default
|
|
const globalDefault = await Element_type_defaultsDBApi.MODEL.findOne({
|
|
where: {
|
|
element_type: projectDefault.element_type,
|
|
deletedAt: null,
|
|
},
|
|
});
|
|
|
|
if (!globalDefault) {
|
|
return {
|
|
projectDefault,
|
|
globalDefault: null,
|
|
hasGlobalDefault: false,
|
|
isDifferent: true,
|
|
};
|
|
}
|
|
|
|
// Parse JSON settings for comparison
|
|
const projectSettings =
|
|
typeof projectDefault.settings_json === 'string'
|
|
? JSON.parse(projectDefault.settings_json || '{}')
|
|
: projectDefault.settings_json || {};
|
|
|
|
const globalSettings =
|
|
typeof globalDefault.default_settings_json === 'string'
|
|
? JSON.parse(globalDefault.default_settings_json || '{}')
|
|
: globalDefault.default_settings_json || {};
|
|
|
|
const isDifferent =
|
|
JSON.stringify(projectSettings) !== JSON.stringify(globalSettings) ||
|
|
projectDefault.name !== globalDefault.name ||
|
|
projectDefault.sort_order !== globalDefault.sort_order;
|
|
|
|
return {
|
|
projectDefault,
|
|
globalDefault,
|
|
hasGlobalDefault: true,
|
|
isDifferent,
|
|
projectSettings,
|
|
globalSettings,
|
|
};
|
|
}
|
|
}
|
|
|
|
module.exports = Project_element_defaultsDBApi;
|