1
0
Fork 0
mirror of https://github.com/plankanban/planka.git synced 2025-07-18 20:59:44 +02:00

feat: Add board activity log

This commit is contained in:
Maksim Eltyshev 2025-05-22 23:14:46 +02:00
parent 777ff467f3
commit 86cfd155f2
72 changed files with 833 additions and 169 deletions

View file

@ -0,0 +1,68 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
const { idInput } = require('../../../utils/inputs');
const Errors = {
BOARD_NOT_FOUND: {
boardNotFound: 'Board not found',
},
};
module.exports = {
inputs: {
boardId: {
...idInput,
required: true,
},
beforeId: idInput,
},
exits: {
boardNotFound: {
responseType: 'notFound',
},
},
async fn(inputs) {
const { currentUser } = this.req;
const { board, project } = await sails.helpers.boards
.getPathToProjectById(inputs.boardId)
.intercept('pathNotFound', () => Errors.BOARD_NOT_FOUND);
const boardMembership = await BoardMembership.qm.getOneByBoardIdAndUserId(
board.id,
currentUser.id,
);
if (!boardMembership) {
if (currentUser.role !== User.Roles.ADMIN || project.ownerProjectManagerId) {
const isProjectManager = await sails.helpers.users.isProjectManager(
currentUser.id,
project.id,
);
if (!isProjectManager) {
throw Errors.BOARD_NOT_FOUND; // Forbidden
}
}
}
const actions = await Action.qm.getByBoardId(board.id, {
beforeId: inputs.beforeId,
});
const userIds = sails.helpers.utils.mapRecords(actions, 'userId', true, true);
const users = await User.qm.getByIds(userIds);
return {
items: actions,
included: {
users: sails.helpers.users.presentMany(users, currentUser),
},
};
},
};

View file

@ -115,6 +115,7 @@ module.exports = {
const action = await Action.qm.createOne({
...values,
boardId: values.card.boardId,
cardId: values.card.id,
userId: values.user.id,
});
@ -149,10 +150,7 @@ module.exports = {
values: {
action,
type: action.type,
data: {
...action.data,
card: _.pick(values.card, ['name']),
},
data: action.data,
userId: action.data.user.id,
creatorUser: values.user,
card: values.card,
@ -182,10 +180,7 @@ module.exports = {
userId,
action,
type: action.type,
data: {
...action.data,
card: _.pick(values.card, ['name']),
},
data: action.data,
creatorUser: values.user,
card: values.card,
},

View file

@ -35,6 +35,15 @@ module.exports = {
await sails.helpers.lists.deleteRelated(lists);
await Action.qm.update(
{
boardId: boardIdOrIds,
},
{
boardId: null,
},
);
await NotificationService.qm.delete({
boardId: boardIdOrIds,
});

View file

@ -110,6 +110,7 @@ module.exports = {
type: Action.Types.ADD_MEMBER_TO_CARD,
data: {
user: _.pick(values.user, ['id', 'name']),
card: _.pick(values.card, ['name']),
},
user: inputs.actorUser,
card: values.card,

View file

@ -86,6 +86,7 @@ module.exports = {
type: Action.Types.REMOVE_MEMBER_FROM_CARD,
data: {
user: _.pick(inputs.user, ['id', 'name']),
card: _.pick(inputs.card, ['name']),
},
user: inputs.actorUser,
card: inputs.card,

View file

@ -124,6 +124,7 @@ module.exports = {
card,
type: Action.Types.CREATE_CARD,
data: {
card: _.pick(card, ['name']),
list: _.pick(values.list, ['id', 'type', 'name']),
},
user: values.creatorUser,

View file

@ -276,6 +276,7 @@ module.exports = {
card,
type: Action.Types.CREATE_CARD, // TODO: introduce separate type?
data: {
card: _.pick(card, ['name']),
list: _.pick(inputs.list, ['id', 'type', 'name']),
},
user: values.creatorUser,

View file

@ -463,6 +463,7 @@ module.exports = {
card,
type: Action.Types.MOVE_CARD,
data: {
card: _.pick(card, ['name']),
fromList: _.pick(inputs.list, ['id', 'type', 'name']),
toList: _.pick(values.list, ['id', 'type', 'name']),
},

View file

@ -138,6 +138,7 @@ module.exports = {
values: {
type: task.isCompleted ? Action.Types.COMPLETE_TASK : Action.Types.UNCOMPLETE_TASK,
data: {
card: _.pick(inputs.card, ['name']),
task: _.pick(task, ['id', 'name']),
},
user: inputs.actorUser,

View file

@ -11,6 +11,20 @@ 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,
@ -33,6 +47,7 @@ const delete_ = (criteria) => Action.destroy(criteria).fetch();
module.exports = {
create,
createOne,
getByBoardId,
getByCardId,
update,
delete: delete_,

View file

@ -52,6 +52,10 @@ module.exports = {
// ╠═╣╚═╗╚═╗║ ║║ ║╠═╣ ║ ║║ ║║║║╚═╗
// ╩ ╩╚═╝╚═╝╚═╝╚═╝╩╩ ╩ ╩ ╩╚═╝╝╚╝╚═╝
boardId: {
model: 'Board',
columnName: 'board_id',
},
cardId: {
model: 'Card',
required: true,

View file

@ -163,7 +163,8 @@ module.exports.routes = {
'PATCH /api/comments/:id': 'comments/update',
'DELETE /api/comments/:id': 'comments/delete',
'GET /api/cards/:cardId/actions': 'actions/index',
'GET /api/boards/:boardId/actions': 'actions/index-in-board',
'GET /api/cards/:cardId/actions': 'actions/index-in-card',
'GET /api/notifications': 'notifications/index',
'GET /api/notifications/:id': 'notifications/show',

View file

@ -0,0 +1,30 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
exports.up = async (knex) => {
await knex.schema.alterTable('action', (table) => {
/* Columns */
table.bigInteger('board_id');
/* Indexes */
table.index('board_id');
});
return knex.raw(`
UPDATE action
SET
board_id = card.board_id,
data = data || jsonb_build_object('card', jsonb_build_object('name', card.name))
FROM card
WHERE action.card_id = card.id;
`);
};
exports.down = (knex) =>
knex.schema.table('action', (table) => {
table.dropColumn('board_id');
});