39948-vm/backend/src/db/api/projects.js
2026-03-31 08:56:49 +04:00

225 lines
5.5 KiB
JavaScript

const GenericDBApi = require('./base.api');
const db = require('../models');
const Utils = require('../utils');
const { getRuntimeProjectSlug } = require('./runtime-context');
const Sequelize = db.Sequelize;
const Op = Sequelize.Op;
class ProjectsDBApi extends GenericDBApi {
static get MODEL() {
return db.projects;
}
static get TABLE_NAME() {
return 'projects';
}
static get SEARCHABLE_FIELDS() {
return [
'name',
'slug',
'description',
'logo_url',
'favicon_url',
'og_image_url',
];
}
static get RANGE_FIELDS() {
return [];
}
static get ENUM_FIELDS() {
return [];
}
static get CSV_FIELDS() {
return [
'id',
'name',
'slug',
'description',
'logo_url',
'createdAt',
];
}
static get AUTOCOMPLETE_FIELD() {
return 'name';
}
static get ASSOCIATIONS() {
return [];
}
static getFieldMapping(data) {
return {
id: data.id || undefined,
name: data.name || null,
slug: data.slug || null,
description: data.description || null,
logo_url: data.logo_url || null,
favicon_url: data.favicon_url || null,
og_image_url: data.og_image_url || null,
};
}
static get DEFAULT_INCLUDES() {
return [];
}
static get ALL_INCLUDES() {
return [
{ association: 'project_memberships_project' },
{ association: 'assets_project' },
{ association: 'presigned_url_requests_project' },
{ association: 'tour_pages_project' },
{ association: 'project_audio_tracks_project' },
{ association: 'publish_events_project' },
{ association: 'pwa_caches_project' },
{ association: 'access_logs_project' },
];
}
static async findBy(where, options = {}) {
const transaction = options.transaction;
const runtimeProjectSlug = getRuntimeProjectSlug(options);
const queryWhere = { ...where };
// Runtime access: filter by project slug
// Skip if finding by ID (unambiguous lookup)
if (runtimeProjectSlug && !where.id) {
queryWhere.slug = runtimeProjectSlug;
}
const include =
options.include !== undefined ? options.include : this.DEFAULT_INCLUDES;
const record = await this.MODEL.findOne({
where: queryWhere,
transaction,
include,
});
if (!record) return null;
return record.get({ plain: true });
}
/**
* Create a new project and auto-snapshot global element defaults
*/
static async create(data, options = {}) {
const transaction = options.transaction;
// Create the project using parent's create
const project = await super.create(data, options);
// Auto-snapshot global element defaults to the new project
try {
const Project_element_defaultsDBApi = require('./project_element_defaults');
await Project_element_defaultsDBApi.snapshotGlobalDefaults(project.id, {
...options,
transaction,
});
} catch (error) {
// Log but don't fail project creation if snapshot fails
console.error(
'Failed to snapshot global element defaults to project:',
error,
);
}
return project;
}
static async findAll(filter = {}, options = {}) {
filter = filter || {};
const limit = filter.limit || 0;
const currentPage = +filter.page || 0;
const offset = currentPage * limit;
let where = {};
let include = [];
if (filter.id) {
where.id = Utils.uuid(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 };
}
}
// Runtime access: filter by project slug
const runtimeProjectSlug = getRuntimeProjectSlug(options);
if (runtimeProjectSlug) {
where.slug = runtimeProjectSlug;
}
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 = ProjectsDBApi;