import Planka upstream
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
const LIMIT = 50;
|
||||
|
||||
/* Query methods */
|
||||
|
||||
const create = (arrayOfValues) => Action.createEach(arrayOfValues).fetch();
|
||||
|
||||
const createOne = (values) => Action.create({ ...values }).fetch();
|
||||
|
||||
const getByBoardId = (boardId, { beforeId } = {}) => {
|
||||
const criteria = {
|
||||
boardId,
|
||||
};
|
||||
|
||||
if (beforeId) {
|
||||
criteria.id = {
|
||||
'<': beforeId,
|
||||
};
|
||||
}
|
||||
|
||||
return Action.find(criteria).sort('id DESC').limit(LIMIT);
|
||||
};
|
||||
|
||||
const getByCardId = (cardId, { beforeId } = {}) => {
|
||||
const criteria = {
|
||||
cardId,
|
||||
};
|
||||
|
||||
if (beforeId) {
|
||||
criteria.id = {
|
||||
'<': beforeId,
|
||||
};
|
||||
}
|
||||
|
||||
return Action.find(criteria).sort('id DESC').limit(LIMIT);
|
||||
};
|
||||
|
||||
const update = (criteria, values) => Action.update(criteria).set(values).fetch();
|
||||
|
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
const delete_ = (criteria) => Action.destroy(criteria).fetch();
|
||||
|
||||
module.exports = {
|
||||
create,
|
||||
createOne,
|
||||
getByBoardId,
|
||||
getByCardId,
|
||||
update,
|
||||
delete: delete_,
|
||||
};
|
||||
@@ -0,0 +1,211 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
// TODO: refactor?
|
||||
|
||||
const defaultFind = (criteria) => Attachment.find(criteria).sort('id');
|
||||
|
||||
/* Query methods */
|
||||
|
||||
const create = (arrayOfValues) => {
|
||||
const arrayOfFileValues = arrayOfValues.filter(({ type }) => type === Attachment.Types.FILE);
|
||||
|
||||
if (arrayOfFileValues.length > 0) {
|
||||
const arrayOfValuesByUploadedFileId = _.groupBy(arrayOfFileValues, 'data.uploadedFileId');
|
||||
const uploadedFileIds = Object.keys(arrayOfValuesByUploadedFileId);
|
||||
|
||||
const uploadedFileIdsByTotal = Object.entries(arrayOfValuesByUploadedFileId).reduce(
|
||||
(result, [uploadedFileId, arrayOfValuesItem]) => ({
|
||||
...result,
|
||||
[arrayOfValuesItem.length]: [...(result[arrayOfValuesItem.length] || []), uploadedFileId],
|
||||
}),
|
||||
{},
|
||||
);
|
||||
|
||||
return sails.getDatastore().transaction(async (db) => {
|
||||
const queryValues = [];
|
||||
let query = `UPDATE uploaded_file SET references_total = references_total + CASE `;
|
||||
|
||||
Object.entries(uploadedFileIdsByTotal).forEach(([total, uploadedFileIdsItem]) => {
|
||||
const inValues = uploadedFileIdsItem.map((uploadedFileId) => {
|
||||
queryValues.push(uploadedFileId);
|
||||
return `$${queryValues.length}`;
|
||||
});
|
||||
|
||||
queryValues.push(total);
|
||||
query += `WHEN id IN (${inValues.join(', ')}) THEN $${queryValues.length}::int `;
|
||||
});
|
||||
|
||||
const inValues = uploadedFileIds.map((uploadedFileId) => {
|
||||
queryValues.push(uploadedFileId);
|
||||
return `$${queryValues.length}`;
|
||||
});
|
||||
|
||||
queryValues.push(new Date().toISOString());
|
||||
query += `END, updated_at = $${queryValues.length} WHERE id IN (${inValues.join(', ')}) AND references_total IS NOT NULL RETURNING id`;
|
||||
|
||||
const queryResult = await sails.sendNativeQuery(query, queryValues).usingConnection(db);
|
||||
const nextUploadedFileIds = sails.helpers.utils.mapRecords(queryResult.rows);
|
||||
|
||||
if (nextUploadedFileIds.length < uploadedFileIds.length) {
|
||||
const nextUploadedFileIdsSet = new Set(nextUploadedFileIds);
|
||||
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
arrayOfValues = arrayOfValues.filter(
|
||||
(values) =>
|
||||
values.type !== Attachment.Types.FILE ||
|
||||
nextUploadedFileIdsSet.has(values.data.uploadedFileId),
|
||||
);
|
||||
}
|
||||
|
||||
return Attachment.createEach(arrayOfValues).fetch().usingConnection(db);
|
||||
});
|
||||
}
|
||||
|
||||
return Attachment.createEach(arrayOfValues).fetch();
|
||||
};
|
||||
|
||||
const createOne = (values) => {
|
||||
if (values.type === Attachment.Types.FILE) {
|
||||
return sails.getDatastore().transaction(async (db) => {
|
||||
const attachment = await Attachment.create({ ...values })
|
||||
.fetch()
|
||||
.usingConnection(db);
|
||||
|
||||
const queryResult = await sails
|
||||
.sendNativeQuery(
|
||||
'UPDATE uploaded_file SET references_total = references_total + 1, updated_at = $1 WHERE id = $2 AND references_total IS NOT NULL',
|
||||
[new Date().toISOString(), values.data.uploadedFileId],
|
||||
)
|
||||
.usingConnection(db);
|
||||
|
||||
if (queryResult.rowCount === 0) {
|
||||
throw 'uploadedFileNotFound';
|
||||
}
|
||||
|
||||
return attachment;
|
||||
});
|
||||
}
|
||||
|
||||
return Attachment.create({ ...values }).fetch();
|
||||
};
|
||||
|
||||
const getByIds = (ids) => defaultFind(ids);
|
||||
|
||||
const getByCardId = (cardId) =>
|
||||
defaultFind({
|
||||
cardId,
|
||||
});
|
||||
|
||||
const getByCardIds = (cardIds) =>
|
||||
defaultFind({
|
||||
cardId: cardIds,
|
||||
});
|
||||
|
||||
const getOneById = (id, { cardId } = {}) => {
|
||||
const criteria = {
|
||||
id,
|
||||
};
|
||||
|
||||
if (cardId) {
|
||||
criteria.cardId = cardId;
|
||||
}
|
||||
|
||||
return Attachment.findOne(criteria);
|
||||
};
|
||||
|
||||
const update = (criteria, values) => Attachment.update(criteria).set(values).fetch();
|
||||
|
||||
const updateOne = (criteria, values) => Attachment.updateOne(criteria).set({ ...values });
|
||||
|
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
const delete_ = (criteria) =>
|
||||
sails.getDatastore().transaction(async (db) => {
|
||||
const attachments = await Attachment.destroy(criteria).fetch().usingConnection(db);
|
||||
const fileAttachments = attachments.filter(({ type }) => type === Attachment.Types.FILE);
|
||||
|
||||
let uploadedFiles = [];
|
||||
if (fileAttachments.length > 0) {
|
||||
const attachmentsByUploadedFileId = _.groupBy(fileAttachments, 'data.uploadedFileId');
|
||||
|
||||
const uploadedFileIdsByTotal = Object.entries(attachmentsByUploadedFileId).reduce(
|
||||
(result, [uploadedFileId, attachmentsItem]) => ({
|
||||
...result,
|
||||
[attachmentsItem.length]: [...(result[attachmentsItem.length] || []), uploadedFileId],
|
||||
}),
|
||||
{},
|
||||
);
|
||||
|
||||
const queryValues = [];
|
||||
let query = 'UPDATE uploaded_file SET references_total = CASE WHEN references_total = CASE ';
|
||||
|
||||
Object.entries(uploadedFileIdsByTotal).forEach(([total, uploadedFileIds]) => {
|
||||
const inValues = uploadedFileIds.map((uploadedFileId) => {
|
||||
queryValues.push(uploadedFileId);
|
||||
return `$${queryValues.length}`;
|
||||
});
|
||||
|
||||
queryValues.push(total);
|
||||
query += `WHEN id IN (${inValues.join(', ')}) THEN $${queryValues.length}::int `;
|
||||
});
|
||||
|
||||
query += 'END THEN NULL ELSE references_total - CASE ';
|
||||
|
||||
Object.entries(uploadedFileIdsByTotal).forEach(([total, uploadedFileIds]) => {
|
||||
const inValues = uploadedFileIds.map((uploadedFileId) => {
|
||||
queryValues.push(uploadedFileId);
|
||||
return `$${queryValues.length}`;
|
||||
});
|
||||
|
||||
queryValues.push(total);
|
||||
query += `WHEN id IN (${inValues.join(', ')}) THEN $${queryValues.length}::int `;
|
||||
});
|
||||
|
||||
const inValues = Object.keys(attachmentsByUploadedFileId).map((uploadedFileId) => {
|
||||
queryValues.push(uploadedFileId);
|
||||
return `$${queryValues.length}`;
|
||||
});
|
||||
|
||||
queryValues.push(new Date().toISOString());
|
||||
query += `END END, updated_at = $${queryValues.length} WHERE id IN (${inValues.join(', ')}) AND references_total IS NOT NULL RETURNING *`;
|
||||
|
||||
const queryResult = await sails.sendNativeQuery(query, queryValues).usingConnection(db);
|
||||
uploadedFiles = queryResult.rows.map((row) => UploadedFile.qm.transformRowToModel(row));
|
||||
}
|
||||
|
||||
return { attachments, uploadedFiles };
|
||||
});
|
||||
|
||||
const deleteOne = (criteria) =>
|
||||
sails.getDatastore().transaction(async (db) => {
|
||||
const attachment = await Attachment.destroyOne(criteria).usingConnection(db);
|
||||
|
||||
let uploadedFile;
|
||||
if (attachment.type === Attachment.Types.FILE) {
|
||||
const queryResult = await sails
|
||||
.sendNativeQuery(
|
||||
'UPDATE uploaded_file SET references_total = CASE WHEN references_total > 1 THEN references_total - 1 END, updated_at = $1 WHERE id = $2 RETURNING *',
|
||||
[new Date().toISOString(), attachment.data.uploadedFileId],
|
||||
)
|
||||
.usingConnection(db);
|
||||
|
||||
uploadedFile = UploadedFile.qm.transformRowToModel(queryResult.rows[0]);
|
||||
}
|
||||
|
||||
return { attachment, uploadedFile };
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
create,
|
||||
createOne,
|
||||
getByIds,
|
||||
getByCardId,
|
||||
getByCardIds,
|
||||
getOneById,
|
||||
update,
|
||||
updateOne,
|
||||
deleteOne,
|
||||
delete: delete_,
|
||||
};
|
||||
@@ -0,0 +1,138 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
const defaultFind = (criteria) => BackgroundImage.find(criteria).sort('id');
|
||||
|
||||
/* Query methods */
|
||||
|
||||
const createOne = (values) =>
|
||||
sails.getDatastore().transaction(async (db) => {
|
||||
const backgroundImage = await BackgroundImage.create({ ...values })
|
||||
.fetch()
|
||||
.usingConnection(db);
|
||||
|
||||
const queryResult = await sails
|
||||
.sendNativeQuery(
|
||||
'UPDATE uploaded_file SET references_total = references_total + 1, updated_at = $1 WHERE id = $2 AND references_total IS NOT NULL',
|
||||
[new Date().toISOString(), values.uploadedFileId],
|
||||
)
|
||||
.usingConnection(db);
|
||||
|
||||
if (queryResult.rowCount === 0) {
|
||||
throw 'uploadedFileNotFound';
|
||||
}
|
||||
|
||||
return backgroundImage;
|
||||
});
|
||||
|
||||
const getByIds = (ids) => defaultFind(ids);
|
||||
|
||||
const getByProjectId = (projectId) =>
|
||||
defaultFind({
|
||||
projectId,
|
||||
});
|
||||
|
||||
const getByProjectIds = (projectIds) =>
|
||||
defaultFind({
|
||||
projectId: projectIds,
|
||||
});
|
||||
|
||||
const getOneById = (id, { projectId } = {}) => {
|
||||
const criteria = {
|
||||
id,
|
||||
};
|
||||
|
||||
if (projectId) {
|
||||
criteria.projectId = projectId;
|
||||
}
|
||||
|
||||
return BackgroundImage.findOne(criteria);
|
||||
};
|
||||
|
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
const delete_ = (criteria) =>
|
||||
sails.getDatastore().transaction(async (db) => {
|
||||
const backgroundImages = await BackgroundImage.destroy(criteria).fetch().usingConnection(db);
|
||||
|
||||
let uploadedFiles = [];
|
||||
if (backgroundImages.length > 0) {
|
||||
const backgroundImagesByUploadedFileId = _.groupBy(backgroundImages, 'uploadedFileId');
|
||||
|
||||
const uploadedFileIdsByTotal = Object.entries(backgroundImagesByUploadedFileId).reduce(
|
||||
(result, [uploadedFileId, backgroundImagesItem]) => ({
|
||||
...result,
|
||||
[backgroundImagesItem.length]: [
|
||||
...(result[backgroundImagesItem.length] || []),
|
||||
uploadedFileId,
|
||||
],
|
||||
}),
|
||||
{},
|
||||
);
|
||||
|
||||
const queryValues = [];
|
||||
let query = 'UPDATE uploaded_file SET references_total = CASE WHEN references_total = CASE ';
|
||||
|
||||
Object.entries(uploadedFileIdsByTotal).forEach(([total, uploadedFileIds]) => {
|
||||
const inValues = uploadedFileIds.map((uploadedFileId) => {
|
||||
queryValues.push(uploadedFileId);
|
||||
return `$${queryValues.length}`;
|
||||
});
|
||||
|
||||
queryValues.push(total);
|
||||
query += `WHEN id IN (${inValues.join(', ')}) THEN $${queryValues.length}::int `;
|
||||
});
|
||||
|
||||
query += 'END THEN NULL ELSE references_total - CASE ';
|
||||
|
||||
Object.entries(uploadedFileIdsByTotal).forEach(([total, uploadedFileIds]) => {
|
||||
const inValues = uploadedFileIds.map((uploadedFileId) => {
|
||||
queryValues.push(uploadedFileId);
|
||||
return `$${queryValues.length}`;
|
||||
});
|
||||
|
||||
queryValues.push(total);
|
||||
query += `WHEN id IN (${inValues.join(', ')}) THEN $${queryValues.length}::int `;
|
||||
});
|
||||
|
||||
const inValues = Object.keys(backgroundImagesByUploadedFileId).map((uploadedFileId) => {
|
||||
queryValues.push(uploadedFileId);
|
||||
return `$${queryValues.length}`;
|
||||
});
|
||||
|
||||
queryValues.push(new Date().toISOString());
|
||||
query += `END END, updated_at = $${queryValues.length} WHERE id IN (${inValues.join(', ')}) AND references_total IS NOT NULL RETURNING *`;
|
||||
|
||||
const queryResult = await sails.sendNativeQuery(query, queryValues).usingConnection(db);
|
||||
uploadedFiles = queryResult.rows.map((row) => UploadedFile.qm.transformRowToModel(row));
|
||||
}
|
||||
|
||||
return { backgroundImages, uploadedFiles };
|
||||
});
|
||||
|
||||
const deleteOne = (criteria) =>
|
||||
sails.getDatastore().transaction(async (db) => {
|
||||
const backgroundImage = await BackgroundImage.destroyOne(criteria).usingConnection(db);
|
||||
|
||||
const queryResult = await sails
|
||||
.sendNativeQuery(
|
||||
'UPDATE uploaded_file SET references_total = CASE WHEN references_total > 1 THEN references_total - 1 END, updated_at = $1 WHERE id = $2 RETURNING *',
|
||||
[new Date().toISOString(), backgroundImage.uploadedFileId],
|
||||
)
|
||||
.usingConnection(db);
|
||||
|
||||
const uploadedFile = UploadedFile.qm.transformRowToModel(queryResult.rows[0]);
|
||||
|
||||
return { backgroundImage, uploadedFile };
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
createOne,
|
||||
getByIds,
|
||||
getByProjectId,
|
||||
getByProjectIds,
|
||||
getOneById,
|
||||
deleteOne,
|
||||
delete: delete_,
|
||||
};
|
||||
@@ -0,0 +1,52 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
const defaultFind = (criteria) => BaseCustomFieldGroup.find(criteria).sort('id');
|
||||
|
||||
/* Query methods */
|
||||
|
||||
const createOne = (values) => BaseCustomFieldGroup.create({ ...values }).fetch();
|
||||
|
||||
const getByIds = (ids) => defaultFind(ids);
|
||||
|
||||
const getByProjectId = (projectId) =>
|
||||
defaultFind({
|
||||
projectId,
|
||||
});
|
||||
|
||||
const getByProjectIds = (projectIds) =>
|
||||
defaultFind({
|
||||
projectId: projectIds,
|
||||
});
|
||||
|
||||
const getOneById = (id, { projectId } = {}) => {
|
||||
const criteria = {
|
||||
id,
|
||||
};
|
||||
|
||||
if (projectId) {
|
||||
criteria.projectId = projectId;
|
||||
}
|
||||
|
||||
return BaseCustomFieldGroup.findOne(criteria);
|
||||
};
|
||||
|
||||
const updateOne = (criteria, values) => BaseCustomFieldGroup.updateOne(criteria).set({ ...values });
|
||||
|
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
const delete_ = (criteria) => BaseCustomFieldGroup.destroy(criteria).fetch();
|
||||
|
||||
const deleteOne = (criteria) => BaseCustomFieldGroup.destroyOne(criteria);
|
||||
|
||||
module.exports = {
|
||||
createOne,
|
||||
getByIds,
|
||||
getByProjectId,
|
||||
getByProjectIds,
|
||||
getOneById,
|
||||
updateOne,
|
||||
deleteOne,
|
||||
delete: delete_,
|
||||
};
|
||||
@@ -0,0 +1,91 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
const defaultFind = (criteria, { sort = 'id' } = {}) => Board.find(criteria).sort(sort);
|
||||
|
||||
/* Query methods */
|
||||
|
||||
const createOne = (values, { user } = {}) =>
|
||||
sails.getDatastore().transaction(async (db) => {
|
||||
const board = await Board.create({ ...values })
|
||||
.fetch()
|
||||
.usingConnection(db);
|
||||
|
||||
const boardMembership = await BoardMembership.create({
|
||||
projectId: board.projectId,
|
||||
boardId: board.id,
|
||||
userId: user.id,
|
||||
role: BoardMembership.Roles.EDITOR,
|
||||
})
|
||||
.fetch()
|
||||
.usingConnection(db);
|
||||
|
||||
const lists = await List.createEach(
|
||||
[List.Types.ARCHIVE, List.Types.TRASH].map((type) => ({
|
||||
type,
|
||||
boardId: board.id,
|
||||
})),
|
||||
)
|
||||
.fetch()
|
||||
.usingConnection(db);
|
||||
|
||||
return { board, boardMembership, lists };
|
||||
});
|
||||
|
||||
const getByIds = (ids, { exceptProjectIdOrIds } = {}) => {
|
||||
const criteria = {
|
||||
id: ids,
|
||||
};
|
||||
|
||||
if (exceptProjectIdOrIds) {
|
||||
criteria.projectId = {
|
||||
'!=': exceptProjectIdOrIds,
|
||||
};
|
||||
}
|
||||
|
||||
return defaultFind(criteria);
|
||||
};
|
||||
|
||||
const getByProjectId = (projectId, { exceptIdOrIds, sort = ['position', 'id'] } = {}) => {
|
||||
const criteria = {
|
||||
projectId,
|
||||
};
|
||||
|
||||
if (exceptIdOrIds) {
|
||||
criteria.id = {
|
||||
'!=': exceptIdOrIds,
|
||||
};
|
||||
}
|
||||
|
||||
return defaultFind(criteria, { sort });
|
||||
};
|
||||
|
||||
const getByProjectIds = (projectIds, { sort = ['position', 'id'] } = {}) =>
|
||||
defaultFind(
|
||||
{
|
||||
projectId: projectIds,
|
||||
},
|
||||
{ sort },
|
||||
);
|
||||
|
||||
const getOneById = (id) => Board.findOne(id);
|
||||
|
||||
const updateOne = (criteria, values) => Board.updateOne(criteria).set({ ...values });
|
||||
|
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
const delete_ = (criteria) => Board.destroy(criteria).fetch();
|
||||
|
||||
const deleteOne = (criteria) => Board.destroyOne(criteria);
|
||||
|
||||
module.exports = {
|
||||
createOne,
|
||||
getByIds,
|
||||
getByProjectId,
|
||||
getByProjectIds,
|
||||
getOneById,
|
||||
updateOne,
|
||||
deleteOne,
|
||||
delete: delete_,
|
||||
};
|
||||
@@ -0,0 +1,100 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
const defaultFind = (criteria) => BoardMembership.find(criteria).sort('id');
|
||||
|
||||
/* Query methods */
|
||||
|
||||
const createOne = (values) => BoardMembership.create({ ...values }).fetch();
|
||||
|
||||
const getByIds = (ids) => defaultFind(ids);
|
||||
|
||||
const getByProjectId = (projectId) =>
|
||||
defaultFind({
|
||||
projectId,
|
||||
});
|
||||
|
||||
const getByProjectIdAndUserId = (projectId, userId) =>
|
||||
defaultFind({
|
||||
projectId,
|
||||
userId,
|
||||
});
|
||||
|
||||
const getByProjectIds = (projectIds) =>
|
||||
defaultFind({
|
||||
projectId: projectIds,
|
||||
});
|
||||
|
||||
const getByBoardId = (boardId) =>
|
||||
defaultFind({
|
||||
boardId,
|
||||
});
|
||||
|
||||
const getByBoardIds = (boardIds, { exceptUserIdOrIds } = {}) => {
|
||||
const criteria = {
|
||||
boardId: boardIds,
|
||||
};
|
||||
|
||||
if (exceptUserIdOrIds) {
|
||||
criteria.userId = {
|
||||
'!=': exceptUserIdOrIds,
|
||||
};
|
||||
}
|
||||
|
||||
return defaultFind(criteria);
|
||||
};
|
||||
|
||||
const getByBoardIdsAndUserId = (boardIds, userId) =>
|
||||
defaultFind({
|
||||
userId,
|
||||
boardId: boardIds,
|
||||
});
|
||||
|
||||
const getByUserId = (userId, { exceptProjectIdOrIds } = {}) => {
|
||||
const criteria = {
|
||||
userId,
|
||||
};
|
||||
|
||||
if (exceptProjectIdOrIds) {
|
||||
criteria.projectId = {
|
||||
'!=': exceptProjectIdOrIds,
|
||||
};
|
||||
}
|
||||
|
||||
return defaultFind(criteria);
|
||||
};
|
||||
|
||||
const getOneById = (id) => BoardMembership.findOne(id);
|
||||
|
||||
const getOneByBoardIdAndUserId = (boardId, userId) =>
|
||||
BoardMembership.findOne({
|
||||
boardId,
|
||||
userId,
|
||||
});
|
||||
|
||||
const updateOne = async (criteria, values) =>
|
||||
BoardMembership.updateOne(criteria).set({ ...values });
|
||||
|
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
const delete_ = (criteria) => BoardMembership.destroy(criteria).fetch();
|
||||
|
||||
const deleteOne = (criteria) => BoardMembership.destroyOne(criteria);
|
||||
|
||||
module.exports = {
|
||||
createOne,
|
||||
getByIds,
|
||||
getByProjectId,
|
||||
getByProjectIdAndUserId,
|
||||
getByProjectIds,
|
||||
getByBoardId,
|
||||
getByBoardIds,
|
||||
getByBoardIdsAndUserId,
|
||||
getByUserId,
|
||||
getOneById,
|
||||
getOneByBoardIdAndUserId,
|
||||
updateOne,
|
||||
deleteOne,
|
||||
delete: delete_,
|
||||
};
|
||||
@@ -0,0 +1,46 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
const defaultFind = (criteria) => BoardSubscription.find(criteria).sort('id');
|
||||
|
||||
/* Query methods */
|
||||
|
||||
const createOne = (values) => BoardSubscription.create({ ...values }).fetch();
|
||||
|
||||
const getByIds = (ids) => defaultFind(ids);
|
||||
|
||||
const getByBoardId = (boardId, { exceptUserIdOrIds } = {}) => {
|
||||
const criteria = {
|
||||
boardId,
|
||||
};
|
||||
|
||||
if (exceptUserIdOrIds) {
|
||||
criteria.userId = {
|
||||
'!=': exceptUserIdOrIds,
|
||||
};
|
||||
}
|
||||
|
||||
return defaultFind(criteria);
|
||||
};
|
||||
|
||||
const getOneByBoardIdAndUserId = (boardId, userId) =>
|
||||
BoardSubscription.findOne({
|
||||
boardId,
|
||||
userId,
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
const delete_ = (criteria) => BoardSubscription.destroy(criteria).fetch();
|
||||
|
||||
const deleteOne = (criteria) => BoardSubscription.destroyOne(criteria);
|
||||
|
||||
module.exports = {
|
||||
createOne,
|
||||
getByIds,
|
||||
getByBoardId,
|
||||
getOneByBoardIdAndUserId,
|
||||
deleteOne,
|
||||
delete: delete_,
|
||||
};
|
||||
@@ -0,0 +1,247 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
const buildSearchParts = require('../../../../utils/build-query-parts');
|
||||
const { makeRowToModelTransformer } = require('../helpers');
|
||||
|
||||
const LIMIT = 50;
|
||||
|
||||
const transformRowToModel = makeRowToModelTransformer(Card);
|
||||
|
||||
const defaultFind = (criteria, { sort = 'id', limit } = {}) =>
|
||||
Card.find(criteria).sort(sort).limit(limit);
|
||||
|
||||
/* Query methods */
|
||||
|
||||
const createOne = (values) => Card.create({ ...values }).fetch();
|
||||
|
||||
const getByIds = (ids) => defaultFind(ids);
|
||||
|
||||
const getByBoardId = (boardId) =>
|
||||
defaultFind({
|
||||
boardId,
|
||||
});
|
||||
|
||||
const getByListId = async (listId, { exceptIdOrIds, sort = ['position', 'id'] } = {}) => {
|
||||
const criteria = {
|
||||
listId,
|
||||
};
|
||||
|
||||
if (exceptIdOrIds) {
|
||||
criteria.id = {
|
||||
'!=': exceptIdOrIds,
|
||||
};
|
||||
}
|
||||
|
||||
return defaultFind(criteria, { sort });
|
||||
};
|
||||
|
||||
const getByEndlessListId = async (listId, { before, search, userIds, labelIds }) => {
|
||||
if (search || userIds || labelIds) {
|
||||
if (userIds && userIds.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (labelIds && labelIds.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const queryValues = [];
|
||||
let query = 'SELECT DISTINCT card.* FROM card';
|
||||
|
||||
if (userIds) {
|
||||
query += ' LEFT JOIN card_membership ON card.id = card_membership.card_id';
|
||||
query += ' LEFT JOIN task_list ON card.id = task_list.card_id';
|
||||
query += ' LEFT JOIN task ON task_list.id = task.task_list_id';
|
||||
}
|
||||
|
||||
if (labelIds) {
|
||||
query += ' LEFT JOIN card_label ON card.id = card_label.card_id';
|
||||
}
|
||||
|
||||
queryValues.push(listId);
|
||||
query += ` WHERE card.list_id = $${queryValues.length}`;
|
||||
|
||||
if (before) {
|
||||
queryValues.push(before.listChangedAt);
|
||||
query += ` AND (card.list_changed_at < $${queryValues.length} OR (card.list_changed_at = $${queryValues.length}`;
|
||||
|
||||
queryValues.push(before.id);
|
||||
query += ` AND card.id < $${queryValues.length}))`;
|
||||
}
|
||||
|
||||
if (search) {
|
||||
if (search.startsWith('/')) {
|
||||
queryValues.push(search.substring(1));
|
||||
query += ` AND (card.name ~* $${queryValues.length} OR card.description ~* $${queryValues.length})`;
|
||||
} else {
|
||||
const searchParts = buildSearchParts(search);
|
||||
|
||||
if (searchParts.length > 0) {
|
||||
const ilikeValues = searchParts.map((searchPart) => {
|
||||
queryValues.push(searchPart);
|
||||
return `'%' || $${queryValues.length} || '%'`;
|
||||
});
|
||||
|
||||
query += ` AND ((card.name ILIKE ALL(ARRAY[${ilikeValues.join(', ')}])) OR (card.description ILIKE ALL(ARRAY[${ilikeValues.join(', ')}])))`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (userIds) {
|
||||
const inValues = userIds.map((userId) => {
|
||||
queryValues.push(userId);
|
||||
return `$${queryValues.length}`;
|
||||
});
|
||||
|
||||
query += ` AND (card_membership.user_id IN (${inValues.join(', ')}) OR task.assignee_user_id IN (${inValues.join(', ')}))`;
|
||||
}
|
||||
|
||||
if (labelIds) {
|
||||
const inValues = labelIds.map((labelId) => {
|
||||
queryValues.push(labelId);
|
||||
return `$${queryValues.length}`;
|
||||
});
|
||||
|
||||
query += ` AND card_label.label_id IN (${inValues.join(', ')})`;
|
||||
}
|
||||
|
||||
query += ` LIMIT ${LIMIT}`;
|
||||
|
||||
let queryResult;
|
||||
try {
|
||||
queryResult = await sails.sendNativeQuery(query, queryValues);
|
||||
} catch (error) {
|
||||
if (
|
||||
error.code === 'E_QUERY_FAILED' &&
|
||||
error.message.includes('Query failed: invalid regular expression')
|
||||
) {
|
||||
return [];
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
|
||||
return queryResult.rows.map((row) => transformRowToModel(row));
|
||||
}
|
||||
|
||||
const criteria = {
|
||||
and: [{ listId }],
|
||||
};
|
||||
|
||||
if (before) {
|
||||
criteria.and.push({
|
||||
or: [
|
||||
{
|
||||
listChangedAt: {
|
||||
'<': before.listChangedAt,
|
||||
},
|
||||
},
|
||||
{
|
||||
listChangedAt: before.listChangedAt,
|
||||
id: {
|
||||
'<': before.id,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
return defaultFind(criteria, {
|
||||
sort: ['listChangedAt DESC', 'id DESC'],
|
||||
limit: LIMIT,
|
||||
});
|
||||
};
|
||||
|
||||
const getByListIds = async (listIds, { sort = ['position', 'id'] } = {}) =>
|
||||
defaultFind(
|
||||
{
|
||||
listId: listIds,
|
||||
},
|
||||
{ sort },
|
||||
);
|
||||
|
||||
const getOneById = (id, { listId } = {}) => {
|
||||
const criteria = {
|
||||
id,
|
||||
};
|
||||
|
||||
if (listId) {
|
||||
criteria.listId = listId;
|
||||
}
|
||||
|
||||
return Card.findOne(criteria);
|
||||
};
|
||||
|
||||
const update = async (criteria, values) => {
|
||||
if (!_.isUndefined(values.isClosed)) {
|
||||
return sails.getDatastore().transaction(async (db) => {
|
||||
const cards = await Card.update(criteria).set(values).fetch().usingConnection(db);
|
||||
|
||||
let tasks = [];
|
||||
if (!_.isUndefined(values.isClosed)) {
|
||||
tasks = await Task.update({
|
||||
linkedCardId: sails.helpers.utils.mapRecords(cards),
|
||||
})
|
||||
.set({
|
||||
isCompleted: values.isClosed,
|
||||
})
|
||||
.fetch()
|
||||
.usingConnection(db);
|
||||
}
|
||||
|
||||
return { cards, tasks };
|
||||
});
|
||||
}
|
||||
|
||||
const cards = await Card.update(criteria).set(values).fetch();
|
||||
return { cards };
|
||||
};
|
||||
|
||||
const updateOne = async (criteria, values) => {
|
||||
if (!_.isUndefined(values.isClosed)) {
|
||||
return sails.getDatastore().transaction(async (db) => {
|
||||
const card = await Card.updateOne(criteria)
|
||||
.set({ ...values })
|
||||
.usingConnection(db);
|
||||
|
||||
let tasks;
|
||||
if (!_.isUndefined(values.isClosed) && card) {
|
||||
tasks = await Task.update({
|
||||
linkedCardId: card.id,
|
||||
})
|
||||
.set({
|
||||
isCompleted: card.isClosed,
|
||||
})
|
||||
.fetch()
|
||||
.usingConnection(db);
|
||||
}
|
||||
|
||||
return { card, tasks };
|
||||
});
|
||||
}
|
||||
|
||||
const card = await Card.updateOne(criteria).set({ ...values });
|
||||
return { card };
|
||||
};
|
||||
|
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
const delete_ = (criteria) => Card.destroy(criteria).fetch();
|
||||
|
||||
const deleteOne = (criteria) => Card.destroyOne(criteria);
|
||||
|
||||
module.exports = {
|
||||
createOne,
|
||||
getByIds,
|
||||
getByBoardId,
|
||||
getByListId,
|
||||
getByEndlessListId,
|
||||
getByListIds,
|
||||
getOneById,
|
||||
update,
|
||||
updateOne,
|
||||
deleteOne,
|
||||
delete: delete_,
|
||||
};
|
||||
@@ -0,0 +1,46 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
const defaultFind = (criteria) => CardLabel.find(criteria).sort('id');
|
||||
|
||||
/* Query methods */
|
||||
|
||||
const create = (arrayOfValues) => CardLabel.createEach(arrayOfValues).fetch();
|
||||
|
||||
const createOne = (values) => CardLabel.create({ ...values }).fetch();
|
||||
|
||||
const getByIds = (ids) => defaultFind(ids);
|
||||
|
||||
const getByCardId = (cardId) =>
|
||||
defaultFind({
|
||||
cardId,
|
||||
});
|
||||
|
||||
const getByCardIds = (cardIds) =>
|
||||
defaultFind({
|
||||
cardId: cardIds,
|
||||
});
|
||||
|
||||
const getOneByCardIdAndLabelId = (cardId, labelId) =>
|
||||
CardLabel.findOne({
|
||||
cardId,
|
||||
labelId,
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
const delete_ = (criteria) => CardLabel.destroy(criteria).fetch();
|
||||
|
||||
const deleteOne = (criteria) => CardLabel.destroyOne(criteria);
|
||||
|
||||
module.exports = {
|
||||
create,
|
||||
createOne,
|
||||
getByIds,
|
||||
getByCardId,
|
||||
getByCardIds,
|
||||
getOneByCardIdAndLabelId,
|
||||
deleteOne,
|
||||
delete: delete_,
|
||||
};
|
||||
@@ -0,0 +1,52 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
const defaultFind = (criteria) => CardMembership.find(criteria).sort('id');
|
||||
|
||||
/* Query methods */
|
||||
|
||||
const create = (arrayOfValues) => CardMembership.createEach(arrayOfValues).fetch();
|
||||
|
||||
const createOne = (values) => CardMembership.create({ ...values }).fetch();
|
||||
|
||||
const getByIds = (ids) => defaultFind(ids);
|
||||
|
||||
const getByCardId = (cardId, { userIdOrIds } = {}) => {
|
||||
const criteria = {
|
||||
cardId,
|
||||
};
|
||||
if (userIdOrIds) {
|
||||
criteria.userId = userIdOrIds;
|
||||
}
|
||||
|
||||
return defaultFind(criteria);
|
||||
};
|
||||
|
||||
const getByCardIds = (cardIds) =>
|
||||
defaultFind({
|
||||
cardId: cardIds,
|
||||
});
|
||||
|
||||
const getOneByCardIdAndUserId = (cardId, userId) =>
|
||||
CardMembership.findOne({
|
||||
cardId,
|
||||
userId,
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
const delete_ = (criteria) => CardMembership.destroy(criteria).fetch();
|
||||
|
||||
const deleteOne = (criteria) => CardMembership.destroyOne(criteria);
|
||||
|
||||
module.exports = {
|
||||
create,
|
||||
createOne,
|
||||
getByIds,
|
||||
getByCardId,
|
||||
getByCardIds,
|
||||
getOneByCardIdAndUserId,
|
||||
deleteOne,
|
||||
delete: delete_,
|
||||
};
|
||||
@@ -0,0 +1,53 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
const defaultFind = (criteria) => CardSubscription.find(criteria).sort('id');
|
||||
|
||||
/* Query methods */
|
||||
|
||||
const createOne = (values) => CardSubscription.create({ ...values }).fetch();
|
||||
|
||||
const getByIds = (ids) => defaultFind(ids);
|
||||
|
||||
const getByCardId = (cardId, { exceptUserIdOrIds } = {}) => {
|
||||
const criteria = {
|
||||
cardId,
|
||||
};
|
||||
|
||||
if (exceptUserIdOrIds) {
|
||||
criteria.userId = {
|
||||
'!=': exceptUserIdOrIds,
|
||||
};
|
||||
}
|
||||
|
||||
return defaultFind(criteria);
|
||||
};
|
||||
|
||||
const getByCardIdsAndUserId = (cardIds, userId) =>
|
||||
defaultFind({
|
||||
userId,
|
||||
cardId: cardIds,
|
||||
});
|
||||
|
||||
const getOneByCardIdAndUserId = (cardId, userId) =>
|
||||
CardSubscription.findOne({
|
||||
cardId,
|
||||
userId,
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
const delete_ = (criteria) => CardSubscription.destroy(criteria).fetch();
|
||||
|
||||
const deleteOne = (criteria) => CardSubscription.destroyOne(criteria);
|
||||
|
||||
module.exports = {
|
||||
createOne,
|
||||
getByIds,
|
||||
getByCardId,
|
||||
getByCardIdsAndUserId,
|
||||
getOneByCardIdAndUserId,
|
||||
deleteOne,
|
||||
delete: delete_,
|
||||
};
|
||||
@@ -0,0 +1,125 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
const LIMIT = 50;
|
||||
|
||||
const defaultFind = (criteria, { limit } = {}) =>
|
||||
Comment.find(criteria).sort('id DESC').limit(limit);
|
||||
|
||||
/* Query methods */
|
||||
|
||||
const createOne = (values) =>
|
||||
sails.getDatastore().transaction(async (db) => {
|
||||
const comment = await Comment.create({ ...values })
|
||||
.fetch()
|
||||
.usingConnection(db);
|
||||
|
||||
const queryResult = await sails
|
||||
.sendNativeQuery(
|
||||
'UPDATE card SET comments_total = comments_total + 1, updated_at = $1 WHERE id = $2',
|
||||
[new Date().toISOString(), comment.cardId],
|
||||
)
|
||||
.usingConnection(db);
|
||||
|
||||
if (queryResult.rowCount === 0) {
|
||||
throw 'cardNotFound';
|
||||
}
|
||||
|
||||
return comment;
|
||||
});
|
||||
|
||||
const getByIds = (ids) => defaultFind(ids);
|
||||
|
||||
const getByCardId = (cardId, { beforeId } = {}) => {
|
||||
const criteria = {
|
||||
cardId,
|
||||
};
|
||||
|
||||
if (beforeId) {
|
||||
criteria.id = {
|
||||
'<': beforeId,
|
||||
};
|
||||
}
|
||||
|
||||
return defaultFind(criteria, { limit: LIMIT });
|
||||
};
|
||||
|
||||
const getOneById = (id) => Comment.findOne(id);
|
||||
|
||||
const update = (criteria, values) => Comment.update(criteria).set(values).fetch();
|
||||
|
||||
const updateOne = (criteria, values) => Comment.updateOne(criteria).set({ ...values });
|
||||
|
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
const delete_ = (criteria) =>
|
||||
sails.getDatastore().transaction(async (db) => {
|
||||
const comments = await Comment.destroy(criteria).fetch().usingConnection(db);
|
||||
|
||||
if (comments.length > 0) {
|
||||
const commentsByCardId = _.groupBy(comments, 'cardId');
|
||||
|
||||
const cardIdsByTotal = Object.entries(commentsByCardId).reduce(
|
||||
(result, [cardId, commentsItem]) => ({
|
||||
...result,
|
||||
[commentsItem.length]: [...(result[commentsItem.length] || []), cardId],
|
||||
}),
|
||||
{},
|
||||
);
|
||||
|
||||
const queryValues = [];
|
||||
let query = 'UPDATE card SET comments_total = comments_total - CASE ';
|
||||
|
||||
Object.entries(cardIdsByTotal).forEach(([total, cardIds]) => {
|
||||
const inValues = cardIds.map((cardId) => {
|
||||
queryValues.push(cardId);
|
||||
return `$${queryValues.length}`;
|
||||
});
|
||||
|
||||
queryValues.push(total);
|
||||
query += `WHEN id IN (${inValues.join(', ')}) THEN $${queryValues.length}::int `;
|
||||
});
|
||||
|
||||
const inValues = Object.keys(commentsByCardId).map((cardId) => {
|
||||
queryValues.push(cardId);
|
||||
return `$${queryValues.length}`;
|
||||
});
|
||||
|
||||
queryValues.push(new Date().toISOString());
|
||||
query += `END, updated_at = $${queryValues.length} WHERE id IN (${inValues.join(', ')})`;
|
||||
|
||||
await sails.sendNativeQuery(query, queryValues).usingConnection(db);
|
||||
}
|
||||
|
||||
return comments;
|
||||
});
|
||||
|
||||
const deleteOne = (criteria) =>
|
||||
sails.getDatastore().transaction(async (db) => {
|
||||
const comment = await Comment.destroyOne(criteria).usingConnection(db);
|
||||
|
||||
const queryResult = await sails
|
||||
.sendNativeQuery(
|
||||
'UPDATE card SET comments_total = comments_total - 1, updated_at = $1 WHERE id = $2',
|
||||
[new Date().toISOString(), comment.cardId],
|
||||
)
|
||||
.usingConnection(db);
|
||||
|
||||
if (queryResult.rowCount === 0) {
|
||||
throw 'cardNotFound';
|
||||
}
|
||||
|
||||
return comment;
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
createOne,
|
||||
getByIds,
|
||||
getByCardId,
|
||||
getOneById,
|
||||
update,
|
||||
updateOne,
|
||||
deleteOne,
|
||||
delete: delete_,
|
||||
};
|
||||
@@ -0,0 +1,15 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
/* Query methods */
|
||||
|
||||
const getOneMain = () => Config.findOne(Config.MAIN_ID);
|
||||
|
||||
const updateOneMain = (values) => Config.updateOne(Config.MAIN_ID).set({ ...values });
|
||||
|
||||
module.exports = {
|
||||
getOneMain,
|
||||
updateOneMain,
|
||||
};
|
||||
@@ -0,0 +1,87 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
const defaultFind = (criteria, { exceptIdOrIds, sort = 'id' } = {}) => {
|
||||
if (exceptIdOrIds) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
criteria.id = {
|
||||
'!=': exceptIdOrIds,
|
||||
};
|
||||
}
|
||||
|
||||
return CustomField.find(criteria).sort(sort);
|
||||
};
|
||||
|
||||
/* Query methods */
|
||||
|
||||
const create = (arrayOfValues) => CustomField.createEach(arrayOfValues).fetch();
|
||||
|
||||
const createOne = (values) => CustomField.create({ ...values }).fetch();
|
||||
|
||||
const getByIds = (ids) => defaultFind(ids);
|
||||
|
||||
const getByBaseCustomFieldGroupId = (
|
||||
baseCustomFieldGroupId,
|
||||
{ exceptIdOrIds, sort = ['position', 'id'] } = {},
|
||||
) =>
|
||||
defaultFind(
|
||||
{
|
||||
baseCustomFieldGroupId,
|
||||
},
|
||||
{ exceptIdOrIds, sort },
|
||||
);
|
||||
|
||||
const getByBaseCustomFieldGroupIds = (
|
||||
baseCustomFieldGroupIds,
|
||||
{ sort = ['position', 'id'] } = {},
|
||||
) =>
|
||||
defaultFind(
|
||||
{
|
||||
baseCustomFieldGroupId: baseCustomFieldGroupIds,
|
||||
},
|
||||
{ sort },
|
||||
);
|
||||
|
||||
const getByCustomFieldGroupId = async (
|
||||
customFieldGroupId,
|
||||
{ exceptIdOrIds, sort = ['position', 'id'] } = {},
|
||||
) =>
|
||||
defaultFind(
|
||||
{
|
||||
customFieldGroupId,
|
||||
},
|
||||
{ exceptIdOrIds, sort },
|
||||
);
|
||||
|
||||
const getByCustomFieldGroupIds = async (customFieldGroupIds, { sort = ['position', 'id'] } = {}) =>
|
||||
defaultFind(
|
||||
{
|
||||
customFieldGroupId: customFieldGroupIds,
|
||||
},
|
||||
{ sort },
|
||||
);
|
||||
|
||||
const getOneById = (id) => CustomField.findOne(id);
|
||||
|
||||
const updateOne = (criteria, values) => CustomField.updateOne(criteria).set({ ...values });
|
||||
|
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
const delete_ = (criteria) => CustomField.destroy(criteria).fetch();
|
||||
|
||||
const deleteOne = (criteria) => CustomField.destroyOne(criteria);
|
||||
|
||||
module.exports = {
|
||||
create,
|
||||
createOne,
|
||||
getByIds,
|
||||
getByBaseCustomFieldGroupId,
|
||||
getByBaseCustomFieldGroupIds,
|
||||
getByCustomFieldGroupId,
|
||||
getByCustomFieldGroupIds,
|
||||
getOneById,
|
||||
updateOne,
|
||||
deleteOne,
|
||||
delete: delete_,
|
||||
};
|
||||
@@ -0,0 +1,72 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
const defaultFind = (criteria, { exceptIdOrIds, sort = 'id' } = {}) => {
|
||||
if (exceptIdOrIds) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
criteria.id = {
|
||||
'!=': exceptIdOrIds,
|
||||
};
|
||||
}
|
||||
|
||||
return CustomFieldGroup.find(criteria).sort(sort);
|
||||
};
|
||||
|
||||
/* Query methods */
|
||||
|
||||
const create = (arrayOfValues) => CustomFieldGroup.createEach(arrayOfValues).fetch();
|
||||
|
||||
const createOne = (values) => CustomFieldGroup.create({ ...values }).fetch();
|
||||
|
||||
const getByIds = (ids) => defaultFind(ids);
|
||||
|
||||
const getByBoardId = (boardId, { exceptIdOrIds, sort = ['position', 'id'] } = {}) =>
|
||||
defaultFind(
|
||||
{
|
||||
boardId,
|
||||
},
|
||||
{ exceptIdOrIds, sort },
|
||||
);
|
||||
|
||||
const getByCardId = (cardId, { exceptIdOrIds, sort = ['position', 'id'] } = {}) =>
|
||||
defaultFind(
|
||||
{
|
||||
cardId,
|
||||
},
|
||||
{ exceptIdOrIds, sort },
|
||||
);
|
||||
|
||||
const getByCardIds = (cardIds, { sort = ['position', 'id'] } = {}) =>
|
||||
defaultFind(
|
||||
{
|
||||
cardId: cardIds,
|
||||
},
|
||||
{ sort },
|
||||
);
|
||||
|
||||
const getOneById = (id) => CustomFieldGroup.findOne(id);
|
||||
|
||||
const update = (criteria, values) => CustomFieldGroup.update(criteria).set(values).fetch();
|
||||
|
||||
const updateOne = (criteria, values) => CustomFieldGroup.updateOne(criteria).set({ ...values });
|
||||
|
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
const delete_ = (criteria) => CustomFieldGroup.destroy(criteria).fetch();
|
||||
|
||||
const deleteOne = (criteria) => CustomFieldGroup.destroyOne(criteria);
|
||||
|
||||
module.exports = {
|
||||
create,
|
||||
createOne,
|
||||
getByBoardId,
|
||||
getByIds,
|
||||
getByCardId,
|
||||
getByCardIds,
|
||||
getOneById,
|
||||
update,
|
||||
updateOne,
|
||||
deleteOne,
|
||||
delete: delete_,
|
||||
};
|
||||
@@ -0,0 +1,94 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
const { makeRowToModelTransformer } = require('../helpers');
|
||||
|
||||
const transformRowToModel = makeRowToModelTransformer(CustomFieldValue);
|
||||
|
||||
const defaultFind = (criteria, { customFieldGroupIdOrIds } = {}) => {
|
||||
if (customFieldGroupIdOrIds) {
|
||||
criteria.customFieldGroupId = customFieldGroupIdOrIds; // eslint-disable-line no-param-reassign
|
||||
}
|
||||
|
||||
return CustomFieldValue.find(criteria).sort('id');
|
||||
};
|
||||
|
||||
/* Query methods */
|
||||
|
||||
const create = (arrayOfValues) => CustomFieldValue.createEach(arrayOfValues).fetch();
|
||||
|
||||
const createOrUpdateOne = async (values) => {
|
||||
const query = `
|
||||
INSERT INTO custom_field_value (card_id, custom_field_group_id, custom_field_id, content, created_at)
|
||||
VALUES ($1, $2, $3, $4, $5)
|
||||
ON CONFLICT (card_id, custom_field_group_id, custom_field_id)
|
||||
DO UPDATE SET content = EXCLUDED.content, updated_at = EXCLUDED.created_at
|
||||
RETURNING *
|
||||
`;
|
||||
|
||||
const queryResult = await sails.sendNativeQuery(query, [
|
||||
values.cardId,
|
||||
values.customFieldGroupId,
|
||||
values.customFieldId,
|
||||
values.content,
|
||||
new Date().toISOString(),
|
||||
]);
|
||||
|
||||
return transformRowToModel(queryResult.rows[0]);
|
||||
};
|
||||
|
||||
const getByIds = (ids) => defaultFind(ids);
|
||||
|
||||
const getByCardId = (cardId, { customFieldGroupIdOrIds } = {}) =>
|
||||
defaultFind(
|
||||
{
|
||||
cardId,
|
||||
},
|
||||
{ customFieldGroupIdOrIds },
|
||||
);
|
||||
|
||||
const getByCardIds = (cardIds, { customFieldGroupIdOrIds } = {}) =>
|
||||
defaultFind(
|
||||
{
|
||||
cardId: cardIds,
|
||||
},
|
||||
{ customFieldGroupIdOrIds },
|
||||
);
|
||||
|
||||
const getByCustomFieldGroupId = (customFieldGroupId) =>
|
||||
defaultFind({
|
||||
customFieldGroupId,
|
||||
});
|
||||
|
||||
const getOneByCardIdAndCustomFieldGroupIdAndCustomFieldId = (
|
||||
cardId,
|
||||
customFieldGroupId,
|
||||
customFieldId,
|
||||
) =>
|
||||
CustomFieldValue.findOne({
|
||||
cardId,
|
||||
customFieldGroupId,
|
||||
customFieldId,
|
||||
});
|
||||
|
||||
const updateOne = (criteria, values) => CustomFieldValue.updateOne(criteria).set({ ...values });
|
||||
|
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
const delete_ = (criteria) => CustomFieldValue.destroy(criteria).fetch();
|
||||
|
||||
const deleteOne = (criteria) => CustomFieldValue.destroyOne(criteria);
|
||||
|
||||
module.exports = {
|
||||
create,
|
||||
createOrUpdateOne,
|
||||
getByIds,
|
||||
getByCardId,
|
||||
getByCardIds,
|
||||
getByCustomFieldGroupId,
|
||||
getOneByCardIdAndCustomFieldGroupIdAndCustomFieldId,
|
||||
updateOne,
|
||||
deleteOne,
|
||||
delete: delete_,
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
/* Query methods */
|
||||
|
||||
const createOne = (values) => IdentityProviderUser.create({ ...values }).fetch();
|
||||
|
||||
const getOneByIssuerAndSub = (issuer, sub) =>
|
||||
IdentityProviderUser.findOne({
|
||||
issuer,
|
||||
sub,
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
const delete_ = (criteria) => IdentityProviderUser.destroy(criteria).fetch();
|
||||
|
||||
module.exports = {
|
||||
createOne,
|
||||
getOneByIssuerAndSub,
|
||||
delete: delete_,
|
||||
};
|
||||
@@ -0,0 +1,66 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
const { makeRowToModelTransformer } = require('../helpers');
|
||||
|
||||
const transformRowToModel = makeRowToModelTransformer(InternalConfig);
|
||||
|
||||
/* Query methods */
|
||||
|
||||
const getOneMain = () => InternalConfig.findOne(InternalConfig.MAIN_ID);
|
||||
|
||||
const updateOneMain = (values) =>
|
||||
sails.getDatastore().transaction(async (db) => {
|
||||
let queryResult = await sails
|
||||
.sendNativeQuery(
|
||||
'SELECT active_users_limit FROM internal_config WHERE id = $1 LIMIT 1 FOR UPDATE',
|
||||
[InternalConfig.MAIN_ID],
|
||||
)
|
||||
.usingConnection(db);
|
||||
|
||||
const prev = transformRowToModel(queryResult.rows[0]);
|
||||
|
||||
const internalConfig = await InternalConfig.updateOne(InternalConfig.MAIN_ID)
|
||||
.set({ ...values })
|
||||
.usingConnection(db);
|
||||
|
||||
let deactivatedUserIds;
|
||||
if (
|
||||
_.isInteger(internalConfig.activeUsersLimit) &&
|
||||
(prev.activeUsersLimit === null || internalConfig.activeUsersLimit < prev.activeUsersLimit)
|
||||
) {
|
||||
const { defaultAdminEmail } = sails.config.custom;
|
||||
|
||||
const query = `
|
||||
WITH user_to_deactivate AS (
|
||||
SELECT id
|
||||
FROM user_account
|
||||
WHERE is_deactivated = false
|
||||
ORDER BY
|
||||
CASE ${defaultAdminEmail ? 'WHEN email = $1 THEN 0 WHEN role = $2 THEN 1' : 'WHEN role = $1 THEN 0'} ELSE ${defaultAdminEmail ? '2' : '1'} END,
|
||||
id
|
||||
OFFSET $${defaultAdminEmail ? 3 : 2}
|
||||
)
|
||||
UPDATE user_account
|
||||
SET is_deactivated = true
|
||||
WHERE id IN (SELECT id FROM user_to_deactivate)
|
||||
RETURNING id
|
||||
`;
|
||||
|
||||
const queryValues = defaultAdminEmail
|
||||
? [defaultAdminEmail, User.Roles.ADMIN, internalConfig.activeUsersLimit]
|
||||
: [User.Roles.ADMIN, internalConfig.activeUsersLimit];
|
||||
|
||||
queryResult = await sails.sendNativeQuery(query, queryValues).usingConnection(db);
|
||||
deactivatedUserIds = queryResult.rows.map((row) => row.id);
|
||||
}
|
||||
|
||||
return { internalConfig, deactivatedUserIds, prev };
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
getOneMain,
|
||||
updateOneMain,
|
||||
};
|
||||
@@ -0,0 +1,55 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
const defaultFind = (criteria, { sort = 'id' } = {}) => Label.find(criteria).sort(sort);
|
||||
|
||||
/* Query methods */
|
||||
|
||||
const createOne = (values) => Label.create({ ...values }).fetch();
|
||||
|
||||
const getByIds = (ids) => defaultFind(ids);
|
||||
|
||||
const getByBoardId = (boardId, { exceptIdOrIds, sort = ['position', 'id'] } = {}) => {
|
||||
const criteria = {
|
||||
boardId,
|
||||
};
|
||||
|
||||
if (exceptIdOrIds) {
|
||||
criteria.id = {
|
||||
'!=': exceptIdOrIds,
|
||||
};
|
||||
}
|
||||
|
||||
return defaultFind(criteria, { sort });
|
||||
};
|
||||
|
||||
const getOneById = (id, { boardId } = {}) => {
|
||||
const criteria = {
|
||||
id,
|
||||
};
|
||||
|
||||
if (boardId) {
|
||||
criteria.boardId = boardId;
|
||||
}
|
||||
|
||||
return Label.findOne(criteria);
|
||||
};
|
||||
|
||||
const updateOne = (criteria, values) => Label.updateOne(criteria).set({ ...values });
|
||||
|
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
const delete_ = (criteria) => Label.destroy(criteria).fetch();
|
||||
|
||||
const deleteOne = (criteria) => Label.destroyOne(criteria);
|
||||
|
||||
module.exports = {
|
||||
createOne,
|
||||
getByIds,
|
||||
getByBoardId,
|
||||
getOneById,
|
||||
updateOne,
|
||||
deleteOne,
|
||||
delete: delete_,
|
||||
};
|
||||
@@ -0,0 +1,144 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
const { makeRowToModelTransformer, makeWhereQueryBuilder } = require('../helpers');
|
||||
|
||||
const buildWhereQuery = makeWhereQueryBuilder(List);
|
||||
const transformRowToModel = makeRowToModelTransformer(List);
|
||||
|
||||
const defaultFind = (criteria, { sort = 'id' } = {}) => List.find(criteria).sort(sort);
|
||||
|
||||
/* Query methods */
|
||||
|
||||
const createOne = (values) => List.create({ ...values }).fetch();
|
||||
|
||||
const getByIds = (ids) => defaultFind(ids);
|
||||
|
||||
const getByBoardId = (boardId, { exceptIdOrIds, typeOrTypes, sort = ['position', 'id'] } = {}) => {
|
||||
const criteria = {
|
||||
boardId,
|
||||
};
|
||||
|
||||
if (exceptIdOrIds) {
|
||||
criteria.id = {
|
||||
'!=': exceptIdOrIds,
|
||||
};
|
||||
}
|
||||
|
||||
if (typeOrTypes) {
|
||||
criteria.type = typeOrTypes;
|
||||
}
|
||||
|
||||
return defaultFind(criteria, { sort });
|
||||
};
|
||||
|
||||
const getOneById = (id, { boardId } = {}) => {
|
||||
const criteria = {
|
||||
id,
|
||||
};
|
||||
|
||||
if (boardId) {
|
||||
criteria.boardId = boardId;
|
||||
}
|
||||
|
||||
return List.findOne(criteria);
|
||||
};
|
||||
|
||||
const getOneTrashByBoardId = (boardId) =>
|
||||
List.findOne({
|
||||
boardId,
|
||||
type: List.Types.TRASH,
|
||||
});
|
||||
|
||||
const updateOne = async (criteria, values) => {
|
||||
if (values.boardId || values.type) {
|
||||
return sails.getDatastore().transaction(async (db) => {
|
||||
const [whereQuery, whereQueryValues] = buildWhereQuery(criteria);
|
||||
|
||||
const queryResult = await sails
|
||||
.sendNativeQuery(
|
||||
`SELECT board_id, type FROM list WHERE ${whereQuery} LIMIT 1 FOR UPDATE`,
|
||||
whereQueryValues,
|
||||
)
|
||||
.usingConnection(db);
|
||||
|
||||
if (queryResult.rowCount === 0) {
|
||||
return { list: null };
|
||||
}
|
||||
|
||||
const prev = transformRowToModel(queryResult.rows[0]);
|
||||
|
||||
const list = await List.updateOne(criteria)
|
||||
.set({ ...values })
|
||||
.usingConnection(db);
|
||||
|
||||
let tasks = [];
|
||||
if (list) {
|
||||
if (list.boardId !== prev.boardId) {
|
||||
await Card.update({
|
||||
listId: list.id,
|
||||
})
|
||||
.set({
|
||||
boardId: list.boardId,
|
||||
})
|
||||
.usingConnection(db);
|
||||
}
|
||||
|
||||
const prevTypeState = List.TYPE_STATE_BY_TYPE[prev.type];
|
||||
const typeState = List.TYPE_STATE_BY_TYPE[list.type];
|
||||
|
||||
const transitions = {
|
||||
[`${List.TypeStates.CLOSED}->${List.TypeStates.OPENED}`]: false,
|
||||
[`${List.TypeStates.OPENED}->${List.TypeStates.CLOSED}`]: true,
|
||||
};
|
||||
|
||||
const isClosed = transitions[`${prevTypeState}->${typeState}`];
|
||||
|
||||
if (!_.isUndefined(isClosed)) {
|
||||
const cards = await Card.update({
|
||||
listId: list.id,
|
||||
})
|
||||
.set({
|
||||
isClosed,
|
||||
})
|
||||
.fetch()
|
||||
.usingConnection(db);
|
||||
|
||||
if (cards.length > 0) {
|
||||
tasks = await Task.update({
|
||||
linkedCardId: sails.helpers.utils.mapRecords(cards),
|
||||
})
|
||||
.set({
|
||||
isCompleted: isClosed,
|
||||
})
|
||||
.fetch()
|
||||
.usingConnection(db);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { list, tasks };
|
||||
});
|
||||
}
|
||||
|
||||
const list = await List.updateOne(criteria).set({ ...values });
|
||||
return { list };
|
||||
};
|
||||
|
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
const delete_ = (criteria) => List.destroy(criteria).fetch();
|
||||
|
||||
const deleteOne = (criteria) => List.destroyOne(criteria);
|
||||
|
||||
module.exports = {
|
||||
createOne,
|
||||
getByIds,
|
||||
getByBoardId,
|
||||
getOneById,
|
||||
getOneTrashByBoardId,
|
||||
updateOne,
|
||||
deleteOne,
|
||||
delete: delete_,
|
||||
};
|
||||
@@ -0,0 +1,108 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
const LIMIT = 100;
|
||||
|
||||
const defaultFind = (criteria) => Notification.find(criteria).sort('id DESC');
|
||||
|
||||
/* Query methods */
|
||||
|
||||
const create = (arrayOfValues) =>
|
||||
sails.getDatastore().transaction(async (db) => {
|
||||
const notifications = await Notification.createEach(arrayOfValues).fetch().usingConnection(db);
|
||||
const userIds = sails.helpers.utils.mapRecords(notifications, 'userId', true, true);
|
||||
|
||||
if (userIds.length > 0) {
|
||||
const queryValues = [];
|
||||
const inValues = userIds.map((userId) => {
|
||||
queryValues.push(userId);
|
||||
return `$${queryValues.length}`;
|
||||
});
|
||||
|
||||
queryValues.push(LIMIT);
|
||||
|
||||
const query = `
|
||||
WITH exceeded_notification AS (
|
||||
SELECT id, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY id DESC) AS rank
|
||||
FROM notification
|
||||
WHERE user_id IN (${inValues.join(', ')}) AND is_read = FALSE
|
||||
)
|
||||
UPDATE notification
|
||||
SET is_read = TRUE
|
||||
WHERE id IN (SELECT id FROM exceeded_notification WHERE rank > $${queryValues.length})
|
||||
`;
|
||||
|
||||
await sails.sendNativeQuery(query, queryValues).usingConnection(db);
|
||||
}
|
||||
|
||||
return notifications;
|
||||
});
|
||||
|
||||
const createOne = (values) => {
|
||||
if (values.userId) {
|
||||
return sails.getDatastore().transaction(async (db) => {
|
||||
const notification = await Notification.create({ ...values })
|
||||
.fetch()
|
||||
.usingConnection(db);
|
||||
|
||||
const query = `
|
||||
WITH exceeded_notification AS (
|
||||
SELECT id
|
||||
FROM notification
|
||||
WHERE user_id = $1 AND is_read = FALSE
|
||||
ORDER BY id DESC
|
||||
OFFSET $2
|
||||
)
|
||||
UPDATE notification
|
||||
SET is_read = TRUE
|
||||
WHERE id IN (SELECT id FROM exceeded_notification)
|
||||
`;
|
||||
|
||||
await sails.sendNativeQuery(query, [values.userId, LIMIT]).usingConnection(db);
|
||||
|
||||
return notification;
|
||||
});
|
||||
}
|
||||
|
||||
return Notification.create({ ...values }).fetch();
|
||||
};
|
||||
|
||||
const getByIds = (ids) => defaultFind(ids);
|
||||
|
||||
const getUnreadByUserId = (userId) =>
|
||||
defaultFind({
|
||||
userId,
|
||||
isRead: false,
|
||||
});
|
||||
|
||||
const getOneById = (id, { userId } = {}) => {
|
||||
const criteria = {
|
||||
id,
|
||||
};
|
||||
|
||||
if (userId) {
|
||||
criteria.userId = userId;
|
||||
}
|
||||
|
||||
return Notification.findOne(criteria);
|
||||
};
|
||||
|
||||
const update = (criteria, values) => Notification.update(criteria).set(values).fetch();
|
||||
|
||||
const updateOne = (criteria, values) => Notification.updateOne(criteria).set({ ...values });
|
||||
|
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
const delete_ = (criteria) => Notification.destroy(criteria).fetch();
|
||||
|
||||
module.exports = {
|
||||
create,
|
||||
createOne,
|
||||
getByIds,
|
||||
getUnreadByUserId,
|
||||
getOneById,
|
||||
update,
|
||||
updateOne,
|
||||
delete: delete_,
|
||||
};
|
||||
@@ -0,0 +1,51 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
const defaultFind = (criteria) => NotificationService.find(criteria).sort('id');
|
||||
|
||||
/* Query methods */
|
||||
|
||||
const createOne = (values) => NotificationService.create({ ...values }).fetch();
|
||||
|
||||
const getByUserId = (userId) =>
|
||||
defaultFind({
|
||||
userId,
|
||||
});
|
||||
|
||||
const getByUserIds = (userIds) =>
|
||||
defaultFind({
|
||||
userId: userIds,
|
||||
});
|
||||
|
||||
const getByBoardId = (boardId) =>
|
||||
defaultFind({
|
||||
boardId,
|
||||
});
|
||||
|
||||
const getByBoardIds = (boardIds) =>
|
||||
defaultFind({
|
||||
boardId: boardIds,
|
||||
});
|
||||
|
||||
const getOneById = (id) => NotificationService.findOne(id);
|
||||
|
||||
const updateOne = (criteria, values) => NotificationService.updateOne(criteria).set({ ...values });
|
||||
|
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
const delete_ = (criteria) => NotificationService.destroy(criteria).fetch();
|
||||
|
||||
const deleteOne = (criteria) => NotificationService.destroyOne(criteria);
|
||||
|
||||
module.exports = {
|
||||
createOne,
|
||||
getByUserId,
|
||||
getByUserIds,
|
||||
getByBoardId,
|
||||
getByBoardIds,
|
||||
getOneById,
|
||||
updateOne,
|
||||
deleteOne,
|
||||
delete: delete_,
|
||||
};
|
||||
@@ -0,0 +1,67 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
const defaultFind = (criteria) => Project.find(criteria).sort('id');
|
||||
|
||||
/* Query methods */
|
||||
|
||||
const createOne = (values, { user } = {}) =>
|
||||
sails.getDatastore().transaction(async (db) => {
|
||||
let project = await Project.create({ ...values })
|
||||
.fetch()
|
||||
.usingConnection(db);
|
||||
|
||||
const projectManager = await ProjectManager.create({
|
||||
projectId: project.id,
|
||||
userId: user.id,
|
||||
})
|
||||
.fetch()
|
||||
.usingConnection(db);
|
||||
|
||||
if (values.type === Project.Types.PRIVATE) {
|
||||
project = await Project.updateOne(project.id)
|
||||
.set({
|
||||
ownerProjectManagerId: projectManager.id,
|
||||
})
|
||||
.usingConnection(db);
|
||||
}
|
||||
|
||||
return { project, projectManager };
|
||||
});
|
||||
|
||||
const getByIds = (ids) => defaultFind(ids);
|
||||
|
||||
const getShared = ({ exceptIdOrIds } = {}) => {
|
||||
const criteria = {
|
||||
ownerProjectManagerId: null,
|
||||
};
|
||||
|
||||
if (exceptIdOrIds) {
|
||||
criteria.id = {
|
||||
'!=': exceptIdOrIds,
|
||||
};
|
||||
}
|
||||
|
||||
return defaultFind(criteria);
|
||||
};
|
||||
|
||||
const getOneById = (id) => Project.findOne(id);
|
||||
|
||||
const updateOne = (criteria, values) => Project.updateOne(criteria).set({ ...values });
|
||||
|
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
const delete_ = (criteria) => Project.destroy(criteria).fetch();
|
||||
|
||||
const deleteOne = (criteria) => Project.destroyOne(criteria);
|
||||
|
||||
module.exports = {
|
||||
createOne,
|
||||
getByIds,
|
||||
getShared,
|
||||
getOneById,
|
||||
updateOne,
|
||||
deleteOne,
|
||||
delete: delete_,
|
||||
};
|
||||
@@ -0,0 +1,35 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
const defaultFind = (criteria) => ProjectFavorite.find(criteria).sort('id');
|
||||
|
||||
/* Query methods */
|
||||
|
||||
const createOne = (values) => ProjectFavorite.create({ ...values }).fetch();
|
||||
|
||||
const getByProjectIdsAndUserId = (projectIds, userId) =>
|
||||
defaultFind({
|
||||
userId,
|
||||
projectId: projectIds,
|
||||
});
|
||||
|
||||
const getOneByProjectIdAndUserId = (projectId, userId) =>
|
||||
ProjectFavorite.findOne({
|
||||
projectId,
|
||||
userId,
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
const delete_ = (criteria) => ProjectFavorite.destroy(criteria).fetch();
|
||||
|
||||
const deleteOne = (criteria) => ProjectFavorite.destroyOne(criteria);
|
||||
|
||||
module.exports = {
|
||||
createOne,
|
||||
getByProjectIdsAndUserId,
|
||||
getOneByProjectIdAndUserId,
|
||||
deleteOne,
|
||||
delete: delete_,
|
||||
};
|
||||
@@ -0,0 +1,80 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
const defaultFind = (criteria) => ProjectManager.find(criteria).sort('id');
|
||||
|
||||
/* Query methods */
|
||||
|
||||
const createOne = (values) => ProjectManager.create({ ...values }).fetch();
|
||||
|
||||
const getByIds = (ids) => defaultFind(ids);
|
||||
|
||||
const getByProjectId = (projectId, { exceptIdOrIds } = {}) => {
|
||||
const criteria = {
|
||||
projectId,
|
||||
};
|
||||
|
||||
if (exceptIdOrIds) {
|
||||
criteria.id = {
|
||||
'!=': exceptIdOrIds,
|
||||
};
|
||||
}
|
||||
|
||||
return defaultFind(criteria);
|
||||
};
|
||||
|
||||
const getByProjectIds = (projectIds, { exceptUserIdOrIds } = {}) => {
|
||||
const criteria = {
|
||||
projectId: projectIds,
|
||||
};
|
||||
|
||||
if (exceptUserIdOrIds) {
|
||||
criteria.userId = {
|
||||
'!=': exceptUserIdOrIds,
|
||||
};
|
||||
}
|
||||
|
||||
return defaultFind(criteria);
|
||||
};
|
||||
|
||||
const getByUserId = (userId) =>
|
||||
defaultFind({
|
||||
userId,
|
||||
});
|
||||
|
||||
const getOneById = (id, { projectId } = {}) => {
|
||||
const criteria = {
|
||||
id,
|
||||
};
|
||||
|
||||
if (projectId) {
|
||||
criteria.projectId = projectId;
|
||||
}
|
||||
|
||||
return ProjectManager.findOne(criteria);
|
||||
};
|
||||
|
||||
const getOneByProjectIdAndUserId = (projectId, userId) =>
|
||||
ProjectManager.findOne({
|
||||
projectId,
|
||||
userId,
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
const delete_ = (criteria) => ProjectManager.destroy(criteria).fetch();
|
||||
|
||||
const deleteOne = (criteria) => ProjectManager.destroyOne(criteria);
|
||||
|
||||
module.exports = {
|
||||
createOne,
|
||||
getByIds,
|
||||
getByProjectId,
|
||||
getByProjectIds,
|
||||
getByUserId,
|
||||
getOneById,
|
||||
getOneByProjectIdAndUserId,
|
||||
deleteOne,
|
||||
delete: delete_,
|
||||
};
|
||||
@@ -0,0 +1,42 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
/* Query methods */
|
||||
|
||||
const createOne = (values) => Session.create({ ...values }).fetch();
|
||||
|
||||
const getOneUndeletedByAccessToken = (accessToken) =>
|
||||
Session.findOne({
|
||||
accessToken,
|
||||
deletedAt: null,
|
||||
});
|
||||
|
||||
const getOneUndeletedByPendingToken = (pendingToken) =>
|
||||
Session.findOne({
|
||||
pendingToken,
|
||||
deletedAt: null,
|
||||
});
|
||||
|
||||
const updateOne = (criteria, values) => Session.updateOne(criteria).set({ ...values });
|
||||
|
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
const delete_ = (criteria) => Session.destroy(criteria).fetch();
|
||||
|
||||
const deleteOneById = (id) =>
|
||||
Session.updateOne({
|
||||
id,
|
||||
deletedAt: null,
|
||||
}).set({
|
||||
deletedAt: new Date().toISOString(),
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
createOne,
|
||||
getOneUndeletedByAccessToken,
|
||||
getOneUndeletedByPendingToken,
|
||||
updateOne,
|
||||
deleteOneById,
|
||||
delete: delete_,
|
||||
};
|
||||
@@ -0,0 +1,12 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
/* Query methods */
|
||||
|
||||
const getOneMain = () => StorageUsage.findOne(StorageUsage.MAIN_ID);
|
||||
|
||||
module.exports = {
|
||||
getOneMain,
|
||||
};
|
||||
@@ -0,0 +1,71 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
const defaultFind = (criteria, { sort = 'id', limit } = {}) =>
|
||||
Task.find(criteria).sort(sort).limit(limit);
|
||||
|
||||
/* Query methods */
|
||||
|
||||
const create = (arrayOfValues) => Task.createEach(arrayOfValues).fetch();
|
||||
|
||||
const createOne = (values) => Task.create({ ...values }).fetch();
|
||||
|
||||
const getByIds = (ids) => defaultFind(ids);
|
||||
|
||||
const getByTaskListId = async (taskListId, { exceptIdOrIds, sort = ['position', 'id'] } = {}) => {
|
||||
const criteria = {
|
||||
taskListId,
|
||||
};
|
||||
|
||||
if (exceptIdOrIds) {
|
||||
criteria.id = {
|
||||
'!=': exceptIdOrIds,
|
||||
};
|
||||
}
|
||||
|
||||
return defaultFind(criteria, { sort });
|
||||
};
|
||||
|
||||
const getByTaskListIds = async (taskListIds, { sort = ['position', 'id'] } = {}) =>
|
||||
defaultFind(
|
||||
{
|
||||
taskListId: taskListIds,
|
||||
},
|
||||
{ sort },
|
||||
);
|
||||
|
||||
const getOneById = (id, { taskListId } = {}) => {
|
||||
const criteria = {
|
||||
id,
|
||||
};
|
||||
|
||||
if (taskListId) {
|
||||
criteria.taskListId = taskListId;
|
||||
}
|
||||
|
||||
return Task.findOne(criteria);
|
||||
};
|
||||
|
||||
const update = (criteria, values) => Task.update(criteria).set(values).fetch();
|
||||
|
||||
const updateOne = (criteria, values) => Task.updateOne(criteria).set({ ...values });
|
||||
|
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
const delete_ = (criteria) => Task.destroy(criteria).fetch();
|
||||
|
||||
const deleteOne = (criteria) => Task.destroyOne(criteria);
|
||||
|
||||
module.exports = {
|
||||
create,
|
||||
createOne,
|
||||
getByIds,
|
||||
getByTaskListId,
|
||||
getByTaskListIds,
|
||||
getOneById,
|
||||
update,
|
||||
updateOne,
|
||||
deleteOne,
|
||||
delete: delete_,
|
||||
};
|
||||
@@ -0,0 +1,67 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
const defaultFind = (criteria, { sort = 'id' } = {}) => TaskList.find(criteria).sort(sort);
|
||||
|
||||
/* Query methods */
|
||||
|
||||
const create = (arrayOfValues) => TaskList.createEach(arrayOfValues).fetch();
|
||||
|
||||
const createOne = (values) => TaskList.create({ ...values }).fetch();
|
||||
|
||||
const getByIds = (ids) => defaultFind(ids);
|
||||
|
||||
const getByCardId = (cardId, { exceptIdOrIds, sort = ['position', 'id'] } = {}) => {
|
||||
const criteria = {
|
||||
cardId,
|
||||
};
|
||||
|
||||
if (exceptIdOrIds) {
|
||||
criteria.id = {
|
||||
'!=': exceptIdOrIds,
|
||||
};
|
||||
}
|
||||
|
||||
return defaultFind(criteria, { sort });
|
||||
};
|
||||
|
||||
const getByCardIds = (cardIds, { sort = ['position', 'id'] } = {}) =>
|
||||
defaultFind(
|
||||
{
|
||||
cardId: cardIds,
|
||||
},
|
||||
{ sort },
|
||||
);
|
||||
|
||||
const getOneById = (id, { cardId } = {}) => {
|
||||
const criteria = {
|
||||
id,
|
||||
};
|
||||
|
||||
if (cardId) {
|
||||
criteria.cardId = cardId;
|
||||
}
|
||||
|
||||
return TaskList.findOne(criteria);
|
||||
};
|
||||
|
||||
const updateOne = (criteria, values) => TaskList.updateOne(criteria).set({ ...values });
|
||||
|
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
const delete_ = (criteria) => TaskList.destroy(criteria).fetch();
|
||||
|
||||
const deleteOne = (criteria) => TaskList.destroyOne(criteria);
|
||||
|
||||
module.exports = {
|
||||
create,
|
||||
createOne,
|
||||
getByIds,
|
||||
getByCardId,
|
||||
getByCardIds,
|
||||
getOneById,
|
||||
updateOne,
|
||||
deleteOne,
|
||||
delete: delete_,
|
||||
};
|
||||
@@ -0,0 +1,56 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
const { makeRowToModelTransformer } = require('../helpers');
|
||||
|
||||
const COLUMN_NAME_BY_TYPE = {
|
||||
[UploadedFile.Types.USER_AVATAR]: 'user_avatars',
|
||||
[UploadedFile.Types.BACKGROUND_IMAGE]: 'background_images',
|
||||
[UploadedFile.Types.ATTACHMENT]: 'attachments',
|
||||
};
|
||||
|
||||
const transformRowToModel = makeRowToModelTransformer(UploadedFile);
|
||||
|
||||
/* Query methods */
|
||||
|
||||
const createOne = (values) =>
|
||||
sails.getDatastore().transaction(async (db) => {
|
||||
const uploadedFile = await UploadedFile.create({ ...values })
|
||||
.fetch()
|
||||
.usingConnection(db);
|
||||
|
||||
const columnName = COLUMN_NAME_BY_TYPE[uploadedFile.type];
|
||||
|
||||
await sails
|
||||
.sendNativeQuery(
|
||||
`UPDATE storage_usage SET total = total + $1, ${columnName} = ${columnName} + $1, updated_at = $2 WHERE id = $3`,
|
||||
[uploadedFile.size, new Date().toISOString(), StorageUsage.MAIN_ID],
|
||||
)
|
||||
.usingConnection(db);
|
||||
|
||||
return uploadedFile;
|
||||
});
|
||||
|
||||
const deleteOne = (criteria) =>
|
||||
sails.getDatastore().transaction(async (db) => {
|
||||
const uploadedFile = await UploadedFile.destroyOne(criteria).usingConnection(db);
|
||||
const columnName = COLUMN_NAME_BY_TYPE[uploadedFile.type];
|
||||
|
||||
await sails
|
||||
.sendNativeQuery(
|
||||
`UPDATE storage_usage SET total = total - $1, ${columnName} = ${columnName} - $1, updated_at = $2 WHERE id = $3`,
|
||||
[uploadedFile.size, new Date().toISOString(), StorageUsage.MAIN_ID],
|
||||
)
|
||||
.usingConnection(db);
|
||||
|
||||
return uploadedFile;
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
createOne,
|
||||
deleteOne,
|
||||
|
||||
transformRowToModel,
|
||||
};
|
||||
@@ -0,0 +1,205 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
const { makeRowToModelTransformer, makeWhereQueryBuilder } = require('../helpers');
|
||||
|
||||
const hasAvatarChanged = (avatar, prevAvatar) => {
|
||||
if (!avatar && !prevAvatar) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!avatar || !prevAvatar) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return avatar.uploadedFileId !== prevAvatar.uploadedFileId;
|
||||
};
|
||||
|
||||
const buildWhereQuery = makeWhereQueryBuilder(User);
|
||||
const transformRowToModel = makeRowToModelTransformer(User);
|
||||
|
||||
const defaultFind = (criteria) => User.find(criteria).sort('id');
|
||||
|
||||
/* Query methods */
|
||||
|
||||
const createOne = async (values) => {
|
||||
const { activeUsersLimit } = await InternalConfig.qm.getOneMain();
|
||||
|
||||
if (activeUsersLimit !== null) {
|
||||
return sails.getDatastore().transaction(async (db) => {
|
||||
const queryResult = await sails
|
||||
.sendNativeQuery('SELECT NULL FROM user_account WHERE is_deactivated = $1 FOR UPDATE', [
|
||||
false,
|
||||
])
|
||||
.usingConnection(db);
|
||||
|
||||
if (queryResult.rowCount >= activeUsersLimit) {
|
||||
throw 'activeLimitReached';
|
||||
}
|
||||
|
||||
return User.create({ ...values })
|
||||
.fetch()
|
||||
.usingConnection(db);
|
||||
});
|
||||
}
|
||||
|
||||
return User.create({ ...values }).fetch();
|
||||
};
|
||||
|
||||
const getByIds = (ids, { withDeactivated = true } = {}) => {
|
||||
const criteria = {
|
||||
id: ids,
|
||||
};
|
||||
|
||||
if (!withDeactivated) {
|
||||
criteria.isDeactivated = false;
|
||||
}
|
||||
|
||||
return defaultFind(criteria);
|
||||
};
|
||||
|
||||
const getAll = ({ roleOrRoles, isDeactivated } = {}) =>
|
||||
defaultFind({
|
||||
isDeactivated,
|
||||
role: roleOrRoles,
|
||||
});
|
||||
|
||||
const getOneById = (id, { withDeactivated = true } = {}) => {
|
||||
const criteria = {
|
||||
id,
|
||||
};
|
||||
|
||||
if (!withDeactivated) {
|
||||
criteria.isDeactivated = false;
|
||||
}
|
||||
|
||||
return User.findOne(criteria);
|
||||
};
|
||||
|
||||
const getOneByEmail = (email) =>
|
||||
User.findOne({
|
||||
email: email.toLowerCase(),
|
||||
});
|
||||
|
||||
const getOneActiveByEmailOrUsername = (emailOrUsername) => {
|
||||
const fieldName = emailOrUsername.includes('@') ? 'email' : 'username';
|
||||
|
||||
return User.findOne({
|
||||
[fieldName]: emailOrUsername.toLowerCase(),
|
||||
isDeactivated: false,
|
||||
});
|
||||
};
|
||||
|
||||
const getOneActiveByApiKeyHash = (apiKeyHash) =>
|
||||
User.findOne({
|
||||
apiKeyHash,
|
||||
isDeactivated: false,
|
||||
});
|
||||
|
||||
const updateOne = async (criteria, values) => {
|
||||
const { activeUsersLimit } = await InternalConfig.qm.getOneMain();
|
||||
const enforceActiveLimit = values.isDeactivated === false && activeUsersLimit !== null;
|
||||
|
||||
if (!_.isUndefined(values.avatar) || enforceActiveLimit) {
|
||||
return sails.getDatastore().transaction(async (db) => {
|
||||
if (enforceActiveLimit) {
|
||||
const queryResult = await sails
|
||||
.sendNativeQuery('SELECT NULL FROM user_account WHERE is_deactivated = $1 FOR UPDATE', [
|
||||
false,
|
||||
])
|
||||
.usingConnection(db);
|
||||
|
||||
if (queryResult.rowCount >= activeUsersLimit) {
|
||||
throw 'activeLimitReached';
|
||||
}
|
||||
}
|
||||
|
||||
let prev;
|
||||
if (!_.isUndefined(values.avatar)) {
|
||||
const [whereQuery, whereQueryValues] = buildWhereQuery(criteria);
|
||||
|
||||
const queryResult = await sails
|
||||
.sendNativeQuery(
|
||||
`SELECT avatar FROM user_account WHERE ${whereQuery} LIMIT 1 FOR UPDATE`,
|
||||
whereQueryValues,
|
||||
)
|
||||
.usingConnection(db);
|
||||
|
||||
if (queryResult.rowCount === 0) {
|
||||
return { user: null };
|
||||
}
|
||||
|
||||
prev = transformRowToModel(queryResult.rows[0]);
|
||||
}
|
||||
|
||||
const user = await User.updateOne(criteria)
|
||||
.set({ ...values })
|
||||
.usingConnection(db);
|
||||
|
||||
let uploadedFile;
|
||||
if (!_.isUndefined(values.avatar) && hasAvatarChanged(user.avatar, prev.avatar)) {
|
||||
if (prev.avatar) {
|
||||
const queryResult = await sails
|
||||
.sendNativeQuery(
|
||||
'UPDATE uploaded_file SET references_total = CASE WHEN references_total > 1 THEN references_total - 1 END, updated_at = $1 WHERE id = $2 RETURNING *',
|
||||
[new Date().toISOString(), prev.avatar.uploadedFileId],
|
||||
)
|
||||
.usingConnection(db);
|
||||
|
||||
uploadedFile = UploadedFile.qm.transformRowToModel(queryResult.rows[0]);
|
||||
}
|
||||
|
||||
if (user.avatar) {
|
||||
const queryResult = await sails
|
||||
.sendNativeQuery(
|
||||
'UPDATE uploaded_file SET references_total = references_total + 1, updated_at = $1 WHERE id = $2 AND references_total IS NOT NULL',
|
||||
[new Date().toISOString(), user.avatar.uploadedFileId],
|
||||
)
|
||||
.usingConnection(db);
|
||||
|
||||
if (queryResult.rowCount === 0) {
|
||||
throw 'uploadedFileNotFound';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { user, uploadedFile };
|
||||
});
|
||||
}
|
||||
|
||||
const user = await User.updateOne(criteria).set({ ...values });
|
||||
return { user };
|
||||
};
|
||||
|
||||
const deleteOne = (criteria) =>
|
||||
sails.getDatastore().transaction(async (db) => {
|
||||
const user = await User.destroyOne(criteria).usingConnection(db);
|
||||
|
||||
let uploadedFile;
|
||||
if (user.avatar) {
|
||||
const queryResult = await sails
|
||||
.sendNativeQuery(
|
||||
'UPDATE uploaded_file SET references_total = CASE WHEN references_total > 1 THEN references_total - 1 END, updated_at = $1 WHERE id = $2 RETURNING *',
|
||||
[new Date().toISOString(), user.avatar.uploadedFileId],
|
||||
)
|
||||
.usingConnection(db);
|
||||
|
||||
uploadedFile = UploadedFile.qm.transformRowToModel(queryResult.rows[0]);
|
||||
}
|
||||
|
||||
return { user, uploadedFile };
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
createOne,
|
||||
getByIds,
|
||||
getAll,
|
||||
getOneById,
|
||||
getOneByEmail,
|
||||
getOneActiveByEmailOrUsername,
|
||||
getOneActiveByApiKeyHash,
|
||||
updateOne,
|
||||
deleteOne,
|
||||
};
|
||||
@@ -0,0 +1,24 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
/* Query methods */
|
||||
|
||||
const createOne = (values) => Webhook.create({ ...values }).fetch();
|
||||
|
||||
const getAll = () => Webhook.find().sort('id');
|
||||
|
||||
const getOneById = (id) => Webhook.findOne(id);
|
||||
|
||||
const updateOne = (criteria, values) => Webhook.updateOne(criteria).set({ ...values });
|
||||
|
||||
const deleteOne = (criteria) => Webhook.destroyOne(criteria);
|
||||
|
||||
module.exports = {
|
||||
createOne,
|
||||
getAll,
|
||||
getOneById,
|
||||
updateOne,
|
||||
deleteOne,
|
||||
};
|
||||
Reference in New Issue
Block a user