- 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
203 lines
6 KiB
JavaScript
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,
|
|
};
|