Adds initial models, migrations, and services to support role-based access and sharing; wires routes to prepare for permission-driven features.
101 lines
4.7 KiB
JavaScript
101 lines
4.7 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 };
|