tududi/backend/services/permissionsCalculators.js
antanst b8611d9338 chore(lint): remove unnecessary try/catch and tighten error handling
- Projects: remove superfluous try/catch around toast; keep explicit error path
- AdminUsers/Sidebar/ShareService: keep minimal catch blocks only to ignore non-JSON parse failures, without swallowing errors
- Lint/format pass remains green
2025-09-22 15:20:46 +03:00

203 lines
6 KiB
JavaScript

const { Project, Task, Note } = require('../models');
function emptyChanges() {
return { upserts: [], deletes: [] };
}
function pushUpsert(changes, u) {
changes.upserts.push(u);
}
function pushDelete(changes, d) {
changes.deletes.push(d);
}
async function collectProjectDescendants(projectId) {
// tasks (all levels) and notes
const rootTasks = await Task.findAll({
where: { project_id: projectId },
attributes: ['id', 'uid', 'parent_task_id'],
raw: true,
});
const notes = await Note.findAll({
where: { project_id: projectId },
attributes: ['uid'],
raw: true,
});
const taskUids = new Set();
const queue = [...rootTasks];
for (const t of rootTasks) taskUids.add(t.uid);
while (queue.length) {
const node = queue.shift();
const children = await Task.findAll({
where: { parent_task_id: node.id },
attributes: ['id', 'uid'],
raw: true,
});
for (const c of children) {
if (!taskUids.has(c.uid)) {
taskUids.add(c.uid);
queue.push({ id: c.id });
}
}
}
return {
taskUids: Array.from(taskUids),
noteUids: notes.map((n) => n.uid),
};
}
async function calculateProjectPerms(ctx, action) {
const changes = emptyChanges();
// find project id
const project = await Project.findOne({
where: { uid: action.resourceUid },
attributes: ['id', 'user_id'],
transaction: ctx.tx,
});
if (!project) return changes;
const { taskUids, noteUids } = await collectProjectDescendants(project.id);
if (action.verb === 'share_grant') {
const direct = {
userId: action.targetUserId,
resourceType: 'project',
resourceUid: action.resourceUid,
accessLevel: action.accessLevel,
propagation: 'direct',
grantedByUserId: action.actorUserId,
};
pushUpsert(changes, direct);
for (const tuid of taskUids)
pushUpsert(changes, {
userId: action.targetUserId,
resourceType: 'task',
resourceUid: tuid,
accessLevel: action.accessLevel,
propagation: 'inherited',
grantedByUserId: action.actorUserId,
});
for (const nuid of noteUids)
pushUpsert(changes, {
userId: action.targetUserId,
resourceType: 'note',
resourceUid: nuid,
accessLevel: action.accessLevel,
propagation: 'inherited',
grantedByUserId: action.actorUserId,
});
} else if (action.verb === 'share_revoke') {
pushDelete(changes, {
userId: action.targetUserId,
resourceType: 'project',
resourceUid: action.resourceUid,
});
for (const tuid of taskUids)
pushDelete(changes, {
userId: action.targetUserId,
resourceType: 'task',
resourceUid: tuid,
});
for (const nuid of noteUids)
pushDelete(changes, {
userId: action.targetUserId,
resourceType: 'note',
resourceUid: nuid,
});
}
return changes;
}
async function calculateTaskPerms(ctx, action) {
// Handle single task subtree (task + subtasks)
const changes = emptyChanges();
const task = await Task.findOne({
where: { uid: action.resourceUid },
attributes: ['id'],
transaction: ctx.tx,
});
if (!task) return changes;
const taskUids = new Set([action.resourceUid]);
const queue = [{ id: task.id }];
while (queue.length) {
const node = queue.shift();
const children = await Task.findAll({
where: { parent_task_id: node.id },
attributes: ['id', 'uid'],
transaction: ctx.tx,
raw: true,
});
for (const c of children) {
if (!taskUids.has(c.uid)) {
taskUids.add(c.uid);
queue.push({ id: c.id });
}
}
}
if (action.verb === 'share_grant') {
for (const tuid of taskUids)
pushUpsert(changes, {
userId: action.targetUserId,
resourceType: 'task',
resourceUid: tuid,
accessLevel: action.accessLevel,
propagation:
tuid === action.resourceUid ? 'direct' : 'inherited',
grantedByUserId: action.actorUserId,
});
} else if (action.verb === 'share_revoke') {
for (const tuid of taskUids)
pushDelete(changes, {
userId: action.targetUserId,
resourceType: 'task',
resourceUid: tuid,
});
}
return changes;
}
async function calculateNotePerms(ctx, action) {
const changes = emptyChanges();
if (action.verb === 'share_grant') {
pushUpsert(changes, {
userId: action.targetUserId,
resourceType: 'note',
resourceUid: action.resourceUid,
accessLevel: action.accessLevel,
propagation: 'direct',
grantedByUserId: action.actorUserId,
});
} else if (action.verb === 'share_revoke') {
pushDelete(changes, {
userId: action.targetUserId,
resourceType: 'note',
resourceUid: action.resourceUid,
});
}
return changes;
}
async function calculateAreaPerms(ctx, action) {
const changes = emptyChanges();
// TODO: implement area→projects→tasks/notes cascade later
return changes;
}
async function calculateTagPerms(ctx, action) {
const changes = emptyChanges();
// No-op for now (tags excluded from project cascade)
return changes;
}
module.exports = {
calculateProjectPerms,
calculateTaskPerms,
calculateNotePerms,
calculateAreaPerms,
calculateTagPerms,
};