187 lines
4.9 KiB
Go
187 lines
4.9 KiB
Go
|
|
package cmd
|
||
|
|
|
||
|
|
import (
|
||
|
|
"fmt"
|
||
|
|
|
||
|
|
"code.gitea.io/sdk/gitea"
|
||
|
|
"forgejo.zerova.net/public/fgj-sid/internal/api"
|
||
|
|
"forgejo.zerova.net/public/fgj-sid/internal/config"
|
||
|
|
"github.com/spf13/cobra"
|
||
|
|
)
|
||
|
|
|
||
|
|
var orgCmd = &cobra.Command{
|
||
|
|
Use: "org",
|
||
|
|
Aliases: []string{"organization", "organizations"},
|
||
|
|
Short: "Manage organizations",
|
||
|
|
Long: "List, create, and delete organizations on the current host.",
|
||
|
|
}
|
||
|
|
|
||
|
|
var orgListCmd = &cobra.Command{
|
||
|
|
Use: "list",
|
||
|
|
Aliases: []string{"ls"},
|
||
|
|
Short: "List organizations",
|
||
|
|
Long: "List organizations the authenticated user is a member of.",
|
||
|
|
Example: ` # List your organizations
|
||
|
|
fgj org list
|
||
|
|
|
||
|
|
# Output as JSON
|
||
|
|
fgj org list --json`,
|
||
|
|
RunE: runOrgList,
|
||
|
|
}
|
||
|
|
|
||
|
|
var orgCreateCmd = &cobra.Command{
|
||
|
|
Use: "create <name>",
|
||
|
|
Short: "Create an organization",
|
||
|
|
Long: "Create a new organization. You become the initial owner.",
|
||
|
|
Example: ` # Create an organization
|
||
|
|
fgj org create my-org
|
||
|
|
|
||
|
|
# Create with description and visibility
|
||
|
|
fgj org create my-org --description "Internal tooling" --visibility private`,
|
||
|
|
Args: cobra.ExactArgs(1),
|
||
|
|
RunE: runOrgCreate,
|
||
|
|
}
|
||
|
|
|
||
|
|
var orgDeleteCmd = &cobra.Command{
|
||
|
|
Use: "delete <name>",
|
||
|
|
Aliases: []string{"rm"},
|
||
|
|
Short: "Delete an organization",
|
||
|
|
Long: "Delete an organization. This is irreversible and removes all the organization's repositories.",
|
||
|
|
Args: cobra.ExactArgs(1),
|
||
|
|
RunE: runOrgDelete,
|
||
|
|
}
|
||
|
|
|
||
|
|
func init() {
|
||
|
|
rootCmd.AddCommand(orgCmd)
|
||
|
|
orgCmd.AddCommand(orgListCmd)
|
||
|
|
orgCmd.AddCommand(orgCreateCmd)
|
||
|
|
orgCmd.AddCommand(orgDeleteCmd)
|
||
|
|
|
||
|
|
orgListCmd.Flags().IntP("limit", "L", 50, "Maximum number of organizations to list")
|
||
|
|
addJSONFlags(orgListCmd, "Output as JSON")
|
||
|
|
|
||
|
|
orgCreateCmd.Flags().String("description", "", "Organization description")
|
||
|
|
orgCreateCmd.Flags().String("full-name", "", "Full display name")
|
||
|
|
orgCreateCmd.Flags().String("website", "", "Website URL")
|
||
|
|
orgCreateCmd.Flags().String("location", "", "Location")
|
||
|
|
orgCreateCmd.Flags().String("visibility", "public", "Visibility: public, limited, or private")
|
||
|
|
|
||
|
|
orgDeleteCmd.Flags().BoolP("yes", "y", false, "Skip confirmation prompt")
|
||
|
|
}
|
||
|
|
|
||
|
|
func runOrgList(cmd *cobra.Command, args []string) error {
|
||
|
|
client, err := loadClient()
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
|
||
|
|
limit, _ := cmd.Flags().GetInt("limit")
|
||
|
|
if limit <= 0 {
|
||
|
|
limit = 50
|
||
|
|
}
|
||
|
|
|
||
|
|
orgs, _, err := client.ListMyOrgs(gitea.ListOrgsOptions{
|
||
|
|
ListOptions: gitea.ListOptions{PageSize: limit},
|
||
|
|
})
|
||
|
|
if err != nil {
|
||
|
|
return fmt.Errorf("failed to list organizations: %w", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
if wantJSON(cmd) {
|
||
|
|
return outputJSON(cmd, orgs)
|
||
|
|
}
|
||
|
|
|
||
|
|
if len(orgs) == 0 {
|
||
|
|
fmt.Fprintln(ios.Out, "No organizations found.")
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
tp := ios.NewTablePrinter()
|
||
|
|
tp.AddHeader("NAME", "FULL NAME", "VISIBILITY", "DESCRIPTION")
|
||
|
|
for _, o := range orgs {
|
||
|
|
tp.AddRow(o.UserName, o.FullName, string(o.Visibility), o.Description)
|
||
|
|
}
|
||
|
|
return tp.Render()
|
||
|
|
}
|
||
|
|
|
||
|
|
func runOrgCreate(cmd *cobra.Command, args []string) error {
|
||
|
|
client, err := loadClient()
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
|
||
|
|
name := args[0]
|
||
|
|
desc, _ := cmd.Flags().GetString("description")
|
||
|
|
fullName, _ := cmd.Flags().GetString("full-name")
|
||
|
|
website, _ := cmd.Flags().GetString("website")
|
||
|
|
location, _ := cmd.Flags().GetString("location")
|
||
|
|
visStr, _ := cmd.Flags().GetString("visibility")
|
||
|
|
|
||
|
|
var vis gitea.VisibleType
|
||
|
|
switch visStr {
|
||
|
|
case "public", "":
|
||
|
|
vis = gitea.VisibleTypePublic
|
||
|
|
case "limited":
|
||
|
|
vis = gitea.VisibleTypeLimited
|
||
|
|
case "private":
|
||
|
|
vis = gitea.VisibleTypePrivate
|
||
|
|
default:
|
||
|
|
return fmt.Errorf("invalid visibility %q (must be public, limited, or private)", visStr)
|
||
|
|
}
|
||
|
|
|
||
|
|
org, _, err := client.CreateOrg(gitea.CreateOrgOption{
|
||
|
|
Name: name,
|
||
|
|
FullName: fullName,
|
||
|
|
Description: desc,
|
||
|
|
Website: website,
|
||
|
|
Location: location,
|
||
|
|
Visibility: vis,
|
||
|
|
})
|
||
|
|
if err != nil {
|
||
|
|
return fmt.Errorf("failed to create organization %q: %w", name, err)
|
||
|
|
}
|
||
|
|
|
||
|
|
cs := ios.ColorScheme()
|
||
|
|
fmt.Fprintf(ios.Out, "%s Created organization %q\n", cs.SuccessIcon(), org.UserName)
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func runOrgDelete(cmd *cobra.Command, args []string) error {
|
||
|
|
client, err := loadClient()
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
|
||
|
|
name := args[0]
|
||
|
|
skipConfirm, _ := cmd.Flags().GetBool("yes")
|
||
|
|
|
||
|
|
if !skipConfirm && ios.IsStdinTTY() {
|
||
|
|
answer, err := promptLine(fmt.Sprintf("Delete organization %q? This is irreversible and deletes all repositories. [y/N]: ", name))
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
if answer != "y" && answer != "Y" && answer != "yes" {
|
||
|
|
fmt.Fprintln(ios.ErrOut, "Cancelled.")
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if _, err := client.DeleteOrg(name); err != nil {
|
||
|
|
return fmt.Errorf("failed to delete organization %q: %w", name, err)
|
||
|
|
}
|
||
|
|
|
||
|
|
cs := ios.ColorScheme()
|
||
|
|
fmt.Fprintf(ios.Out, "%s Deleted organization %q\n", cs.SuccessIcon(), name)
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// loadClient constructs an api.Client from config without requiring a repo context.
|
||
|
|
// Use this for commands that operate on the host itself (orgs, notifications, user).
|
||
|
|
func loadClient() (*api.Client, error) {
|
||
|
|
cfg, err := config.Load()
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
return api.NewClientFromConfig(cfg, "", getDetectedHost(), getCwd())
|
||
|
|
}
|