39948-vm/backend/src/db/api/project_transition_settings.js

274 lines
7.2 KiB
JavaScript

const GenericDBApi = require('./base.api');
const db = require('../models');
const Utils = require('../utils');
const {
applyRuntimeEnvironment,
applyRuntimeProjectFilter,
} = require('./runtime-context');
const Sequelize = db.Sequelize;
const Op = Sequelize.Op;
class Project_transition_settingsDBApi extends GenericDBApi {
static get MODEL() {
return db.project_transition_settings;
}
static get TABLE_NAME() {
return 'project_transition_settings';
}
static get SEARCHABLE_FIELDS() {
return ['source_key', 'transition_type', 'easing', 'overlay_color'];
}
static get RANGE_FIELDS() {
return ['duration_ms'];
}
static get ENUM_FIELDS() {
return ['environment'];
}
static get CSV_FIELDS() {
return [
'id',
'environment',
'source_key',
'transition_type',
'duration_ms',
'easing',
'overlay_color',
'createdAt',
];
}
static get AUTOCOMPLETE_FIELD() {
return 'transition_type';
}
static get ASSOCIATIONS() {
return [{ field: 'project', setter: 'setProject', isArray: false }];
}
static getFieldMapping(data) {
// Note: environment and projectId are NOT included here because they are
// set explicitly in upsertForProject and should never be changed via data
return {
id: data.id || undefined,
source_key: data.source_key || null,
transition_type: data.transition_type || 'fade',
duration_ms: data.duration_ms !== undefined ? data.duration_ms : 700,
easing: data.easing || 'ease-in-out',
overlay_color: data.overlay_color || '#000000',
};
}
/**
* Find settings by project ID and environment.
* This is the primary method for fetching transition settings.
*
* @param {string} projectId - Project ID
* @param {string} environment - Environment (dev, stage, production)
* @param {object} options - Query options
* @returns {object|null} Settings record or null
*/
static async findByProjectAndEnvironment(projectId, environment, options = {}) {
const transaction = options.transaction;
const record = await this.MODEL.findOne({
where: {
projectId,
environment,
},
transaction,
include: [
{
model: db.projects,
as: 'project',
},
],
});
if (!record) return null;
return record.get({ plain: true });
}
/**
* Create or update settings for a project/environment combination.
* Uses upsert semantics - creates if not exists, updates if exists.
*
* @param {string} projectId - Project ID
* @param {string} environment - Environment (dev, stage, production)
* @param {object} data - Settings data
* @param {object} options - Query options
* @returns {object} Created or updated record
*/
static async upsertForProject(projectId, environment, data, options = {}) {
const transaction = options.transaction;
const currentUser = options.currentUser;
// Check if record exists
const existing = await this.MODEL.findOne({
where: { projectId, environment },
transaction,
});
if (existing) {
// Update existing record
await existing.update(
{
...this.getFieldMapping(data),
updatedById: currentUser?.id || null,
},
{ transaction },
);
return existing.get({ plain: true });
}
// Create new record
const newRecord = await this.MODEL.create(
{
...this.getFieldMapping(data),
projectId,
environment,
createdById: currentUser?.id || null,
updatedById: currentUser?.id || null,
},
{ transaction },
);
return newRecord.get({ plain: true });
}
static async findBy(where, options = {}) {
const transaction = options.transaction;
const queryWhere = applyRuntimeEnvironment({ ...where }, options);
const projectInclude = applyRuntimeProjectFilter(
{ model: db.projects, as: 'project' },
options,
);
const record = await this.MODEL.findOne({
where: queryWhere,
transaction,
include: [projectInclude],
});
if (!record) return null;
return record.get({ plain: true });
}
static async findAll(filter = {}, options = {}) {
filter = filter || {};
const limit = filter.limit || 0;
const currentPage = +filter.page || 0;
const offset = currentPage * limit;
let where = {};
const terms = filter.project ? filter.project.split('|') : [];
const validUuids = Utils.filterValidUuids(terms);
let include = [
{
model: db.projects,
as: 'project',
where: filter.project
? {
[Op.or]: [
...(validUuids.length > 0
? [{ id: { [Op.in]: validUuids } }]
: []),
{
name: {
[Op.or]: terms.map((term) => ({ [Op.iLike]: `%${term}%` })),
},
},
],
}
: {},
},
];
include[0] = applyRuntimeProjectFilter(include[0], options);
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.active !== undefined) {
where.active = filter.active === true || filter.active === 'true';
}
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 };
}
}
where = applyRuntimeEnvironment(where, options);
const queryOptions = {
where,
include,
distinct: true,
order:
filter.field && filter.sort
? [[filter.field, filter.sort]]
: [['createdAt', 'desc']],
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;
}
}
}
module.exports = Project_transition_settingsDBApi;