swadhinbiswas feat: OpenCodeHub v1.1.0 - Enterprise Features & Storage Adapters · 2/15/2026
719a1cb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
import { logger } from "@/lib/logger";
export interface JiraConfig {
host: string;
email: string;
apiToken: string;
}
export interface JiraIssue {
key: string;
fields: {
summary: string;
description: string;
status: { name: string };
assignee?: { displayName: string };
priority?: { name: string };
issuetype?: { name: string; iconUrl: string };
};
webUrl: string; // Constructed URL
}
export class JiraClient {
private config: JiraConfig;
private baseUrl: string;
constructor(config: JiraConfig) {
this.config = config;
this.baseUrl = `https://${config.host}`;
}
private getAuthHeader(): string {
return `Basic ${Buffer.from(`${this.config.email}:${this.config.apiToken}`).toString("base64")}`;
}
/**
* Fetch issue details from Jira
*/
async getIssue(issueKey: string): Promise<JiraIssue | null> {
try {
const response = await fetch(`${this.baseUrl}/rest/api/3/issue/${issueKey}`, {
method: "GET",
headers: {
"Authorization": this.getAuthHeader(),
"Accept": "application/json",
},
});
if (response.status === 404) {
return null;
}
if (!response.ok) {
const error = await response.text();
logger.error({ status: response.status, error, issueKey }, "Failed to fetch Jira issue");
throw new Error(`Jira API error: ${response.status}`);
}
const data = await response.json();
return {
key: data.key,
fields: {
summary: data.fields.summary,
description: data.fields.description, // Complex object in v3, simplify?
status: data.fields.status,
assignee: data.fields.assignee,
priority: data.fields.priority,
issuetype: data.fields.issuetype,
},
webUrl: `${this.baseUrl}/browse/${data.key}`,
};
} catch (error) {
logger.error({ error, issueKey }, "Jira client error");
return null;
}
}
/**
* Transition an issue (e.g., In Progress, Done)
*/
async transitionIssue(issueKey: string, transitionId: string): Promise<boolean> {
try {
const response = await fetch(`${this.baseUrl}/rest/api/3/issue/${issueKey}/transitions`, {
method: "POST",
headers: {
"Authorization": this.getAuthHeader(),
"Accept": "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify({
transition: { id: transitionId },
}),
});
if (!response.ok) {
logger.error({ status: response.status, issueKey }, "Failed to transition Jira issue");
return false;
}
return true;
} catch (error) {
logger.error({ error, issueKey }, "Jira client transition error");
return false;
}
}
}
/**
* Factory to create Jira client from environment or DB config
*/
export function getJiraClient(): JiraClient | null {
const host = process.env.JIRA_HOST;
const email = process.env.JIRA_EMAIL;
const apiToken = process.env.JIRA_API_TOKEN;
if (host && email && apiToken) {
return new JiraClient({ host, email, apiToken });
}
return null;
}