refactoring

This commit is contained in:
2026-02-19 20:44:08 +01:00
parent 75aa3027cf
commit ea75f07421
7 changed files with 441 additions and 356 deletions

74
domain/model/issue.go Normal file
View File

@@ -0,0 +1,74 @@
package model
import (
"encoding/json"
"fmt"
"io"
"net/http"
)
// --- Redmine API structs ---
type IssueRef struct {
ID int `json:"id"`
Title string `json:"title"`
}
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"`
}
// 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
}

67
domain/model/node.go Normal file
View File

@@ -0,0 +1,67 @@
package model
import (
"fmt"
)
type Node struct {
Issue Issue // Assuming Issue struct is imported or defined in the same package
Children []*Node
}
// BuildTree builds a hierarchical tree of issues based on parent-child relationships.
func BuildTree(issues []Issue) []*Node {
nodeMap := make(map[int]*Node, len(issues))
for i := range issues {
nodeMap[issues[i].ID] = &Node{Issue: issues[i]}
}
var roots []*Node
for _, node := range nodeMap {
if node.Issue.Parent == nil {
roots = append(roots, node)
} else {
parent, ok := nodeMap[node.Issue.Parent.ID]
if ok {
parent.Children = append(parent.Children, node)
} else {
// Parent not in project scope → treat as root
roots = append(roots, node)
}
}
}
return roots
}
// PrintTree prints the issue tree to standard output.
func PrintTree(nodes []*Node, prefix string, isLast bool) {
for i, node := range nodes {
last := i == len(nodes)-1
connector := "├── "
if last {
connector = "└── "
}
parentInfo := ""
if node.Issue.Parent != nil {
parentInfo = fmt.Sprintf(" [parent: #%d]", node.Issue.Parent.ID)
}
fmt.Printf("%s%s#%d %s (%s)%s\n",
prefix, connector,
node.Issue.ID,
node.Issue.Subject,
node.Issue.Status.Title,
parentInfo,
)
childPrefix := prefix
if last {
childPrefix += " "
} else {
childPrefix += "│ "
}
PrintTree(node.Children, childPrefix, isLast)
}
}

100
domain/model/project.go Normal file
View File

@@ -0,0 +1,100 @@
package model
import (
"encoding/json"
"fmt"
"io"
"net/http"
)
// --- Redmine Project API structs ---
type Project struct {
ID int `json:"id"`
Name string `json:"name"`
}
type ProjectResponse struct {
Project Project `json:"project"`
}
type ProjectsResponse struct {
Projects []Project `json:"projects"`
TotalCount int `json:"total_count"`
Offset int `json:"offset"`
Limit int `json:"limit"`
}
// Fetch a single project's details
func FetchProject(baseURL, apiKey, projectID string) (*Project, error) {
client := &http.Client{}
url := fmt.Sprintf("%s/projects/%s.json", baseURL, projectID)
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 ProjectResponse
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return nil, fmt.Errorf("JSON decode error: %w", err)
}
return &result.Project, nil
}
// Fetch all projects with pagination
func FetchAllProjects(baseURL, apiKey string) ([]Project, error) {
var all []Project
limit := 100
offset := 0
client := &http.Client{}
for {
url := fmt.Sprintf("%s/projects.json?limit=%d&offset=%d",
baseURL, 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 ProjectsResponse
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return nil, fmt.Errorf("JSON decode error: %w", err)
}
all = append(all, result.Projects...)
if offset+limit >= result.TotalCount {
break
}
offset += limit
}
return all, nil
}