internal/control/org.go

8ff79f104466ce76ae50fb73fd4169750b88deed
gitbay/internal/control/org.go history · blame · raw

216 lines · 6626 bytes

  1package control
  2
  3import (
  4	"errors"
  5	"fmt"
  6	"io"
  7
  8	"gitbay.org/gitbay/internal/policy"
  9	"gitbay.org/gitbay/internal/protocol"
 10	"gitbay.org/gitbay/internal/store"
 11)
 12
 13func init() {
 14	register(Command{Path: []string{"org", "create"},
 15		Summary: "create an organization (you become its first admin): org create <name>", Run: runOrgCreate})
 16	register(Command{Path: []string{"org", "list"},
 17		Summary: "list organizations you belong to", ReadOnly: true, Run: runOrgList})
 18	register(Command{Path: []string{"org", "show"},
 19		Summary: "show an organization and its members: org show <name>", ReadOnly: true, Run: runOrgShow})
 20	register(Command{Path: []string{"org", "delete"},
 21		Summary: "delete an empty organization: org delete <name> --yes", Run: runOrgDelete})
 22	register(Command{Path: []string{"org", "members", "add"},
 23		Summary: "add or update a member: org members add <org> <user> [--role member|admin]", Run: runOrgMembersAdd})
 24	register(Command{Path: []string{"org", "members", "remove"},
 25		Summary: "remove a member: org members remove <org> <user>", Run: runOrgMembersRemove})
 26	register(Command{Path: []string{"org", "members", "list"},
 27		Summary: "list members: org members list <org>", ReadOnly: true, Run: runOrgMembersList})
 28}
 29
 30// orgAdmin loads an org and requires the caller to be one of its admins.
 31func orgAdmin(c *Ctx, name string) (store.Org, int) {
 32	org, err := c.Store.OrgByName(name)
 33	if errors.Is(err, store.ErrNotFound) {
 34		return org, c.fail(protocol.ExitNotFound, "no organization %q", name)
 35	}
 36	if err != nil {
 37		return org, c.fail(protocol.ExitFailure, "%v", err)
 38	}
 39	role, err := c.Store.OrgRole(org.ID, c.User.ID)
 40	if err != nil {
 41		return org, c.fail(protocol.ExitFailure, "%v", err)
 42	}
 43	if role != "admin" {
 44		return org, c.fail(protocol.ExitDenied, "only admins of %s can do that", name)
 45	}
 46	return org, -1
 47}
 48
 49func runOrgCreate(c *Ctx, args []string) int {
 50	if len(args) != 1 {
 51		return c.fail(protocol.ExitUsage, "usage: org create <name>")
 52	}
 53	if err := policy.ValidateOwnerName(args[0]); err != nil {
 54		return c.fail(protocol.ExitUsage, "%v", err)
 55	}
 56	if _, err := c.Store.CreateOrg(args[0], c.User.ID); err != nil {
 57		return c.fail(protocol.ExitFailure, "%v", err)
 58	}
 59	return c.emit(map[string]string{"org": args[0], "role": "admin"}, func(w io.Writer) {
 60		fmt.Fprintf(w, "created organization %s; you are its admin\n", args[0])
 61	})
 62}
 63
 64func runOrgList(c *Ctx, args []string) int {
 65	orgs, err := c.Store.ListOrgsForUser(c.User.ID)
 66	if err != nil {
 67		return c.fail(protocol.ExitFailure, "%v", err)
 68	}
 69	type out struct {
 70		Org  string `json:"org"`
 71		Role string `json:"role"`
 72	}
 73	var ds []out
 74	for _, o := range orgs {
 75		ds = append(ds, out{o.Username, o.Role})
 76	}
 77	return c.emit(ds, func(w io.Writer) {
 78		for _, d := range ds {
 79			fmt.Fprintf(w, "%s\t%s\n", d.Org, d.Role)
 80		}
 81	})
 82}
 83
 84func runOrgShow(c *Ctx, args []string) int {
 85	if len(args) != 1 {
 86		return c.fail(protocol.ExitUsage, "usage: org show <name>")
 87	}
 88	org, err := c.Store.OrgByName(args[0])
 89	if errors.Is(err, store.ErrNotFound) {
 90		return c.fail(protocol.ExitNotFound, "no organization %q", args[0])
 91	}
 92	if err != nil {
 93		return c.fail(protocol.ExitFailure, "%v", err)
 94	}
 95	members, err := c.Store.OrgMembers(org.ID)
 96	if err != nil {
 97		return c.fail(protocol.ExitFailure, "%v", err)
 98	}
 99	type memberOut struct {
100		User string `json:"user"`
101		Role string `json:"role"`
102	}
103	var ms []memberOut
104	for _, m := range members {
105		ms = append(ms, memberOut{m.Username, m.Role})
106	}
107	d := struct {
108		Org     string      `json:"org"`
109		Members []memberOut `json:"members"`
110	}{org.Name, ms}
111	return c.emit(d, func(w io.Writer) {
112		fmt.Fprintf(w, "%s\n", d.Org)
113		for _, m := range ms {
114			fmt.Fprintf(w, "  %s\t%s\n", m.User, m.Role)
115		}
116	})
117}
118
119func runOrgDelete(c *Ctx, args []string) int {
120	var name string
121	yes := false
122	for _, a := range args {
123		if a == "--yes" {
124			yes = true
125		} else if name == "" {
126			name = a
127		} else {
128			return c.fail(protocol.ExitUsage, "usage: org delete <name> --yes")
129		}
130	}
131	if name == "" {
132		return c.fail(protocol.ExitUsage, "usage: org delete <name> --yes")
133	}
134	org, code := orgAdmin(c, name)
135	if code >= 0 {
136		return code
137	}
138	if !yes {
139		return c.fail(protocol.ExitUsage, "org delete is permanent; re-run with --yes")
140	}
141	if err := c.Store.DeleteOrg(org.ID); err != nil {
142		return c.fail(protocol.ExitFailure, "%v", err)
143	}
144	return c.emit(map[string]string{"deleted": name}, func(w io.Writer) {
145		fmt.Fprintf(w, "deleted organization %s\n", name)
146	})
147}
148
149func runOrgMembersAdd(c *Ctx, args []string) int {
150	role := "member"
151	var rest []string
152	for i := 0; i < len(args); i++ {
153		if args[i] == "--role" {
154			if i+1 >= len(args) {
155				return c.fail(protocol.ExitUsage, "--role requires member|admin")
156			}
157			role = args[i+1]
158			i++
159			continue
160		}
161		rest = append(rest, args[i])
162	}
163	if len(rest) != 2 || (role != "member" && role != "admin") {
164		return c.fail(protocol.ExitUsage, "usage: org members add <org> <user> [--role member|admin]")
165	}
166	org, code := orgAdmin(c, rest[0])
167	if code >= 0 {
168		return code
169	}
170	target, err := c.Store.UserByUsername(rest[1])
171	if errors.Is(err, store.ErrNotFound) {
172		return c.fail(protocol.ExitNotFound, "no such user %q", rest[1])
173	}
174	if err != nil {
175		return c.fail(protocol.ExitFailure, "%v", err)
176	}
177	if err := c.Store.SetOrgMember(org.ID, target.ID, role); err != nil {
178		return c.fail(protocol.ExitUsage, "%v", err)
179	}
180	return c.emit(map[string]string{"org": org.Name, "user": target.Username, "role": role}, func(w io.Writer) {
181		fmt.Fprintf(w, "%s is now a %s of %s\n", target.Username, role, org.Name)
182	})
183}
184
185func runOrgMembersRemove(c *Ctx, args []string) int {
186	if len(args) != 2 {
187		return c.fail(protocol.ExitUsage, "usage: org members remove <org> <user>")
188	}
189	org, code := orgAdmin(c, args[0])
190	if code >= 0 {
191		return code
192	}
193	target, err := c.Store.UserByUsername(args[1])
194	if errors.Is(err, store.ErrNotFound) {
195		return c.fail(protocol.ExitNotFound, "no such user %q", args[1])
196	}
197	if err != nil {
198		return c.fail(protocol.ExitFailure, "%v", err)
199	}
200	if err := c.Store.RemoveOrgMember(org.ID, target.ID); err != nil {
201		if errors.Is(err, store.ErrNotFound) {
202			return c.fail(protocol.ExitNotFound, "%s is not a member of %s", target.Username, org.Name)
203		}
204		return c.fail(protocol.ExitUsage, "%v", err)
205	}
206	return c.emit(map[string]string{"org": org.Name, "removed": target.Username}, func(w io.Writer) {
207		fmt.Fprintf(w, "removed %s from %s\n", target.Username, org.Name)
208	})
209}
210
211func runOrgMembersList(c *Ctx, args []string) int {
212	if len(args) != 1 {
213		return c.fail(protocol.ExitUsage, "usage: org members list <org>")
214	}
215	return runOrgShow(c, args)
216}