*: implement add/update org member
This commit is contained in:
parent
8069063e0d
commit
620bae68df
|
@ -17,6 +17,7 @@ package action
|
|||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/sorintlab/agola/internal/datamanager"
|
||||
|
@ -49,12 +50,12 @@ func (h *ActionHandler) CreateOrg(ctx context.Context, org *types.Organization)
|
|||
}
|
||||
|
||||
// check duplicate org name
|
||||
u, err := h.readDB.GetOrgByName(tx, org.Name)
|
||||
o, err := h.readDB.GetOrgByName(tx, org.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if u != nil {
|
||||
return util.NewErrBadRequest(errors.Errorf("org %q already exists", u.Name))
|
||||
if o != nil {
|
||||
return util.NewErrBadRequest(errors.Errorf("org %q already exists", o.Name))
|
||||
}
|
||||
|
||||
if org.CreatorUserID != "" {
|
||||
|
@ -180,3 +181,84 @@ func (h *ActionHandler) DeleteOrg(ctx context.Context, orgRef string) error {
|
|||
_, err = h.dm.WriteWal(ctx, actions, cgt)
|
||||
return err
|
||||
}
|
||||
|
||||
// AddOrgMember add/updates an org member.
|
||||
// TODO(sgotti) handle invitation when implemented
|
||||
func (h *ActionHandler) AddOrgMember(ctx context.Context, orgRef, userRef string, role types.MemberRole) (*types.OrganizationMember, error) {
|
||||
if !types.IsValidMemberRole(role) {
|
||||
return nil, util.NewErrBadRequest(errors.Errorf("invalid role %q", role))
|
||||
}
|
||||
|
||||
var org *types.Organization
|
||||
var user *types.User
|
||||
var orgmember *types.OrganizationMember
|
||||
var cgt *datamanager.ChangeGroupsUpdateToken
|
||||
|
||||
// must do all the checks in a single transaction to avoid concurrent changes
|
||||
err := h.readDB.Do(func(tx *db.Tx) error {
|
||||
var err error
|
||||
// check existing org
|
||||
org, err = h.readDB.GetOrg(tx, orgRef)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if org == nil {
|
||||
return util.NewErrBadRequest(errors.Errorf("org %q doesn't exists", orgRef))
|
||||
}
|
||||
// check existing user
|
||||
user, err = h.readDB.GetUser(tx, userRef)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if user == nil {
|
||||
return util.NewErrBadRequest(errors.Errorf("user %q doesn't exists", userRef))
|
||||
}
|
||||
|
||||
// fetch org member if it already exist
|
||||
orgmember, err = h.readDB.GetOrgMemberByOrgUserID(tx, org.ID, user.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cgNames := []string{util.EncodeSha256Hex(fmt.Sprintf("orgmember-%s-%s", org.ID, user.ID))}
|
||||
cgt, err = h.readDB.GetChangeGroupsUpdateTokens(tx, cgNames)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// update if role changed
|
||||
if orgmember != nil {
|
||||
if orgmember.MemberRole == role {
|
||||
return orgmember, nil
|
||||
}
|
||||
orgmember.MemberRole = role
|
||||
} else {
|
||||
orgmember = &types.OrganizationMember{
|
||||
ID: uuid.NewV4().String(),
|
||||
OrganizationID: org.ID,
|
||||
UserID: user.ID,
|
||||
MemberRole: role,
|
||||
}
|
||||
}
|
||||
|
||||
actions := []*datamanager.Action{}
|
||||
orgmemberj, err := json.Marshal(orgmember)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to marshal project group")
|
||||
}
|
||||
actions = append(actions, &datamanager.Action{
|
||||
ActionType: datamanager.ActionTypePut,
|
||||
DataType: string(types.ConfigTypeOrgMember),
|
||||
ID: orgmember.ID,
|
||||
Data: orgmemberj,
|
||||
})
|
||||
|
||||
_, err = h.dm.WriteWal(ctx, actions, cgt)
|
||||
return orgmember, err
|
||||
}
|
||||
|
|
|
@ -460,6 +460,20 @@ func (c *Client) DeleteOrg(ctx context.Context, orgRef string) (*http.Response,
|
|||
return c.getResponse(ctx, "DELETE", fmt.Sprintf("/orgs/%s", orgRef), nil, jsonContent, nil)
|
||||
}
|
||||
|
||||
func (c *Client) AddOrgMember(ctx context.Context, orgRef, userRef string, role types.MemberRole) (*types.OrganizationMember, *http.Response, error) {
|
||||
req := &AddOrgMemberRequest{
|
||||
Role: role,
|
||||
}
|
||||
omj, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
orgmember := new(types.OrganizationMember)
|
||||
resp, err := c.getParsedResponse(ctx, "PUT", fmt.Sprintf("/orgs/%s/members/%s", orgRef, userRef), nil, jsonContent, bytes.NewReader(omj), orgmember)
|
||||
return orgmember, resp, err
|
||||
}
|
||||
|
||||
func (c *Client) GetOrgs(ctx context.Context, start string, limit int, asc bool) ([]*types.Organization, *http.Response, error) {
|
||||
q := url.Values{}
|
||||
if start != "" {
|
||||
|
|
|
@ -177,3 +177,41 @@ func (h *OrgsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||
h.log.Errorf("err: %+v", err)
|
||||
}
|
||||
}
|
||||
|
||||
type AddOrgMemberRequest struct {
|
||||
Role types.MemberRole
|
||||
}
|
||||
|
||||
type AddOrgMemberHandler struct {
|
||||
log *zap.SugaredLogger
|
||||
ah *action.ActionHandler
|
||||
}
|
||||
|
||||
func NewAddOrgMemberHandler(logger *zap.Logger, ah *action.ActionHandler) *AddOrgMemberHandler {
|
||||
return &AddOrgMemberHandler{log: logger.Sugar(), ah: ah}
|
||||
}
|
||||
|
||||
func (h *AddOrgMemberHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
vars := mux.Vars(r)
|
||||
orgRef := vars["orgref"]
|
||||
userRef := vars["userref"]
|
||||
|
||||
var req AddOrgMemberRequest
|
||||
d := json.NewDecoder(r.Body)
|
||||
if err := d.Decode(&req); err != nil {
|
||||
httpError(w, util.NewErrBadRequest(err))
|
||||
return
|
||||
}
|
||||
|
||||
org, err := h.ah.AddOrgMember(ctx, orgRef, userRef, req.Role)
|
||||
if httpError(w, err) {
|
||||
h.log.Errorf("err: %+v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := httpResponse(w, http.StatusCreated, org); err != nil {
|
||||
h.log.Errorf("err: %+v", err)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -163,6 +163,8 @@ func (s *Configstore) Run(ctx context.Context) error {
|
|||
createOrgHandler := api.NewCreateOrgHandler(logger, s.ah)
|
||||
deleteOrgHandler := api.NewDeleteOrgHandler(logger, s.ah)
|
||||
|
||||
addOrgMemberHandler := api.NewAddOrgMemberHandler(logger, s.ah)
|
||||
|
||||
remoteSourceHandler := api.NewRemoteSourceHandler(logger, s.readDB)
|
||||
remoteSourcesHandler := api.NewRemoteSourcesHandler(logger, s.readDB)
|
||||
createRemoteSourceHandler := api.NewCreateRemoteSourceHandler(logger, s.ah)
|
||||
|
@ -213,6 +215,7 @@ func (s *Configstore) Run(ctx context.Context) error {
|
|||
apirouter.Handle("/orgs", orgsHandler).Methods("GET")
|
||||
apirouter.Handle("/orgs", createOrgHandler).Methods("POST")
|
||||
apirouter.Handle("/orgs/{orgref}", deleteOrgHandler).Methods("DELETE")
|
||||
apirouter.Handle("/orgs/{orgref}/members/{userref}", addOrgMemberHandler).Methods("PUT")
|
||||
|
||||
apirouter.Handle("/remotesources/{remotesourceref}", remoteSourceHandler).Methods("GET")
|
||||
apirouter.Handle("/remotesources", remoteSourcesHandler).Methods("GET")
|
||||
|
|
|
@ -232,6 +232,26 @@ func (r *ReadDB) deleteOrgMember(tx *db.Tx, orgmemberID string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (r *ReadDB) GetOrgMemberByOrgUserID(tx *db.Tx, orgID, userID string) (*types.OrganizationMember, error) {
|
||||
q, args, err := orgmemberSelect.Where(sq.Eq{"orgmember.orgid": orgID, "orgmember.userid": userID}).ToSql()
|
||||
r.log.Debugf("q: %s, args: %s", q, util.Dump(args))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to build query")
|
||||
}
|
||||
|
||||
oms, _, err := fetchOrgMembers(tx, q, args...)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
if len(oms) > 1 {
|
||||
return nil, errors.Errorf("too many rows returned")
|
||||
}
|
||||
if len(oms) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
return oms[0], nil
|
||||
}
|
||||
|
||||
func fetchOrgMembers(tx *db.Tx, q string, args ...interface{}) ([]*types.OrganizationMember, []string, error) {
|
||||
rows, err := tx.Query(q, args...)
|
||||
if err != nil {
|
||||
|
|
|
@ -100,3 +100,39 @@ func (h *ActionHandler) DeleteOrg(ctx context.Context, orgRef string) error {
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type AddOrgMemberResponse struct {
|
||||
OrganizationMember *types.OrganizationMember
|
||||
Org *types.Organization
|
||||
User *types.User
|
||||
}
|
||||
|
||||
func (h *ActionHandler) AddOrgMember(ctx context.Context, orgRef, userRef string, role types.MemberRole) (*AddOrgMemberResponse, error) {
|
||||
org, resp, err := h.configstoreClient.GetOrg(ctx, orgRef)
|
||||
if err != nil {
|
||||
return nil, ErrFromRemote(resp, err)
|
||||
}
|
||||
user, resp, err := h.configstoreClient.GetUser(ctx, userRef)
|
||||
if err != nil {
|
||||
return nil, ErrFromRemote(resp, err)
|
||||
}
|
||||
|
||||
isOrgOwner, err := h.IsOrgOwner(ctx, org.ID)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to determine ownership")
|
||||
}
|
||||
if !isOrgOwner {
|
||||
return nil, util.NewErrForbidden(errors.Errorf("user not authorized"))
|
||||
}
|
||||
|
||||
orgmember, resp, err := h.configstoreClient.AddOrgMember(ctx, orgRef, userRef, role)
|
||||
if err != nil {
|
||||
return nil, ErrFromRemote(resp, errors.Wrapf(err, "failed to add/update organization member"))
|
||||
}
|
||||
|
||||
return &AddOrgMemberResponse{
|
||||
OrganizationMember: orgmember,
|
||||
Org: org,
|
||||
User: user,
|
||||
}, nil
|
||||
}
|
||||
|
|
|
@ -195,3 +195,56 @@ func (h *OrgsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||
h.log.Errorf("err: %+v", err)
|
||||
}
|
||||
}
|
||||
|
||||
type OrgMemberResponse struct {
|
||||
Organization *OrgResponse `json:"organization,omitempty"`
|
||||
User *UserResponse `json:"user,omitempty"`
|
||||
Role types.MemberRole `json:"role,omitempty"`
|
||||
}
|
||||
|
||||
func createOrgMemberResponse(org *types.Organization, user *types.User, role types.MemberRole) *OrgMemberResponse {
|
||||
return &OrgMemberResponse{
|
||||
Organization: createOrgResponse(org),
|
||||
User: createUserResponse(user),
|
||||
Role: role,
|
||||
}
|
||||
}
|
||||
|
||||
type AddOrgMemberRequest struct {
|
||||
Role types.MemberRole `json:"role"`
|
||||
}
|
||||
|
||||
type AddOrgMemberHandler struct {
|
||||
log *zap.SugaredLogger
|
||||
ah *action.ActionHandler
|
||||
}
|
||||
|
||||
func NewAddOrgMemberHandler(logger *zap.Logger, ah *action.ActionHandler) *AddOrgMemberHandler {
|
||||
return &AddOrgMemberHandler{log: logger.Sugar(), ah: ah}
|
||||
}
|
||||
|
||||
func (h *AddOrgMemberHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
vars := mux.Vars(r)
|
||||
orgRef := vars["orgref"]
|
||||
userRef := vars["userref"]
|
||||
|
||||
var req AddOrgMemberRequest
|
||||
d := json.NewDecoder(r.Body)
|
||||
if err := d.Decode(&req); err != nil {
|
||||
httpError(w, util.NewErrBadRequest(err))
|
||||
return
|
||||
}
|
||||
|
||||
ares, err := h.ah.AddOrgMember(ctx, orgRef, userRef, req.Role)
|
||||
if httpError(w, err) {
|
||||
h.log.Errorf("err: %+v", err)
|
||||
return
|
||||
}
|
||||
|
||||
res := createOrgMemberResponse(ares.Org, ares.User, ares.OrganizationMember.MemberRole)
|
||||
if err := httpResponse(w, http.StatusOK, res); err != nil {
|
||||
h.log.Errorf("err: %+v", err)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -188,6 +188,8 @@ func (g *Gateway) Run(ctx context.Context) error {
|
|||
createOrgHandler := api.NewCreateOrgHandler(logger, g.ah)
|
||||
deleteOrgHandler := api.NewDeleteOrgHandler(logger, g.ah)
|
||||
|
||||
addOrgMemberHandler := api.NewAddOrgMemberHandler(logger, g.ah)
|
||||
|
||||
runHandler := api.NewRunHandler(logger, g.ah)
|
||||
runsHandler := api.NewRunsHandler(logger, g.ah)
|
||||
runtaskHandler := api.NewRuntaskHandler(logger, g.ah)
|
||||
|
@ -261,6 +263,7 @@ func (g *Gateway) Run(ctx context.Context) error {
|
|||
apirouter.Handle("/orgs", authForcedHandler(orgsHandler)).Methods("GET")
|
||||
apirouter.Handle("/orgs", authForcedHandler(createOrgHandler)).Methods("POST")
|
||||
apirouter.Handle("/orgs/{orgref}", authForcedHandler(deleteOrgHandler)).Methods("DELETE")
|
||||
apirouter.Handle("/orgs/{orgref}/members/{userref}", authForcedHandler(addOrgMemberHandler)).Methods("PUT")
|
||||
|
||||
apirouter.Handle("/runs/{runid}", authForcedHandler(runHandler)).Methods("GET")
|
||||
apirouter.Handle("/runs/{runid}/actions", authForcedHandler(runActionsHandler)).Methods("PUT")
|
||||
|
|
|
@ -59,6 +59,16 @@ const (
|
|||
MemberRoleMember MemberRole = "member"
|
||||
)
|
||||
|
||||
func IsValidMemberRole(r MemberRole) bool {
|
||||
switch r {
|
||||
case MemberRoleOwner:
|
||||
case MemberRoleMember:
|
||||
default:
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
type Parent struct {
|
||||
Type ConfigType `json:"type,omitempty"`
|
||||
ID string `json:"id,omitempty"`
|
||||
|
|
Loading…
Reference in New Issue