import Planka upstream

This commit is contained in:
xiaojibeier
2026-07-18 11:48:22 +08:00
commit c26fd3b190
1564 changed files with 214813 additions and 0 deletions
@@ -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,
};