swadhinbiswas feat: OpenCodeHub v1.1.0 - Enterprise Features & Storage Adapters · 2/15/2026
719a1cb
123 lines · 3.6 KB
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;
}