/home/techb158/cosmic-risk.abdallabala.com/src/integrations/pm-clients
NameSizeModeActions
asana-client.js32490644editdlrm
http-client.js24180644editdlrm
index.js22710644editdlrm
jira-client.js43610644editdlrm
planner-client.js41170644editdlrm
trello-client.js87900644editdlrm
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 };