SMQ-3032 - Remove view invitations endpoint (#3042)
Continuous Delivery / Build and Push (push) Has been cancelled
Check the consistency of generated files / check-generated-files (push) Has been cancelled
Check License Header / check-license (push) Has been cancelled
Deploy GitHub Pages / swagger-ui (push) Has been cancelled

Signed-off-by: Felix Gateru <felix.gateru@gmail.com>
This commit is contained in:
Felix Gateru
2025-08-07 21:31:15 +03:00
committed by GitHub
parent 0f20663cfb
commit a0b3970ad6
16 changed files with 26 additions and 346 deletions
+2 -2
View File
@@ -263,8 +263,8 @@ func decodeAcceptInvitationReq(_ context.Context, r *http.Request) (interface{},
return req, nil
}
func decodeInvitationReq(_ context.Context, r *http.Request) (interface{}, error) {
req := invitationReq{
func decodeDeleteInvitationReq(_ context.Context, r *http.Request) (interface{}, error) {
req := deleteInvitationReq{
userID: chi.URLParam(r, "userID"),
domainID: chi.URLParam(r, "domainID"),
}
+1 -23
View File
@@ -201,28 +201,6 @@ func sendInvitationEndpoint(svc domains.Service) endpoint.Endpoint {
}
}
func viewInvitationEndpoint(svc domains.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(invitationReq)
if err := req.validate(); err != nil {
return nil, errors.Wrap(apiutil.ErrValidation, err)
}
session, ok := ctx.Value(api.SessionKey).(authn.Session)
if !ok {
return nil, svcerr.ErrAuthorization
}
session.DomainID = req.domainID
invitation, err := svc.ViewInvitation(ctx, session, req.userID, req.domainID)
if err != nil {
return nil, err
}
return viewInvitationRes{
Invitation: invitation,
}, nil
}
}
func listDomainInvitationsEndpoint(svc domains.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(listInvitationsReq)
@@ -311,7 +289,7 @@ func rejectInvitationEndpoint(svc domains.Service) endpoint.Endpoint {
func deleteInvitationEndpoint(svc domains.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(invitationReq)
req := request.(deleteInvitationReq)
if err := req.validate(); err != nil {
return nil, errors.Wrap(apiutil.ErrValidation, err)
}
-85
View File
@@ -1457,91 +1457,6 @@ func TestListUserInvitations(t *testing.T) {
}
}
func TestViewInvitation(t *testing.T) {
is, svc, auth := newDomainsServer()
cases := []struct {
desc string
token string
session authn.Session
domainID string
userID string
contentType string
status int
svcErr error
authnErr error
}{
{
desc: "view invitation with valid request",
token: validToken,
userID: validID,
domainID: domainID,
status: http.StatusOK,
contentType: contentType,
svcErr: nil,
},
{
desc: "view invitation with invalid token",
token: "",
userID: validID,
domainID: domainID,
status: http.StatusUnauthorized,
contentType: contentType,
svcErr: nil,
},
{
desc: "view invitation with service error",
token: validToken,
userID: validID,
domainID: domainID,
status: http.StatusBadRequest,
contentType: contentType,
svcErr: svcerr.ErrViewEntity,
},
{
desc: "view invitation with empty domain",
token: validToken,
userID: validID,
domainID: "",
status: http.StatusBadRequest,
contentType: contentType,
svcErr: nil,
},
{
desc: "view invitation with empty invitee_user_id and domain_id",
token: validToken,
userID: "",
domainID: "",
status: http.StatusBadRequest,
contentType: contentType,
svcErr: nil,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
if tc.token == validToken {
tc.session = authn.Session{UserID: userID, DomainID: domainID, DomainUserID: domainID + "_" + userID}
}
authnCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authnErr)
repoCall := svc.On("ViewInvitation", mock.Anything, tc.session, tc.userID, tc.domainID).Return(domains.Invitation{}, tc.svcErr)
req := testRequest{
client: is.Client(),
method: http.MethodGet,
url: fmt.Sprintf("%s/domains/%s/invitations/%s", is.URL, tc.domainID, tc.userID),
token: tc.token,
contentType: tc.contentType,
}
res, err := req.make()
assert.Nil(t, err, tc.desc)
assert.Equal(t, tc.status, res.StatusCode, tc.desc)
repoCall.Unset()
authnCall.Unset()
})
}
}
func TestDeleteInvitation(t *testing.T) {
is, svc, auth := newDomainsServer()
+2 -2
View File
@@ -146,12 +146,12 @@ func (req *acceptInvitationReq) validate() error {
return nil
}
type invitationReq struct {
type deleteInvitationReq struct {
userID string
domainID string
}
func (req *invitationReq) validate() error {
func (req *deleteInvitationReq) validate() error {
if req.userID == "" {
return apiutil.ErrMissingID
}
+1 -7
View File
@@ -102,15 +102,9 @@ func MakeHandler(svc domains.Service, authn authn.Authentication, mux *chi.Mux,
opts...,
), "list_domain_invitations").ServeHTTP)
r.Route("/{userID}", func(r chi.Router) {
r.Get("/", otelhttp.NewHandler(kithttp.NewServer(
viewInvitationEndpoint(svc),
decodeInvitationReq,
api.EncodeResponse,
opts...,
), "view_invitation").ServeHTTP)
r.Delete("/", otelhttp.NewHandler(kithttp.NewServer(
deleteInvitationEndpoint(svc),
decodeInvitationReq,
decodeDeleteInvitationReq,
api.EncodeResponse,
opts...,
), "delete_invitation").ServeHTTP)
-8
View File
@@ -193,14 +193,6 @@ type Service interface {
// Only domain administrators and platform administrators can send invitations.
SendInvitation(ctx context.Context, session authn.Session, invitation Invitation) (err error)
// ViewInvitation returns an invitation.
// People who can view invitations are:
// - the invited user: they can view their own invitations
// - the user who sent the invitation
// - domain administrators
// - platform administrators
ViewInvitation(ctx context.Context, session authn.Session, inviteeUserID, domainID string) (invitation Invitation, err error)
// ListInvitations returns a list of invitations.
// By default, it will list invitations the current user has received.
ListInvitations(ctx context.Context, session authn.Session, page InvitationPageMeta) (invitations InvitationPage, err error)
-24
View File
@@ -27,7 +27,6 @@ const (
invitationReject = invitationPrefix + "reject"
invitationList = invitationPrefix + "list"
invitationListDomain = invitationPrefix + "list_domain"
invitationRetrieve = invitationPrefix + "retrieve"
invitationDelete = invitationPrefix + "delete"
)
@@ -41,7 +40,6 @@ var (
_ events.Event = (*freezeDomainEvent)(nil)
_ events.Event = (*listDomainsEvent)(nil)
_ events.Event = (*sendInvitationEvent)(nil)
_ events.Event = (*viewInvitationEvent)(nil)
_ events.Event = (*listInvitationsEvent)(nil)
_ events.Event = (*listDomainInvitationsEvent)(nil)
_ events.Event = (*acceptInvitationEvent)(nil)
@@ -324,28 +322,6 @@ func (sie sendInvitationEvent) Encode() (map[string]interface{}, error) {
return val, nil
}
type viewInvitationEvent struct {
inviteeUserID string
domainID string
roleID string
roleName string
session authn.Session
}
func (vie viewInvitationEvent) Encode() (map[string]interface{}, error) {
val := map[string]interface{}{
"operation": invitationRetrieve,
"invitee_user_id": vie.inviteeUserID,
"domain_id": vie.domainID,
"role_id": vie.roleID,
"role_name": vie.roleName,
"token_type": vie.session.Type.String(),
"super_admin": vie.session.SuperAdmin,
}
return val, nil
}
type listInvitationsEvent struct {
domains.InvitationPageMeta
session authn.Session
-22
View File
@@ -29,7 +29,6 @@ const (
rejectInvitationStream = supermqPrefix + invitationReject
listInvitationsStream = supermqPrefix + invitationList
listDomainInvitationsStream = supermqPrefix + invitationListDomain
retrieveInvitationStream = supermqPrefix + invitationRetrieve
deleteInvitationStream = supermqPrefix + invitationDelete
)
@@ -214,27 +213,6 @@ func (es *eventStore) SendInvitation(ctx context.Context, session authn.Session,
return es.Publish(ctx, sendInvitationStream, event)
}
func (es *eventStore) ViewInvitation(ctx context.Context, session authn.Session, userID, domainID string) (domains.Invitation, error) {
invitation, err := es.svc.ViewInvitation(ctx, session, userID, domainID)
if err != nil {
return invitation, err
}
event := viewInvitationEvent{
inviteeUserID: userID,
domainID: domainID,
roleID: invitation.RoleID,
roleName: invitation.RoleName,
session: session,
}
if err := es.Publish(ctx, retrieveInvitationStream, event); err != nil {
return invitation, err
}
return invitation, nil
}
func (es *eventStore) ListInvitations(ctx context.Context, session authn.Session, pm domains.InvitationPageMeta) (domains.InvitationPage, error) {
ip, err := es.svc.ListInvitations(ctx, session, pm)
if err != nil {
-19
View File
@@ -206,25 +206,6 @@ func (am *authorizationMiddleware) SendInvitation(ctx context.Context, session a
return am.svc.SendInvitation(ctx, session, invitation)
}
func (am *authorizationMiddleware) ViewInvitation(ctx context.Context, session authn.Session, inviteeUserID, domain string) (invitation domains.Invitation, err error) {
session.DomainUserID = auth.EncodeDomainUserID(session.DomainID, session.UserID)
if session.UserID != inviteeUserID {
if err := am.checkAdmin(ctx, session); err != nil {
return domains.Invitation{}, err
}
}
params := map[string]any{
"invitee_user_id": inviteeUserID,
"domain": domain,
}
if err := am.callOut(ctx, session, domains.OpViewInvitation.String(domains.OperationNames), params); err != nil {
return domains.Invitation{}, err
}
return am.svc.ViewInvitation(ctx, session, inviteeUserID, domain)
}
func (am *authorizationMiddleware) ListInvitations(ctx context.Context, session authn.Session, page domains.InvitationPageMeta) (invs domains.InvitationPage, err error) {
params := map[string]any{
"page": page,
-17
View File
@@ -192,23 +192,6 @@ func (lm *loggingMiddleware) SendInvitation(ctx context.Context, session authn.S
return lm.svc.SendInvitation(ctx, session, invitation)
}
func (lm *loggingMiddleware) ViewInvitation(ctx context.Context, session authn.Session, inviteeUserID, domainID string) (invitation domains.Invitation, err error) {
defer func(begin time.Time) {
args := []any{
slog.String("duration", time.Since(begin).String()),
slog.String("invitee_user_id", inviteeUserID),
slog.String("domain_id", domainID),
}
if err != nil {
args = append(args, slog.String("error", err.Error()))
lm.logger.Warn("View invitation failed", args...)
return
}
lm.logger.Info("View invitation completed successfully", args...)
}(time.Now())
return lm.svc.ViewInvitation(ctx, session, inviteeUserID, domainID)
}
func (lm *loggingMiddleware) ListInvitations(ctx context.Context, session authn.Session, pm domains.InvitationPageMeta) (invs domains.InvitationPage, err error) {
defer func(begin time.Time) {
args := []any{
-8
View File
@@ -101,14 +101,6 @@ func (mm *metricsMiddleware) SendInvitation(ctx context.Context, session authn.S
return mm.svc.SendInvitation(ctx, session, invitation)
}
func (mm *metricsMiddleware) ViewInvitation(ctx context.Context, session authn.Session, userID, domainID string) (invitation domains.Invitation, err error) {
defer func(begin time.Time) {
mm.counter.With("method", "view_invitation").Add(1)
mm.latency.With("method", "view_invitation").Observe(time.Since(begin).Seconds())
}(time.Now())
return mm.svc.ViewInvitation(ctx, session, userID, domainID)
}
func (mm *metricsMiddleware) ListInvitations(ctx context.Context, session authn.Session, pm domains.InvitationPageMeta) (invs domains.InvitationPage, err error) {
defer func(begin time.Time) {
mm.counter.With("method", "list_invitations").Add(1)
+18 -24
View File
@@ -2459,47 +2459,46 @@ func (_c *Service_UpdateRoleName_Call) RunAndReturn(run func(ctx context.Context
return _c
}
// ViewInvitation provides a mock function for the type Service
func (_mock *Service) ViewInvitation(ctx context.Context, session authn.Session, inviteeUserID string, domainID string) (domains.Invitation, error) {
ret := _mock.Called(ctx, session, inviteeUserID, domainID)
// ViewDomainInvitation provides a mock function for the type Service
func (_mock *Service) ViewDomainInvitation(ctx context.Context, session authn.Session, inviteeUserID string) (domains.Invitation, error) {
ret := _mock.Called(ctx, session, inviteeUserID)
if len(ret) == 0 {
panic("no return value specified for ViewInvitation")
panic("no return value specified for ViewDomainInvitation")
}
var r0 domains.Invitation
var r1 error
if returnFunc, ok := ret.Get(0).(func(context.Context, authn.Session, string, string) (domains.Invitation, error)); ok {
return returnFunc(ctx, session, inviteeUserID, domainID)
if returnFunc, ok := ret.Get(0).(func(context.Context, authn.Session, string) (domains.Invitation, error)); ok {
return returnFunc(ctx, session, inviteeUserID)
}
if returnFunc, ok := ret.Get(0).(func(context.Context, authn.Session, string, string) domains.Invitation); ok {
r0 = returnFunc(ctx, session, inviteeUserID, domainID)
if returnFunc, ok := ret.Get(0).(func(context.Context, authn.Session, string) domains.Invitation); ok {
r0 = returnFunc(ctx, session, inviteeUserID)
} else {
r0 = ret.Get(0).(domains.Invitation)
}
if returnFunc, ok := ret.Get(1).(func(context.Context, authn.Session, string, string) error); ok {
r1 = returnFunc(ctx, session, inviteeUserID, domainID)
if returnFunc, ok := ret.Get(1).(func(context.Context, authn.Session, string) error); ok {
r1 = returnFunc(ctx, session, inviteeUserID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Service_ViewInvitation_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ViewInvitation'
type Service_ViewInvitation_Call struct {
// Service_ViewDomainInvitation_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ViewDomainInvitation'
type Service_ViewDomainInvitation_Call struct {
*mock.Call
}
// ViewInvitation is a helper method to define mock.On call
// ViewDomainInvitation is a helper method to define mock.On call
// - ctx context.Context
// - session authn.Session
// - inviteeUserID string
// - domainID string
func (_e *Service_Expecter) ViewInvitation(ctx interface{}, session interface{}, inviteeUserID interface{}, domainID interface{}) *Service_ViewInvitation_Call {
return &Service_ViewInvitation_Call{Call: _e.mock.On("ViewInvitation", ctx, session, inviteeUserID, domainID)}
func (_e *Service_Expecter) ViewDomainInvitation(ctx interface{}, session interface{}, inviteeUserID interface{}) *Service_ViewDomainInvitation_Call {
return &Service_ViewDomainInvitation_Call{Call: _e.mock.On("ViewDomainInvitation", ctx, session, inviteeUserID)}
}
func (_c *Service_ViewInvitation_Call) Run(run func(ctx context.Context, session authn.Session, inviteeUserID string, domainID string)) *Service_ViewInvitation_Call {
func (_c *Service_ViewDomainInvitation_Call) Run(run func(ctx context.Context, session authn.Session, inviteeUserID string)) *Service_ViewDomainInvitation_Call {
_c.Call.Run(func(args mock.Arguments) {
var arg0 context.Context
if args[0] != nil {
@@ -2513,26 +2512,21 @@ func (_c *Service_ViewInvitation_Call) Run(run func(ctx context.Context, session
if args[2] != nil {
arg2 = args[2].(string)
}
var arg3 string
if args[3] != nil {
arg3 = args[3].(string)
}
run(
arg0,
arg1,
arg2,
arg3,
)
})
return _c
}
func (_c *Service_ViewInvitation_Call) Return(invitation domains.Invitation, err error) *Service_ViewInvitation_Call {
func (_c *Service_ViewDomainInvitation_Call) Return(invitation domains.Invitation, err error) *Service_ViewDomainInvitation_Call {
_c.Call.Return(invitation, err)
return _c
}
func (_c *Service_ViewInvitation_Call) RunAndReturn(run func(ctx context.Context, session authn.Session, inviteeUserID string, domainID string) (domains.Invitation, error)) *Service_ViewInvitation_Call {
func (_c *Service_ViewDomainInvitation_Call) RunAndReturn(run func(ctx context.Context, session authn.Session, inviteeUserID string) (domains.Invitation, error)) *Service_ViewDomainInvitation_Call {
_c.Call.Return(run)
return _c
}
+2 -2
View File
@@ -18,7 +18,7 @@ const (
OpCreateDomain
OpFreezeDomain
OpListDomains
OpViewInvitation
OpViewDomainInvitation
OpListInvitations
OpListDomainInvitations
OpRejectInvitation
@@ -42,7 +42,7 @@ var OperationNames = []string{
"OpCreateDomain",
"OpFreezeDomain",
"OpListDomains",
"OpViewInvitation",
"OpViewDomainInvitation",
"OpListInvitations",
"OpListDomainInvitations",
"OpRejectInvitation",
-19
View File
@@ -224,25 +224,6 @@ func (svc *service) resendInvitation(ctx context.Context, invitation Invitation)
return nil
}
func (svc *service) ViewInvitation(ctx context.Context, session authn.Session, inviteeUserID, domainID string) (invitation Invitation, err error) {
inv, err := svc.repo.RetrieveInvitation(ctx, inviteeUserID, domainID)
if err != nil {
return Invitation{}, errors.Wrap(svcerr.ErrViewEntity, err)
}
role, err := svc.repo.RetrieveRole(ctx, inv.RoleID)
if err != nil {
return Invitation{}, errors.Wrap(svcerr.ErrViewEntity, err)
}
actions, err := svc.repo.RoleListActions(ctx, inv.RoleID)
if err != nil {
return Invitation{}, errors.Wrap(svcerr.ErrViewEntity, err)
}
inv.Actions = actions
inv.RoleName = role.Name
return inv, nil
}
func (svc *service) ListInvitations(ctx context.Context, session authn.Session, page InvitationPageMeta) (invitations InvitationPage, err error) {
page.InviteeUserID = session.UserID
ip, err := svc.repo.RetrieveAllInvitations(ctx, page)
-74
View File
@@ -709,80 +709,6 @@ func TestSendInvitation(t *testing.T) {
}
}
func TestViewInvitation(t *testing.T) {
svc := newService()
validInvitation := domains.Invitation{
InvitedBy: testsutil.GenerateUUID(t),
InviteeUserID: testsutil.GenerateUUID(t),
DomainID: testsutil.GenerateUUID(t),
RoleID: testsutil.GenerateUUID(t),
Actions: []string{"read", "delete"},
CreatedAt: time.Now().Add(-time.Hour),
UpdatedAt: time.Now().Add(-time.Hour),
ConfirmedAt: time.Now().Add(-time.Hour),
}
cases := []struct {
desc string
userID string
domainID string
session authn.Session
req domains.Invitation
resp domains.Invitation
retrieveInvitationErr error
listRolesErr error
retrieveRoleErr error
err error
}{
{
desc: "view invitation successful",
userID: validInvitation.InviteeUserID,
domainID: validInvitation.DomainID,
session: validSession,
resp: validInvitation,
err: nil,
},
{
desc: "view invitation with error retrieving invitation",
userID: validInvitation.InviteeUserID,
domainID: validInvitation.DomainID,
session: validSession,
retrieveInvitationErr: repoerr.ErrNotFound,
err: svcerr.ErrViewEntity,
},
{
desc: "view invitation with failed to retrieve role actions",
userID: validInvitation.InviteeUserID,
domainID: validInvitation.DomainID,
session: validSession,
listRolesErr: repoerr.ErrNotFound,
err: svcerr.ErrViewEntity,
},
{
desc: "view invitation with failed to retrieve role",
userID: validInvitation.InviteeUserID,
domainID: validInvitation.DomainID,
session: validSession,
retrieveRoleErr: repoerr.ErrNotFound,
err: svcerr.ErrViewEntity,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
repoCall := drepo.On("RetrieveInvitation", context.Background(), mock.Anything, mock.Anything).Return(tc.resp, tc.retrieveInvitationErr)
repoCall1 := drepo.On("RoleListActions", context.Background(), tc.resp.RoleID).Return(tc.resp.Actions, tc.listRolesErr)
repoCall2 := drepo.On("RetrieveRole", context.Background(), tc.resp.RoleID).Return(roles.Role{}, tc.retrieveRoleErr)
inv, err := svc.ViewInvitation(context.Background(), tc.session, tc.userID, tc.domainID)
assert.True(t, errors.Contains(err, tc.err))
assert.Equal(t, tc.resp, inv, tc.desc)
repoCall.Unset()
repoCall1.Unset()
repoCall2.Unset()
})
}
}
func TestListInvitations(t *testing.T) {
svc := newService()
-10
View File
@@ -93,16 +93,6 @@ func (tm *tracingMiddleware) SendInvitation(ctx context.Context, session authn.S
return tm.svc.SendInvitation(ctx, session, invitation)
}
func (tm *tracingMiddleware) ViewInvitation(ctx context.Context, session authn.Session, inviteeUserID, domain string) (invitation domains.Invitation, err error) {
ctx, span := tracing.StartSpan(ctx, tm.tracer, "view_invitation", trace.WithAttributes(
attribute.String("invitee_user_id", inviteeUserID),
attribute.String("domain_id", domain),
))
defer span.End()
return tm.svc.ViewInvitation(ctx, session, inviteeUserID, domain)
}
func (tm *tracingMiddleware) ListInvitations(ctx context.Context, session authn.Session, pm domains.InvitationPageMeta) (invs domains.InvitationPage, err error) {
ctx, span := tracing.StartSpan(ctx, tm.tracer, "list_invitations", trace.WithAttributes(
attribute.Int("limit", int(pm.Limit)),