Standartized service/DB API contracts
This commit is contained in:
parent
27170d90ec
commit
cf3deb14f7
95
backend/src/contracts/entity-options.js
Normal file
95
backend/src/contracts/entity-options.js
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
/**
|
||||||
|
* @typedef {Object} ServiceCreateOptions
|
||||||
|
* @property {Object} data
|
||||||
|
* @property {Object} [currentUser]
|
||||||
|
* @property {Object} [transaction]
|
||||||
|
* @property {Object} [runtimeContext]
|
||||||
|
* @property {boolean} [sendInvitationEmails]
|
||||||
|
* @property {string} [host]
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} EntityIdOptions
|
||||||
|
* @property {string} id
|
||||||
|
* @property {Object} [currentUser]
|
||||||
|
* @property {Object} [transaction]
|
||||||
|
* @property {Object} [runtimeContext]
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} DeleteByIdsOptions
|
||||||
|
* @property {string[]} ids
|
||||||
|
* @property {Object} [currentUser]
|
||||||
|
* @property {Object} [transaction]
|
||||||
|
* @property {Object} [runtimeContext]
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} AutocompleteOptions
|
||||||
|
* @property {string} [query]
|
||||||
|
* @property {number} [limit]
|
||||||
|
* @property {number} [offset]
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} UpdateOptions
|
||||||
|
* @property {string} id
|
||||||
|
* @property {Object} data
|
||||||
|
* @property {Object} [currentUser]
|
||||||
|
* @property {Object} [transaction]
|
||||||
|
* @property {Object} [runtimeContext]
|
||||||
|
*/
|
||||||
|
|
||||||
|
function assertOptionsObject(options, contractName, methodName) {
|
||||||
|
if (!options || typeof options !== 'object' || Array.isArray(options)) {
|
||||||
|
throw new TypeError(
|
||||||
|
`${contractName}.${methodName} expects an options object`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function assertCreateOptions(options, contractName) {
|
||||||
|
assertOptionsObject(options, contractName, 'create');
|
||||||
|
|
||||||
|
if (options.data === undefined) {
|
||||||
|
throw new TypeError(`${contractName}.create requires { data }`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function assertIdOptions(options, contractName, methodName) {
|
||||||
|
assertOptionsObject(options, contractName, methodName);
|
||||||
|
|
||||||
|
if (!options.id) {
|
||||||
|
throw new TypeError(`${contractName}.${methodName} requires { id }`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function assertDeleteByIdsOptions(options, contractName) {
|
||||||
|
assertOptionsObject(options, contractName, 'deleteByIds');
|
||||||
|
|
||||||
|
if (!Array.isArray(options.ids)) {
|
||||||
|
throw new TypeError(`${contractName}.deleteByIds requires { ids }`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function assertAutocompleteOptions(options, contractName) {
|
||||||
|
assertOptionsObject(options, contractName, 'findAllAutocomplete');
|
||||||
|
}
|
||||||
|
|
||||||
|
function assertUpdateOptions(options, contractName) {
|
||||||
|
assertOptionsObject(options, contractName, 'update');
|
||||||
|
|
||||||
|
if (!options.id || options.data === undefined) {
|
||||||
|
throw new TypeError(
|
||||||
|
`${contractName}.update requires { id, data } in the options object`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
assertAutocompleteOptions,
|
||||||
|
assertCreateOptions,
|
||||||
|
assertDeleteByIdsOptions,
|
||||||
|
assertIdOptions,
|
||||||
|
assertUpdateOptions,
|
||||||
|
};
|
||||||
@ -2,6 +2,13 @@ const db = require('../models');
|
|||||||
const Utils = require('../utils');
|
const Utils = require('../utils');
|
||||||
const { parse } = require('json2csv');
|
const { parse } = require('json2csv');
|
||||||
const { logger } = require('../../utils/logger');
|
const { logger } = require('../../utils/logger');
|
||||||
|
const {
|
||||||
|
assertAutocompleteOptions,
|
||||||
|
assertCreateOptions,
|
||||||
|
assertDeleteByIdsOptions,
|
||||||
|
assertIdOptions,
|
||||||
|
assertUpdateOptions,
|
||||||
|
} = require('../../contracts/entity-options');
|
||||||
|
|
||||||
const Sequelize = db.Sequelize;
|
const Sequelize = db.Sequelize;
|
||||||
const Op = Sequelize.Op;
|
const Op = Sequelize.Op;
|
||||||
@ -147,9 +154,10 @@ class GenericDBApi {
|
|||||||
return mapped;
|
return mapped;
|
||||||
}
|
}
|
||||||
|
|
||||||
static async create(data, options = {}) {
|
static async create(options) {
|
||||||
const currentUser = options.currentUser || { id: null };
|
assertCreateOptions(options, 'DBApi');
|
||||||
const transaction = options.transaction;
|
|
||||||
|
const { data, currentUser = { id: null }, transaction } = options;
|
||||||
|
|
||||||
const mappedData = this.getFieldMapping(data);
|
const mappedData = this.getFieldMapping(data);
|
||||||
|
|
||||||
@ -190,9 +198,17 @@ class GenericDBApi {
|
|||||||
return this.MODEL.bulkCreate(recordsData, { transaction });
|
return this.MODEL.bulkCreate(recordsData, { transaction });
|
||||||
}
|
}
|
||||||
|
|
||||||
static async update(id, data, options = {}) {
|
/**
|
||||||
const currentUser = options.currentUser || { id: null };
|
* @param {Object} options
|
||||||
const transaction = options.transaction;
|
* @param {string} options.id
|
||||||
|
* @param {Object} options.data
|
||||||
|
* @param {Object} [options.currentUser]
|
||||||
|
* @param {Object} [options.transaction]
|
||||||
|
*/
|
||||||
|
static async update(options) {
|
||||||
|
assertUpdateOptions(options, 'DBApi');
|
||||||
|
|
||||||
|
const { id, data, currentUser = { id: null }, transaction } = options;
|
||||||
|
|
||||||
const record = await this.MODEL.findByPk(id, { transaction });
|
const record = await this.MODEL.findByPk(id, { transaction });
|
||||||
|
|
||||||
@ -227,13 +243,16 @@ class GenericDBApi {
|
|||||||
*
|
*
|
||||||
* Use this when you need to update specific fields without affecting others.
|
* Use this when you need to update specific fields without affecting others.
|
||||||
*
|
*
|
||||||
* @param {string} id - Record ID
|
* @param {Object} options
|
||||||
* @param {Object} data - Fields to update (only these will be modified)
|
* @param {string} options.id - Record ID
|
||||||
* @param {Object} options - Options with currentUser and transaction
|
* @param {Object} options.data - Fields to update
|
||||||
|
* @param {Object} [options.currentUser]
|
||||||
|
* @param {Object} [options.transaction]
|
||||||
*/
|
*/
|
||||||
static async partialUpdate(id, data, options = {}) {
|
static async partialUpdate(options) {
|
||||||
const currentUser = options.currentUser || { id: null };
|
assertUpdateOptions(options, 'DBApi');
|
||||||
const transaction = options.transaction;
|
|
||||||
|
const { id, data, currentUser = { id: null }, transaction } = options;
|
||||||
|
|
||||||
const record = await this.MODEL.findByPk(id, { transaction });
|
const record = await this.MODEL.findByPk(id, { transaction });
|
||||||
|
|
||||||
@ -255,9 +274,10 @@ class GenericDBApi {
|
|||||||
return record;
|
return record;
|
||||||
}
|
}
|
||||||
|
|
||||||
static async deleteByIds(ids, options = {}) {
|
static async deleteByIds(options) {
|
||||||
const currentUser = options.currentUser || { id: null };
|
assertDeleteByIdsOptions(options, 'DBApi');
|
||||||
const transaction = options.transaction;
|
|
||||||
|
const { ids, currentUser = { id: null }, transaction } = options;
|
||||||
|
|
||||||
const records = await this.MODEL.findAll({
|
const records = await this.MODEL.findAll({
|
||||||
where: { id: { [Op.in]: ids } },
|
where: { id: { [Op.in]: ids } },
|
||||||
@ -274,9 +294,10 @@ class GenericDBApi {
|
|||||||
return records;
|
return records;
|
||||||
}
|
}
|
||||||
|
|
||||||
static async remove(id, options = {}) {
|
static async remove(options) {
|
||||||
const currentUser = options.currentUser || { id: null };
|
assertIdOptions(options, 'DBApi', 'remove');
|
||||||
const transaction = options.transaction;
|
|
||||||
|
const { id, currentUser = { id: null }, transaction } = options;
|
||||||
|
|
||||||
const record = await this.MODEL.findByPk(id, { transaction });
|
const record = await this.MODEL.findByPk(id, { transaction });
|
||||||
|
|
||||||
@ -453,7 +474,11 @@ class GenericDBApi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static async findAllAutocomplete(query, limit, offset) {
|
static async findAllAutocomplete(options, queryOptions = {}) {
|
||||||
|
assertAutocompleteOptions(options, 'DBApi');
|
||||||
|
|
||||||
|
const { query, limit, offset } = options;
|
||||||
|
const transaction = queryOptions.transaction;
|
||||||
let where = {};
|
let where = {};
|
||||||
|
|
||||||
if (query) {
|
if (query) {
|
||||||
@ -474,6 +499,7 @@ class GenericDBApi {
|
|||||||
limit: limit ? Number(limit) : undefined,
|
limit: limit ? Number(limit) : undefined,
|
||||||
offset: offset ? Number(offset) : undefined,
|
offset: offset ? Number(offset) : undefined,
|
||||||
order: [[this.AUTOCOMPLETE_FIELD, 'ASC']],
|
order: [[this.AUTOCOMPLETE_FIELD, 'ASC']],
|
||||||
|
transaction,
|
||||||
});
|
});
|
||||||
|
|
||||||
return records.map((record) => ({
|
return records.map((record) => ({
|
||||||
|
|||||||
@ -363,9 +363,9 @@ class Element_type_defaultsDBApi extends GenericDBApi {
|
|||||||
await this.initializationPromise;
|
await this.initializationPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
static async create(data, options = {}) {
|
static async create(options) {
|
||||||
await this.ensureInitialized();
|
await this.ensureInitialized();
|
||||||
return super.create(data, options);
|
return super.create(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
static async bulkImport(data, options = {}) {
|
static async bulkImport(data, options = {}) {
|
||||||
@ -373,19 +373,19 @@ class Element_type_defaultsDBApi extends GenericDBApi {
|
|||||||
return super.bulkImport(data, options);
|
return super.bulkImport(data, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
static async update(id, data, options = {}) {
|
static async update({ id, data, currentUser, transaction, runtimeContext }) {
|
||||||
await this.ensureInitialized();
|
await this.ensureInitialized();
|
||||||
return super.update(id, data, options);
|
return super.update({ id, data, currentUser, transaction, runtimeContext });
|
||||||
}
|
}
|
||||||
|
|
||||||
static async deleteByIds(ids, options = {}) {
|
static async deleteByIds(options) {
|
||||||
await this.ensureInitialized();
|
await this.ensureInitialized();
|
||||||
return super.deleteByIds(ids, options);
|
return super.deleteByIds(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
static async remove(id, options = {}) {
|
static async remove(options) {
|
||||||
await this.ensureInitialized();
|
await this.ensureInitialized();
|
||||||
return super.remove(id, options);
|
return super.remove(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
static async findBy(where, options = {}) {
|
static async findBy(where, options = {}) {
|
||||||
@ -398,9 +398,9 @@ class Element_type_defaultsDBApi extends GenericDBApi {
|
|||||||
return super.findAll(filter, options);
|
return super.findAll(filter, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
static async findAllAutocomplete(query, limit, offset) {
|
static async findAllAutocomplete(options, queryOptions = {}) {
|
||||||
await this.ensureInitialized();
|
await this.ensureInitialized();
|
||||||
return super.findAllAutocomplete(query, limit, offset);
|
return super.findAllAutocomplete(options, queryOptions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -134,9 +134,9 @@ class Global_transition_defaultsDBApi extends GenericDBApi {
|
|||||||
return this.findOne(options);
|
return this.findOne(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
static async update(id, data, options = {}) {
|
static async update({ id, data, currentUser, transaction, runtimeContext }) {
|
||||||
await this.ensureInitialized();
|
await this.ensureInitialized();
|
||||||
return super.update(id, data, options);
|
return super.update({ id, data, currentUser, transaction, runtimeContext });
|
||||||
}
|
}
|
||||||
|
|
||||||
static async findBy(where, options = {}) {
|
static async findBy(where, options = {}) {
|
||||||
|
|||||||
@ -148,9 +148,9 @@ class Global_ui_control_defaultsDBApi extends GenericDBApi {
|
|||||||
return record.get({ plain: true });
|
return record.get({ plain: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
static async update(id, data, options = {}) {
|
static async update({ id, data, currentUser, transaction, runtimeContext }) {
|
||||||
await this.ensureInitialized();
|
await this.ensureInitialized();
|
||||||
return super.update(id, data, options);
|
return super.update({ id, data, currentUser, transaction, runtimeContext });
|
||||||
}
|
}
|
||||||
|
|
||||||
static async findBy(where, options = {}) {
|
static async findBy(where, options = {}) {
|
||||||
|
|||||||
@ -115,11 +115,11 @@ class ProjectsDBApi extends GenericDBApi {
|
|||||||
/**
|
/**
|
||||||
* Create a new project and auto-snapshot global element defaults
|
* Create a new project and auto-snapshot global element defaults
|
||||||
*/
|
*/
|
||||||
static async create(data, options = {}) {
|
static async create(options) {
|
||||||
const transaction = options.transaction;
|
const { transaction } = options;
|
||||||
|
|
||||||
// Create the project using parent's create
|
// Create the project using parent's create
|
||||||
const project = await super.create(data, options);
|
const project = await super.create(options);
|
||||||
|
|
||||||
// Auto-snapshot global element defaults to the new project
|
// Auto-snapshot global element defaults to the new project
|
||||||
// Errors propagate to service layer → transaction rollback → proper error to client
|
// Errors propagate to service layer → transaction rollback → proper error to client
|
||||||
|
|||||||
@ -124,9 +124,8 @@ class Tour_pagesDBApi extends GenericDBApi {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static async create(data, options = {}) {
|
static async create(options) {
|
||||||
const currentUser = options.currentUser || { id: null };
|
const { data, currentUser = { id: null }, transaction } = options;
|
||||||
const transaction = options.transaction;
|
|
||||||
const projectId = data.project || data.projectId || null;
|
const projectId = data.project || data.projectId || null;
|
||||||
|
|
||||||
const record = await this.MODEL.create(
|
const record = await this.MODEL.create(
|
||||||
|
|||||||
@ -6,6 +6,13 @@ const Utils = require('../utils');
|
|||||||
const bcrypt = require('bcrypt');
|
const bcrypt = require('bcrypt');
|
||||||
const config = require('../../config');
|
const config = require('../../config');
|
||||||
const { logger } = require('../../utils/logger');
|
const { logger } = require('../../utils/logger');
|
||||||
|
const {
|
||||||
|
assertAutocompleteOptions,
|
||||||
|
assertCreateOptions,
|
||||||
|
assertDeleteByIdsOptions,
|
||||||
|
assertIdOptions,
|
||||||
|
assertUpdateOptions,
|
||||||
|
} = require('../../contracts/entity-options');
|
||||||
|
|
||||||
const Sequelize = db.Sequelize;
|
const Sequelize = db.Sequelize;
|
||||||
const Op = Sequelize.Op;
|
const Op = Sequelize.Op;
|
||||||
@ -60,40 +67,42 @@ module.exports = class UsersDBApi {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
static async create(data, options) {
|
static async create(options) {
|
||||||
const currentUser = (options && options.currentUser) || { id: null };
|
assertCreateOptions(options, 'DBApi');
|
||||||
const transaction = (options && options.transaction) || undefined;
|
|
||||||
|
const { data, currentUser = { id: null }, transaction } = options;
|
||||||
|
const userData = data.data || data;
|
||||||
const password =
|
const password =
|
||||||
data.data.password || crypto.randomBytes(20).toString('hex');
|
userData.password || crypto.randomBytes(20).toString('hex');
|
||||||
|
|
||||||
const users = await db.users.create(
|
const users = await db.users.create(
|
||||||
{
|
{
|
||||||
id: data.data.id || undefined,
|
id: userData.id || undefined,
|
||||||
|
|
||||||
firstName: data.data.firstName || null,
|
firstName: userData.firstName || null,
|
||||||
lastName: data.data.lastName || null,
|
lastName: userData.lastName || null,
|
||||||
phoneNumber: data.data.phoneNumber || null,
|
phoneNumber: userData.phoneNumber || null,
|
||||||
email: data.data.email || null,
|
email: userData.email || null,
|
||||||
disabled: data.data.disabled || false,
|
disabled: userData.disabled || false,
|
||||||
|
|
||||||
password: bcrypt.hashSync(password, config.bcrypt.saltRounds),
|
password: bcrypt.hashSync(password, config.bcrypt.saltRounds),
|
||||||
emailVerified: data.data.emailVerified || true,
|
emailVerified: userData.emailVerified || true,
|
||||||
|
|
||||||
emailVerificationToken: data.data.emailVerificationToken || null,
|
emailVerificationToken: userData.emailVerificationToken || null,
|
||||||
emailVerificationTokenExpiresAt:
|
emailVerificationTokenExpiresAt:
|
||||||
data.data.emailVerificationTokenExpiresAt || null,
|
userData.emailVerificationTokenExpiresAt || null,
|
||||||
passwordResetToken: data.data.passwordResetToken || null,
|
passwordResetToken: userData.passwordResetToken || null,
|
||||||
passwordResetTokenExpiresAt:
|
passwordResetTokenExpiresAt:
|
||||||
data.data.passwordResetTokenExpiresAt || null,
|
userData.passwordResetTokenExpiresAt || null,
|
||||||
provider: data.data.provider || config.providers.LOCAL,
|
provider: userData.provider || config.providers.LOCAL,
|
||||||
importHash: data.data.importHash || null,
|
importHash: userData.importHash || null,
|
||||||
createdById: currentUser.id,
|
createdById: currentUser.id,
|
||||||
updatedById: currentUser.id,
|
updatedById: currentUser.id,
|
||||||
},
|
},
|
||||||
{ transaction },
|
{ transaction },
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!data.data.app_role) {
|
if (!userData.app_role) {
|
||||||
const role = await db.roles.findOne({
|
const role = await db.roles.findOne({
|
||||||
where: { name: 'User' },
|
where: { name: 'User' },
|
||||||
});
|
});
|
||||||
@ -103,12 +112,12 @@ module.exports = class UsersDBApi {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
await users.setApp_role(data.data.app_role || null, {
|
await users.setApp_role(userData.app_role || null, {
|
||||||
transaction,
|
transaction,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
await users.setCustom_permissions(data.data.custom_permissions || [], {
|
await users.setCustom_permissions(userData.custom_permissions || [], {
|
||||||
transaction,
|
transaction,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -118,7 +127,7 @@ module.exports = class UsersDBApi {
|
|||||||
belongsToColumn: 'avatar',
|
belongsToColumn: 'avatar',
|
||||||
belongsToId: users.id,
|
belongsToId: users.id,
|
||||||
},
|
},
|
||||||
data.data.avatar,
|
userData.avatar,
|
||||||
options,
|
options,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -177,9 +186,16 @@ module.exports = class UsersDBApi {
|
|||||||
return users;
|
return users;
|
||||||
}
|
}
|
||||||
|
|
||||||
static async update(id, data, options) {
|
static async update(updateOptions) {
|
||||||
const currentUser = (options && options.currentUser) || { id: null };
|
assertUpdateOptions(updateOptions, 'DBApi');
|
||||||
const transaction = (options && options.transaction) || undefined;
|
const {
|
||||||
|
id,
|
||||||
|
data,
|
||||||
|
currentUser = { id: null },
|
||||||
|
transaction,
|
||||||
|
runtimeContext,
|
||||||
|
} = updateOptions;
|
||||||
|
const dbOptions = { currentUser, transaction, runtimeContext };
|
||||||
|
|
||||||
const users = await db.users.findByPk(id, { transaction });
|
const users = await db.users.findByPk(id, { transaction });
|
||||||
|
|
||||||
@ -272,15 +288,15 @@ module.exports = class UsersDBApi {
|
|||||||
belongsToId: users.id,
|
belongsToId: users.id,
|
||||||
},
|
},
|
||||||
data.avatar,
|
data.avatar,
|
||||||
options,
|
dbOptions,
|
||||||
);
|
);
|
||||||
|
|
||||||
return users;
|
return users;
|
||||||
}
|
}
|
||||||
|
|
||||||
static async deleteByIds(ids, options) {
|
static async deleteByIds(options) {
|
||||||
const currentUser = (options && options.currentUser) || { id: null };
|
assertDeleteByIdsOptions(options, 'DBApi');
|
||||||
const transaction = (options && options.transaction) || undefined;
|
const { ids, currentUser = { id: null }, transaction } = options;
|
||||||
|
|
||||||
const users = await db.users.findAll({
|
const users = await db.users.findAll({
|
||||||
where: {
|
where: {
|
||||||
@ -291,23 +307,21 @@ module.exports = class UsersDBApi {
|
|||||||
transaction,
|
transaction,
|
||||||
});
|
});
|
||||||
|
|
||||||
await db.sequelize.transaction(async (transaction) => {
|
|
||||||
for (const record of users) {
|
for (const record of users) {
|
||||||
await record.update({ deletedBy: currentUser.id }, { transaction });
|
await record.update({ deletedBy: currentUser.id }, { transaction });
|
||||||
}
|
}
|
||||||
for (const record of users) {
|
for (const record of users) {
|
||||||
await record.destroy({ transaction });
|
await record.destroy({ transaction });
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
return users;
|
return users;
|
||||||
}
|
}
|
||||||
|
|
||||||
static async remove(id, options) {
|
static async remove(options) {
|
||||||
const currentUser = (options && options.currentUser) || { id: null };
|
assertIdOptions(options, 'DBApi', 'remove');
|
||||||
const transaction = (options && options.transaction) || undefined;
|
const { id, currentUser = { id: null }, transaction } = options;
|
||||||
|
|
||||||
const users = await db.users.findByPk(id, options);
|
const users = await db.users.findByPk(id, { transaction });
|
||||||
|
|
||||||
await users.update(
|
await users.update(
|
||||||
{
|
{
|
||||||
@ -694,7 +708,10 @@ module.exports = class UsersDBApi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static async findAllAutocomplete(query, limit, offset) {
|
static async findAllAutocomplete(options, queryOptions = {}) {
|
||||||
|
assertAutocompleteOptions(options, 'DBApi');
|
||||||
|
const { query, limit, offset } = options;
|
||||||
|
const transaction = queryOptions.transaction;
|
||||||
let where = {};
|
let where = {};
|
||||||
|
|
||||||
if (query) {
|
if (query) {
|
||||||
@ -713,6 +730,7 @@ module.exports = class UsersDBApi {
|
|||||||
limit: limit ? Number(limit) : undefined,
|
limit: limit ? Number(limit) : undefined,
|
||||||
offset: offset ? Number(offset) : undefined,
|
offset: offset ? Number(offset) : undefined,
|
||||||
orderBy: [['firstName', 'ASC']],
|
orderBy: [['firstName', 'ASC']],
|
||||||
|
transaction,
|
||||||
});
|
});
|
||||||
|
|
||||||
return records.map((record) => ({
|
return records.map((record) => ({
|
||||||
|
|||||||
@ -0,0 +1,67 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const ROLE_PERMISSIONS = [
|
||||||
|
['Administrator', 'CREATE_PAGE_ELEMENTS'],
|
||||||
|
['Administrator', 'READ_PAGE_ELEMENTS'],
|
||||||
|
['Administrator', 'UPDATE_PAGE_ELEMENTS'],
|
||||||
|
['Administrator', 'DELETE_PAGE_ELEMENTS'],
|
||||||
|
['Platform Owner', 'CREATE_PAGE_ELEMENTS'],
|
||||||
|
['Platform Owner', 'READ_PAGE_ELEMENTS'],
|
||||||
|
['Platform Owner', 'UPDATE_PAGE_ELEMENTS'],
|
||||||
|
['Platform Owner', 'DELETE_PAGE_ELEMENTS'],
|
||||||
|
['Account Manager', 'CREATE_PAGE_ELEMENTS'],
|
||||||
|
['Account Manager', 'READ_PAGE_ELEMENTS'],
|
||||||
|
['Account Manager', 'UPDATE_PAGE_ELEMENTS'],
|
||||||
|
['Tour Designer', 'CREATE_PAGE_ELEMENTS'],
|
||||||
|
['Tour Designer', 'READ_PAGE_ELEMENTS'],
|
||||||
|
['Tour Designer', 'UPDATE_PAGE_ELEMENTS'],
|
||||||
|
['Content Reviewer', 'READ_PAGE_ELEMENTS'],
|
||||||
|
['Content Reviewer', 'UPDATE_PAGE_ELEMENTS'],
|
||||||
|
['Analytics Viewer', 'READ_PAGE_ELEMENTS'],
|
||||||
|
];
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
async up(queryInterface) {
|
||||||
|
await queryInterface.sequelize.query(
|
||||||
|
`
|
||||||
|
WITH expected(role_name, permission_name) AS (
|
||||||
|
VALUES ${ROLE_PERMISSIONS.map(() => '(?, ?)').join(', ')}
|
||||||
|
)
|
||||||
|
INSERT INTO "rolesPermissionsPermissions"
|
||||||
|
("createdAt", "updatedAt", "roles_permissionsId", "permissionId")
|
||||||
|
SELECT NOW(), NOW(), r.id, p.id
|
||||||
|
FROM expected e
|
||||||
|
JOIN roles r ON r.name = e.role_name
|
||||||
|
JOIN permissions p ON p.name = e.permission_name
|
||||||
|
WHERE NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM "rolesPermissionsPermissions" rp
|
||||||
|
WHERE rp."roles_permissionsId" = r.id
|
||||||
|
AND rp."permissionId" = p.id
|
||||||
|
)
|
||||||
|
`,
|
||||||
|
{
|
||||||
|
replacements: ROLE_PERMISSIONS.flat(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
async down(queryInterface) {
|
||||||
|
await queryInterface.sequelize.query(
|
||||||
|
`
|
||||||
|
WITH expected(role_name, permission_name) AS (
|
||||||
|
VALUES ${ROLE_PERMISSIONS.map(() => '(?, ?)').join(', ')}
|
||||||
|
)
|
||||||
|
DELETE FROM "rolesPermissionsPermissions" rp
|
||||||
|
USING expected e, roles r, permissions p
|
||||||
|
WHERE rp."roles_permissionsId" = r.id
|
||||||
|
AND rp."permissionId" = p.id
|
||||||
|
AND r.name = e.role_name
|
||||||
|
AND p.name = e.permission_name
|
||||||
|
`,
|
||||||
|
{
|
||||||
|
replacements: ROLE_PERMISSIONS.flat(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
@ -68,12 +68,13 @@ function createEntityRouter(entityName, Service, DBApi, options = {}) {
|
|||||||
req.headers.referer ||
|
req.headers.referer ||
|
||||||
`${req.protocol}://${req.hostname}${req.originalUrl}`;
|
`${req.protocol}://${req.hostname}${req.originalUrl}`;
|
||||||
const link = new URL(referer);
|
const link = new URL(referer);
|
||||||
const payload = await Service.create(
|
const payload = await Service.create({
|
||||||
req.body.data,
|
data: req.body.data,
|
||||||
req.currentUser,
|
currentUser: req.currentUser,
|
||||||
true,
|
runtimeContext: req.runtimeContext,
|
||||||
link.origin,
|
sendInvitationEmails: true,
|
||||||
);
|
host: link.origin,
|
||||||
|
});
|
||||||
res.status(200).send(payload);
|
res.status(200).send(payload);
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@ -95,7 +96,12 @@ function createEntityRouter(entityName, Service, DBApi, options = {}) {
|
|||||||
validateRequest(schemaFor('update')),
|
validateRequest(schemaFor('update')),
|
||||||
wrapAsync(async (req, res) => {
|
wrapAsync(async (req, res) => {
|
||||||
assertRouteIdMatchesBody(req);
|
assertRouteIdMatchesBody(req);
|
||||||
await Service.update(req.body.data, req.params.id, req.currentUser);
|
await Service.update({
|
||||||
|
id: req.params.id,
|
||||||
|
data: req.body.data,
|
||||||
|
currentUser: req.currentUser,
|
||||||
|
runtimeContext: req.runtimeContext,
|
||||||
|
});
|
||||||
res.status(200).send(true);
|
res.status(200).send(true);
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@ -104,7 +110,11 @@ function createEntityRouter(entityName, Service, DBApi, options = {}) {
|
|||||||
'/:id',
|
'/:id',
|
||||||
validateRequest(schemaFor('remove')),
|
validateRequest(schemaFor('remove')),
|
||||||
wrapAsync(async (req, res) => {
|
wrapAsync(async (req, res) => {
|
||||||
await Service.remove(req.params.id, req.currentUser);
|
await Service.remove({
|
||||||
|
id: req.params.id,
|
||||||
|
currentUser: req.currentUser,
|
||||||
|
runtimeContext: req.runtimeContext,
|
||||||
|
});
|
||||||
res.status(200).send(true);
|
res.status(200).send(true);
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@ -113,7 +123,11 @@ function createEntityRouter(entityName, Service, DBApi, options = {}) {
|
|||||||
'/deleteByIds',
|
'/deleteByIds',
|
||||||
validateRequest(schemaFor('deleteByIds')),
|
validateRequest(schemaFor('deleteByIds')),
|
||||||
wrapAsync(async (req, res) => {
|
wrapAsync(async (req, res) => {
|
||||||
await Service.deleteByIds(req.body.data, req.currentUser);
|
await Service.deleteByIds({
|
||||||
|
ids: req.body.data,
|
||||||
|
currentUser: req.currentUser,
|
||||||
|
runtimeContext: req.runtimeContext,
|
||||||
|
});
|
||||||
res.status(200).send(true);
|
res.status(200).send(true);
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@ -174,11 +188,11 @@ function createEntityRouter(entityName, Service, DBApi, options = {}) {
|
|||||||
defaultLimit: 20,
|
defaultLimit: 20,
|
||||||
maxLimit: MAX_AUTOCOMPLETE_LIMIT,
|
maxLimit: MAX_AUTOCOMPLETE_LIMIT,
|
||||||
});
|
});
|
||||||
const payload = await DBApi.findAllAutocomplete(
|
const payload = await DBApi.findAllAutocomplete({
|
||||||
req.query.query,
|
query: req.query.query,
|
||||||
limit,
|
limit,
|
||||||
req.query.offset,
|
offset: req.query.offset,
|
||||||
);
|
});
|
||||||
res.status(200).send(payload);
|
res.status(200).send(payload);
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|||||||
@ -3,19 +3,41 @@ const processFile = require('../middlewares/upload');
|
|||||||
const ValidationError = require('../services/notifications/errors/validation');
|
const ValidationError = require('../services/notifications/errors/validation');
|
||||||
const csv = require('csv-parser');
|
const csv = require('csv-parser');
|
||||||
const stream = require('stream');
|
const stream = require('stream');
|
||||||
|
const {
|
||||||
|
assertCreateOptions,
|
||||||
|
assertDeleteByIdsOptions,
|
||||||
|
assertIdOptions,
|
||||||
|
assertUpdateOptions,
|
||||||
|
} = require('../contracts/entity-options');
|
||||||
|
|
||||||
function createEntityService(DBApi, options = {}) {
|
function createEntityService(DBApi, options = {}) {
|
||||||
const entityName = options.entityName || 'Entity';
|
const entityName = options.entityName || 'Entity';
|
||||||
|
|
||||||
return class GenericService {
|
return class GenericService {
|
||||||
static async create(data, currentUser) {
|
static async create(options) {
|
||||||
const transaction = await db.sequelize.transaction();
|
assertCreateOptions(options, 'Service');
|
||||||
|
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
currentUser,
|
||||||
|
transaction: externalTransaction,
|
||||||
|
runtimeContext,
|
||||||
|
} = options;
|
||||||
|
const transaction =
|
||||||
|
externalTransaction || (await db.sequelize.transaction());
|
||||||
|
const ownsTransaction = !externalTransaction;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const record = await DBApi.create(data, { currentUser, transaction });
|
const record = await DBApi.create({
|
||||||
await transaction.commit();
|
data,
|
||||||
|
currentUser,
|
||||||
|
transaction,
|
||||||
|
runtimeContext,
|
||||||
|
});
|
||||||
|
if (ownsTransaction) await transaction.commit();
|
||||||
return record;
|
return record;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await transaction.rollback();
|
if (ownsTransaction) await transaction.rollback();
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -52,45 +74,103 @@ function createEntityService(DBApi, options = {}) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static async update(data, id, currentUser) {
|
/**
|
||||||
const transaction = await db.sequelize.transaction();
|
* @param {Object} options
|
||||||
|
* @param {string} options.id
|
||||||
|
* @param {Object} options.data
|
||||||
|
* @param {Object} [options.currentUser]
|
||||||
|
* @param {Object} [options.transaction]
|
||||||
|
* @param {Object} [options.runtimeContext]
|
||||||
|
*/
|
||||||
|
static async update(options) {
|
||||||
|
assertUpdateOptions(options, 'Service');
|
||||||
|
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
data,
|
||||||
|
currentUser,
|
||||||
|
transaction: externalTransaction,
|
||||||
|
runtimeContext,
|
||||||
|
} = options;
|
||||||
|
const transaction =
|
||||||
|
externalTransaction || (await db.sequelize.transaction());
|
||||||
|
const ownsTransaction = !externalTransaction;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const record = await DBApi.findBy({ id }, { transaction });
|
const record = await DBApi.findBy(
|
||||||
|
{ id },
|
||||||
|
{ transaction, runtimeContext },
|
||||||
|
);
|
||||||
|
|
||||||
if (!record) {
|
if (!record) {
|
||||||
throw new ValidationError(`${entityName}NotFound`);
|
throw new ValidationError(`${entityName}NotFound`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const updated = await DBApi.update(id, data, {
|
const updated = await DBApi.update({
|
||||||
|
id,
|
||||||
|
data,
|
||||||
currentUser,
|
currentUser,
|
||||||
transaction,
|
transaction,
|
||||||
|
runtimeContext,
|
||||||
});
|
});
|
||||||
await transaction.commit();
|
if (ownsTransaction) await transaction.commit();
|
||||||
return updated;
|
return updated;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await transaction.rollback();
|
if (ownsTransaction) await transaction.rollback();
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static async deleteByIds(ids, currentUser) {
|
static async deleteByIds(options) {
|
||||||
const transaction = await db.sequelize.transaction();
|
assertDeleteByIdsOptions(options, 'Service');
|
||||||
|
|
||||||
|
const {
|
||||||
|
ids,
|
||||||
|
currentUser,
|
||||||
|
transaction: externalTransaction,
|
||||||
|
runtimeContext,
|
||||||
|
} = options;
|
||||||
|
const transaction =
|
||||||
|
externalTransaction || (await db.sequelize.transaction());
|
||||||
|
const ownsTransaction = !externalTransaction;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await DBApi.deleteByIds(ids, { currentUser, transaction });
|
await DBApi.deleteByIds({
|
||||||
await transaction.commit();
|
ids,
|
||||||
|
currentUser,
|
||||||
|
transaction,
|
||||||
|
runtimeContext,
|
||||||
|
});
|
||||||
|
if (ownsTransaction) await transaction.commit();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await transaction.rollback();
|
if (ownsTransaction) await transaction.rollback();
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static async remove(id, currentUser) {
|
static async remove(options) {
|
||||||
const transaction = await db.sequelize.transaction();
|
assertIdOptions(options, 'Service', 'remove');
|
||||||
|
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
currentUser,
|
||||||
|
transaction: externalTransaction,
|
||||||
|
runtimeContext,
|
||||||
|
} = options;
|
||||||
|
const transaction =
|
||||||
|
externalTransaction || (await db.sequelize.transaction());
|
||||||
|
const ownsTransaction = !externalTransaction;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await DBApi.remove(id, { currentUser, transaction });
|
await DBApi.remove({
|
||||||
await transaction.commit();
|
id,
|
||||||
|
currentUser,
|
||||||
|
transaction,
|
||||||
|
runtimeContext,
|
||||||
|
});
|
||||||
|
if (ownsTransaction) await transaction.commit();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await transaction.rollback();
|
if (ownsTransaction) await transaction.rollback();
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -123,11 +123,12 @@ router.put(
|
|||||||
jwtAuth,
|
jwtAuth,
|
||||||
wrapAsync(async (req, res) => {
|
wrapAsync(async (req, res) => {
|
||||||
assertRouteIdMatchesBody(req);
|
assertRouteIdMatchesBody(req);
|
||||||
await Global_transition_defaultsService.update(
|
await Global_transition_defaultsService.update({
|
||||||
req.body.data,
|
id: req.params.id,
|
||||||
req.params.id,
|
data: req.body.data,
|
||||||
req.currentUser,
|
currentUser: req.currentUser,
|
||||||
);
|
runtimeContext: req.runtimeContext,
|
||||||
|
});
|
||||||
res.status(200).send(true);
|
res.status(200).send(true);
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|||||||
@ -44,11 +44,12 @@ router.put(
|
|||||||
'/:id',
|
'/:id',
|
||||||
jwtAuth,
|
jwtAuth,
|
||||||
wrapAsync(async (req, res) => {
|
wrapAsync(async (req, res) => {
|
||||||
await Global_ui_control_defaultsService.update(
|
await Global_ui_control_defaultsService.update({
|
||||||
req.body.data,
|
id: req.params.id,
|
||||||
req.params.id,
|
data: req.body.data,
|
||||||
req.currentUser,
|
currentUser: req.currentUser,
|
||||||
);
|
runtimeContext: req.runtimeContext,
|
||||||
|
});
|
||||||
const payload = await Global_ui_control_defaultsDBApi.findOne();
|
const payload = await Global_ui_control_defaultsDBApi.findOne();
|
||||||
res.status(200).send(payload);
|
res.status(200).send(payload);
|
||||||
}),
|
}),
|
||||||
|
|||||||
@ -284,10 +284,11 @@ router.delete(
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (settings) {
|
if (settings) {
|
||||||
await Project_transition_settingsService.remove(
|
await Project_transition_settingsService.remove({
|
||||||
settings.id,
|
id: settings.id,
|
||||||
req.currentUser,
|
currentUser: req.currentUser,
|
||||||
);
|
runtimeContext: req.runtimeContext,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
res.status(200).send({ success: true });
|
res.status(200).send({ success: true });
|
||||||
@ -340,10 +341,11 @@ router.post(
|
|||||||
'/',
|
'/',
|
||||||
jwtAuth,
|
jwtAuth,
|
||||||
wrapAsync(async (req, res) => {
|
wrapAsync(async (req, res) => {
|
||||||
const payload = await Project_transition_settingsService.create(
|
const payload = await Project_transition_settingsService.create({
|
||||||
req.body.data,
|
data: req.body.data,
|
||||||
req.currentUser,
|
currentUser: req.currentUser,
|
||||||
);
|
runtimeContext: req.runtimeContext,
|
||||||
|
});
|
||||||
res.status(200).send(payload);
|
res.status(200).send(payload);
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@ -415,11 +417,12 @@ router.put(
|
|||||||
jwtAuth,
|
jwtAuth,
|
||||||
wrapAsync(async (req, res) => {
|
wrapAsync(async (req, res) => {
|
||||||
assertRouteIdMatchesBody(req);
|
assertRouteIdMatchesBody(req);
|
||||||
await Project_transition_settingsService.update(
|
await Project_transition_settingsService.update({
|
||||||
req.body.data,
|
id: req.params.id,
|
||||||
req.params.id,
|
data: req.body.data,
|
||||||
req.currentUser,
|
currentUser: req.currentUser,
|
||||||
);
|
runtimeContext: req.runtimeContext,
|
||||||
|
});
|
||||||
res.status(200).send(true);
|
res.status(200).send(true);
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@ -447,10 +450,11 @@ router.delete(
|
|||||||
'/:id',
|
'/:id',
|
||||||
jwtAuth,
|
jwtAuth,
|
||||||
wrapAsync(async (req, res) => {
|
wrapAsync(async (req, res) => {
|
||||||
await Project_transition_settingsService.remove(
|
await Project_transition_settingsService.remove({
|
||||||
req.params.id,
|
id: req.params.id,
|
||||||
req.currentUser,
|
currentUser: req.currentUser,
|
||||||
);
|
runtimeContext: req.runtimeContext,
|
||||||
|
});
|
||||||
res.status(200).send(true);
|
res.status(200).send(true);
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|||||||
@ -161,10 +161,11 @@ router.delete(
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (settings) {
|
if (settings) {
|
||||||
await Project_ui_control_settingsService.remove(
|
await Project_ui_control_settingsService.remove({
|
||||||
settings.id,
|
id: settings.id,
|
||||||
req.currentUser,
|
currentUser: req.currentUser,
|
||||||
);
|
runtimeContext: req.runtimeContext,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
res.status(200).send({ success: true });
|
res.status(200).send({ success: true });
|
||||||
|
|||||||
@ -175,12 +175,13 @@ router.post(
|
|||||||
req.headers.referer ||
|
req.headers.referer ||
|
||||||
`${req.protocol}://${req.hostname}${req.originalUrl}`;
|
`${req.protocol}://${req.hostname}${req.originalUrl}`;
|
||||||
const link = new URL(referer);
|
const link = new URL(referer);
|
||||||
const payload = await Tour_pagesService.create(
|
const payload = await Tour_pagesService.create({
|
||||||
req.body.data,
|
data: req.body.data,
|
||||||
req.currentUser,
|
currentUser: req.currentUser,
|
||||||
true,
|
runtimeContext: req.runtimeContext,
|
||||||
link.origin,
|
sendInvitationEmails: true,
|
||||||
);
|
host: link.origin,
|
||||||
|
});
|
||||||
res.status(200).send(payload);
|
res.status(200).send(payload);
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@ -231,11 +232,12 @@ router.put(
|
|||||||
validateRequest(tourPageSchemas.update),
|
validateRequest(tourPageSchemas.update),
|
||||||
wrapAsync(async (req, res) => {
|
wrapAsync(async (req, res) => {
|
||||||
assertRouteIdMatchesBody(req);
|
assertRouteIdMatchesBody(req);
|
||||||
await Tour_pagesService.update(
|
await Tour_pagesService.update({
|
||||||
req.body.data,
|
id: req.params.id,
|
||||||
req.params.id,
|
data: req.body.data,
|
||||||
req.currentUser,
|
currentUser: req.currentUser,
|
||||||
);
|
runtimeContext: req.runtimeContext,
|
||||||
|
});
|
||||||
res.status(200).send(true);
|
res.status(200).send(true);
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@ -245,7 +247,11 @@ router.delete(
|
|||||||
'/:id',
|
'/:id',
|
||||||
validateRequest(crudSchemas.remove),
|
validateRequest(crudSchemas.remove),
|
||||||
wrapAsync(async (req, res) => {
|
wrapAsync(async (req, res) => {
|
||||||
await Tour_pagesService.remove(req.params.id, req.currentUser);
|
await Tour_pagesService.remove({
|
||||||
|
id: req.params.id,
|
||||||
|
currentUser: req.currentUser,
|
||||||
|
runtimeContext: req.runtimeContext,
|
||||||
|
});
|
||||||
res.status(200).send(true);
|
res.status(200).send(true);
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@ -255,7 +261,11 @@ router.post(
|
|||||||
'/deleteByIds',
|
'/deleteByIds',
|
||||||
validateRequest(crudSchemas.deleteByIds),
|
validateRequest(crudSchemas.deleteByIds),
|
||||||
wrapAsync(async (req, res) => {
|
wrapAsync(async (req, res) => {
|
||||||
await Tour_pagesService.deleteByIds(req.body.data, req.currentUser);
|
await Tour_pagesService.deleteByIds({
|
||||||
|
ids: req.body.data,
|
||||||
|
currentUser: req.currentUser,
|
||||||
|
runtimeContext: req.runtimeContext,
|
||||||
|
});
|
||||||
res.status(200).send(true);
|
res.status(200).send(true);
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@ -334,11 +344,11 @@ router.get(
|
|||||||
'/autocomplete',
|
'/autocomplete',
|
||||||
validateRequest(crudSchemas.autocomplete),
|
validateRequest(crudSchemas.autocomplete),
|
||||||
wrapAsync(async (req, res) => {
|
wrapAsync(async (req, res) => {
|
||||||
const payload = await Tour_pagesDBApi.findAllAutocomplete(
|
const payload = await Tour_pagesDBApi.findAllAutocomplete({
|
||||||
req.query.query,
|
query: req.query.query,
|
||||||
req.query.limit,
|
limit: req.query.limit,
|
||||||
req.query.offset,
|
offset: req.query.offset,
|
||||||
);
|
});
|
||||||
res.status(200).send(payload);
|
res.status(200).send(payload);
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,5 +1,9 @@
|
|||||||
const AssetsDBApi = require('../db/api/assets');
|
const AssetsDBApi = require('../db/api/assets');
|
||||||
const { createEntityService } = require('../factories/service.factory');
|
const { createEntityService } = require('../factories/service.factory');
|
||||||
|
const {
|
||||||
|
assertCreateOptions,
|
||||||
|
assertUpdateOptions,
|
||||||
|
} = require('../contracts/entity-options');
|
||||||
const ValidationError = require('./notifications/errors/validation');
|
const ValidationError = require('./notifications/errors/validation');
|
||||||
const { downloadToTempFile } = require('./file');
|
const { downloadToTempFile } = require('./file');
|
||||||
const { probeMediaMetadata } = require('./videoProcessing');
|
const { probeMediaMetadata } = require('./videoProcessing');
|
||||||
@ -196,7 +200,11 @@ class AssetsService extends BaseService {
|
|||||||
/**
|
/**
|
||||||
* Create asset with MIME type validation, embed URL validation, and video pre-processing
|
* Create asset with MIME type validation, embed URL validation, and video pre-processing
|
||||||
*/
|
*/
|
||||||
static async create(data, currentUser) {
|
static async create(options) {
|
||||||
|
assertCreateOptions(options, 'Service');
|
||||||
|
let { data } = options;
|
||||||
|
const { currentUser, transaction, runtimeContext } = options;
|
||||||
|
|
||||||
// Validate asset_type and mime_type match
|
// Validate asset_type and mime_type match
|
||||||
const assetType = data.asset_type;
|
const assetType = data.asset_type;
|
||||||
const mimeType = data.mime_type;
|
const mimeType = data.mime_type;
|
||||||
@ -216,7 +224,12 @@ class AssetsService extends BaseService {
|
|||||||
data = await AssetsService.enrichStoredMediaMetadata(data);
|
data = await AssetsService.enrichStoredMediaMetadata(data);
|
||||||
|
|
||||||
// Call parent create
|
// Call parent create
|
||||||
const asset = await super.create(data, currentUser);
|
const asset = await super.create({
|
||||||
|
data,
|
||||||
|
currentUser,
|
||||||
|
transaction,
|
||||||
|
runtimeContext,
|
||||||
|
});
|
||||||
|
|
||||||
// Note: Reversed video generation is handled by tour_pages.js when the video
|
// Note: Reversed video generation is handled by tour_pages.js when the video
|
||||||
// is assigned to a navigation element. This avoids unnecessary processing
|
// is assigned to a navigation element. This avoids unnecessary processing
|
||||||
@ -228,8 +241,15 @@ class AssetsService extends BaseService {
|
|||||||
/**
|
/**
|
||||||
* Update asset with MIME type validation and embed URL validation
|
* Update asset with MIME type validation and embed URL validation
|
||||||
*/
|
*/
|
||||||
static async update(data, id, currentUser) {
|
static async update(options) {
|
||||||
const existingAsset = await AssetsDBApi.findBy({ id });
|
assertUpdateOptions(options, 'Service');
|
||||||
|
let { data } = options;
|
||||||
|
const { id, currentUser, transaction, runtimeContext } = options;
|
||||||
|
|
||||||
|
const existingAsset = await AssetsDBApi.findBy(
|
||||||
|
{ id },
|
||||||
|
{ transaction, runtimeContext },
|
||||||
|
);
|
||||||
|
|
||||||
// If updating asset_type or mime_type, validate they match
|
// If updating asset_type or mime_type, validate they match
|
||||||
if (data.asset_type || data.mime_type) {
|
if (data.asset_type || data.mime_type) {
|
||||||
@ -258,7 +278,13 @@ class AssetsService extends BaseService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Call parent update
|
// Call parent update
|
||||||
return super.update(data, id, currentUser);
|
return super.update({
|
||||||
|
id,
|
||||||
|
data,
|
||||||
|
currentUser,
|
||||||
|
transaction,
|
||||||
|
runtimeContext,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -155,7 +155,9 @@ class Auth {
|
|||||||
try {
|
try {
|
||||||
await UsersDBApi.findBy({ id: currentUser.id }, { transaction });
|
await UsersDBApi.findBy({ id: currentUser.id }, { transaction });
|
||||||
|
|
||||||
await UsersDBApi.update(currentUser.id, data, {
|
await UsersDBApi.update({
|
||||||
|
id: currentUser.id,
|
||||||
|
data,
|
||||||
currentUser,
|
currentUser,
|
||||||
transaction,
|
transaction,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -2,22 +2,40 @@ const db = require('../db/models');
|
|||||||
const Project_audio_tracksDBApi = require('../db/api/project_audio_tracks');
|
const Project_audio_tracksDBApi = require('../db/api/project_audio_tracks');
|
||||||
const processFile = require('../middlewares/upload');
|
const processFile = require('../middlewares/upload');
|
||||||
const ValidationError = require('./notifications/errors/validation');
|
const ValidationError = require('./notifications/errors/validation');
|
||||||
|
const {
|
||||||
|
assertCreateOptions,
|
||||||
|
assertDeleteByIdsOptions,
|
||||||
|
assertIdOptions,
|
||||||
|
assertUpdateOptions,
|
||||||
|
} = require('../contracts/entity-options');
|
||||||
const csv = require('csv-parser');
|
const csv = require('csv-parser');
|
||||||
const stream = require('stream');
|
const stream = require('stream');
|
||||||
|
|
||||||
module.exports = class Project_audio_tracksService {
|
module.exports = class Project_audio_tracksService {
|
||||||
static async create(data, currentUser) {
|
static async create(options) {
|
||||||
const transaction = await db.sequelize.transaction();
|
assertCreateOptions(options, 'Service');
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
currentUser,
|
||||||
|
transaction: externalTransaction,
|
||||||
|
runtimeContext,
|
||||||
|
} = options;
|
||||||
|
const transaction =
|
||||||
|
externalTransaction || (await db.sequelize.transaction());
|
||||||
|
const ownsTransaction = !externalTransaction;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const createdTrack = await Project_audio_tracksDBApi.create(data, {
|
const createdTrack = await Project_audio_tracksDBApi.create({
|
||||||
|
data,
|
||||||
currentUser,
|
currentUser,
|
||||||
transaction,
|
transaction,
|
||||||
|
runtimeContext,
|
||||||
});
|
});
|
||||||
|
|
||||||
await transaction.commit();
|
if (ownsTransaction) await transaction.commit();
|
||||||
return createdTrack;
|
return createdTrack;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await transaction.rollback();
|
if (ownsTransaction) await transaction.rollback();
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -57,12 +75,23 @@ module.exports = class Project_audio_tracksService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static async update(data, id, currentUser) {
|
static async update(options) {
|
||||||
const transaction = await db.sequelize.transaction();
|
assertUpdateOptions(options, 'Service');
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
data,
|
||||||
|
currentUser,
|
||||||
|
transaction: externalTransaction,
|
||||||
|
runtimeContext,
|
||||||
|
} = options;
|
||||||
|
const transaction =
|
||||||
|
externalTransaction || (await db.sequelize.transaction());
|
||||||
|
const ownsTransaction = !externalTransaction;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let project_audio_tracks = await Project_audio_tracksDBApi.findBy(
|
let project_audio_tracks = await Project_audio_tracksDBApi.findBy(
|
||||||
{ id },
|
{ id },
|
||||||
{ transaction },
|
{ transaction, runtimeContext },
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!project_audio_tracks) {
|
if (!project_audio_tracks) {
|
||||||
@ -70,47 +99,72 @@ module.exports = class Project_audio_tracksService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const updatedProject_audio_tracks =
|
const updatedProject_audio_tracks =
|
||||||
await Project_audio_tracksDBApi.update(id, data, {
|
await Project_audio_tracksDBApi.update({
|
||||||
|
id,
|
||||||
|
data,
|
||||||
currentUser,
|
currentUser,
|
||||||
transaction,
|
transaction,
|
||||||
|
runtimeContext,
|
||||||
});
|
});
|
||||||
|
|
||||||
await transaction.commit();
|
if (ownsTransaction) await transaction.commit();
|
||||||
return updatedProject_audio_tracks;
|
return updatedProject_audio_tracks;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await transaction.rollback();
|
if (ownsTransaction) await transaction.rollback();
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static async deleteByIds(ids, currentUser) {
|
static async deleteByIds(options) {
|
||||||
const transaction = await db.sequelize.transaction();
|
assertDeleteByIdsOptions(options, 'Service');
|
||||||
|
const {
|
||||||
|
ids,
|
||||||
|
currentUser,
|
||||||
|
transaction: externalTransaction,
|
||||||
|
runtimeContext,
|
||||||
|
} = options;
|
||||||
|
const transaction =
|
||||||
|
externalTransaction || (await db.sequelize.transaction());
|
||||||
|
const ownsTransaction = !externalTransaction;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await Project_audio_tracksDBApi.deleteByIds(ids, {
|
await Project_audio_tracksDBApi.deleteByIds({
|
||||||
|
ids,
|
||||||
currentUser,
|
currentUser,
|
||||||
transaction,
|
transaction,
|
||||||
|
runtimeContext,
|
||||||
});
|
});
|
||||||
|
|
||||||
await transaction.commit();
|
if (ownsTransaction) await transaction.commit();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await transaction.rollback();
|
if (ownsTransaction) await transaction.rollback();
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static async remove(id, currentUser) {
|
static async remove(options) {
|
||||||
const transaction = await db.sequelize.transaction();
|
assertIdOptions(options, 'Service', 'remove');
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
currentUser,
|
||||||
|
transaction: externalTransaction,
|
||||||
|
runtimeContext,
|
||||||
|
} = options;
|
||||||
|
const transaction =
|
||||||
|
externalTransaction || (await db.sequelize.transaction());
|
||||||
|
const ownsTransaction = !externalTransaction;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await Project_audio_tracksDBApi.remove(id, {
|
await Project_audio_tracksDBApi.remove({
|
||||||
|
id,
|
||||||
currentUser,
|
currentUser,
|
||||||
transaction,
|
transaction,
|
||||||
|
runtimeContext,
|
||||||
});
|
});
|
||||||
|
|
||||||
await transaction.commit();
|
if (ownsTransaction) await transaction.commit();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await transaction.rollback();
|
if (ownsTransaction) await transaction.rollback();
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,25 +2,40 @@ const db = require('../db/models');
|
|||||||
const Project_transition_settingsDBApi = require('../db/api/project_transition_settings');
|
const Project_transition_settingsDBApi = require('../db/api/project_transition_settings');
|
||||||
const processFile = require('../middlewares/upload');
|
const processFile = require('../middlewares/upload');
|
||||||
const ValidationError = require('./notifications/errors/validation');
|
const ValidationError = require('./notifications/errors/validation');
|
||||||
|
const {
|
||||||
|
assertCreateOptions,
|
||||||
|
assertDeleteByIdsOptions,
|
||||||
|
assertIdOptions,
|
||||||
|
assertUpdateOptions,
|
||||||
|
} = require('../contracts/entity-options');
|
||||||
const csv = require('csv-parser');
|
const csv = require('csv-parser');
|
||||||
const stream = require('stream');
|
const stream = require('stream');
|
||||||
|
|
||||||
module.exports = class Project_transition_settingsService {
|
module.exports = class Project_transition_settingsService {
|
||||||
static async create(data, currentUser) {
|
static async create(options) {
|
||||||
const transaction = await db.sequelize.transaction();
|
assertCreateOptions(options, 'Service');
|
||||||
try {
|
const {
|
||||||
const createdRecord = await Project_transition_settingsDBApi.create(
|
data,
|
||||||
|
currentUser,
|
||||||
|
transaction: externalTransaction,
|
||||||
|
runtimeContext,
|
||||||
|
} = options;
|
||||||
|
const transaction =
|
||||||
|
externalTransaction || (await db.sequelize.transaction());
|
||||||
|
const ownsTransaction = !externalTransaction;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const createdRecord = await Project_transition_settingsDBApi.create({
|
||||||
data,
|
data,
|
||||||
{
|
|
||||||
currentUser,
|
currentUser,
|
||||||
transaction,
|
transaction,
|
||||||
},
|
runtimeContext,
|
||||||
);
|
});
|
||||||
|
|
||||||
await transaction.commit();
|
if (ownsTransaction) await transaction.commit();
|
||||||
return createdRecord;
|
return createdRecord;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await transaction.rollback();
|
if (ownsTransaction) await transaction.rollback();
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -60,63 +75,95 @@ module.exports = class Project_transition_settingsService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static async update(data, id, currentUser) {
|
static async update(options) {
|
||||||
const transaction = await db.sequelize.transaction();
|
assertUpdateOptions(options, 'Service');
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
data,
|
||||||
|
currentUser,
|
||||||
|
transaction: externalTransaction,
|
||||||
|
runtimeContext,
|
||||||
|
} = options;
|
||||||
|
const transaction =
|
||||||
|
externalTransaction || (await db.sequelize.transaction());
|
||||||
|
const ownsTransaction = !externalTransaction;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let record = await Project_transition_settingsDBApi.findBy(
|
let record = await Project_transition_settingsDBApi.findBy(
|
||||||
{ id },
|
{ id },
|
||||||
{ transaction },
|
{ transaction, runtimeContext },
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!record) {
|
if (!record) {
|
||||||
throw new ValidationError('project_transition_settingsNotFound');
|
throw new ValidationError('project_transition_settingsNotFound');
|
||||||
}
|
}
|
||||||
|
|
||||||
const updatedRecord = await Project_transition_settingsDBApi.update(
|
const updatedRecord = await Project_transition_settingsDBApi.update({
|
||||||
id,
|
id,
|
||||||
data,
|
data,
|
||||||
{
|
|
||||||
currentUser,
|
currentUser,
|
||||||
transaction,
|
transaction,
|
||||||
},
|
runtimeContext,
|
||||||
);
|
});
|
||||||
|
|
||||||
await transaction.commit();
|
if (ownsTransaction) await transaction.commit();
|
||||||
return updatedRecord;
|
return updatedRecord;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await transaction.rollback();
|
if (ownsTransaction) await transaction.rollback();
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static async deleteByIds(ids, currentUser) {
|
static async deleteByIds(options) {
|
||||||
const transaction = await db.sequelize.transaction();
|
assertDeleteByIdsOptions(options, 'Service');
|
||||||
|
const {
|
||||||
|
ids,
|
||||||
|
currentUser,
|
||||||
|
transaction: externalTransaction,
|
||||||
|
runtimeContext,
|
||||||
|
} = options;
|
||||||
|
const transaction =
|
||||||
|
externalTransaction || (await db.sequelize.transaction());
|
||||||
|
const ownsTransaction = !externalTransaction;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await Project_transition_settingsDBApi.deleteByIds(ids, {
|
await Project_transition_settingsDBApi.deleteByIds({
|
||||||
|
ids,
|
||||||
currentUser,
|
currentUser,
|
||||||
transaction,
|
transaction,
|
||||||
|
runtimeContext,
|
||||||
});
|
});
|
||||||
|
|
||||||
await transaction.commit();
|
if (ownsTransaction) await transaction.commit();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await transaction.rollback();
|
if (ownsTransaction) await transaction.rollback();
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static async remove(id, currentUser) {
|
static async remove(options) {
|
||||||
const transaction = await db.sequelize.transaction();
|
assertIdOptions(options, 'Service', 'remove');
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
currentUser,
|
||||||
|
transaction: externalTransaction,
|
||||||
|
runtimeContext,
|
||||||
|
} = options;
|
||||||
|
const transaction =
|
||||||
|
externalTransaction || (await db.sequelize.transaction());
|
||||||
|
const ownsTransaction = !externalTransaction;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await Project_transition_settingsDBApi.remove(id, {
|
await Project_transition_settingsDBApi.remove({
|
||||||
|
id,
|
||||||
currentUser,
|
currentUser,
|
||||||
transaction,
|
transaction,
|
||||||
|
runtimeContext,
|
||||||
});
|
});
|
||||||
|
|
||||||
await transaction.commit();
|
if (ownsTransaction) await transaction.commit();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await transaction.rollback();
|
if (ownsTransaction) await transaction.rollback();
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
const db = require('../db/models');
|
const db = require('../db/models');
|
||||||
const Project_ui_control_settingsDBApi = require('../db/api/project_ui_control_settings');
|
const Project_ui_control_settingsDBApi = require('../db/api/project_ui_control_settings');
|
||||||
const ValidationError = require('./notifications/errors/validation');
|
const ValidationError = require('./notifications/errors/validation');
|
||||||
|
const { assertIdOptions } = require('../contracts/entity-options');
|
||||||
|
|
||||||
module.exports = class Project_ui_control_settingsService {
|
module.exports = class Project_ui_control_settingsService {
|
||||||
static async findByProjectAndEnvironment(
|
static async findByProjectAndEnvironment(
|
||||||
@ -34,27 +35,38 @@ module.exports = class Project_ui_control_settingsService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static async remove(id, currentUser) {
|
static async remove(options) {
|
||||||
const transaction = await db.sequelize.transaction();
|
assertIdOptions(options, 'Service', 'remove');
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
currentUser,
|
||||||
|
transaction: externalTransaction,
|
||||||
|
runtimeContext,
|
||||||
|
} = options;
|
||||||
|
const transaction =
|
||||||
|
externalTransaction || (await db.sequelize.transaction());
|
||||||
|
const ownsTransaction = !externalTransaction;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const record = await Project_ui_control_settingsDBApi.findBy(
|
const record = await Project_ui_control_settingsDBApi.findBy(
|
||||||
{ id },
|
{ id },
|
||||||
{ transaction },
|
{ transaction, runtimeContext },
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!record) {
|
if (!record) {
|
||||||
throw new ValidationError('project_ui_control_settingsNotFound');
|
throw new ValidationError('project_ui_control_settingsNotFound');
|
||||||
}
|
}
|
||||||
|
|
||||||
await Project_ui_control_settingsDBApi.remove(id, {
|
await Project_ui_control_settingsDBApi.remove({
|
||||||
|
id,
|
||||||
currentUser,
|
currentUser,
|
||||||
transaction,
|
transaction,
|
||||||
|
runtimeContext,
|
||||||
});
|
});
|
||||||
|
|
||||||
await transaction.commit();
|
if (ownsTransaction) await transaction.commit();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await transaction.rollback();
|
if (ownsTransaction) await transaction.rollback();
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,6 +3,10 @@ const { v4: uuidv4 } = require('uuid');
|
|||||||
const db = require('../db/models');
|
const db = require('../db/models');
|
||||||
const ProjectsDBApi = require('../db/api/projects');
|
const ProjectsDBApi = require('../db/api/projects');
|
||||||
const { createEntityService } = require('../factories/service.factory');
|
const { createEntityService } = require('../factories/service.factory');
|
||||||
|
const {
|
||||||
|
assertCreateOptions,
|
||||||
|
assertUpdateOptions,
|
||||||
|
} = require('../contracts/entity-options');
|
||||||
const ValidationError = require('./notifications/errors/validation');
|
const ValidationError = require('./notifications/errors/validation');
|
||||||
const FileService = require('./file');
|
const FileService = require('./file');
|
||||||
const { logger } = require('../utils/logger');
|
const { logger } = require('../utils/logger');
|
||||||
@ -147,8 +151,18 @@ class ProjectsService extends BaseProjectsService {
|
|||||||
/**
|
/**
|
||||||
* Create project with slug validation
|
* Create project with slug validation
|
||||||
*/
|
*/
|
||||||
static async create(data, currentUser) {
|
static async create(options) {
|
||||||
const transaction = await db.sequelize.transaction();
|
assertCreateOptions(options, 'Service');
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
currentUser,
|
||||||
|
transaction: externalTransaction,
|
||||||
|
runtimeContext,
|
||||||
|
} = options;
|
||||||
|
const transaction =
|
||||||
|
externalTransaction || (await db.sequelize.transaction());
|
||||||
|
const ownsTransaction = !externalTransaction;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (data.slug) {
|
if (data.slug) {
|
||||||
data.slug = await ProjectsService.validateSlugUniqueness(
|
data.slug = await ProjectsService.validateSlugUniqueness(
|
||||||
@ -158,15 +172,17 @@ class ProjectsService extends BaseProjectsService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const project = await ProjectsDBApi.create(data, {
|
const project = await ProjectsDBApi.create({
|
||||||
|
data,
|
||||||
currentUser,
|
currentUser,
|
||||||
transaction,
|
transaction,
|
||||||
|
runtimeContext,
|
||||||
});
|
});
|
||||||
|
|
||||||
await transaction.commit();
|
if (ownsTransaction) await transaction.commit();
|
||||||
return project;
|
return project;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await transaction.rollback();
|
if (ownsTransaction) await transaction.rollback();
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -174,10 +190,24 @@ class ProjectsService extends BaseProjectsService {
|
|||||||
/**
|
/**
|
||||||
* Update project with slug validation
|
* Update project with slug validation
|
||||||
*/
|
*/
|
||||||
static async update(data, id, currentUser) {
|
static async update(options) {
|
||||||
const transaction = await db.sequelize.transaction();
|
assertUpdateOptions(options, 'Service');
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
data,
|
||||||
|
currentUser,
|
||||||
|
transaction: externalTransaction,
|
||||||
|
runtimeContext,
|
||||||
|
} = options;
|
||||||
|
const transaction =
|
||||||
|
externalTransaction || (await db.sequelize.transaction());
|
||||||
|
const ownsTransaction = !externalTransaction;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const project = await ProjectsDBApi.findBy({ id }, { transaction });
|
const project = await ProjectsDBApi.findBy(
|
||||||
|
{ id },
|
||||||
|
{ transaction, runtimeContext },
|
||||||
|
);
|
||||||
|
|
||||||
if (!project) {
|
if (!project) {
|
||||||
throw new ValidationError('projectsNotFound');
|
throw new ValidationError('projectsNotFound');
|
||||||
@ -191,15 +221,18 @@ class ProjectsService extends BaseProjectsService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const updated = await ProjectsDBApi.update(id, data, {
|
const updated = await ProjectsDBApi.update({
|
||||||
|
id,
|
||||||
|
data,
|
||||||
currentUser,
|
currentUser,
|
||||||
transaction,
|
transaction,
|
||||||
|
runtimeContext,
|
||||||
});
|
});
|
||||||
|
|
||||||
await transaction.commit();
|
if (ownsTransaction) await transaction.commit();
|
||||||
return updated;
|
return updated;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await transaction.rollback();
|
if (ownsTransaction) await transaction.rollback();
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -238,8 +271,8 @@ class ProjectsService extends BaseProjectsService {
|
|||||||
transaction,
|
transaction,
|
||||||
);
|
);
|
||||||
|
|
||||||
const clonedProject = await ProjectsDBApi.create(
|
const clonedProject = await ProjectsDBApi.create({
|
||||||
{
|
data: {
|
||||||
name: `${sourceProject.name} (Copy)`,
|
name: `${sourceProject.name} (Copy)`,
|
||||||
slug: uniqueSlug,
|
slug: uniqueSlug,
|
||||||
description: sourceProject.description,
|
description: sourceProject.description,
|
||||||
@ -249,8 +282,9 @@ class ProjectsService extends BaseProjectsService {
|
|||||||
design_width: sourceProject.design_width,
|
design_width: sourceProject.design_width,
|
||||||
design_height: sourceProject.design_height,
|
design_height: sourceProject.design_height,
|
||||||
},
|
},
|
||||||
{ currentUser, transaction },
|
currentUser,
|
||||||
);
|
transaction,
|
||||||
|
});
|
||||||
|
|
||||||
// ============================================
|
// ============================================
|
||||||
// Phase B: Collect all copy operations
|
// Phase B: Collect all copy operations
|
||||||
|
|||||||
@ -8,6 +8,12 @@ const config = require('../config');
|
|||||||
const stream = require('stream');
|
const stream = require('stream');
|
||||||
const { validateReadOnlySql } = require('../utils/sqlValidator');
|
const { validateReadOnlySql } = require('../utils/sqlValidator');
|
||||||
const { logger } = require('../utils/logger');
|
const { logger } = require('../utils/logger');
|
||||||
|
const {
|
||||||
|
assertCreateOptions,
|
||||||
|
assertDeleteByIdsOptions,
|
||||||
|
assertIdOptions,
|
||||||
|
assertUpdateOptions,
|
||||||
|
} = require('../contracts/entity-options');
|
||||||
|
|
||||||
const WIDGET_SQL_MAX_LENGTH = 5000;
|
const WIDGET_SQL_MAX_LENGTH = 5000;
|
||||||
const WIDGET_SQL_MAX_ROWS = 1000;
|
const WIDGET_SQL_MAX_ROWS = 1000;
|
||||||
@ -48,20 +54,32 @@ module.exports = class RolesService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static async create(data, currentUser) {
|
static async create(options) {
|
||||||
const transaction = await db.sequelize.transaction();
|
assertCreateOptions(options, 'Service');
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
currentUser,
|
||||||
|
transaction: externalTransaction,
|
||||||
|
runtimeContext,
|
||||||
|
} = options;
|
||||||
|
const transaction =
|
||||||
|
externalTransaction || (await db.sequelize.transaction());
|
||||||
|
const ownsTransaction = !externalTransaction;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.assertPublicRoleHasNoPermissions(data);
|
this.assertPublicRoleHasNoPermissions(data);
|
||||||
|
|
||||||
const createdRole = await RolesDBApi.create(data, {
|
const createdRole = await RolesDBApi.create({
|
||||||
|
data,
|
||||||
currentUser,
|
currentUser,
|
||||||
transaction,
|
transaction,
|
||||||
|
runtimeContext,
|
||||||
});
|
});
|
||||||
|
|
||||||
await transaction.commit();
|
if (ownsTransaction) await transaction.commit();
|
||||||
return createdRole;
|
return createdRole;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await transaction.rollback();
|
if (ownsTransaction) await transaction.rollback();
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -104,10 +122,24 @@ module.exports = class RolesService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static async update(data, id, currentUser) {
|
static async update(options) {
|
||||||
const transaction = await db.sequelize.transaction();
|
assertUpdateOptions(options, 'Service');
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
data,
|
||||||
|
currentUser,
|
||||||
|
transaction: externalTransaction,
|
||||||
|
runtimeContext,
|
||||||
|
} = options;
|
||||||
|
const transaction =
|
||||||
|
externalTransaction || (await db.sequelize.transaction());
|
||||||
|
const ownsTransaction = !externalTransaction;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let roles = await RolesDBApi.findBy({ id }, { transaction });
|
let roles = await RolesDBApi.findBy(
|
||||||
|
{ id },
|
||||||
|
{ transaction, runtimeContext },
|
||||||
|
);
|
||||||
|
|
||||||
if (!roles) {
|
if (!roles) {
|
||||||
throw new ValidationError('rolesNotFound');
|
throw new ValidationError('rolesNotFound');
|
||||||
@ -115,47 +147,72 @@ module.exports = class RolesService {
|
|||||||
|
|
||||||
this.assertPublicRoleHasNoPermissions(data, roles);
|
this.assertPublicRoleHasNoPermissions(data, roles);
|
||||||
|
|
||||||
const updatedRoles = await RolesDBApi.update(id, data, {
|
const updatedRoles = await RolesDBApi.update({
|
||||||
|
id,
|
||||||
|
data,
|
||||||
currentUser,
|
currentUser,
|
||||||
transaction,
|
transaction,
|
||||||
|
runtimeContext,
|
||||||
});
|
});
|
||||||
|
|
||||||
await transaction.commit();
|
if (ownsTransaction) await transaction.commit();
|
||||||
return updatedRoles;
|
return updatedRoles;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await transaction.rollback();
|
if (ownsTransaction) await transaction.rollback();
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static async deleteByIds(ids, currentUser) {
|
static async deleteByIds(options) {
|
||||||
const transaction = await db.sequelize.transaction();
|
assertDeleteByIdsOptions(options, 'Service');
|
||||||
|
const {
|
||||||
|
ids,
|
||||||
|
currentUser,
|
||||||
|
transaction: externalTransaction,
|
||||||
|
runtimeContext,
|
||||||
|
} = options;
|
||||||
|
const transaction =
|
||||||
|
externalTransaction || (await db.sequelize.transaction());
|
||||||
|
const ownsTransaction = !externalTransaction;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await RolesDBApi.deleteByIds(ids, {
|
await RolesDBApi.deleteByIds({
|
||||||
|
ids,
|
||||||
currentUser,
|
currentUser,
|
||||||
transaction,
|
transaction,
|
||||||
|
runtimeContext,
|
||||||
});
|
});
|
||||||
|
|
||||||
await transaction.commit();
|
if (ownsTransaction) await transaction.commit();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await transaction.rollback();
|
if (ownsTransaction) await transaction.rollback();
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static async remove(id, currentUser) {
|
static async remove(options) {
|
||||||
const transaction = await db.sequelize.transaction();
|
assertIdOptions(options, 'Service', 'remove');
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
currentUser,
|
||||||
|
transaction: externalTransaction,
|
||||||
|
runtimeContext,
|
||||||
|
} = options;
|
||||||
|
const transaction =
|
||||||
|
externalTransaction || (await db.sequelize.transaction());
|
||||||
|
const ownsTransaction = !externalTransaction;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await RolesDBApi.remove(id, {
|
await RolesDBApi.remove({
|
||||||
|
id,
|
||||||
currentUser,
|
currentUser,
|
||||||
transaction,
|
transaction,
|
||||||
|
runtimeContext,
|
||||||
});
|
});
|
||||||
|
|
||||||
await transaction.commit();
|
if (ownsTransaction) await transaction.commit();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await transaction.rollback();
|
if (ownsTransaction) await transaction.rollback();
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -197,18 +254,16 @@ module.exports = class RolesService {
|
|||||||
customization[key] = [widgetId];
|
customization[key] = [widgetId];
|
||||||
}
|
}
|
||||||
|
|
||||||
const newRole = await RolesDBApi.update(
|
const newRole = await RolesDBApi.update({
|
||||||
role.id,
|
id: role.id,
|
||||||
{
|
data: {
|
||||||
role_customization: JSON.stringify(customization),
|
role_customization: JSON.stringify(customization),
|
||||||
name: role.name,
|
name: role.name,
|
||||||
permissions: role.permissions,
|
permissions: role.permissions,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
currentUser,
|
currentUser,
|
||||||
transaction,
|
transaction,
|
||||||
},
|
});
|
||||||
);
|
|
||||||
|
|
||||||
await transaction.commit();
|
await transaction.commit();
|
||||||
|
|
||||||
@ -249,18 +304,16 @@ module.exports = class RolesService {
|
|||||||
`${config.flHost}/${config.project_uuid}/project_customization_widgets/${infoId}.json`,
|
`${config.flHost}/${config.project_uuid}/project_customization_widgets/${infoId}.json`,
|
||||||
);
|
);
|
||||||
try {
|
try {
|
||||||
const result = await RolesDBApi.update(
|
const result = await RolesDBApi.update({
|
||||||
role.id,
|
id: role.id,
|
||||||
{
|
data: {
|
||||||
role_customization: JSON.stringify(customization),
|
role_customization: JSON.stringify(customization),
|
||||||
name: role.name,
|
name: role.name,
|
||||||
permissions: role.permissions,
|
permissions: role.permissions,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
currentUser,
|
currentUser,
|
||||||
transaction,
|
transaction,
|
||||||
},
|
});
|
||||||
);
|
|
||||||
|
|
||||||
await transaction.commit();
|
await transaction.commit();
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
@ -9,6 +9,10 @@ const Tour_pagesDBApi = require('../db/api/tour_pages');
|
|||||||
const AssetsDBApi = require('../db/api/assets');
|
const AssetsDBApi = require('../db/api/assets');
|
||||||
const Asset_variantsDBApi = require('../db/api/asset_variants');
|
const Asset_variantsDBApi = require('../db/api/asset_variants');
|
||||||
const { createEntityService } = require('../factories/service.factory');
|
const { createEntityService } = require('../factories/service.factory');
|
||||||
|
const {
|
||||||
|
assertCreateOptions,
|
||||||
|
assertUpdateOptions,
|
||||||
|
} = require('../contracts/entity-options');
|
||||||
const {
|
const {
|
||||||
downloadToBuffer,
|
downloadToBuffer,
|
||||||
downloadToTempFile,
|
downloadToTempFile,
|
||||||
@ -302,11 +306,12 @@ class TourPagesService extends BaseService {
|
|||||||
|
|
||||||
const updatedPages = [];
|
const updatedPages = [];
|
||||||
for (const [index, pageId] of orderedPageIds.entries()) {
|
for (const [index, pageId] of orderedPageIds.entries()) {
|
||||||
const page = await Tour_pagesDBApi.partialUpdate(
|
const page = await Tour_pagesDBApi.partialUpdate({
|
||||||
pageId,
|
id: pageId,
|
||||||
{ sort_order: index + 1 },
|
data: { sort_order: index + 1 },
|
||||||
{ currentUser, transaction },
|
currentUser,
|
||||||
);
|
transaction,
|
||||||
|
});
|
||||||
updatedPages.push(page);
|
updatedPages.push(page);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -429,7 +434,8 @@ class TourPagesService extends BaseService {
|
|||||||
currentUser,
|
currentUser,
|
||||||
);
|
);
|
||||||
|
|
||||||
return Tour_pagesDBApi.create(processedPayload, {
|
return Tour_pagesDBApi.create({
|
||||||
|
data: processedPayload,
|
||||||
currentUser,
|
currentUser,
|
||||||
transaction,
|
transaction,
|
||||||
});
|
});
|
||||||
@ -439,7 +445,10 @@ class TourPagesService extends BaseService {
|
|||||||
/**
|
/**
|
||||||
* Create tour page - generate reversed videos if needed
|
* Create tour page - generate reversed videos if needed
|
||||||
*/
|
*/
|
||||||
static async create(data, currentUser) {
|
static async create(options) {
|
||||||
|
assertCreateOptions(options, 'Service');
|
||||||
|
const { data, currentUser, transaction, runtimeContext } = options;
|
||||||
|
|
||||||
// Process reversed videos and get updated ui_schema_json
|
// Process reversed videos and get updated ui_schema_json
|
||||||
const updatedData =
|
const updatedData =
|
||||||
await TourPagesService.processReversedVideosAndUpdateSchema(
|
await TourPagesService.processReversedVideosAndUpdateSchema(
|
||||||
@ -447,15 +456,26 @@ class TourPagesService extends BaseService {
|
|||||||
currentUser,
|
currentUser,
|
||||||
);
|
);
|
||||||
|
|
||||||
return super.create(updatedData, currentUser);
|
return super.create({
|
||||||
|
data: updatedData,
|
||||||
|
currentUser,
|
||||||
|
transaction,
|
||||||
|
runtimeContext,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update tour page - generate reversed videos if needed
|
* Update tour page - generate reversed videos if needed
|
||||||
*/
|
*/
|
||||||
static async update(data, id, currentUser) {
|
static async update(options) {
|
||||||
|
assertUpdateOptions(options, 'Service');
|
||||||
|
const { id, data, currentUser, transaction, runtimeContext } = options;
|
||||||
|
|
||||||
// Fetch existing page to get projectId (not included in update request body)
|
// Fetch existing page to get projectId (not included in update request body)
|
||||||
const existingPage = await Tour_pagesDBApi.findBy({ id });
|
const existingPage = await Tour_pagesDBApi.findBy(
|
||||||
|
{ id },
|
||||||
|
{ transaction, runtimeContext },
|
||||||
|
);
|
||||||
const projectId =
|
const projectId =
|
||||||
existingPage?.projectId || data.projectId || data.project_id;
|
existingPage?.projectId || data.projectId || data.project_id;
|
||||||
|
|
||||||
@ -466,7 +486,13 @@ class TourPagesService extends BaseService {
|
|||||||
currentUser,
|
currentUser,
|
||||||
);
|
);
|
||||||
|
|
||||||
return super.update(updatedData, id, currentUser);
|
return super.update({
|
||||||
|
id,
|
||||||
|
data: updatedData,
|
||||||
|
currentUser,
|
||||||
|
transaction,
|
||||||
|
runtimeContext,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -828,11 +854,11 @@ class TourPagesService extends BaseService {
|
|||||||
|
|
||||||
if (!pageModified) continue;
|
if (!pageModified) continue;
|
||||||
|
|
||||||
await Tour_pagesDBApi.partialUpdate(
|
await Tour_pagesDBApi.partialUpdate({
|
||||||
page.id,
|
id: page.id,
|
||||||
{ ui_schema_json: JSON.stringify(uiSchema) },
|
data: { ui_schema_json: JSON.stringify(uiSchema) },
|
||||||
{ currentUser },
|
currentUser,
|
||||||
);
|
});
|
||||||
pagesUpdated++;
|
pagesUpdated++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -914,16 +940,16 @@ class TourPagesService extends BaseService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Create variant record
|
// Create variant record
|
||||||
await Asset_variantsDBApi.create(
|
await Asset_variantsDBApi.create({
|
||||||
{
|
data: {
|
||||||
assetId: asset.id,
|
assetId: asset.id,
|
||||||
variant_type: 'reversed',
|
variant_type: 'reversed',
|
||||||
cdn_url: result.url,
|
cdn_url: result.url,
|
||||||
storage_key: reversedKey,
|
storage_key: reversedKey,
|
||||||
size_mb: reversedBuffer.length / (1024 * 1024),
|
size_mb: reversedBuffer.length / (1024 * 1024),
|
||||||
},
|
},
|
||||||
{ currentUser },
|
currentUser,
|
||||||
);
|
});
|
||||||
|
|
||||||
log.info(
|
log.info(
|
||||||
{ reversedKey, size: reversedBuffer.length },
|
{ reversedKey, size: reversedBuffer.length },
|
||||||
@ -1034,11 +1060,11 @@ class TourPagesService extends BaseService {
|
|||||||
if (pageModified) {
|
if (pageModified) {
|
||||||
// Use partialUpdate to only update ui_schema_json field
|
// Use partialUpdate to only update ui_schema_json field
|
||||||
// This avoids the regular update's getFieldMapping which sets other fields to null
|
// This avoids the regular update's getFieldMapping which sets other fields to null
|
||||||
await Tour_pagesDBApi.partialUpdate(
|
await Tour_pagesDBApi.partialUpdate({
|
||||||
page.id,
|
id: page.id,
|
||||||
{ ui_schema_json: JSON.stringify(uiSchema) },
|
data: { ui_schema_json: JSON.stringify(uiSchema) },
|
||||||
{ currentUser },
|
currentUser,
|
||||||
);
|
});
|
||||||
pagesUpdated++;
|
pagesUpdated++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,11 @@
|
|||||||
const db = require('../db/models');
|
const db = require('../db/models');
|
||||||
const UsersDBApi = require('../db/api/users');
|
const UsersDBApi = require('../db/api/users');
|
||||||
const { createEntityService } = require('../factories/service.factory');
|
const { createEntityService } = require('../factories/service.factory');
|
||||||
|
const {
|
||||||
|
assertCreateOptions,
|
||||||
|
assertIdOptions,
|
||||||
|
assertUpdateOptions,
|
||||||
|
} = require('../contracts/entity-options');
|
||||||
const ValidationError = require('./notifications/errors/validation');
|
const ValidationError = require('./notifications/errors/validation');
|
||||||
const config = require('../config');
|
const config = require('../config');
|
||||||
const AuthService = require('./auth');
|
const AuthService = require('./auth');
|
||||||
@ -126,8 +131,19 @@ class UsersService extends BaseUsersService {
|
|||||||
/**
|
/**
|
||||||
* Create user with email validation and optional invitation
|
* Create user with email validation and optional invitation
|
||||||
*/
|
*/
|
||||||
static async create(data, currentUser, sendInvitationEmails = true, host) {
|
static async create(options) {
|
||||||
const transaction = await db.sequelize.transaction();
|
assertCreateOptions(options, 'Service');
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
currentUser,
|
||||||
|
sendInvitationEmails = true,
|
||||||
|
host,
|
||||||
|
transaction: externalTransaction,
|
||||||
|
runtimeContext,
|
||||||
|
} = options;
|
||||||
|
const transaction =
|
||||||
|
externalTransaction || (await db.sequelize.transaction());
|
||||||
|
const ownsTransaction = !externalTransaction;
|
||||||
const email = data.email;
|
const email = data.email;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -157,15 +173,20 @@ class UsersService extends BaseUsersService {
|
|||||||
|
|
||||||
if (existingUser?.deletedAt) {
|
if (existingUser?.deletedAt) {
|
||||||
await existingUser.restore({ transaction });
|
await existingUser.restore({ transaction });
|
||||||
user = await UsersDBApi.update(existingUser.id, sanitizedData, {
|
user = await UsersDBApi.update({
|
||||||
|
id: existingUser.id,
|
||||||
|
data: sanitizedData,
|
||||||
currentUser,
|
currentUser,
|
||||||
transaction,
|
transaction,
|
||||||
|
runtimeContext,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
user = await UsersDBApi.create(
|
user = await UsersDBApi.create({
|
||||||
{ data: sanitizedData },
|
data: sanitizedData,
|
||||||
{ currentUser, transaction },
|
currentUser,
|
||||||
);
|
transaction,
|
||||||
|
runtimeContext,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.createProductionPresentationAccessForPublicUser({
|
await this.createProductionPresentationAccessForPublicUser({
|
||||||
@ -174,7 +195,7 @@ class UsersService extends BaseUsersService {
|
|||||||
currentUser,
|
currentUser,
|
||||||
transaction,
|
transaction,
|
||||||
});
|
});
|
||||||
await transaction.commit();
|
if (ownsTransaction) await transaction.commit();
|
||||||
|
|
||||||
// Send invitation email after successful commit
|
// Send invitation email after successful commit
|
||||||
if (sendInvitationEmails) {
|
if (sendInvitationEmails) {
|
||||||
@ -188,16 +209,29 @@ class UsersService extends BaseUsersService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await transaction.rollback();
|
if (ownsTransaction) await transaction.rollback();
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static async update(data, id, currentUser) {
|
static async update(options) {
|
||||||
const transaction = await db.sequelize.transaction();
|
assertUpdateOptions(options, 'Service');
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
data,
|
||||||
|
currentUser,
|
||||||
|
transaction: externalTransaction,
|
||||||
|
runtimeContext,
|
||||||
|
} = options;
|
||||||
|
const transaction =
|
||||||
|
externalTransaction || (await db.sequelize.transaction());
|
||||||
|
const ownsTransaction = !externalTransaction;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const existingUser = await UsersDBApi.findBy({ id }, { transaction });
|
const existingUser = await UsersDBApi.findBy(
|
||||||
|
{ id },
|
||||||
|
{ transaction, runtimeContext },
|
||||||
|
);
|
||||||
|
|
||||||
if (!existingUser) {
|
if (!existingUser) {
|
||||||
throw new ValidationError('UsersNotFound');
|
throw new ValidationError('UsersNotFound');
|
||||||
@ -229,9 +263,12 @@ class UsersService extends BaseUsersService {
|
|||||||
? { ...data, custom_permissions: [] }
|
? { ...data, custom_permissions: [] }
|
||||||
: data;
|
: data;
|
||||||
|
|
||||||
const user = await UsersDBApi.update(id, sanitizedData, {
|
const user = await UsersDBApi.update({
|
||||||
|
id,
|
||||||
|
data: sanitizedData,
|
||||||
currentUser,
|
currentUser,
|
||||||
transaction,
|
transaction,
|
||||||
|
runtimeContext,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@ -248,10 +285,10 @@ class UsersService extends BaseUsersService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
await transaction.commit();
|
if (ownsTransaction) await transaction.commit();
|
||||||
return user;
|
return user;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await transaction.rollback();
|
if (ownsTransaction) await transaction.rollback();
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -259,7 +296,10 @@ class UsersService extends BaseUsersService {
|
|||||||
/**
|
/**
|
||||||
* Remove user with self-deletion and permission checks
|
* Remove user with self-deletion and permission checks
|
||||||
*/
|
*/
|
||||||
static async remove(id, currentUser) {
|
static async remove(options) {
|
||||||
|
assertIdOptions(options, 'Service', 'remove');
|
||||||
|
const { id, currentUser, transaction, runtimeContext } = options;
|
||||||
|
|
||||||
if (currentUser.id === id) {
|
if (currentUser.id === id) {
|
||||||
throw new ValidationError('iam.errors.deletingHimself');
|
throw new ValidationError('iam.errors.deletingHimself');
|
||||||
}
|
}
|
||||||
@ -269,7 +309,7 @@ class UsersService extends BaseUsersService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Delegate to parent (factory) implementation
|
// Delegate to parent (factory) implementation
|
||||||
return super.remove(id, currentUser);
|
return super.remove({ id, currentUser, transaction, runtimeContext });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
486
backend/tests/update-contracts.test.js
Normal file
486
backend/tests/update-contracts.test.js
Normal file
@ -0,0 +1,486 @@
|
|||||||
|
const assert = require('node:assert/strict');
|
||||||
|
const test = require('node:test');
|
||||||
|
|
||||||
|
const db = require('../src/db/models');
|
||||||
|
const GenericDBApi = require('../src/db/api/base.api');
|
||||||
|
const { createEntityService } = require('../src/factories/service.factory');
|
||||||
|
|
||||||
|
test('GenericDBApi.update uses object signature and forwards update context', async () => {
|
||||||
|
const calls = {};
|
||||||
|
const transaction = { id: 'tx-db-api' };
|
||||||
|
const currentUser = { id: 'user-1' };
|
||||||
|
const record = {
|
||||||
|
async update(payload, options) {
|
||||||
|
calls.update = { payload, options };
|
||||||
|
},
|
||||||
|
async setTags(value, options) {
|
||||||
|
calls.setTags = { value, options };
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
class TestDBApi extends GenericDBApi {
|
||||||
|
static get MODEL() {
|
||||||
|
return {
|
||||||
|
rawAttributes: {},
|
||||||
|
getTableName: () => 'test_records',
|
||||||
|
async findByPk(id, options) {
|
||||||
|
calls.findByPk = { id, options };
|
||||||
|
return record;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static get ASSOCIATIONS() {
|
||||||
|
return [{ field: 'tags', setter: 'setTags', isArray: true }];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await TestDBApi.update({
|
||||||
|
id: 'record-1',
|
||||||
|
data: { name: 'Updated', tags: ['a', 'b'], skipped: undefined },
|
||||||
|
currentUser,
|
||||||
|
transaction,
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(result, record);
|
||||||
|
assert.deepEqual(calls.findByPk, {
|
||||||
|
id: 'record-1',
|
||||||
|
options: { transaction },
|
||||||
|
});
|
||||||
|
assert.deepEqual(calls.update, {
|
||||||
|
payload: {
|
||||||
|
updatedById: 'user-1',
|
||||||
|
name: 'Updated',
|
||||||
|
tags: ['a', 'b'],
|
||||||
|
},
|
||||||
|
options: { transaction },
|
||||||
|
});
|
||||||
|
assert.deepEqual(calls.setTags, {
|
||||||
|
value: ['a', 'b'],
|
||||||
|
options: { transaction },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('GenericDBApi.update rejects positional signature', async () => {
|
||||||
|
class TestDBApi extends GenericDBApi {
|
||||||
|
static get MODEL() {
|
||||||
|
return {
|
||||||
|
rawAttributes: {},
|
||||||
|
getTableName: () => 'test_records',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await assert.rejects(
|
||||||
|
() => TestDBApi.update('record-1', { name: 'Updated' }, {}),
|
||||||
|
/DBApi\.update expects an options object/,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('GenericDBApi.create uses object signature and forwards create context', async () => {
|
||||||
|
const calls = {};
|
||||||
|
const transaction = { id: 'tx-create' };
|
||||||
|
const currentUser = { id: 'user-1' };
|
||||||
|
const record = {
|
||||||
|
id: 'record-1',
|
||||||
|
async setTags(value, options) {
|
||||||
|
calls.setTags = { value, options };
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
class TestDBApi extends GenericDBApi {
|
||||||
|
static get MODEL() {
|
||||||
|
return {
|
||||||
|
rawAttributes: {},
|
||||||
|
getTableName: () => 'test_records',
|
||||||
|
async create(payload, options) {
|
||||||
|
calls.create = { payload, options };
|
||||||
|
return record;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static get ASSOCIATIONS() {
|
||||||
|
return [{ field: 'tags', setter: 'setTags', isArray: true }];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await TestDBApi.create({
|
||||||
|
data: { name: 'Created', tags: ['a', 'b'] },
|
||||||
|
currentUser,
|
||||||
|
transaction,
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(result, record);
|
||||||
|
assert.deepEqual(calls.create, {
|
||||||
|
payload: {
|
||||||
|
name: 'Created',
|
||||||
|
tags: ['a', 'b'],
|
||||||
|
importHash: null,
|
||||||
|
createdById: 'user-1',
|
||||||
|
updatedById: 'user-1',
|
||||||
|
},
|
||||||
|
options: { transaction },
|
||||||
|
});
|
||||||
|
assert.deepEqual(calls.setTags, {
|
||||||
|
value: ['a', 'b'],
|
||||||
|
options: { transaction },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('GenericDBApi.create rejects positional signature', async () => {
|
||||||
|
class TestDBApi extends GenericDBApi {
|
||||||
|
static get MODEL() {
|
||||||
|
return {
|
||||||
|
rawAttributes: {},
|
||||||
|
getTableName: () => 'test_records',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await assert.rejects(
|
||||||
|
() => TestDBApi.create({ name: 'Created' }, {}),
|
||||||
|
/DBApi\.create requires \{ data \}/,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('GenericDBApi.partialUpdate uses object signature and only updates defined fields', async () => {
|
||||||
|
const calls = {};
|
||||||
|
const transaction = { id: 'tx-partial' };
|
||||||
|
const currentUser = { id: 'user-1' };
|
||||||
|
const record = {
|
||||||
|
async update(payload, options) {
|
||||||
|
calls.update = { payload, options };
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
class TestDBApi extends GenericDBApi {
|
||||||
|
static get MODEL() {
|
||||||
|
return {
|
||||||
|
rawAttributes: {},
|
||||||
|
getTableName: () => 'test_records',
|
||||||
|
async findByPk(id, options) {
|
||||||
|
calls.findByPk = { id, options };
|
||||||
|
return record;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await TestDBApi.partialUpdate({
|
||||||
|
id: 'record-1',
|
||||||
|
data: { name: 'Updated', skipped: undefined },
|
||||||
|
currentUser,
|
||||||
|
transaction,
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(result, record);
|
||||||
|
assert.deepEqual(calls.findByPk, {
|
||||||
|
id: 'record-1',
|
||||||
|
options: { transaction },
|
||||||
|
});
|
||||||
|
assert.deepEqual(calls.update, {
|
||||||
|
payload: {
|
||||||
|
updatedById: 'user-1',
|
||||||
|
name: 'Updated',
|
||||||
|
},
|
||||||
|
options: { transaction },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('GenericDBApi.partialUpdate rejects positional signature', async () => {
|
||||||
|
class TestDBApi extends GenericDBApi {
|
||||||
|
static get MODEL() {
|
||||||
|
return {
|
||||||
|
rawAttributes: {},
|
||||||
|
getTableName: () => 'test_records',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await assert.rejects(
|
||||||
|
() => TestDBApi.partialUpdate('record-1', { name: 'Updated' }, {}),
|
||||||
|
/DBApi\.update expects an options object/,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('createEntityService update uses object signature and manages own transaction', async () => {
|
||||||
|
const originalTransaction = db.sequelize.transaction;
|
||||||
|
const calls = {};
|
||||||
|
const transaction = {
|
||||||
|
async commit() {
|
||||||
|
calls.committed = true;
|
||||||
|
},
|
||||||
|
async rollback() {
|
||||||
|
calls.rolledBack = true;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
db.sequelize.transaction = async () => transaction;
|
||||||
|
|
||||||
|
const DBApi = {
|
||||||
|
async findBy(where, options) {
|
||||||
|
calls.findBy = { where, options };
|
||||||
|
return { id: where.id };
|
||||||
|
},
|
||||||
|
async update(options) {
|
||||||
|
calls.update = options;
|
||||||
|
return { id: options.id, ...options.data };
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const Service = createEntityService(DBApi, { entityName: 'TestEntity' });
|
||||||
|
const currentUser = { id: 'user-1' };
|
||||||
|
const runtimeContext = { environment: 'dev' };
|
||||||
|
|
||||||
|
const result = await Service.update({
|
||||||
|
id: 'record-1',
|
||||||
|
data: { name: 'Updated' },
|
||||||
|
currentUser,
|
||||||
|
runtimeContext,
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.deepEqual(result, { id: 'record-1', name: 'Updated' });
|
||||||
|
assert.deepEqual(calls.findBy, {
|
||||||
|
where: { id: 'record-1' },
|
||||||
|
options: { transaction, runtimeContext },
|
||||||
|
});
|
||||||
|
assert.deepEqual(calls.update, {
|
||||||
|
id: 'record-1',
|
||||||
|
data: { name: 'Updated' },
|
||||||
|
currentUser,
|
||||||
|
transaction,
|
||||||
|
runtimeContext,
|
||||||
|
});
|
||||||
|
assert.equal(calls.committed, true);
|
||||||
|
assert.equal(calls.rolledBack, undefined);
|
||||||
|
} finally {
|
||||||
|
db.sequelize.transaction = originalTransaction;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('createEntityService update rejects positional signature', async () => {
|
||||||
|
const Service = createEntityService(
|
||||||
|
{
|
||||||
|
async findBy() {
|
||||||
|
throw new Error('should not be called');
|
||||||
|
},
|
||||||
|
async update() {
|
||||||
|
throw new Error('should not be called');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ entityName: 'TestEntity' },
|
||||||
|
);
|
||||||
|
|
||||||
|
await assert.rejects(
|
||||||
|
() => Service.update({ name: 'Updated' }, 'record-1', { id: 'user-1' }),
|
||||||
|
/Service\.update requires \{ id, data \}/,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('createEntityService create, deleteByIds, and remove use object signatures', async () => {
|
||||||
|
const originalTransaction = db.sequelize.transaction;
|
||||||
|
const calls = {};
|
||||||
|
const transaction = {
|
||||||
|
async commit() {
|
||||||
|
calls.commits = (calls.commits || 0) + 1;
|
||||||
|
},
|
||||||
|
async rollback() {
|
||||||
|
calls.rollbacks = (calls.rollbacks || 0) + 1;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
db.sequelize.transaction = async () => transaction;
|
||||||
|
|
||||||
|
const DBApi = {
|
||||||
|
async create(options) {
|
||||||
|
calls.create = options;
|
||||||
|
return { id: 'created-1', ...options.data };
|
||||||
|
},
|
||||||
|
async deleteByIds(options) {
|
||||||
|
calls.deleteByIds = options;
|
||||||
|
},
|
||||||
|
async remove(options) {
|
||||||
|
calls.remove = options;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const Service = createEntityService(DBApi, { entityName: 'TestEntity' });
|
||||||
|
const currentUser = { id: 'user-1' };
|
||||||
|
const runtimeContext = { environment: 'stage' };
|
||||||
|
|
||||||
|
const created = await Service.create({
|
||||||
|
data: { name: 'Created' },
|
||||||
|
currentUser,
|
||||||
|
runtimeContext,
|
||||||
|
});
|
||||||
|
await Service.deleteByIds({
|
||||||
|
ids: ['record-1', 'record-2'],
|
||||||
|
currentUser,
|
||||||
|
runtimeContext,
|
||||||
|
});
|
||||||
|
await Service.remove({
|
||||||
|
id: 'record-1',
|
||||||
|
currentUser,
|
||||||
|
runtimeContext,
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.deepEqual(created, { id: 'created-1', name: 'Created' });
|
||||||
|
assert.deepEqual(calls.create, {
|
||||||
|
data: { name: 'Created' },
|
||||||
|
currentUser,
|
||||||
|
transaction,
|
||||||
|
runtimeContext,
|
||||||
|
});
|
||||||
|
assert.deepEqual(calls.deleteByIds, {
|
||||||
|
ids: ['record-1', 'record-2'],
|
||||||
|
currentUser,
|
||||||
|
transaction,
|
||||||
|
runtimeContext,
|
||||||
|
});
|
||||||
|
assert.deepEqual(calls.remove, {
|
||||||
|
id: 'record-1',
|
||||||
|
currentUser,
|
||||||
|
transaction,
|
||||||
|
runtimeContext,
|
||||||
|
});
|
||||||
|
assert.equal(calls.commits, 3);
|
||||||
|
assert.equal(calls.rollbacks, undefined);
|
||||||
|
} finally {
|
||||||
|
db.sequelize.transaction = originalTransaction;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('createEntityService create, deleteByIds, and remove reject positional signatures', async () => {
|
||||||
|
const Service = createEntityService(
|
||||||
|
{
|
||||||
|
async create() {
|
||||||
|
throw new Error('should not be called');
|
||||||
|
},
|
||||||
|
async deleteByIds() {
|
||||||
|
throw new Error('should not be called');
|
||||||
|
},
|
||||||
|
async remove() {
|
||||||
|
throw new Error('should not be called');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ entityName: 'TestEntity' },
|
||||||
|
);
|
||||||
|
|
||||||
|
await assert.rejects(
|
||||||
|
() => Service.create({ name: 'Created' }, { id: 'user-1' }),
|
||||||
|
/Service\.create requires \{ data \}/,
|
||||||
|
);
|
||||||
|
await assert.rejects(
|
||||||
|
() => Service.deleteByIds(['record-1'], { id: 'user-1' }),
|
||||||
|
/Service\.deleteByIds expects an options object/,
|
||||||
|
);
|
||||||
|
await assert.rejects(
|
||||||
|
() => Service.remove('record-1', { id: 'user-1' }),
|
||||||
|
/Service\.remove expects an options object/,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('GenericDBApi deleteByIds, remove, and findAllAutocomplete use object signatures', async () => {
|
||||||
|
const calls = {};
|
||||||
|
const transaction = { id: 'tx-db-api' };
|
||||||
|
const currentUser = { id: 'user-1' };
|
||||||
|
const deletedRecords = [
|
||||||
|
{
|
||||||
|
id: 'record-1',
|
||||||
|
async update(payload, options) {
|
||||||
|
calls.deletedUpdate = { payload, options };
|
||||||
|
},
|
||||||
|
async destroy(options) {
|
||||||
|
calls.deletedDestroy = options;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const removedRecord = {
|
||||||
|
id: 'record-2',
|
||||||
|
async update(payload, options) {
|
||||||
|
calls.removedUpdate = { payload, options };
|
||||||
|
},
|
||||||
|
async destroy(options) {
|
||||||
|
calls.removedDestroy = options;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const autocompleteRecords = [{ id: 'record-3', name: 'Alpha' }];
|
||||||
|
|
||||||
|
class TestDBApi extends GenericDBApi {
|
||||||
|
static get MODEL() {
|
||||||
|
return {
|
||||||
|
rawAttributes: {},
|
||||||
|
getTableName: () => 'test_records',
|
||||||
|
async findAll(options) {
|
||||||
|
calls.findAll = options;
|
||||||
|
return options.attributes ? autocompleteRecords : deletedRecords;
|
||||||
|
},
|
||||||
|
async findByPk(id, options) {
|
||||||
|
calls.findByPk = { id, options };
|
||||||
|
return removedRecord;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleted = await TestDBApi.deleteByIds({
|
||||||
|
ids: ['record-1'],
|
||||||
|
currentUser,
|
||||||
|
transaction,
|
||||||
|
});
|
||||||
|
const removed = await TestDBApi.remove({
|
||||||
|
id: 'record-2',
|
||||||
|
currentUser,
|
||||||
|
transaction,
|
||||||
|
});
|
||||||
|
const autocomplete = await TestDBApi.findAllAutocomplete(
|
||||||
|
{ query: 'Alpha', limit: 5, offset: 0 },
|
||||||
|
{ transaction },
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.equal(deleted, deletedRecords);
|
||||||
|
assert.equal(removed, removedRecord);
|
||||||
|
assert.deepEqual(calls.deletedUpdate, {
|
||||||
|
payload: { deletedBy: 'user-1' },
|
||||||
|
options: { transaction },
|
||||||
|
});
|
||||||
|
assert.deepEqual(calls.deletedDestroy, { transaction });
|
||||||
|
assert.deepEqual(calls.findByPk, {
|
||||||
|
id: 'record-2',
|
||||||
|
options: { transaction },
|
||||||
|
});
|
||||||
|
assert.deepEqual(calls.removedUpdate, {
|
||||||
|
payload: { deletedBy: 'user-1' },
|
||||||
|
options: { transaction },
|
||||||
|
});
|
||||||
|
assert.deepEqual(calls.removedDestroy, { transaction });
|
||||||
|
assert.deepEqual(autocomplete, [{ id: 'record-3', label: 'Alpha' }]);
|
||||||
|
assert.equal(calls.findAll.limit, 5);
|
||||||
|
assert.equal(calls.findAll.transaction, transaction);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('GenericDBApi deleteByIds, remove, and findAllAutocomplete reject positional signatures', async () => {
|
||||||
|
class TestDBApi extends GenericDBApi {
|
||||||
|
static get MODEL() {
|
||||||
|
return {
|
||||||
|
rawAttributes: {},
|
||||||
|
getTableName: () => 'test_records',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await assert.rejects(
|
||||||
|
() => TestDBApi.deleteByIds(['record-1'], {}),
|
||||||
|
/DBApi\.deleteByIds expects an options object/,
|
||||||
|
);
|
||||||
|
await assert.rejects(
|
||||||
|
() => TestDBApi.remove('record-1', {}),
|
||||||
|
/DBApi\.remove expects an options object/,
|
||||||
|
);
|
||||||
|
await assert.rejects(
|
||||||
|
() => TestDBApi.findAllAutocomplete('Alpha', 5, 0),
|
||||||
|
/DBApi\.findAllAutocomplete expects an options object/,
|
||||||
|
);
|
||||||
|
});
|
||||||
@ -46,7 +46,7 @@ export function useProjectSelector({
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await axios.get(
|
const response = await axios.get(
|
||||||
'/projects?limit=100&page=0&sort=desc&field=updatedAt',
|
'/projects?limit=100&page=1&sort=desc&field=updatedAt',
|
||||||
);
|
);
|
||||||
const rows = Array.isArray(response?.data?.rows)
|
const rows = Array.isArray(response?.data?.rows)
|
||||||
? response.data.rows
|
? response.data.rows
|
||||||
|
|||||||
@ -61,7 +61,7 @@ async function fetchRelationOptions(
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await axios(`/${entityRef}/autocomplete?limit=100`);
|
const response = await axios(`/${entityRef}/autocomplete?limit=50`);
|
||||||
return response.data;
|
return response.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(
|
logger.error(
|
||||||
@ -159,15 +159,34 @@ function buildColumn(
|
|||||||
|
|
||||||
if (col.type === 'singleSelectRelation' && col.entityRef) {
|
if (col.type === 'singleSelectRelation' && col.entityRef) {
|
||||||
const singleSelectColumn = baseColumn as GridSingleSelectColDef;
|
const singleSelectColumn = baseColumn as GridSingleSelectColDef;
|
||||||
|
const valueOptions = valueOptionsMap.get(col.entityRef) || [];
|
||||||
|
|
||||||
singleSelectColumn.type = 'singleSelect';
|
singleSelectColumn.type = 'singleSelect';
|
||||||
singleSelectColumn.sortable = false;
|
singleSelectColumn.sortable = false;
|
||||||
singleSelectColumn.getOptionValue = (value: { id?: string }) => value?.id;
|
singleSelectColumn.getOptionValue = (value: { id?: string }) => value?.id;
|
||||||
singleSelectColumn.getOptionLabel = (value: { label?: string }) =>
|
singleSelectColumn.getOptionLabel = (value: { label?: string }) =>
|
||||||
value?.label;
|
value?.label;
|
||||||
singleSelectColumn.valueOptions = valueOptionsMap.get(col.entityRef) || [];
|
singleSelectColumn.valueOptions = valueOptions;
|
||||||
singleSelectColumn.valueGetter = (value: { id?: string } | string | null) =>
|
singleSelectColumn.valueGetter = (
|
||||||
|
value: { id?: string; label?: string; name?: string } | string | null,
|
||||||
|
) =>
|
||||||
(typeof value === 'object' && value !== null ? value?.id : value) ??
|
(typeof value === 'object' && value !== null ? value?.id : value) ??
|
||||||
value;
|
value;
|
||||||
|
singleSelectColumn.valueFormatter = (value: unknown) => {
|
||||||
|
if (typeof value === 'object' && value !== null) {
|
||||||
|
const relationValue = value as {
|
||||||
|
id?: string;
|
||||||
|
label?: string;
|
||||||
|
name?: string;
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
relationValue.label || relationValue.name || relationValue.id || ''
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const option = valueOptions.find((item) => item.id === value);
|
||||||
|
return option?.label || String(value || '');
|
||||||
|
};
|
||||||
return singleSelectColumn;
|
return singleSelectColumn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -13,7 +13,7 @@ const DataGridMultiSelect = (props: GridRenderEditCellParams & Props) => {
|
|||||||
const [options, setOptions] = useState([]);
|
const [options, setOptions] = useState([]);
|
||||||
|
|
||||||
async function callApi(entityName: string) {
|
async function callApi(entityName: string) {
|
||||||
const data = await axios(`/${entityName}/autocomplete?limit=100`);
|
const data = await axios(`/${entityName}/autocomplete?limit=50`);
|
||||||
return data.data;
|
return data.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -12,7 +12,7 @@ export const SelectField = ({
|
|||||||
onOptionChange,
|
onOptionChange,
|
||||||
}) => {
|
}) => {
|
||||||
const [value, setValue] = useState(null);
|
const [value, setValue] = useState(null);
|
||||||
const PAGE_SIZE = 100;
|
const PAGE_SIZE = 50;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (options?.id && field?.value?.id) {
|
if (options?.id && field?.value?.id) {
|
||||||
|
|||||||
@ -23,7 +23,7 @@ export const SelectFieldMany = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const [value, setValue] = useState([]);
|
const [value, setValue] = useState([]);
|
||||||
const appliedOptionsSignatureRef = useRef<string | null>(null);
|
const appliedOptionsSignatureRef = useRef<string | null>(null);
|
||||||
const PAGE_SIZE = 100;
|
const PAGE_SIZE = 50;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (field.value?.[0] && typeof field.value[0] !== 'string') {
|
if (field.value?.[0] && typeof field.value[0] !== 'string') {
|
||||||
|
|||||||
@ -176,7 +176,7 @@ const TourFlowManager = () => {
|
|||||||
const canDeleteTransition = hasPermission(currentUser, 'DELETE_TRANSITIONS');
|
const canDeleteTransition = hasPermission(currentUser, 'DELETE_TRANSITIONS');
|
||||||
|
|
||||||
const loadProjects = useCallback(async () => {
|
const loadProjects = useCallback(async () => {
|
||||||
const response = await axios.get('/projects/autocomplete?limit=100');
|
const response = await axios.get('/projects/autocomplete?limit=50');
|
||||||
const projectOptions = Array.isArray(response?.data) ? response.data : [];
|
const projectOptions = Array.isArray(response?.data) ? response.data : [];
|
||||||
setProjects(projectOptions);
|
setProjects(projectOptions);
|
||||||
|
|
||||||
|
|||||||
@ -11,7 +11,7 @@ export const RoleSelect = ({
|
|||||||
currentUser,
|
currentUser,
|
||||||
}) => {
|
}) => {
|
||||||
const [value, setValue] = useState(null);
|
const [value, setValue] = useState(null);
|
||||||
const PAGE_SIZE = 100;
|
const PAGE_SIZE = 50;
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (currentUser.app_role.id) {
|
if (currentUser.app_role.id) {
|
||||||
|
|||||||
@ -32,7 +32,7 @@ export function useElementDefaultsQuery(projectId: string | undefined) {
|
|||||||
Partial<Record<CanvasElementType, Partial<CanvasElement>>>
|
Partial<Record<CanvasElementType, Partial<CanvasElement>>>
|
||||||
> => {
|
> => {
|
||||||
const response = await axios.get<ElementDefaultsResponse>(
|
const response = await axios.get<ElementDefaultsResponse>(
|
||||||
`project-element-defaults?projectId=${projectId}&limit=200&page=0&sort=asc&field=sort_order`,
|
`project-element-defaults?projectId=${projectId}&limit=200&page=1&sort=asc&field=sort_order`,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Process and normalize the defaults
|
// Process and normalize the defaults
|
||||||
|
|||||||
@ -107,7 +107,7 @@ const AssetsTablesPage = () => {
|
|||||||
if (!projectId) return;
|
if (!projectId) return;
|
||||||
dispatch(
|
dispatch(
|
||||||
fetchAssets({
|
fetchAssets({
|
||||||
query: `?page=0&sort=desc&field=createdAt&projectId=${projectId}`,
|
query: `?page=1&sort=desc&field=createdAt&projectId=${projectId}`,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@ -134,7 +134,7 @@ const ElementTypeDefaultsPage = () => {
|
|||||||
setErrorMessage('');
|
setErrorMessage('');
|
||||||
|
|
||||||
const response = await axios.get(
|
const response = await axios.get(
|
||||||
'/element-type-defaults?limit=1000&page=0&sort=asc&field=sort_order',
|
'/element-type-defaults?limit=1000&page=1&sort=asc&field=sort_order',
|
||||||
);
|
);
|
||||||
|
|
||||||
const nextRows: ElementTypeDefault[] = Array.isArray(response?.data?.rows)
|
const nextRows: ElementTypeDefault[] = Array.isArray(response?.data?.rows)
|
||||||
|
|||||||
@ -64,7 +64,7 @@ const ProjectElementDefaultsPage = () => {
|
|||||||
setErrorMessage('');
|
setErrorMessage('');
|
||||||
|
|
||||||
const response = await axios.get(
|
const response = await axios.get(
|
||||||
`/project-element-defaults?projectId=${projectId}&limit=1000&page=0&sort=asc&field=sort_order`,
|
`/project-element-defaults?projectId=${projectId}&limit=1000&page=1&sort=asc&field=sort_order`,
|
||||||
);
|
);
|
||||||
|
|
||||||
const nextRows: ProjectElementDefault[] = Array.isArray(
|
const nextRows: ProjectElementDefault[] = Array.isArray(
|
||||||
|
|||||||
@ -171,7 +171,7 @@ const ProjectElementDefaultDetailsPage = () => {
|
|||||||
if (nextItem.projectId) {
|
if (nextItem.projectId) {
|
||||||
try {
|
try {
|
||||||
const assetsResponse = await axios.get(
|
const assetsResponse = await axios.get(
|
||||||
`/assets?limit=500&page=0&sort=desc&field=createdAt&projectId=${nextItem.projectId}`,
|
`/assets?limit=500&page=1&sort=desc&field=createdAt&projectId=${nextItem.projectId}`,
|
||||||
);
|
);
|
||||||
const assetRows: ConstructorAsset[] = Array.isArray(
|
const assetRows: ConstructorAsset[] = Array.isArray(
|
||||||
assetsResponse?.data?.rows,
|
assetsResponse?.data?.rows,
|
||||||
|
|||||||
@ -75,7 +75,7 @@ const EditProjectsPage = () => {
|
|||||||
setIsLoadingLogoAssets(true);
|
setIsLoadingLogoAssets(true);
|
||||||
try {
|
try {
|
||||||
const response = await axios.get(
|
const response = await axios.get(
|
||||||
`/assets?limit=500&page=0&sort=desc&field=createdAt&projectId=${projectId}`,
|
`/assets?limit=500&page=1&sort=desc&field=createdAt&projectId=${projectId}`,
|
||||||
);
|
);
|
||||||
const rows = Array.isArray(response?.data?.rows)
|
const rows = Array.isArray(response?.data?.rows)
|
||||||
? response.data.rows
|
? response.data.rows
|
||||||
|
|||||||
@ -41,7 +41,7 @@ const ProjectsListPage = () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dispatch(
|
dispatch(
|
||||||
fetchProjects({ query: '?limit=100&page=0&sort=desc&field=updatedAt' }),
|
fetchProjects({ query: '?limit=100&page=1&sort=desc&field=updatedAt' }),
|
||||||
);
|
);
|
||||||
}, [dispatch]);
|
}, [dispatch]);
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user