forked from Gitlink/gitlink-cli
316 lines
9.0 KiB
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
|
|
}
|