/home/techb158/cosmic-risk.abdallabala.com/src/integrations/pm-clients
Edit: /home/techb158/cosmic-risk.abdallabala.com/src/integrations/pm-clients/planner-client.js (4117B)
const { fetchJson, buildRiskDescription } = require("./http-client");
class PlannerClient {
constructor(options = {}) {
this.accessToken = options.accessToken;
this.planId = options.planId;
this.bucketId = options.bucketId;
this.fetchImpl = options.fetchImpl;
if (!this.accessToken) {
throw new Error("Microsoft Planner live sync requires accessToken.");
}
}
headers(extra = {}) {
return Object.assign({
"Authorization": `Bearer ${this.accessToken}`,
"Accept": "application/json",
"Content-Type": "application/json"
}, extra);
}
async testConnection() {
const user = await fetchJson("https://graph.microsoft.com/v1.0/me", { headers: this.headers() }, this.fetchImpl);
return { ok: true, provider: "MICROSOFT_PLANNER", account: user.displayName || user.userPrincipalName || user.id };
}
async patchTaskDetails(taskId, text) {
const details = await fetchJson(`https://graph.microsoft.com/v1.0/planner/tasks/${encodeURIComponent(taskId)}/details`, {
headers: this.headers()
}, this.fetchImpl);
const etag = details["@odata.etag"];
if (!etag) return;
await fetchJson(`https://graph.microsoft.com/v1.0/planner/tasks/${encodeURIComponent(taskId)}/details`, {
method: "PATCH",
headers: this.headers({ "If-Match": etag }),
body: JSON.stringify({ description: text })
}, this.fetchImpl);
}
async createRiskWorkItem(risk, mitigations = [], integration = {}) {
const planId = integration.externalProjectKey || this.planId;
const bucketId = integration.liveConfig?.bucketId || this.bucketId;
if (!planId || !bucketId) {
throw new Error("Microsoft Planner live sync requires externalProjectKey/planId and liveConfig.bucketId/credentials.bucketId.");
}
const task = await fetchJson("https://graph.microsoft.com/v1.0/planner/tasks", {
method: "POST",
headers: this.headers(),
body: JSON.stringify({ planId, bucketId, title: `[COSMIC Risk] ${risk.title}` })
}, this.fetchImpl);
await this.patchTaskDetails(task.id, buildRiskDescription(risk, mitigations)).catch(() => {});
return {
externalId: task.id,
externalKey: task.id,
externalUrl: `https://tasks.office.com/Home/Task/${task.id}`,
externalStatus: "Created"
};
}
async fetchWorkItems(options = {}) {
const planId = options.planId || this.planId;
if (!planId) {
throw new Error("Planner fetchWorkItems requires externalProjectKey or credentials.planId.");
}
const tasks = await fetchJson(`https://graph.microsoft.com/v1.0/planner/plans/${encodeURIComponent(planId)}/tasks`, { headers: this.headers() }, this.fetchImpl);
const items = tasks.value || [];
const enriched = await Promise.all(items.map(async (task) => {
let description = "";
try {
const details = await fetchJson(`https://graph.microsoft.com/v1.0/planner/tasks/${encodeURIComponent(task.id)}/details`, { headers: this.headers() }, this.fetchImpl);
description = details.description || "";
} catch (err) {
}
const bucketName = task.bucketId || "";
return {
id: task.id,
title: task.title || "",
description,
bucketId: task.bucketId || "",
planId: task.planId || "",
dueDate: task.dueDateTime || null,
percentComplete: task.percentComplete || 0,
completed: task.percentComplete === 100,
createdBy: task.createdBy?.user?.id || null,
externalUrl: `https://tasks.office.com/Home/Task/${task.id}`,
raw: task
};
}));
return enriched;
}
async discoverResources() { return []; }
async updateRiskWorkItem(mapping, risk, mitigations = []) {
await this.patchTaskDetails(mapping.externalItemId, buildRiskDescription(risk, mitigations)).catch(() => {});
return {
externalId: mapping.externalItemId,
externalKey: mapping.externalItemKey,
externalUrl: mapping.externalUrl,
externalStatus: "Updated"
};
}
}
module.exports = { PlannerClient };