Merge pull request '日常操作命令:issues/pulls/labels/milestones/releases/repos/api(#8)' (#16) from feature/issue8-dailyops into main

This commit was merged in pull request #16.
This commit is contained in:
ceo
2026-09-10 09:54:23 +08:00
14 changed files with 3320 additions and 32 deletions
+55 -10
View File
@@ -7,6 +7,7 @@
package gitea
import (
"bytes"
"context"
"encoding/json"
"fmt"
@@ -79,36 +80,80 @@ const MaxLimit = 50
// getJSON 對 path(相對 baseURL,以 / 開頭)發 GET,把 JSON 回應解到 out。
// 失敗時回傳 *ErrAPI。rawQuery 為組好的查詢字串(見 ListOptions.query)。
func (c *Client) getJSON(ctx context.Context, path, rawQuery string, out any) error {
_, err := c.do(ctx, http.MethodGet, path, rawQuery, "", nil, out)
return err
}
// DoJSON 對 path 發 method 請求(POST/PATCH/PUT/DELETE…),body 為可 JSON
// 化的值(nil 表示不帶 body),2xx 時把回應解到 out(out 非 nil 且回應
// 有內容時)。供寫入類操作(issues create、pulls merge…)使用。
func (c *Client) DoJSON(ctx context.Context, method, path, rawQuery string, body any, out any) error {
var payload []byte
if body != nil {
b, err := json.Marshal(body)
if err != nil {
return fmt.Errorf("gitea: marshal request body: %w", err)
}
payload = b
}
_, err := c.do(ctx, method, path, rawQuery, "application/json", payload, out)
return err
}
// DoRaw 以原樣 body 送出請求並回傳原始回應內容(teai api 用)。
// 非 2xx 回 *ErrAPI。
func (c *Client) DoRaw(ctx context.Context, method, path, rawQuery string, body []byte) ([]byte, error) {
contentType := ""
if body != nil {
contentType = "application/json"
}
return c.do(ctx, method, path, rawQuery, contentType, body, nil)
}
// do 是所有請求的共同核心:組 URL、帶認證表頭、送出、檢查狀態碼、
// 解析回應。2xx 時回傳原始回應位元組;out 非 nil 時另解到 out。
// 錯誤訊息不含 token 或回應內容。
func (c *Client) do(ctx context.Context, method, path, rawQuery, contentType string, payload []byte, out any) ([]byte, error) {
u := *c.baseURL
u.Path += path
u.RawQuery = rawQuery
op := "GET " + u.Path
op := method + " " + u.Path
req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil)
var body io.Reader
if payload != nil {
body = bytes.NewReader(payload)
}
req, err := http.NewRequestWithContext(ctx, method, u.String(), body)
if err != nil {
return &ErrAPI{Op: op, Err: err}
return nil, &ErrAPI{Op: op, Err: err}
}
req.Header.Set("Accept", "application/json")
if payload != nil && contentType != "" {
req.Header.Set("Content-Type", contentType)
}
if c.token != "" {
req.Header.Set("Authorization", "token "+c.token)
}
resp, err := c.hc.Do(req)
if err != nil {
return &ErrAPI{Op: op, Err: err}
return nil, &ErrAPI{Op: op, Err: err}
}
defer resp.Body.Close()
if resp.StatusCode < 200 || resp.StatusCode > 299 {
// 把內容讀掉再丟棄,讓連線可重用;錯誤訊息不含回應內容。
_, _ = io.Copy(io.Discard, io.LimitReader(resp.Body, 4096))
return &ErrAPI{Op: op, StatusCode: resp.StatusCode}
return nil, &ErrAPI{Op: op, StatusCode: resp.StatusCode}
}
if out == nil {
return nil
data, err := io.ReadAll(resp.Body)
if err != nil {
return nil, &ErrAPI{Op: op, Err: err}
}
if err := json.NewDecoder(resp.Body).Decode(out); err != nil {
return &ErrAPI{Op: op, Err: fmt.Errorf("decode response: %w", err)}
if out != nil && len(data) > 0 {
if err := json.Unmarshal(data, out); err != nil {
return nil, &ErrAPI{Op: op, Err: fmt.Errorf("decode response: %w", err)}
}
}
return nil
return data, nil
}
// GetJSON 是 getJSON 的公開介面,供非清單端點(如 /user)使用。