/home/techb158/cosmic.abdallabala.com/src/integrations/pmClients
Edit: /home/techb158/cosmic.abdallabala.com/src/integrations/pmClients/jiraClient.js (3165B)
const { fetchJson, buildRiskDescription, jiraDoc } = require("./httpClient.js");
class JiraClient {
constructor(options = {}) {
this.accessToken = options.accessToken;
this.apiEmail = options.apiEmail;
this.apiToken = options.apiToken;
this.baseUrl = (options.baseUrl || "").replace(/\/$/, "");
this.cloudId = options.cloudId;
this.projectKey = options.projectKey;
this.fetchImpl = options.fetchImpl;
if (!this.accessToken && !(this.apiEmail && this.apiToken && this.baseUrl)) {
throw new Error("Jira requires OAuth access token with JIRA_CLOUD_ID, or JIRA_API_EMAIL + JIRA_API_TOKEN + JIRA_BASE_URL");
}
}
apiBase() {
if (this.accessToken) {
if (!this.cloudId) throw new Error("Jira OAuth live sync requires JIRA_CLOUD_ID");
return `https://api.atlassian.com/ex/jira/${encodeURIComponent(this.cloudId)}/rest/api/3`;
}
return `${this.baseUrl}/rest/api/3`;
}
headers(extra = {}) {
const auth = this.accessToken
? `Bearer ${this.accessToken}`
: `Basic ${Buffer.from(`${this.apiEmail}:${this.apiToken}`).toString("base64")}`;
return Object.assign({ "Authorization": auth, "Accept": "application/json", "Content-Type": "application/json" }, extra);
}
async testConnection() {
const user = await fetchJson(`${this.apiBase()}/myself`, { headers: this.headers() }, this.fetchImpl);
return { ok: true, provider: "Jira", account: user.displayName || user.emailAddress || user.accountId, raw: user };
}
async createRiskWorkItem(risk, mitigations = [], integration = {}) {
const projectKey = integration.external_project_key || this.projectKey;
if (!projectKey) throw new Error("Jira live sync requires JIRA_PROJECT_KEY or integration.external_project_key");
const description = jiraDoc(buildRiskDescription(risk, mitigations));
const issue = await fetchJson(`${this.apiBase()}/issue`, {
method: "POST",
headers: this.headers(),
body: JSON.stringify({
fields: {
project: { key: projectKey },
summary: `[COSMIC Risk] ${risk.title}`,
description,
issuetype: { name: "Task" },
labels: ["cosmic-ai-risk", String(risk.dimension || "risk").toLowerCase().replace(/\s+/g, "-")]
}
})
}, this.fetchImpl);
return {
externalId: issue.id,
externalKey: issue.key,
externalUrl: this.accessToken ? "" : `${this.baseUrl}/browse/${issue.key}`,
externalStatus: "Created"
};
}
async updateRiskWorkItem(mapping, risk, mitigations = []) {
const description = jiraDoc(buildRiskDescription(risk, mitigations));
await fetchJson(`${this.apiBase()}/issue/${encodeURIComponent(mapping.external_item_key || mapping.external_item_id)}`, {
method: "PUT",
headers: this.headers(),
body: JSON.stringify({ fields: { summary: `[COSMIC Risk] ${risk.title}`, description } })
}, this.fetchImpl);
return {
externalId: mapping.external_item_id,
externalKey: mapping.external_item_key,
externalUrl: mapping.external_url,
externalStatus: "Updated"
};
}
}
module.exports = { JiraClient };