package model import ( "encoding/json" "fmt" "io" "net/http" "strings" ) // --- Redmine API structs --- type IssueRef struct { ID int `json:"id"` Title string `json:"name"` } type Issue struct { ID int `json:"id"` Subject string `json:"subject"` Status IssueRef `json:"status"` Parent *IssueRef `json:"parent"` } type IssuesResponse struct { Issues []Issue `json:"issues"` TotalCount int `json:"total_count"` Offset int `json:"offset"` Limit int `json:"limit"` } // FilterIssues filters issues based on included and excluded status names or IDs. func FilterIssues(issues []Issue, includeStatuses, excludeStatuses []string) []Issue { if len(includeStatuses) == 0 && len(excludeStatuses) == 0 { return issues } var filtered []Issue for _, issue := range issues { statusID := fmt.Sprintf("%d", issue.Status.ID) statusName := strings.ToLower(issue.Status.Title) keep := true // If includeStatuses is specified, issue MUST match at least one if len(includeStatuses) > 0 { match := false for _, s := range includeStatuses { s = strings.TrimSpace(strings.ToLower(s)) if s == "" { continue } if s == statusID || s == statusName { match = true break } } if !match { keep = false } } // If excludeStatuses is specified, issue MUST NOT match any if keep && len(excludeStatuses) > 0 { for _, s := range excludeStatuses { s = strings.TrimSpace(strings.ToLower(s)) if s == "" { continue } if s == statusID || s == statusName { keep = false break } } } if keep { filtered = append(filtered, issue) } } return filtered } // Fetch all issues with pagination func FetchAllIssues(baseURL, apiKey, projectID string) ([]Issue, error) { var all []Issue limit := 100 offset := 0 client := &http.Client{} for { url := fmt.Sprintf("%s/issues.json?project_id=%s&limit=%d&offset=%d&status_id=*", baseURL, projectID, limit, offset) req, err := http.NewRequest("GET", url, nil) if err != nil { return nil, err } req.Header.Set("X-Redmine-API-Key", apiKey) resp, err := client.Do(req) if err != nil { return nil, fmt.Errorf("HTTP request failed: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { body, _ := io.ReadAll(resp.Body) return nil, fmt.Errorf("API error %d: %s", resp.StatusCode, string(body)) } var result IssuesResponse if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { return nil, fmt.Errorf("JSON decode error: %w", err) } all = append(all, result.Issues...) if offset+limit >= result.TotalCount { break } offset += limit } return all, nil }