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) }