fixed project settings issue

This commit is contained in:
Dmitri 2026-03-31 12:45:22 +04:00
parent 34770304c5
commit 91c24165bf
8 changed files with 129 additions and 87 deletions

View File

@ -19,7 +19,11 @@ class AssetsDBApi extends GenericDBApi {
} }
static get ENUM_FIELDS() { static get ENUM_FIELDS() {
return ['asset_type', 'type', 'is_public', 'projectId']; return ['asset_type', 'type', 'is_public'];
}
static get UUID_FIELDS() {
return ['projectId'];
} }
static get CSV_FIELDS() { static get CSV_FIELDS() {

View File

@ -26,6 +26,17 @@ class GenericDBApi {
return []; return [];
} }
/**
* UUID fields that require validation before querying.
* These are typically foreign key fields like 'projectId'.
* Invalid UUIDs will return empty results instead of causing DB errors.
* Override in subclass to specify fields.
* Example: return ['projectId', 'userId'];
*/
static get UUID_FIELDS() {
return [];
}
static get RELATION_FILTERS() { static get RELATION_FILTERS() {
return []; return [];
} }
@ -265,13 +276,10 @@ class GenericDBApi {
let include = [...this.FIND_ALL_INCLUDES]; let include = [...this.FIND_ALL_INCLUDES];
if (filter.id) { if (filter.id) {
const validId = Utils.uuid(filter.id); if (!Utils.isValidUuid(filter.id)) {
if (validId) {
where.id = validId;
} else {
// Invalid UUID provided - return empty results immediately
return { rows: [], count: 0 }; return { rows: [], count: 0 };
} }
where.id = filter.id;
} }
for (const field of this.SEARCHABLE_FIELDS) { for (const field of this.SEARCHABLE_FIELDS) {
@ -299,6 +307,16 @@ class GenericDBApi {
} }
} }
// Validate UUID fields - return empty results for invalid UUIDs
for (const field of this.UUID_FIELDS) {
if (filter[field] !== undefined) {
if (!Utils.isValidUuid(filter[field])) {
return { rows: [], count: 0 };
}
where[field] = filter[field];
}
}
if (filter.active !== undefined) { if (filter.active !== undefined) {
where.active = filter.active === true || filter.active === 'true'; where.active = filter.active === true || filter.active === 'true';
} }
@ -316,11 +334,7 @@ class GenericDBApi {
for (const rel of this.RELATION_FILTERS) { for (const rel of this.RELATION_FILTERS) {
if (filter[rel.filterKey]) { if (filter[rel.filterKey]) {
const searchTerms = filter[rel.filterKey].split('|'); const searchTerms = filter[rel.filterKey].split('|');
const validUuids = Utils.filterValidUuids(searchTerms);
// Filter out null UUIDs - only keep valid ones
const validUuids = searchTerms
.map((term) => Utils.uuid(term))
.filter((id) => id !== null);
// Build OR conditions array // Build OR conditions array
const orConditions = []; const orConditions = [];
@ -384,12 +398,15 @@ class GenericDBApi {
let where = {}; let where = {};
if (query) { if (query) {
where = { const orConditions = [
[Op.or]: [ Utils.ilike(this.TABLE_NAME, this.AUTOCOMPLETE_FIELD, query),
{ id: Utils.uuid(query) }, ];
Utils.ilike(this.TABLE_NAME, this.AUTOCOMPLETE_FIELD, query),
], if (Utils.isValidUuid(query)) {
}; orConditions.unshift({ id: query });
}
where = { [Op.or]: orConditions };
} }
const records = await this.MODEL.findAll({ const records = await this.MODEL.findAll({

View File

@ -93,6 +93,9 @@ class Project_audio_tracksDBApi extends GenericDBApi {
let where = {}; let where = {};
const terms = filter.project ? filter.project.split('|') : [];
const validUuids = Utils.filterValidUuids(terms);
let include = [ let include = [
{ {
model: db.projects, model: db.projects,
@ -100,18 +103,12 @@ class Project_audio_tracksDBApi extends GenericDBApi {
where: filter.project where: filter.project
? { ? {
[Op.or]: [ [Op.or]: [
{ ...(validUuids.length > 0
id: { ? [{ id: { [Op.in]: validUuids } }]
[Op.in]: filter.project : []),
.split('|')
.map((term) => Utils.uuid(term)),
},
},
{ {
name: { name: {
[Op.or]: filter.project [Op.or]: terms.map((term) => ({ [Op.iLike]: `%${term}%` })),
.split('|')
.map((term) => ({ [Op.iLike]: `%${term}%` })),
}, },
}, },
], ],
@ -123,7 +120,10 @@ class Project_audio_tracksDBApi extends GenericDBApi {
include[0] = applyRuntimeProjectFilter(include[0], options); include[0] = applyRuntimeProjectFilter(include[0], options);
if (filter.id) { if (filter.id) {
where.id = Utils.uuid(filter.id); if (!Utils.isValidUuid(filter.id)) {
return { rows: [], count: 0 };
}
where.id = filter.id;
} }
for (const field of this.SEARCHABLE_FIELDS) { for (const field of this.SEARCHABLE_FIELDS) {

View File

@ -111,6 +111,8 @@ class Project_element_defaultsDBApi extends GenericDBApi {
// Support both 'project' and 'projectId' query params // Support both 'project' and 'projectId' query params
const projectFilter = filter.project || filter.projectId; const projectFilter = filter.project || filter.projectId;
const terms = projectFilter ? projectFilter.split('|') : [];
const validUuids = Utils.filterValidUuids(terms);
let include = [ let include = [
{ {
@ -119,18 +121,12 @@ class Project_element_defaultsDBApi extends GenericDBApi {
where: projectFilter where: projectFilter
? { ? {
[Op.or]: [ [Op.or]: [
{ ...(validUuids.length > 0
id: { ? [{ id: { [Op.in]: validUuids } }]
[Op.in]: projectFilter : []),
.split('|')
.map((term) => Utils.uuid(term)),
},
},
{ {
name: { name: {
[Op.or]: projectFilter [Op.or]: terms.map((term) => ({ [Op.iLike]: `%${term}%` })),
.split('|')
.map((term) => ({ [Op.iLike]: `%${term}%` })),
}, },
}, },
], ],
@ -145,7 +141,10 @@ class Project_element_defaultsDBApi extends GenericDBApi {
]; ];
if (filter.id) { if (filter.id) {
where.id = Utils.uuid(filter.id); if (!Utils.isValidUuid(filter.id)) {
return { rows: [], count: 0 };
}
where.id = filter.id;
} }
for (const field of this.SEARCHABLE_FIELDS) { for (const field of this.SEARCHABLE_FIELDS) {

View File

@ -136,7 +136,10 @@ class ProjectsDBApi extends GenericDBApi {
let include = []; let include = [];
if (filter.id) { if (filter.id) {
where.id = Utils.uuid(filter.id); if (!Utils.isValidUuid(filter.id)) {
return { rows: [], count: 0 };
}
where.id = filter.id;
} }
for (const field of this.SEARCHABLE_FIELDS) { for (const field of this.SEARCHABLE_FIELDS) {

View File

@ -125,6 +125,9 @@ class Tour_pagesDBApi extends GenericDBApi {
let where = {}; let where = {};
const terms = filter.project ? filter.project.split('|') : [];
const validUuids = Utils.filterValidUuids(terms);
let include = [ let include = [
{ {
model: db.projects, model: db.projects,
@ -132,18 +135,12 @@ class Tour_pagesDBApi extends GenericDBApi {
where: filter.project where: filter.project
? { ? {
[Op.or]: [ [Op.or]: [
{ ...(validUuids.length > 0
id: { ? [{ id: { [Op.in]: validUuids } }]
[Op.in]: filter.project : []),
.split('|')
.map((term) => Utils.uuid(term)),
},
},
{ {
name: { name: {
[Op.or]: filter.project [Op.or]: terms.map((term) => ({ [Op.iLike]: `%${term}%` })),
.split('|')
.map((term) => ({ [Op.iLike]: `%${term}%` })),
}, },
}, },
], ],
@ -155,7 +152,10 @@ class Tour_pagesDBApi extends GenericDBApi {
include[0] = applyRuntimeProjectFilter(include[0], options); include[0] = applyRuntimeProjectFilter(include[0], options);
if (filter.id) { if (filter.id) {
where.id = Utils.uuid(filter.id); if (!Utils.isValidUuid(filter.id)) {
return { rows: [], count: 0 };
}
where.id = filter.id;
} }
for (const field of this.SEARCHABLE_FIELDS) { for (const field of this.SEARCHABLE_FIELDS) {

View File

@ -298,6 +298,9 @@ module.exports = class UsersDBApi {
offset = currentPage * limit; offset = currentPage * limit;
const appRoleTerms = filter.app_role ? filter.app_role.split('|') : [];
const appRoleValidUuids = Utils.filterValidUuids(appRoleTerms);
let include = [ let include = [
{ {
model: db.roles, model: db.roles,
@ -306,18 +309,14 @@ module.exports = class UsersDBApi {
where: filter.app_role where: filter.app_role
? { ? {
[Op.or]: [ [Op.or]: [
{ ...(appRoleValidUuids.length > 0
id: { ? [{ id: { [Op.in]: appRoleValidUuids } }]
[Op.in]: filter.app_role : []),
.split('|')
.map((term) => Utils.uuid(term)),
},
},
{ {
name: { name: {
[Op.or]: filter.app_role [Op.or]: appRoleTerms.map((term) => ({
.split('|') [Op.iLike]: `%${term}%`,
.map((term) => ({ [Op.iLike]: `%${term}%` })), })),
}, },
}, },
], ],
@ -339,10 +338,10 @@ module.exports = class UsersDBApi {
if (filter) { if (filter) {
if (filter.id) { if (filter.id) {
where = { if (!Utils.isValidUuid(filter.id)) {
...where, return { rows: [], count: 0 };
['id']: Utils.uuid(filter.id), }
}; where = { ...where, id: filter.id };
} }
if (filter.firstName) { if (filter.firstName) {
@ -480,6 +479,7 @@ module.exports = class UsersDBApi {
if (filter.custom_permissions) { if (filter.custom_permissions) {
const searchTerms = filter.custom_permissions.split('|'); const searchTerms = filter.custom_permissions.split('|');
const permissionValidUuids = Utils.filterValidUuids(searchTerms);
include = [ include = [
{ {
@ -490,11 +490,9 @@ module.exports = class UsersDBApi {
searchTerms.length > 0 searchTerms.length > 0
? { ? {
[Op.or]: [ [Op.or]: [
{ ...(permissionValidUuids.length > 0
id: { ? [{ id: { [Op.in]: permissionValidUuids } }]
[Op.in]: searchTerms.map((term) => Utils.uuid(term)), : []),
},
},
{ {
name: { name: {
[Op.or]: searchTerms.map((term) => ({ [Op.or]: searchTerms.map((term) => ({
@ -568,12 +566,13 @@ module.exports = class UsersDBApi {
let where = {}; let where = {};
if (query) { if (query) {
where = { const orConditions = [Utils.ilike('users', 'firstName', query)];
[Op.or]: [
{ ['id']: Utils.uuid(query) }, if (Utils.isValidUuid(query)) {
Utils.ilike('users', 'firstName', query), orConditions.unshift({ id: query });
], }
};
where = { [Op.or]: orConditions };
} }
const records = await db.users.findAll({ const records = await db.users.findAll({

View File

@ -1,25 +1,45 @@
const validator = require('validator'); const validator = require('validator');
const { v4: uuidv4 } = require('uuid');
const Sequelize = require('./models').Sequelize; const Sequelize = require('./models').Sequelize;
module.exports = class Utils { module.exports = class Utils {
/** /**
* Validates a UUID string. * Check if value is a valid UUID
* @param {*} value - The value to validate as UUID * @param {*} value - The value to check
* @returns {string|null} - The valid UUID string, or null if invalid * @returns {boolean} - True if valid UUID, false otherwise
*/ */
static uuid(value) { static isValidUuid(value) {
if (value && validator.isUUID(String(value))) { return Boolean(value && validator.isUUID(String(value)));
return value;
}
return null;
} }
/**
* Generate a new UUID v4
* @returns {string} - A new UUID v4 string
*/
static generateUuid() {
return uuidv4();
}
/**
* Filter array to only valid UUIDs
* @param {Array} values - Array of values to filter
* @returns {string[]} - Array containing only valid UUIDs
*/
static filterValidUuids(values) {
return values.filter((v) => this.isValidUuid(v));
}
/**
* Case-insensitive LIKE query
* @param {string} model - The model/table name
* @param {string} column - The column name
* @param {string} value - The value to search for
* @returns {Object} - Sequelize where clause
*/
static ilike(model, column, value) { static ilike(model, column, value) {
return Sequelize.where( return Sequelize.where(
Sequelize.fn('lower', Sequelize.col(`${model}.${column}`)), Sequelize.fn('lower', Sequelize.col(`${model}.${column}`)),
{ { [Sequelize.Op.like]: `%${value}%`.toLowerCase() },
[Sequelize.Op.like]: `%${value}%`.toLowerCase(),
},
); );
} }
}; };