package memos import ( "context" "encoding/json" "fmt" "io" "net/http" "net/url" "strconv" ) type Client struct { baseURL string token string httpClient *http.Client } func NewClient(baseURL, token string) *Client { return &Client{ baseURL: baseURL, token: token, httpClient: &http.Client{}, } } // ListMemos fetches memos from the API with the given CEL filter. func (c *Client) ListMemos(ctx context.Context, filter string, pageSize int, pageToken string) (*ListMemosResponse, error) { u, err := url.Parse(c.baseURL + "/api/v1/memos") if err != nil { return nil, fmt.Errorf("parse url: %w", err) } q := u.Query() if filter != "" { q.Set("filter", filter) } if pageSize > 0 { q.Set("pageSize", strconv.Itoa(pageSize)) } if pageToken != "" { q.Set("pageToken", pageToken) } u.RawQuery = q.Encode() req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), http.NoBody) if err != nil { return nil, fmt.Errorf("create request: %w", err) } req.Header.Set("Authorization", "Bearer "+c.token) resp, err := c.httpClient.Do(req) if err != nil { return nil, fmt.Errorf("do request: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { body, _ := io.ReadAll(resp.Body) return nil, fmt.Errorf("memos API returned %d: %s", resp.StatusCode, body) } var result ListMemosResponse if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { return nil, fmt.Errorf("decode response: %w", err) } return &result, nil } // GetMemo fetches a single memo by its resource name (e.g. "memos/123"). func (c *Client) GetMemo(ctx context.Context, name string) (*Memo, error) { reqURL := fmt.Sprintf("%s/api/v1/%s", c.baseURL, name) req, err := http.NewRequestWithContext(ctx, http.MethodGet, reqURL, http.NoBody) if err != nil { return nil, fmt.Errorf("create request: %w", err) } req.Header.Set("Authorization", "Bearer "+c.token) resp, err := c.httpClient.Do(req) if err != nil { return nil, fmt.Errorf("do request: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { body, _ := io.ReadAll(resp.Body) return nil, fmt.Errorf("memos API returned %d: %s", resp.StatusCode, body) } var memo Memo if err := json.NewDecoder(resp.Body).Decode(&memo); err != nil { return nil, fmt.Errorf("decode memo: %w", err) } return &memo, nil } // DownloadAttachment downloads the attachment data as bytes. func (c *Client) DownloadAttachment(ctx context.Context, att Attachment) ([]byte, error) { var reqURL string var needsAuth bool if att.ExternalLink != "" { reqURL = att.ExternalLink } else { reqURL = fmt.Sprintf("%s/file/%s/%s", c.baseURL, att.Name, att.Filename) needsAuth = true } req, err := http.NewRequestWithContext(ctx, http.MethodGet, reqURL, http.NoBody) if err != nil { return nil, fmt.Errorf("create request: %w", err) } if needsAuth { req.Header.Set("Authorization", "Bearer "+c.token) } resp, err := c.httpClient.Do(req) if err != nil { return nil, fmt.Errorf("do request: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("download attachment %s: status %d", att.Name, resp.StatusCode) } data, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("read body: %w", err) } return data, nil }