Scout/pkg/client/projects.go
2026-08-31 21:19:15 +08:00

187 lines
5.3 KiB
Go

package client
import (
"context"
"errors"
"net/http"
"net/url"
"strings"
)
type Project struct {
ID string `json:"id"`
Name string `json:"name"`
Description string `json:"description,omitempty"`
Status string `json:"status"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}
type ProjectMember struct {
ProjectID string `json:"project_id"`
UserID string `json:"user_id"`
Role string `json:"role"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}
type ProjectToken struct {
ID string `json:"id"`
ProjectID string `json:"project_id"`
Name string `json:"name"`
Prefix string `json:"prefix"`
Role string `json:"role"`
CreatedAt string `json:"created_at"`
ExpiresAt *string `json:"expires_at,omitempty"`
RevokedAt *string `json:"revoked_at,omitempty"`
}
type CreatedProjectToken struct {
Token ProjectToken `json:"token"`
Secret string `json:"secret"`
}
func (c *Client) ListProjects(ctx context.Context) ([]Project, error) {
var response struct {
Projects []Project `json:"projects"`
}
if err := c.getJSON(ctx, "/api/v1/projects", &response); err != nil {
return nil, err
}
return response.Projects, nil
}
func (c *Client) GetProject(ctx context.Context, project string) (Project, error) {
project = strings.TrimSpace(project)
if project == "" {
return Project{}, errors.New("项目标识不能为空")
}
var result Project
path := "/api/v1/projects/" + url.PathEscape(project)
if err := c.getJSON(ctx, path, &result); err != nil {
return Project{}, err
}
return result, nil
}
func (c *Client) CreateProject(ctx context.Context, project Project) (Project, error) {
var result Project
if err := c.postJSON(ctx, "/api/v1/projects", map[string]string{
"id": project.ID,
"name": project.Name,
"description": project.Description,
}, &result); err != nil {
return Project{}, err
}
return result, nil
}
func (c *Client) UpdateProject(ctx context.Context, project Project) (Project, error) {
project.ID = strings.TrimSpace(project.ID)
if err := validateProjectID(project.ID); err != nil {
return Project{}, err
}
var result Project
path := "/api/v1/projects/" + url.PathEscape(project.ID)
if err := c.patchJSON(ctx, path, map[string]string{
"name": project.Name,
"description": project.Description,
"status": project.Status,
}, &result); err != nil {
return Project{}, err
}
return result, nil
}
func (c *Client) ListProjectMembers(ctx context.Context) ([]ProjectMember, error) {
var response struct {
Members []ProjectMember `json:"members"`
}
if err := c.getJSON(ctx, c.projectPath("members"), &response); err != nil {
return nil, err
}
return response.Members, nil
}
func (c *Client) AddProjectMember(ctx context.Context, userID, role string) (ProjectMember, error) {
var result ProjectMember
if err := c.postJSON(ctx, c.projectPath("members"), map[string]string{"user_id": userID, "role": role}, &result); err != nil {
return ProjectMember{}, err
}
return result, nil
}
func (c *Client) UpdateProjectMember(ctx context.Context, userID, role string) (ProjectMember, error) {
var result ProjectMember
path := c.projectPath("members") + "/" + url.PathEscape(strings.TrimSpace(userID))
if err := c.patchJSON(ctx, path, map[string]string{"role": role}, &result); err != nil {
return ProjectMember{}, err
}
return result, nil
}
func (c *Client) DeleteProjectMember(ctx context.Context, userID string) error {
path := c.projectPath("members") + "/" + url.PathEscape(strings.TrimSpace(userID))
request, err := c.newRequest(ctx, http.MethodDelete, path, nil)
if err != nil {
return err
}
response, err := c.httpClient.Do(request)
if err != nil {
return err
}
defer response.Body.Close()
return checkResponse(response)
}
func (c *Client) ListProjectTokens(ctx context.Context) ([]ProjectToken, error) {
var response struct {
Tokens []ProjectToken `json:"tokens"`
}
if err := c.getJSON(ctx, c.projectPath("tokens"), &response); err != nil {
return nil, err
}
return response.Tokens, nil
}
func (c *Client) CreateProjectToken(ctx context.Context, name, role string, expiresAt *string) (CreatedProjectToken, error) {
var result CreatedProjectToken
input := map[string]string{"name": name, "role": role}
if expiresAt != nil {
input["expires_at"] = *expiresAt
}
if err := c.postJSON(ctx, c.projectPath("tokens"), input, &result); err != nil {
return CreatedProjectToken{}, err
}
return result, nil
}
func (c *Client) RevokeProjectToken(ctx context.Context, tokenID string) (ProjectToken, error) {
var result ProjectToken
path := c.projectPath("tokens") + "/" + url.PathEscape(strings.TrimSpace(tokenID)) + "/revoke"
if err := c.postJSON(ctx, path, map[string]string{}, &result); err != nil {
return ProjectToken{}, err
}
return result, nil
}
func (c *Client) projectPath(resource string) string {
if c.project == "" {
return "/api/v1/projects/" + url.PathEscape(resource)
}
return "/api/v1/projects/" + url.PathEscape(c.project) + "/" + url.PathEscape(resource)
}
func (c *Client) ProjectHealth(ctx context.Context) error {
request, err := c.newRequest(ctx, http.MethodGet, "/healthz", nil)
if err != nil {
return err
}
response, err := c.httpClient.Do(request)
if err != nil {
return err
}
defer response.Body.Close()
return checkResponse(response)
}