gitlink-cli/shortcuts/org/org.go

316 lines
9.0 KiB
Go

package org
import (
"fmt"
"net/url"
"strconv"
"strings"
"github.com/gitlink-org/gitlink-cli/shortcuts/common"
)
func Shortcuts() []*common.Shortcut {
shortcuts := []*common.Shortcut{
{
Name: "list",
Description: "List organizations",
Flags: []common.Flag{
{Name: "page", Short: "p", Usage: "Page number", Default: "1"},
{Name: "limit", Short: "l", Usage: "Items per page", Default: "20"},
},
Run: func(ctx *common.RuntimeContext) error {
q := url.Values{}
q.Set("page", ctx.Arg("page"))
q.Set("limit", ctx.Arg("limit"))
env, err := ctx.CallAPIWithQuery("GET", "/organizations", q)
if err != nil {
return fmt.Errorf("获取组织列表失败: %w", err)
}
return ctx.Output(env)
},
},
{
Name: "info",
Description: "Show organization details",
Flags: []common.Flag{
{Name: "id", Short: "i", Usage: "Organization ID or login", Required: true},
},
Run: func(ctx *common.RuntimeContext) error {
id, err := ctx.RequireArg("id", "--id my-org")
if err != nil {
return err
}
env, err := ctx.CallAPI("GET", fmt.Sprintf("/organizations/%s", id), nil)
if err != nil {
return fmt.Errorf("查看组织失败: %w", err)
}
return ctx.Output(env)
},
},
{
Name: "members",
Description: "List organization members",
Flags: []common.Flag{
{Name: "id", Short: "i", Usage: "Organization ID", Required: true},
{Name: "page", Short: "p", Usage: "Page number", Default: "1"},
{Name: "limit", Short: "l", Usage: "Items per page", Default: "20"},
},
Run: func(ctx *common.RuntimeContext) error {
id, err := ctx.RequireArg("id", "--id my-org")
if err != nil {
return err
}
q := url.Values{}
q.Set("page", ctx.Arg("page"))
q.Set("limit", ctx.Arg("limit"))
env, err := ctx.CallAPIWithQuery("GET", fmt.Sprintf("/organizations/%s/organization_users", id), q)
if err != nil {
return fmt.Errorf("获取组织成员失败: %w", err)
}
return ctx.Output(env)
},
},
{
Name: "create",
Description: "Create an organization",
DryRun: true,
DryRunHint: func(ctx *common.RuntimeContext) (string, error) {
name := ctx.Arg("name")
return fmt.Sprintf("Create organization: %s", name), nil
},
Flags: []common.Flag{
{Name: "name", Short: "n", Usage: "Organization name", Required: true},
{Name: "description", Short: "d", Usage: "Description"},
},
Run: func(ctx *common.RuntimeContext) error {
name, err := ctx.RequireArg("name", `--name "My Organization"`)
if err != nil {
return err
}
payload := map[string]interface{}{
"name": name,
}
if d := ctx.Arg("description"); d != "" {
payload["description"] = d
}
env, err := ctx.CallAPI("POST", "/organizations", payload)
if err != nil {
return fmt.Errorf("创建组织失败: %w", err)
}
return ctx.Output(env)
},
},
{
Name: "update",
Description: "Update an organization",
Flags: []common.Flag{
{Name: "id", Short: "i", Usage: "Organization ID", Required: true},
{Name: "name", Short: "n", Usage: "New name"},
{Name: "description", Short: "d", Usage: "New description"},
},
Run: func(ctx *common.RuntimeContext) error {
id, _ := ctx.RequireArg("id", "--id my-org")
body := map[string]interface{}{}
if n := ctx.Arg("name"); n != "" {
body["name"] = n
}
if d := ctx.Arg("description"); d != "" {
body["description"] = d
}
if len(body) == 0 {
return fmt.Errorf("at least one of --name, --description is required")
}
env, err := ctx.CallAPI("PATCH", fmt.Sprintf("/organizations/%s", id), body)
if err != nil {
return err
}
return ctx.Output(env)
},
},
{
Name: "delete",
Description: "Delete an organization",
Flags: []common.Flag{
{Name: "id", Short: "i", Usage: "Organization ID", Required: true},
},
Run: func(ctx *common.RuntimeContext) error {
id, _ := ctx.RequireArg("id", "--id my-org")
env, err := ctx.CallAPI("DELETE", fmt.Sprintf("/organizations/%s", id), nil)
if err != nil {
return err
}
return ctx.Output(env)
},
},
{
Name: "invite",
Description: "Invite a member to a project",
DryRun: true,
DryRunHint: func(ctx *common.RuntimeContext) (string, error) {
userID := ctx.Arg("user-id")
owner := ctx.Arg("owner")
repo := ctx.Arg("repo")
return fmt.Sprintf("Invite user %s to %s/%s", userID, owner, repo), nil
},
Flags: []common.Flag{
{Name: "user-id", Usage: "User ID to invite (required)", Required: true},
{Name: "owner", Short: "o", Usage: "Project owner (e.g., zzx-coder)", Required: true},
{Name: "repo", Short: "r", Usage: "Project repo name (e.g., gitlink-cli)", Required: true},
},
Run: func(ctx *common.RuntimeContext) error {
userID, err := ctx.RequireArg("user-id", "--user-id 42")
if err != nil {
return err
}
uid, err := strconv.Atoi(userID)
if err != nil {
return fmt.Errorf("invalid user-id: %s (must be an integer)", userID)
}
return inviteToOrgProjects(ctx, []int{uid}, false)
},
},
{
Name: "remove-member",
Description: "Remove a member from a project",
DryRun: true,
DryRunHint: func(ctx *common.RuntimeContext) (string, error) {
userID := ctx.Arg("user-id")
owner := ctx.Arg("owner")
repo := ctx.Arg("repo")
return fmt.Sprintf("Remove user %s from %s/%s", userID, owner, repo), nil
},
Flags: []common.Flag{
{Name: "user-id", Usage: "User ID to remove (required)", Required: true},
{Name: "owner", Short: "o", Usage: "Project owner (e.g., zzx-coder)", Required: true},
{Name: "repo", Short: "r", Usage: "Project repo name (e.g., gitlink-cli)", Required: true},
},
Run: func(ctx *common.RuntimeContext) error {
userID, err := ctx.RequireArg("user-id", "--user-id 42")
if err != nil {
return err
}
uid, err := strconv.Atoi(userID)
if err != nil {
return fmt.Errorf("invalid user-id: %s (must be an integer)", userID)
}
return removeFromOrgProjects(ctx, []int{uid}, false)
},
},
}
// 合并批量成员管理命令
shortcuts = append(shortcuts, BatchShortcuts()...)
return shortcuts
}
// inviteToOrgProjects 向指定项目邀请用户
func inviteToOrgProjects(ctx *common.RuntimeContext, userIDs []int, dryRun bool) error {
owner, repo, err := resolveOrgProject(ctx)
if err != nil {
return err
}
results := make([]map[string]interface{}, 0, len(userIDs))
for _, uid := range userIDs {
if dryRun {
results = append(results, map[string]interface{}{
"user_id": uid,
"project": fmt.Sprintf("%s/%s", owner, repo),
"action": "invite",
"status": "would execute (dry-run)",
})
continue
}
body := map[string]interface{}{"user_id": uid}
_, err := ctx.CallAPI("POST", fmt.Sprintf("/%s/%s/collaborators", owner, repo), body)
status := "success"
msg := ""
if err != nil {
status = "failed"
msg = err.Error()
}
results = append(results, map[string]interface{}{
"user_id": uid,
"project": fmt.Sprintf("%s/%s", owner, repo),
"action": "invite",
"status": status,
"message": msg,
})
}
return ctx.OutputData(results)
}
// removeFromOrgProjects 从指定项目移除用户
func removeFromOrgProjects(ctx *common.RuntimeContext, userIDs []int, dryRun bool) error {
owner, repo, err := resolveOrgProject(ctx)
if err != nil {
return err
}
results := make([]map[string]interface{}, 0, len(userIDs))
for _, uid := range userIDs {
if dryRun {
results = append(results, map[string]interface{}{
"user_id": uid,
"project": fmt.Sprintf("%s/%s", owner, repo),
"action": "remove",
"status": "would execute (dry-run)",
})
continue
}
body := map[string]interface{}{"user_id": uid}
_, err := ctx.CallAPI("DELETE", fmt.Sprintf("/%s/%s/collaborators/remove", owner, repo), body)
status := "success"
msg := ""
if err != nil {
status = "failed"
msg = err.Error()
}
results = append(results, map[string]interface{}{
"user_id": uid,
"project": fmt.Sprintf("%s/%s", owner, repo),
"action": "remove",
"status": status,
"message": msg,
})
}
return ctx.OutputData(results)
}
// resolveOrgProject 从 --owner/--repo 解析目标项目
func resolveOrgProject(ctx *common.RuntimeContext) (owner, repo string, err error) {
owner = ctx.Arg("owner")
repo = ctx.Arg("repo")
if owner == "" || repo == "" {
return "", "", fmt.Errorf("must specify --owner and --repo (e.g., --owner zzx-coder --repo gitlink-cli)")
}
return owner, repo, nil
}
// parseUserIDList 解析逗号分隔的用户ID字符串
func parseUserIDList(input string) ([]int, error) {
var ids []int
for _, s := range strings.Split(input, ",") {
s = strings.TrimSpace(s)
if s == "" {
continue
}
uid, err := strconv.Atoi(s)
if err != nil {
return nil, fmt.Errorf("invalid user ID: %s", s)
}
ids = append(ids, uid)
}
if len(ids) == 0 {
return nil, fmt.Errorf("no valid user IDs found")
}
return ids, nil
}