-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathapi.go
More file actions
167 lines (136 loc) · 4.73 KB
/
api.go
File metadata and controls
167 lines (136 loc) · 4.73 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
package helpers
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"github.com/stacktodate/stacktodate-cli/cmd/lib/cache"
)
// Component represents a single technology in the stack
type Component struct {
Name string `json:"name"`
Version string `json:"version"`
}
// ConvertStackToComponents converts the detected stack format to API component format
func ConvertStackToComponents(stack map[string]StackEntry) []Component {
components := make([]Component, 0)
for name, entry := range stack {
components = append(components, Component{
Name: name,
Version: entry.Version,
})
}
return components
}
// TechStackRequest is used for POST /api/tech_stacks
type TechStackRequest struct {
TechStack struct {
Name string `json:"name"`
Components []Component `json:"components"`
} `json:"tech_stack"`
}
// TechStackResponse is the response from both GET and POST tech stack endpoints
type TechStackResponse struct {
Success bool `json:"success,omitempty"`
Message string `json:"message,omitempty"`
TechStack struct {
ID string `json:"id"`
Name string `json:"name"`
Components []Component `json:"components"`
} `json:"tech_stack"`
}
// CreateTechStack creates a new tech stack on the API
// Returns the newly created tech stack with UUID
func CreateTechStack(token, name string, components []Component) (*TechStackResponse, error) {
apiURL := cache.GetAPIURL()
url := fmt.Sprintf("%s/api/tech_stacks", apiURL)
request := TechStackRequest{}
request.TechStack.Name = name
request.TechStack.Components = components
var response TechStackResponse
if err := makeAPIRequest("POST", url, token, request, &response); err != nil {
return nil, err
}
if !response.Success {
return nil, fmt.Errorf("API error: %s", response.Message)
}
if response.TechStack.ID == "" {
return nil, fmt.Errorf("API response missing project ID")
}
return &response, nil
}
// GetTechStack retrieves an existing tech stack from the API by UUID
// This validates that the project exists and returns its details
func GetTechStack(token, uuid string) (*TechStackResponse, error) {
apiURL := cache.GetAPIURL()
url := fmt.Sprintf("%s/api/tech_stacks/%s", apiURL, uuid)
var response TechStackResponse
if err := makeAPIRequest("GET", url, token, nil, &response); err != nil {
return nil, err
}
if response.TechStack.ID == "" {
return nil, fmt.Errorf("API response missing project ID")
}
return &response, nil
}
// makeAPIRequest is a private helper that handles common API request logic
func makeAPIRequest(method, url, token string, requestBody interface{}, response interface{}) error {
var req *http.Request
var err error
// Create request with body if provided
if requestBody != nil {
requestBodyJSON, err := json.Marshal(requestBody)
if err != nil {
return fmt.Errorf("failed to marshal request: %w", err)
}
req, err = http.NewRequest(method, url, bytes.NewBuffer(requestBodyJSON))
} else {
req, err = http.NewRequest(method, url, nil)
}
if err != nil {
return fmt.Errorf("failed to create request: %w", err)
}
// Set headers
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
// Make request
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return fmt.Errorf("failed to connect to StackToDate API: %w\n\nPlease check your internet connection and try again", err)
}
defer resp.Body.Close()
// Read response body
body, err := io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("failed to read response: %w", err)
}
// Handle error responses first
if resp.StatusCode == http.StatusUnauthorized {
return fmt.Errorf("authentication failed: invalid or expired token\n\nPlease update your token with: stacktodate global-config set")
}
if resp.StatusCode == http.StatusNotFound {
return fmt.Errorf("project not found: UUID does not exist\n\nPlease check the UUID or create a new project")
}
if resp.StatusCode == http.StatusUnprocessableEntity {
var errResp struct {
Message string `json:"message"`
}
if err := json.Unmarshal(body, &errResp); err == nil && errResp.Message != "" {
return fmt.Errorf("validation error: %s", errResp.Message)
}
return fmt.Errorf("validation error: the server rejected your request")
}
if resp.StatusCode >= 500 {
return fmt.Errorf("StackToDate API is experiencing issues (status %d)\n\nPlease try again later", resp.StatusCode)
}
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated {
return fmt.Errorf("API error (status %d): %s", resp.StatusCode, string(body))
}
// Parse successful response
if err := json.Unmarshal(body, response); err != nil {
return fmt.Errorf("failed to parse API response: %w", err)
}
return nil
}