FIX - МЕЖПРОЕКТНАЯ КОММУНИКАЦИЯ: АДМИНКА РОЛЕЙ TASKER
This commit is contained in:
@@ -14,6 +14,7 @@ const collectionKeys = [
|
||||
"invites",
|
||||
"syncStatuses",
|
||||
"auditEvents",
|
||||
"taskManagerMemberships",
|
||||
];
|
||||
|
||||
const clientTypes = new Set(["company", "person"]);
|
||||
@@ -167,13 +168,47 @@ export function createControlPlaneStore({ projectRoot }) {
|
||||
const membership = typeof taskManager.membership === "object" && taskManager.membership !== null ? taskManager.membership : {};
|
||||
const workspace = typeof membership.workspace === "object" && membership.workspace !== null ? membership.workspace : {};
|
||||
|
||||
upsertTaskManagerMembership(data, {
|
||||
clientId: client.id,
|
||||
userId: user.id,
|
||||
workspaceSlug: workspace.slug ?? payload?.workspaceSlug,
|
||||
workspaceName: workspace.name ?? payload?.workspaceName ?? null,
|
||||
role: normalizeTaskManagerMembershipRole(payload?.role),
|
||||
planeUserId: membership.member?.id ?? null,
|
||||
planeRole: typeof membership.role === "number" ? membership.role : null,
|
||||
});
|
||||
|
||||
addAuditEvent(data, actor, {
|
||||
action: "Назначен Tasker workspace",
|
||||
objectType: "task-manager-membership",
|
||||
objectName: user.name,
|
||||
clientId: client.id,
|
||||
result: "success",
|
||||
details: `Workspace: ${workspace.name ?? workspace.slug ?? payload?.workspaceSlug}; Role: ${membership.role ?? payload?.role ?? "member"}`,
|
||||
details: `Workspace: ${workspace.name ?? workspace.slug ?? payload?.workspaceSlug}; Role: ${payload?.role ?? "member"}`,
|
||||
});
|
||||
|
||||
await writeData(data);
|
||||
return { data };
|
||||
}
|
||||
|
||||
async function removeTaskManagerWorkspaceMembership(payload, identity) {
|
||||
const data = readData();
|
||||
const actor = resolveActor(data, identity);
|
||||
const client = findById(data.clients, payload?.clientId, "client");
|
||||
const user = findById(data.users, payload?.userId, "user");
|
||||
const workspaceSlug = requireString(payload?.workspaceSlug ?? client.integrations?.taskManager?.workspaceSlug, "workspaceSlug");
|
||||
|
||||
data.taskManagerMemberships = data.taskManagerMemberships.filter(
|
||||
(membership) => !(membership.clientId === client.id && membership.userId === user.id && membership.workspaceSlug === workspaceSlug)
|
||||
);
|
||||
|
||||
addAuditEvent(data, actor, {
|
||||
action: "Снят Tasker workspace",
|
||||
objectType: "task-manager-membership",
|
||||
objectName: user.name,
|
||||
clientId: client.id,
|
||||
result: "success",
|
||||
details: `Workspace: ${workspaceSlug}`,
|
||||
});
|
||||
|
||||
await writeData(data);
|
||||
@@ -1055,6 +1090,7 @@ export function createControlPlaneStore({ projectRoot }) {
|
||||
retrySync,
|
||||
markUserAuthentikProvisioned,
|
||||
recordTaskManagerWorkspaceMembership,
|
||||
removeTaskManagerWorkspaceMembership,
|
||||
setUserServiceAccess,
|
||||
updateClient,
|
||||
updateGroup,
|
||||
@@ -1120,6 +1156,36 @@ function normalizeClientIntegrations(payload, fallback = {}) {
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeTaskManagerMembershipRole(value) {
|
||||
return value === "guest" || value === "admin" || value === "member" ? value : "member";
|
||||
}
|
||||
|
||||
function upsertTaskManagerMembership(data, payload) {
|
||||
const workspaceSlug = requireString(payload.workspaceSlug, "workspaceSlug");
|
||||
const existingMembership = data.taskManagerMemberships.find(
|
||||
(membership) => membership.clientId === payload.clientId && membership.userId === payload.userId && membership.workspaceSlug === workspaceSlug
|
||||
);
|
||||
const nextMembership = {
|
||||
id: existingMembership?.id ?? uniqueId(data.taskManagerMemberships, "tasker_mem", `${payload.clientId}-${payload.userId}-${workspaceSlug}`),
|
||||
clientId: payload.clientId,
|
||||
userId: payload.userId,
|
||||
workspaceSlug,
|
||||
workspaceName: nullableStringWithFallback(payload.workspaceName, existingMembership?.workspaceName ?? null),
|
||||
role: normalizeTaskManagerMembershipRole(payload.role),
|
||||
planeUserId: nullableStringWithFallback(payload.planeUserId, existingMembership?.planeUserId ?? null),
|
||||
planeRole: typeof payload.planeRole === "number" ? payload.planeRole : (existingMembership?.planeRole ?? null),
|
||||
updatedAt: isoNow(),
|
||||
};
|
||||
|
||||
if (existingMembership) {
|
||||
Object.assign(existingMembership, nextMembership);
|
||||
return existingMembership;
|
||||
}
|
||||
|
||||
data.taskManagerMemberships.push(nextMembership);
|
||||
return nextMembership;
|
||||
}
|
||||
|
||||
function resolveActor(data, identity) {
|
||||
const user = data.users.find(
|
||||
(item) =>
|
||||
|
||||
+83
-2
@@ -301,7 +301,7 @@ app.post("/api/internal/handoff/consume", (req, res) => {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
avatarUrl: user.avatarUrl ?? null,
|
||||
avatarUrl: resolveUserAvatarPublicUrl(user),
|
||||
subject: user.authentikUserId || handoff.user.sub,
|
||||
authentikUserId: user.authentikUserId ?? null,
|
||||
groups,
|
||||
@@ -557,6 +557,7 @@ app.post("/api/admin/task-manager/workspace-memberships/ensure", requireLauncher
|
||||
workspaceSlug,
|
||||
email: user.email,
|
||||
subject: user.authentikUserId ?? undefined,
|
||||
avatarUrl: resolveUserAvatarPublicUrl(user),
|
||||
role,
|
||||
companyRole: membership?.role ?? null,
|
||||
setLastWorkspace: req.body?.setLastWorkspace !== false,
|
||||
@@ -578,6 +579,81 @@ app.post("/api/admin/task-manager/workspace-memberships/ensure", requireLauncher
|
||||
res.json({ ...result, taskManager });
|
||||
}));
|
||||
|
||||
app.post("/api/admin/task-manager/workspace-memberships/remove", requireLauncherAdmin, asyncRoute(async (req, res) => {
|
||||
const snapshot = controlPlaneStore.getSnapshot(req.nodedcSession.user);
|
||||
const clientId = typeof req.body?.clientId === "string" ? req.body.clientId : "";
|
||||
const userId = typeof req.body?.userId === "string" ? req.body.userId : "";
|
||||
const client = snapshot.data.clients.find((candidate) => candidate.id === clientId);
|
||||
const user = snapshot.data.users.find((candidate) => candidate.id === userId);
|
||||
|
||||
if (!client) {
|
||||
res.status(404).json({ ok: false, error: "client_not_found" });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
res.status(404).json({ ok: false, error: "user_not_found" });
|
||||
return;
|
||||
}
|
||||
|
||||
const membership = snapshot.data.memberships.find((candidate) => candidate.clientId === client.id && candidate.userId === user.id);
|
||||
const workspaceSlug = client.integrations?.taskManager?.workspaceSlug ?? null;
|
||||
|
||||
if (!workspaceSlug) {
|
||||
res.status(400).json({ ok: false, error: "task_manager_workspace_not_configured" });
|
||||
return;
|
||||
}
|
||||
|
||||
if (membership?.role === "client_owner") {
|
||||
const taskManager = await requestTaskManagerInternalJson("/api/internal/nodedc/workspace-memberships/ensure/", {
|
||||
method: "POST",
|
||||
body: {
|
||||
workspaceSlug,
|
||||
email: user.email,
|
||||
subject: user.authentikUserId ?? undefined,
|
||||
avatarUrl: resolveUserAvatarPublicUrl(user),
|
||||
role: "admin",
|
||||
companyRole: membership.role,
|
||||
setLastWorkspace: false,
|
||||
},
|
||||
});
|
||||
const result = await controlPlaneStore.recordTaskManagerWorkspaceMembership(
|
||||
{
|
||||
clientId: client.id,
|
||||
userId: user.id,
|
||||
workspaceSlug,
|
||||
role: "admin",
|
||||
taskManager,
|
||||
},
|
||||
req.nodedcSession.user
|
||||
);
|
||||
|
||||
publishControlPlaneEvent("admin.task-manager.workspace-membership.updated", [user.id]);
|
||||
res.json({ ...result, taskManager, protected: true });
|
||||
return;
|
||||
}
|
||||
|
||||
const taskManager = await requestTaskManagerInternalJson("/api/internal/nodedc/workspace-memberships/remove/", {
|
||||
method: "POST",
|
||||
body: {
|
||||
workspaceSlug,
|
||||
email: user.email,
|
||||
subject: user.authentikUserId ?? undefined,
|
||||
},
|
||||
});
|
||||
const result = await controlPlaneStore.removeTaskManagerWorkspaceMembership(
|
||||
{
|
||||
clientId: client.id,
|
||||
userId: user.id,
|
||||
workspaceSlug,
|
||||
},
|
||||
req.nodedcSession.user
|
||||
);
|
||||
|
||||
publishControlPlaneEvent("admin.task-manager.workspace-membership.updated", [user.id]);
|
||||
res.json({ ...result, taskManager });
|
||||
}));
|
||||
|
||||
app.post("/api/admin/clients", requireLauncherAdmin, asyncRoute(async (req, res) => {
|
||||
const result = await controlPlaneStore.createClient(req.body, req.nodedcSession.user);
|
||||
res.status(201).json(result);
|
||||
@@ -1289,7 +1365,7 @@ function normalizeOptionalText(value) {
|
||||
}
|
||||
|
||||
function normalizeTaskManagerRole(value) {
|
||||
return value === "admin" || value === "member" ? value : null;
|
||||
return value === "guest" || value === "admin" || value === "member" ? value : null;
|
||||
}
|
||||
|
||||
function resolveTaskManagerRoleForMembership(role) {
|
||||
@@ -1598,6 +1674,11 @@ function resolvePublicUrl(value, baseUrl) {
|
||||
}
|
||||
}
|
||||
|
||||
function resolveUserAvatarPublicUrl(user) {
|
||||
if (!user?.avatarUrl) return null;
|
||||
return resolvePublicUrl(user.avatarUrl, config.appBaseUrl);
|
||||
}
|
||||
|
||||
async function saveUploadedFile(payload) {
|
||||
if (!isUploadPayload(payload)) {
|
||||
throw new Error("Некорректный payload загрузки");
|
||||
|
||||
Reference in New Issue
Block a user