/home/techb158/balavpn.abdallabala.com/src/integrations/pm-clients
Edit: /home/techb158/balavpn.abdallabala.com/src/integrations/pm-clients/jira-client.js (3091B)
const { fetchJson, buildRiskDescription, jiraDoc } = require("./http-client");
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 live sync requires either accessToken + cloudId, or apiEmail + apiToken + baseUrl.");
}
}
apiBase() {
if (this.accessToken) {
if (!this.cloudId) throw new Error("Jira OAuth live sync requires cloudId.");
return `https://api.atlassian.com/ex/jira/${encodeURIComponent(this.cloudId)}/rest/api/3`;
}
return `${this.baseUrl}/rest/api/3`;
}
headers() {
const authorization = this.accessToken
? `Bearer ${this.accessToken}`
: `Basic ${Buffer.from(`${this.apiEmail}:${this.apiToken}`).toString("base64")}`;
return { "Authorization": authorization, "Accept": "application/json", "Content-Type": "application/json" };
}
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 };
}
async createRiskWorkItem(risk, mitigations = [], integration = {}) {
const projectKey = integration.externalProjectKey || this.projectKey;
if (!projectKey) {
throw new Error("Jira live sync requires externalProjectKey or credentials.projectKey.");
}
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: jiraDoc(buildRiskDescription(risk, mitigations)),
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.baseUrl && issue.key ? `${this.baseUrl}/browse/${issue.key}` : "",
externalStatus: "Created"
};
}
async discoverResources() { return []; }
async updateRiskWorkItem(mapping, risk, mitigations = []) {
const key = mapping.externalItemKey || mapping.externalItemId;
await fetchJson(`${this.apiBase()}/issue/${encodeURIComponent(key)}`, {
method: "PUT",
headers: this.headers(),
body: JSON.stringify({ fields: { summary: `[COSMIC Risk] ${risk.title}`, description: jiraDoc(buildRiskDescription(risk, mitigations)) } })
}, this.fetchImpl);
return { externalId: mapping.externalItemId, externalKey: key, externalUrl: mapping.externalUrl, externalStatus: "Updated" };
}
}
module.exports = { JiraClient };