init
This commit is contained in:
commit
932d4ad420
46 changed files with 5800 additions and 0 deletions
148
lib/bugzilla/client.ts
Normal file
148
lib/bugzilla/client.ts
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
// Bugzilla 5.0+ REST API client — thin wrapper around fetch
|
||||
import type { BugzillaConfig } from "./config"
|
||||
import type {
|
||||
BugzillaBug,
|
||||
BugzillaComment,
|
||||
BugzillaProduct,
|
||||
BugzillaComponent,
|
||||
BugzillaSearchParams,
|
||||
BugzillaCreatePayload,
|
||||
BugzillaUpdatePayload,
|
||||
BugzillaCreateProductPayload,
|
||||
BugzillaCreateComponentPayload,
|
||||
BugzillaSearchResponse,
|
||||
BugzillaCreateResponse,
|
||||
BugzillaUpdateResponse,
|
||||
BugzillaCommentsResponse,
|
||||
BugzillaProductResponse,
|
||||
} from "./types"
|
||||
|
||||
export class BugzillaClient {
|
||||
private baseUrl: string
|
||||
private apiKey: string
|
||||
|
||||
constructor(config: BugzillaConfig) {
|
||||
this.baseUrl = config.baseUrl.replace(/\/+$/, "")
|
||||
this.apiKey = config.apiKey
|
||||
}
|
||||
|
||||
private async request<T>(method: string, path: string, body?: unknown): Promise<T> {
|
||||
// Auth: api_key in query for GET, in body for POST/PUT
|
||||
let url: string
|
||||
if (method === "GET") {
|
||||
const sep = path.includes("?") ? "&" : "?"
|
||||
url = `${this.baseUrl}${path}${sep}api_key=${encodeURIComponent(this.apiKey)}`
|
||||
} else {
|
||||
url = `${this.baseUrl}${path}`
|
||||
const bodyObj = (body ?? {}) as Record<string, unknown>
|
||||
body = { ...bodyObj, api_key: this.apiKey }
|
||||
}
|
||||
|
||||
const res = await fetch(url, {
|
||||
method,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: body ? JSON.stringify(body) : undefined,
|
||||
})
|
||||
|
||||
if (!res.ok) {
|
||||
const text = await res.text()
|
||||
throw new Error(`Bugzilla API ${method} ${path} failed (${res.status}): ${text}`)
|
||||
}
|
||||
|
||||
return res.json() as Promise<T>
|
||||
}
|
||||
|
||||
async getBug(id: number): Promise<BugzillaBug> {
|
||||
const data = await this.request<BugzillaSearchResponse>("GET", `/bug/${id}`)
|
||||
if (!data.bugs?.length) throw new Error(`Bug ${id} not found`)
|
||||
return data.bugs[0]
|
||||
}
|
||||
|
||||
async searchBugs(params: BugzillaSearchParams): Promise<BugzillaBug[]> {
|
||||
const query = new URLSearchParams()
|
||||
if (params.product) query.set("product", params.product)
|
||||
if (params.component) query.set("component", params.component)
|
||||
if (params.summary) query.set("summary", params.summary)
|
||||
if (params.limit) query.set("limit", String(params.limit))
|
||||
if (params.offset) query.set("offset", String(params.offset))
|
||||
if (params.last_change_time) query.set("last_change_time", params.last_change_time)
|
||||
|
||||
if (params.status) {
|
||||
const statuses = Array.isArray(params.status) ? params.status : [params.status]
|
||||
for (const s of statuses) query.append("status", s)
|
||||
}
|
||||
|
||||
if (params.id) {
|
||||
const ids = Array.isArray(params.id) ? params.id : [params.id]
|
||||
for (const id of ids) query.append("id", String(id))
|
||||
}
|
||||
|
||||
if (params.alias) {
|
||||
const aliases = Array.isArray(params.alias) ? params.alias : [params.alias]
|
||||
for (const a of aliases) query.append("alias", a)
|
||||
}
|
||||
|
||||
if (params.url) query.set("url", params.url)
|
||||
|
||||
const qs = query.toString()
|
||||
const path = `/bug${qs ? `?${qs}` : ""}`
|
||||
const data = await this.request<BugzillaSearchResponse>("GET", path)
|
||||
return data.bugs ?? []
|
||||
}
|
||||
|
||||
async createBug(payload: BugzillaCreatePayload): Promise<number> {
|
||||
const data = await this.request<BugzillaCreateResponse>("POST", "/bug", payload)
|
||||
return data.id
|
||||
}
|
||||
|
||||
async updateBug(id: number, payload: BugzillaUpdatePayload): Promise<BugzillaUpdateResponse> {
|
||||
return this.request<BugzillaUpdateResponse>("PUT", `/bug/${id}`, payload)
|
||||
}
|
||||
|
||||
async getComments(bugId: number): Promise<BugzillaComment[]> {
|
||||
const data = await this.request<BugzillaCommentsResponse>("GET", `/bug/${bugId}/comment`)
|
||||
return data.bugs?.[String(bugId)]?.comments ?? []
|
||||
}
|
||||
|
||||
async addComment(bugId: number, comment: string): Promise<number> {
|
||||
const data = await this.request<{ id: number }>("POST", `/bug/${bugId}/comment`, { comment })
|
||||
return data.id
|
||||
}
|
||||
|
||||
async tagComment(commentId: number, tags: string[]): Promise<void> {
|
||||
await this.request("PUT", `/bug/comment/${commentId}/tags`, { add: tags })
|
||||
}
|
||||
|
||||
// Product operations
|
||||
|
||||
async getProduct(nameOrId: string | number): Promise<BugzillaProduct> {
|
||||
const data = await this.request<BugzillaProductResponse>(
|
||||
"GET",
|
||||
`/product/${encodeURIComponent(String(nameOrId))}`
|
||||
)
|
||||
if (!data.products?.length) throw new Error(`Product "${nameOrId}" not found`)
|
||||
return data.products[0]
|
||||
}
|
||||
|
||||
async getAccessibleProducts(): Promise<BugzillaProduct[]> {
|
||||
const data = await this.request<BugzillaProductResponse>("GET", "/product?type=accessible")
|
||||
return data.products ?? []
|
||||
}
|
||||
|
||||
async createProduct(payload: BugzillaCreateProductPayload): Promise<number> {
|
||||
const data = await this.request<{ id: number }>("POST", "/product", payload)
|
||||
return data.id
|
||||
}
|
||||
|
||||
// Component operations
|
||||
|
||||
async getComponents(product: string): Promise<BugzillaComponent[]> {
|
||||
const prod = await this.getProduct(product)
|
||||
return prod.components ?? []
|
||||
}
|
||||
|
||||
async createComponent(payload: BugzillaCreateComponentPayload): Promise<number> {
|
||||
const data = await this.request<{ id: number }>("POST", "/component", payload)
|
||||
return data.id
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue