diff --git a/.gitignore b/.gitignore index e88630f..1b02e61 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ .vscode config.yaml *.db +mockery.* mockery diff --git a/cmd/main.go b/cmd/main.go index fadb97d..ee2c7a9 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -163,7 +163,7 @@ func main() { logger.String("area", "group"), ) - groupRepo := group.NewEntRepository(client) + groupRepo := group.NewEntRepository(appLogger, client) groupService := group.NewUseCase(groupRepo) groupHandler := group.NewHTTPHandler(groupService, cfg, tokenAuth) diff --git a/go.sum b/go.sum index f153172..b2094d6 100644 --- a/go.sum +++ b/go.sum @@ -56,6 +56,7 @@ github.com/gookit/validate v1.4.6 h1:Ix8NRy2+6z4YGHWXgZL9+emy9wRI2GWyhW2smPcIlSU github.com/gookit/validate v1.4.6/go.mod h1:1rjeYaYlMK/8od4oge5C+Gt/3DnHkXymLPda7+3urC8= github.com/hashicorp/hcl/v2 v2.17.0 h1:z1XvSUyXd1HP10U4lrLg5e0JMVz6CPaJvAgxM0KNZVY= github.com/hashicorp/hcl/v2 v2.17.0/go.mod h1:gJyW2PTShkJqQBKpAmPO3yxMxIuoXkOF2TpqXzrQyx4= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/knadh/koanf/maps v0.1.1 h1:G5TjmUh2D7G2YWf5SQQqSiHRJEjaicvU0KpypqB3NIs= github.com/knadh/koanf/maps v0.1.1/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI= github.com/knadh/koanf/parsers/yaml v0.1.0 h1:ZZ8/iGfRLvKSaMEECEBPM1HQslrZADk8fP1XFUxVI5w= @@ -113,6 +114,7 @@ github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= +github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -126,6 +128,8 @@ github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTE github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= +github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= @@ -204,6 +208,7 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.8.1-0.20230428195545-5283a0178901 h1:0wxTF6pSjIIhNt7mo9GvjDfzyCOiWhmICgtO/Ah948s= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= diff --git a/internal/auth/httphandler.go b/internal/auth/httphandler.go index 42e7714..2679528 100644 --- a/internal/auth/httphandler.go +++ b/internal/auth/httphandler.go @@ -150,6 +150,12 @@ func (h httpHandler) WhoAmI(w http.ResponseWriter, r *http.Request) { PointsAwardedResetTime: user.PointsAwardedResetTime, GodMode: user.GodMode, Role: user.Role, + Institution: payload.Institution{ + ID: user.Edges.Institution.ID, + Name: user.Edges.Institution.Name, + ShortName: user.Edges.Institution.ShortName, + Description: user.Edges.Institution.Description, + }, }) } diff --git a/internal/auth/mocks/mock_reader.go b/internal/auth/mocks/mock_reader.go index dba8c96..b08601b 100644 --- a/internal/auth/mocks/mock_reader.go +++ b/internal/auth/mocks/mock_reader.go @@ -132,8 +132,8 @@ func (_c *MockReader_FindJWTRevocation_Call) RunAndReturn(run func(context.Conte return _c } -// FindUserByEmail provides a mock function with given fields: ctx, email -func (_m *MockReader) FindUserByEmail(ctx context.Context, email string) (*ent.User, error) { +// FindUserByEmailWithInstitution provides a mock function with given fields: ctx, email +func (_m *MockReader) FindUserByEmailWithInstitution(ctx context.Context, email string) (*ent.User, error) { ret := _m.Called(ctx, email) var r0 *ent.User @@ -158,31 +158,31 @@ func (_m *MockReader) FindUserByEmail(ctx context.Context, email string) (*ent.U return r0, r1 } -// MockReader_FindUserByEmail_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FindUserByEmail' -type MockReader_FindUserByEmail_Call struct { +// MockReader_FindUserByEmailWithInstitution_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FindUserByEmailWithInstitution' +type MockReader_FindUserByEmailWithInstitution_Call struct { *mock.Call } -// FindUserByEmail is a helper method to define mock.On call +// FindUserByEmailWithInstitution is a helper method to define mock.On call // - ctx context.Context // - email string -func (_e *MockReader_Expecter) FindUserByEmail(ctx interface{}, email interface{}) *MockReader_FindUserByEmail_Call { - return &MockReader_FindUserByEmail_Call{Call: _e.mock.On("FindUserByEmail", ctx, email)} +func (_e *MockReader_Expecter) FindUserByEmailWithInstitution(ctx interface{}, email interface{}) *MockReader_FindUserByEmailWithInstitution_Call { + return &MockReader_FindUserByEmailWithInstitution_Call{Call: _e.mock.On("FindUserByEmailWithInstitution", ctx, email)} } -func (_c *MockReader_FindUserByEmail_Call) Run(run func(ctx context.Context, email string)) *MockReader_FindUserByEmail_Call { +func (_c *MockReader_FindUserByEmailWithInstitution_Call) Run(run func(ctx context.Context, email string)) *MockReader_FindUserByEmailWithInstitution_Call { _c.Call.Run(func(args mock.Arguments) { run(args[0].(context.Context), args[1].(string)) }) return _c } -func (_c *MockReader_FindUserByEmail_Call) Return(_a0 *ent.User, _a1 error) *MockReader_FindUserByEmail_Call { +func (_c *MockReader_FindUserByEmailWithInstitution_Call) Return(_a0 *ent.User, _a1 error) *MockReader_FindUserByEmailWithInstitution_Call { _c.Call.Return(_a0, _a1) return _c } -func (_c *MockReader_FindUserByEmail_Call) RunAndReturn(run func(context.Context, string) (*ent.User, error)) *MockReader_FindUserByEmail_Call { +func (_c *MockReader_FindUserByEmailWithInstitution_Call) RunAndReturn(run func(context.Context, string) (*ent.User, error)) *MockReader_FindUserByEmailWithInstitution_Call { _c.Call.Return(run) return _c } diff --git a/internal/auth/mocks/mock_repository.go b/internal/auth/mocks/mock_repository.go index 5ad8880..fcdc516 100644 --- a/internal/auth/mocks/mock_repository.go +++ b/internal/auth/mocks/mock_repository.go @@ -271,8 +271,8 @@ func (_c *MockRepository_FindJWTRevocation_Call) RunAndReturn(run func(context.C return _c } -// FindUserByEmail provides a mock function with given fields: ctx, email -func (_m *MockRepository) FindUserByEmail(ctx context.Context, email string) (*ent.User, error) { +// FindUserByEmailWithInstitution provides a mock function with given fields: ctx, email +func (_m *MockRepository) FindUserByEmailWithInstitution(ctx context.Context, email string) (*ent.User, error) { ret := _m.Called(ctx, email) var r0 *ent.User @@ -297,31 +297,31 @@ func (_m *MockRepository) FindUserByEmail(ctx context.Context, email string) (*e return r0, r1 } -// MockRepository_FindUserByEmail_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FindUserByEmail' -type MockRepository_FindUserByEmail_Call struct { +// MockRepository_FindUserByEmailWithInstitution_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FindUserByEmailWithInstitution' +type MockRepository_FindUserByEmailWithInstitution_Call struct { *mock.Call } -// FindUserByEmail is a helper method to define mock.On call +// FindUserByEmailWithInstitution is a helper method to define mock.On call // - ctx context.Context // - email string -func (_e *MockRepository_Expecter) FindUserByEmail(ctx interface{}, email interface{}) *MockRepository_FindUserByEmail_Call { - return &MockRepository_FindUserByEmail_Call{Call: _e.mock.On("FindUserByEmail", ctx, email)} +func (_e *MockRepository_Expecter) FindUserByEmailWithInstitution(ctx interface{}, email interface{}) *MockRepository_FindUserByEmailWithInstitution_Call { + return &MockRepository_FindUserByEmailWithInstitution_Call{Call: _e.mock.On("FindUserByEmailWithInstitution", ctx, email)} } -func (_c *MockRepository_FindUserByEmail_Call) Run(run func(ctx context.Context, email string)) *MockRepository_FindUserByEmail_Call { +func (_c *MockRepository_FindUserByEmailWithInstitution_Call) Run(run func(ctx context.Context, email string)) *MockRepository_FindUserByEmailWithInstitution_Call { _c.Call.Run(func(args mock.Arguments) { run(args[0].(context.Context), args[1].(string)) }) return _c } -func (_c *MockRepository_FindUserByEmail_Call) Return(_a0 *ent.User, _a1 error) *MockRepository_FindUserByEmail_Call { +func (_c *MockRepository_FindUserByEmailWithInstitution_Call) Return(_a0 *ent.User, _a1 error) *MockRepository_FindUserByEmailWithInstitution_Call { _c.Call.Return(_a0, _a1) return _c } -func (_c *MockRepository_FindUserByEmail_Call) RunAndReturn(run func(context.Context, string) (*ent.User, error)) *MockRepository_FindUserByEmail_Call { +func (_c *MockRepository_FindUserByEmailWithInstitution_Call) RunAndReturn(run func(context.Context, string) (*ent.User, error)) *MockRepository_FindUserByEmailWithInstitution_Call { _c.Call.Return(run) return _c } diff --git a/internal/auth/repository.go b/internal/auth/repository.go index 31014b9..f0ed5af 100644 --- a/internal/auth/repository.go +++ b/internal/auth/repository.go @@ -10,7 +10,7 @@ import ( ) type Reader interface { - FindUserByEmail(ctx context.Context, email string) (*entity.User, error) + FindUserByEmailWithInstitution(ctx context.Context, email string) (*entity.User, error) FindInstitutionInviteLinkWithInstitution(ctx context.Context, code string) (*entity.InstitutionInviteLink, error) FindJWTRevocation(ctx context.Context, jti string) (*entity.JWTRevocation, error) } diff --git a/internal/auth/repository_ent.go b/internal/auth/repository_ent.go index 8b1b71a..25d913c 100644 --- a/internal/auth/repository_ent.go +++ b/internal/auth/repository_ent.go @@ -24,8 +24,8 @@ func NewEntRepository(ent *ent.Client) Repository { return entRepository{ent} } -func (e entRepository) FindUserByEmail(ctx context.Context, email string) (*entity.User, error) { - u, err := e.client.User.Query().Where(entuser.Email(email)).Only(ctx) +func (e entRepository) FindUserByEmailWithInstitution(ctx context.Context, email string) (*entity.User, error) { + u, err := e.client.User.Query().Where(entuser.Email(email)).WithInstitution().Only(ctx) if err != nil { return nil, fmt.Errorf("failed to find user with email: %w", err) } diff --git a/internal/auth/usecase.go b/internal/auth/usecase.go index afa3918..d1593d2 100644 --- a/internal/auth/usecase.go +++ b/internal/auth/usecase.go @@ -46,16 +46,20 @@ func (u useCase) WhoAmI(ctx context.Context, token jwt.Token) (*entity.User, err return nil, fmt.Errorf("failed to find jwt revocation: %w", err) } - user, err := u.repo.FindUserByEmail(ctx, token.Subject()) + user, err := u.repo.FindUserByEmailWithInstitution(ctx, token.Subject()) if err != nil { return nil, fmt.Errorf("failed to find user: %w", err) } + if user.Edges.Institution == nil { + return nil, fmt.Errorf("invariant") + } + return user, nil } func (u useCase) Login(ctx context.Context, email string, password string) (*entity.Session, error) { - user, err := u.repo.FindUserByEmail(ctx, email) + user, err := u.repo.FindUserByEmailWithInstitution(ctx, email) if err != nil { if apperror.IsNotFound(err) { return nil, ErrUserNotFound diff --git a/internal/auth/usecase_test.go b/internal/auth/usecase_test.go index 4e1f684..7330eba 100644 --- a/internal/auth/usecase_test.go +++ b/internal/auth/usecase_test.go @@ -121,7 +121,7 @@ func (suite *UseCaseTestSuite) TestWhoAmI() { configure: func(repository *mocks.MockRepository) { repository.On("FindJWTRevocation", mock.Anything, mock.Anything). Return(&entity.JWTRevocation{}, fixture.RepoNotFoundErr) - repository.On("FindUserByEmail", mock.Anything, mock.Anything). + repository.On("FindUserByEmailWithInstitution", mock.Anything, mock.Anything). Return(nil, fixture.RepoNotFoundErr) }, want: nil, @@ -136,10 +136,10 @@ func (suite *UseCaseTestSuite) TestWhoAmI() { configure: func(repository *mocks.MockRepository) { repository.On("FindJWTRevocation", mock.Anything, mock.Anything). Return(&entity.JWTRevocation{}, fixture.RepoNotFoundErr) - repository.On("FindUserByEmail", mock.Anything, mock.Anything). - Return(&entity.User{}, nil) + repository.On("FindUserByEmailWithInstitution", mock.Anything, mock.Anything). + Return(fixture.UserJohn, nil) }, - want: &entity.User{}, + want: fixture.UserJohn, err: nil, }, } @@ -178,7 +178,7 @@ func (suite *UseCaseTestSuite) TestLogin() { password: "example", }, configure: func(repository *mocks.MockRepository) { - repository.On("FindUserByEmail", mock.Anything, mock.Anything). + repository.On("FindUserByEmailWithInstitution", mock.Anything, mock.Anything). Return(nil, fixture.RepoNotFoundErr) }, assert: func(ret *entity.Session) { @@ -194,7 +194,7 @@ func (suite *UseCaseTestSuite) TestLogin() { password: fixture.UserJohnPassword, }, configure: func(repository *mocks.MockRepository) { - repository.On("FindUserByEmail", mock.Anything, mock.Anything). + repository.On("FindUserByEmailWithInstitution", mock.Anything, mock.Anything). Return(nil, fixture.RepoInternalErr) }, assert: func(ret *entity.Session) { @@ -210,7 +210,7 @@ func (suite *UseCaseTestSuite) TestLogin() { password: "wrong password", }, configure: func(repository *mocks.MockRepository) { - repository.On("FindUserByEmail", mock.Anything, mock.Anything). + repository.On("FindUserByEmailWithInstitution", mock.Anything, mock.Anything). Return(fixture.UserJohn, nil) }, assert: func(ret *entity.Session) { @@ -226,7 +226,7 @@ func (suite *UseCaseTestSuite) TestLogin() { password: fixture.UserJohnPassword, }, configure: func(repository *mocks.MockRepository) { - repository.On("FindUserByEmail", mock.Anything, mock.Anything). + repository.On("FindUserByEmailWithInstitution", mock.Anything, mock.Anything). Return(fixture.UserJohn, nil) }, assert: func(ret *entity.Session) { diff --git a/internal/ent/institution/institution.go b/internal/ent/institution/institution.go index 196441f..15cd61a 100644 --- a/internal/ent/institution/institution.go +++ b/internal/ent/institution/institution.go @@ -90,6 +90,8 @@ var ( NameValidator func(string) error // ShortNameValidator is a validator for the "short_name" field. It is called by the builders before save. ShortNameValidator func(string) error + // DefaultDescription holds the default value on creation for the "Description" field. + DefaultDescription string ) // OrderOption defines the ordering options for the Institution queries. diff --git a/internal/ent/institution_create.go b/internal/ent/institution_create.go index 0407845..66d7616 100644 --- a/internal/ent/institution_create.go +++ b/internal/ent/institution_create.go @@ -44,6 +44,14 @@ func (ic *InstitutionCreate) SetDescription(s string) *InstitutionCreate { return ic } +// SetNillableDescription sets the "Description" field if the given value is not nil. +func (ic *InstitutionCreate) SetNillableDescription(s *string) *InstitutionCreate { + if s != nil { + ic.SetDescription(*s) + } + return ic +} + // AddVoucherIDs adds the "vouchers" edge to the Voucher entity by IDs. func (ic *InstitutionCreate) AddVoucherIDs(ids ...int) *InstitutionCreate { ic.mutation.AddVoucherIDs(ids...) @@ -126,6 +134,7 @@ func (ic *InstitutionCreate) Mutation() *InstitutionMutation { // Save creates the Institution in the database. func (ic *InstitutionCreate) Save(ctx context.Context) (*Institution, error) { + ic.defaults() return withHooks(ctx, ic.sqlSave, ic.mutation, ic.hooks) } @@ -151,6 +160,14 @@ func (ic *InstitutionCreate) ExecX(ctx context.Context) { } } +// defaults sets the default values of the builder before save. +func (ic *InstitutionCreate) defaults() { + if _, ok := ic.mutation.Description(); !ok { + v := entinstitution.DefaultDescription + ic.mutation.SetDescription(v) + } +} + // check runs all checks and user-defined validators on the builder. func (ic *InstitutionCreate) check() error { if _, ok := ic.mutation.Name(); !ok { @@ -509,6 +526,7 @@ func (icb *InstitutionCreateBulk) Save(ctx context.Context) ([]*Institution, err for i := range icb.builders { func(i int, root context.Context) { builder := icb.builders[i] + builder.defaults() var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { mutation, ok := m.(*InstitutionMutation) if !ok { diff --git a/internal/ent/institution_update.go b/internal/ent/institution_update.go index 7766ac3..edb6a11 100644 --- a/internal/ent/institution_update.go +++ b/internal/ent/institution_update.go @@ -50,6 +50,14 @@ func (iu *InstitutionUpdate) SetDescription(s string) *InstitutionUpdate { return iu } +// SetNillableDescription sets the "Description" field if the given value is not nil. +func (iu *InstitutionUpdate) SetNillableDescription(s *string) *InstitutionUpdate { + if s != nil { + iu.SetDescription(*s) + } + return iu +} + // AddVoucherIDs adds the "vouchers" edge to the Voucher entity by IDs. func (iu *InstitutionUpdate) AddVoucherIDs(ids ...int) *InstitutionUpdate { iu.mutation.AddVoucherIDs(ids...) @@ -561,6 +569,14 @@ func (iuo *InstitutionUpdateOne) SetDescription(s string) *InstitutionUpdateOne return iuo } +// SetNillableDescription sets the "Description" field if the given value is not nil. +func (iuo *InstitutionUpdateOne) SetNillableDescription(s *string) *InstitutionUpdateOne { + if s != nil { + iuo.SetDescription(*s) + } + return iuo +} + // AddVoucherIDs adds the "vouchers" edge to the Voucher entity by IDs. func (iuo *InstitutionUpdateOne) AddVoucherIDs(ids ...int) *InstitutionUpdateOne { iuo.mutation.AddVoucherIDs(ids...) diff --git a/internal/ent/migrate/schema.go b/internal/ent/migrate/schema.go index 819601f..19125d4 100644 --- a/internal/ent/migrate/schema.go +++ b/internal/ent/migrate/schema.go @@ -54,7 +54,7 @@ var ( Symbol: "deadlines_groups_deadlines", Columns: []*schema.Column{DeadlinesColumns[4]}, RefColumns: []*schema.Column{GroupsColumns[0]}, - OnDelete: schema.NoAction, + OnDelete: schema.Cascade, }, }, } @@ -78,7 +78,7 @@ var ( Symbol: "events_groups_events", Columns: []*schema.Column{EventsColumns[6]}, RefColumns: []*schema.Column{GroupsColumns[0]}, - OnDelete: schema.NoAction, + OnDelete: schema.Cascade, }, }, } @@ -100,7 +100,7 @@ var ( Symbol: "forums_groups_forums", Columns: []*schema.Column{ForumsColumns[4]}, RefColumns: []*schema.Column{GroupsColumns[0]}, - OnDelete: schema.NoAction, + OnDelete: schema.Cascade, }, }, } @@ -179,7 +179,7 @@ var ( Symbol: "group_invite_links_groups_invites", Columns: []*schema.Column{GroupInviteLinksColumns[3]}, RefColumns: []*schema.Column{GroupsColumns[0]}, - OnDelete: schema.NoAction, + OnDelete: schema.Cascade, }, }, } @@ -214,7 +214,7 @@ var ( {Name: "id", Type: field.TypeInt, Increment: true}, {Name: "name", Type: field.TypeString}, {Name: "short_name", Type: field.TypeString, Unique: true}, - {Name: "description", Type: field.TypeString}, + {Name: "description", Type: field.TypeString, Default: ""}, } // InstitutionsTable holds the schema information for the "institutions" table. InstitutionsTable = &schema.Table{ diff --git a/internal/ent/runtime.go b/internal/ent/runtime.go index ad2b84a..e018dfe 100644 --- a/internal/ent/runtime.go +++ b/internal/ent/runtime.go @@ -100,6 +100,10 @@ func init() { entinstitutionDescShortName := entinstitutionFields[1].Descriptor() // entinstitution.ShortNameValidator is a validator for the "short_name" field. It is called by the builders before save. entinstitution.ShortNameValidator = entinstitutionDescShortName.Validators[0].(func(string) error) + // entinstitutionDescDescription is the schema descriptor for Description field. + entinstitutionDescDescription := entinstitutionFields[2].Descriptor() + // entinstitution.DefaultDescription holds the default value on creation for the Description field. + entinstitution.DefaultDescription = entinstitutionDescDescription.Default.(string) institutioninvitelinkMixin := schema.InstitutionInviteLink{}.Mixin() institutioninvitelinkMixinFields0 := institutioninvitelinkMixin[0].Fields() _ = institutioninvitelinkMixinFields0 diff --git a/internal/ent/schema/group.go b/internal/ent/schema/group.go index ffa0dc1..7be5971 100644 --- a/internal/ent/schema/group.go +++ b/internal/ent/schema/group.go @@ -2,6 +2,7 @@ package schema import ( "entgo.io/ent" + "entgo.io/ent/dialect/entsql" "entgo.io/ent/schema/edge" "entgo.io/ent/schema/field" ) @@ -30,14 +31,19 @@ func (Group) Edges() []ent.Edge { return []ent.Edge{ edge.To("users", User.Type). Through("group_users", GroupUser.Type). + Annotations(entsql.OnDelete(entsql.Cascade)). Comment("Users that are in the group"), edge.To("events", Event.Type). + Annotations(entsql.OnDelete(entsql.Cascade)). Comment("Events from the group"), edge.To("forums", Forum.Type). + Annotations(entsql.OnDelete(entsql.Cascade)). Comment("Forum from the group"), edge.To("deadlines", Deadline.Type). + Annotations(entsql.OnDelete(entsql.Cascade)). Comment("Deadlines created by users from the group"), edge.To("invites", GroupInviteLink.Type). + Annotations(entsql.OnDelete(entsql.Cascade)). Comment("Invites for the group"), edge.From("institution", Institution.Type). Ref("groups"). diff --git a/internal/ent/schema/institution.go b/internal/ent/schema/institution.go index 43158d3..5006dda 100644 --- a/internal/ent/schema/institution.go +++ b/internal/ent/schema/institution.go @@ -23,6 +23,7 @@ func (Institution) Fields() []ent.Field { Unique(). Comment("Short name of the institution (example: np)"), field.String("Description"). + Default(""). Comment("Description of the institution"), } } diff --git a/internal/entity/entity.go b/internal/entity/entity.go index 86a7e60..855eef5 100644 --- a/internal/entity/entity.go +++ b/internal/entity/entity.go @@ -20,3 +20,6 @@ type GroupUserEdges = ent.GroupUserEdges type InstitutionInviteLink = ent.InstitutionInviteLink type InstitutionInviteLinkEdges = ent.InstitutionInviteLinkEdges + +type GroupInviteLink = ent.GroupInviteLink +type GroupInviteLinkEdges = ent.GroupInviteLinkEdges diff --git a/internal/fixture/auth.go b/internal/fixture/auth.go index 4f3f3ae..4c58bf4 100644 --- a/internal/fixture/auth.go +++ b/internal/fixture/auth.go @@ -54,5 +54,8 @@ var ( PointsAwardedResetTime: time.Now(), GodMode: false, Role: institution.RoleMember, + Edges: entity.UserEdges{ + Institution: InstitutionNP, + }, } ) diff --git a/internal/group/error.go b/internal/group/error.go index 666752e..7a0e595 100644 --- a/internal/group/error.go +++ b/internal/group/error.go @@ -3,23 +3,30 @@ package group import ( "errors" "github.com/go-chi/render" + "github.com/gookit/validate" "github.com/np-inprove/server/internal/apperror" "net/http" ) var ( - ErrInstitutionNotFound = errors.New("institution does not exist") + ErrGroupNotFound = errors.New("group does not exist") ErrGroupShortNameConflict = errors.New("a group with the desired short name already exists") ErrUnauthorized = errors.New("not authorized") + ErrInviteLinkNotFound = errors.New("invite link does not exist") ) func mapDomainErr(err error) render.Renderer { - if errors.Is(err, ErrInstitutionNotFound) { + if errors.Is(err, ErrGroupNotFound) { return &apperror.ErrResponse{ Err: err, HTTPStatusCode: http.StatusNotFound, AppErrCode: 40401, - AppErrMessage: "The institution specified does not exist", + AppErrMessage: "The group specified does not exist", + Fields: validate.Errors{ + "shortName": map[string]string{ + "notFound": "group does not exist", + }, + }, } } @@ -29,6 +36,11 @@ func mapDomainErr(err error) render.Renderer { HTTPStatusCode: http.StatusConflict, AppErrCode: 40901, AppErrMessage: "This group short name is unavailable", + Fields: validate.Errors{ + "shortName": map[string]string{ + "conflict": "Short name already in use", + }, + }, } } @@ -38,6 +50,25 @@ func mapDomainErr(err error) render.Renderer { HTTPStatusCode: http.StatusUnauthorized, AppErrCode: 40301, AppErrMessage: "Unauthorized", + Fields: validate.Errors{ + "user": map[string]string{ + "unauthorized": "You must be an owner or educator to perform this action", + }, + }, + } + } + + if errors.Is(err, ErrInviteLinkNotFound) { + return &apperror.ErrResponse{ + Err: err, + HTTPStatusCode: http.StatusNotFound, + AppErrCode: 40402, + AppErrMessage: "The invite link does not exist", + Fields: validate.Errors{ + "code": map[string]string{ + "notFound": "The invite link does not exist", + }, + }, } } diff --git a/internal/group/httphandler.go b/internal/group/httphandler.go index 825613e..af60281 100644 --- a/internal/group/httphandler.go +++ b/internal/group/httphandler.go @@ -37,7 +37,14 @@ func NewHTTPHandler(u UseCase, c *config.Config, j *jwtauth.JWTAuth) chi.Router r.Get("/", a.ListGroups) r.Post("/", a.CreateGroup) - r.Delete("/{path}", a.DeleteGroup) + r.Put("/{shortName}", a.UpdateGroup) + r.Delete("/{shortName}", a.DeleteGroup) + + r.Get("/{shortName}/invites", a.ListInviteLinks) + r.Post("/{shortName}/invites", a.CreateInviteLink) + + r.Post("/{shortName}/invites/{code}", a.JoinGroup) + r.Delete("/{shortName}/invites/{code}", a.DeleteInviteLink) }) return r @@ -99,8 +106,43 @@ func (h httpHandler) CreateGroup(w http.ResponseWriter, r *http.Request) { }) } +func (h httpHandler) UpdateGroup(w http.ResponseWriter, r *http.Request) { + p := &payload.UpdateGroupRequest{} + if err := render.Decode(r, p); err != nil { + _ = render.Render(w, r, apperror.ErrBadRequest(err)) + return + } + + if v := p.Validate(); !v.Validate() { + _ = render.Render(w, r, apperror.ErrValidation(v.Errors)) + return + } + + shortName := chi.URLParam(r, "shortName") + token := r.Context().Value(jwtauth.TokenCtxKey) + email := token.(jwt.Token).Subject() + + res, err := h.service.UpdateGroup(r.Context(), email, shortName, + group.Name(p.Name), + group.ShortName(p.ShortName), + group.Description(p.Description), + ) + + if err != nil { + _ = render.Render(w, r, mapDomainErr(err)) + return + } + + _ = render.Render(w, r, payload.Group{ + ID: res.ID, + ShortName: res.ShortName, + Name: res.Name, + Description: res.Description, + }) +} + func (h httpHandler) DeleteGroup(w http.ResponseWriter, r *http.Request) { - path := chi.URLParam(r, "path") + path := chi.URLParam(r, "shortName") token := r.Context().Value(jwtauth.TokenCtxKey) email := token.(jwt.Token).Subject() @@ -111,3 +153,92 @@ func (h httpHandler) DeleteGroup(w http.ResponseWriter, r *http.Request) { render.NoContent(w, r) } + +func (h httpHandler) ListInviteLinks(w http.ResponseWriter, r *http.Request) { + shortName := chi.URLParam(r, "shortName") + + token := r.Context().Value(jwtauth.TokenCtxKey) + email := token.(jwt.Token).Subject() + + links, err := h.service.ListInviteLinks(r.Context(), email, shortName) + if err != nil { + _ = render.Render(w, r, mapDomainErr(err)) + return + } + + p := make([]render.Renderer, len(links)) + for i, v := range links { + p[i] = payload.GroupInviteLink{ + ID: v.ID, + Code: v.Code, + Role: v.Role, + } + } + + _ = render.RenderList(w, r, p) +} + +func (h httpHandler) JoinGroup(w http.ResponseWriter, r *http.Request) { + code := chi.URLParam(r, "code") + token := r.Context().Value(jwtauth.TokenCtxKey) + email := token.(jwt.Token).Subject() + + grp, err := h.service.JoinGroup(r.Context(), email, code) + if err != nil { + _ = render.Render(w, r, mapDomainErr(err)) + return + } + + _ = render.Render(w, r, payload.Group{ + ID: grp.ID, + Name: grp.Name, + ShortName: grp.ShortName, + Description: grp.Description, + }) +} + +func (h httpHandler) CreateInviteLink(w http.ResponseWriter, r *http.Request) { + p := &payload.CreateGroupInviteLinkRequest{} + + if err := render.Decode(r, p); err != nil { + _ = render.Render(w, r, apperror.ErrBadRequest(err)) + return + } + + if v := p.Validate(); !v.Validate() { + _ = render.Render(w, r, apperror.ErrValidation(v.Errors)) + return + } + + shortName := chi.URLParam(r, "shortName") + token := r.Context().Value(jwtauth.TokenCtxKey) + email := token.(jwt.Token).Subject() + + link, err := h.service.CreateInviteLink(r.Context(), email, shortName, p.Role) + if err != nil { + _ = render.Render(w, r, mapDomainErr(err)) + return + } + + _ = render.Render(w, r, payload.GroupInviteLink{ + ID: link.ID, + Code: link.Code, + Role: link.Role, + }) +} + +func (h httpHandler) DeleteInviteLink(w http.ResponseWriter, r *http.Request) { + shortName := chi.URLParam(r, "shortName") + code := chi.URLParam(r, "code") + + token := r.Context().Value(jwtauth.TokenCtxKey) + email := token.(jwt.Token).Subject() + + err := h.service.DeleteInviteLink(r.Context(), email, shortName, code) + if err != nil { + _ = render.Render(w, r, mapDomainErr(err)) + return + } + + render.Status(r, http.StatusNoContent) +} diff --git a/internal/group/mocks/mock_reader.go b/internal/group/mocks/mock_reader.go index 780ed7b..450570d 100644 --- a/internal/group/mocks/mock_reader.go +++ b/internal/group/mocks/mock_reader.go @@ -23,6 +23,61 @@ func (_m *MockReader) EXPECT() *MockReader_Expecter { return &MockReader_Expecter{mock: &_m.Mock} } +// FindGroup provides a mock function with given fields: ctx, shortName +func (_m *MockReader) FindGroup(ctx context.Context, shortName string) (*ent.Group, error) { + ret := _m.Called(ctx, shortName) + + var r0 *ent.Group + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) (*ent.Group, error)); ok { + return rf(ctx, shortName) + } + if rf, ok := ret.Get(0).(func(context.Context, string) *ent.Group); ok { + r0 = rf(ctx, shortName) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*ent.Group) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, shortName) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockReader_FindGroup_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FindGroup' +type MockReader_FindGroup_Call struct { + *mock.Call +} + +// FindGroup is a helper method to define mock.On call +// - ctx context.Context +// - shortName string +func (_e *MockReader_Expecter) FindGroup(ctx interface{}, shortName interface{}) *MockReader_FindGroup_Call { + return &MockReader_FindGroup_Call{Call: _e.mock.On("FindGroup", ctx, shortName)} +} + +func (_c *MockReader_FindGroup_Call) Run(run func(ctx context.Context, shortName string)) *MockReader_FindGroup_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string)) + }) + return _c +} + +func (_c *MockReader_FindGroup_Call) Return(_a0 *ent.Group, _a1 error) *MockReader_FindGroup_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockReader_FindGroup_Call) RunAndReturn(run func(context.Context, string) (*ent.Group, error)) *MockReader_FindGroup_Call { + _c.Call.Return(run) + return _c +} + // FindGroupByInstitutionIDAndShortName provides a mock function with given fields: ctx, institutionID, shortName func (_m *MockReader) FindGroupByInstitutionIDAndShortName(ctx context.Context, institutionID int, shortName string) (*ent.Group, error) { ret := _m.Called(ctx, institutionID, shortName) @@ -135,6 +190,61 @@ func (_c *MockReader_FindGroupUser_Call) RunAndReturn(run func(context.Context, return _c } +// FindGroupWithInvites provides a mock function with given fields: ctx, shortName +func (_m *MockReader) FindGroupWithInvites(ctx context.Context, shortName string) (*ent.Group, error) { + ret := _m.Called(ctx, shortName) + + var r0 *ent.Group + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) (*ent.Group, error)); ok { + return rf(ctx, shortName) + } + if rf, ok := ret.Get(0).(func(context.Context, string) *ent.Group); ok { + r0 = rf(ctx, shortName) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*ent.Group) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, shortName) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockReader_FindGroupWithInvites_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FindGroupWithInvites' +type MockReader_FindGroupWithInvites_Call struct { + *mock.Call +} + +// FindGroupWithInvites is a helper method to define mock.On call +// - ctx context.Context +// - shortName string +func (_e *MockReader_Expecter) FindGroupWithInvites(ctx interface{}, shortName interface{}) *MockReader_FindGroupWithInvites_Call { + return &MockReader_FindGroupWithInvites_Call{Call: _e.mock.On("FindGroupWithInvites", ctx, shortName)} +} + +func (_c *MockReader_FindGroupWithInvites_Call) Run(run func(ctx context.Context, shortName string)) *MockReader_FindGroupWithInvites_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string)) + }) + return _c +} + +func (_c *MockReader_FindGroupWithInvites_Call) Return(_a0 *ent.Group, _a1 error) *MockReader_FindGroupWithInvites_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockReader_FindGroupWithInvites_Call) RunAndReturn(run func(context.Context, string) (*ent.Group, error)) *MockReader_FindGroupWithInvites_Call { + _c.Call.Return(run) + return _c +} + // FindGroupsByUser provides a mock function with given fields: ctx, principal func (_m *MockReader) FindGroupsByUser(ctx context.Context, principal string) ([]*ent.Group, error) { ret := _m.Called(ctx, principal) @@ -190,6 +300,61 @@ func (_c *MockReader_FindGroupsByUser_Call) RunAndReturn(run func(context.Contex return _c } +// FindInviteWithGroup provides a mock function with given fields: ctx, code +func (_m *MockReader) FindInviteWithGroup(ctx context.Context, code string) (*ent.GroupInviteLink, error) { + ret := _m.Called(ctx, code) + + var r0 *ent.GroupInviteLink + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) (*ent.GroupInviteLink, error)); ok { + return rf(ctx, code) + } + if rf, ok := ret.Get(0).(func(context.Context, string) *ent.GroupInviteLink); ok { + r0 = rf(ctx, code) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*ent.GroupInviteLink) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, code) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockReader_FindInviteWithGroup_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FindInviteWithGroup' +type MockReader_FindInviteWithGroup_Call struct { + *mock.Call +} + +// FindInviteWithGroup is a helper method to define mock.On call +// - ctx context.Context +// - code string +func (_e *MockReader_Expecter) FindInviteWithGroup(ctx interface{}, code interface{}) *MockReader_FindInviteWithGroup_Call { + return &MockReader_FindInviteWithGroup_Call{Call: _e.mock.On("FindInviteWithGroup", ctx, code)} +} + +func (_c *MockReader_FindInviteWithGroup_Call) Run(run func(ctx context.Context, code string)) *MockReader_FindInviteWithGroup_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string)) + }) + return _c +} + +func (_c *MockReader_FindInviteWithGroup_Call) Return(_a0 *ent.GroupInviteLink, _a1 error) *MockReader_FindInviteWithGroup_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockReader_FindInviteWithGroup_Call) RunAndReturn(run func(context.Context, string) (*ent.GroupInviteLink, error)) *MockReader_FindInviteWithGroup_Call { + _c.Call.Return(run) + return _c +} + // FindUserWithInstitution provides a mock function with given fields: ctx, principal func (_m *MockReader) FindUserWithInstitution(ctx context.Context, principal string) (*ent.User, error) { ret := _m.Called(ctx, principal) diff --git a/internal/group/mocks/mock_repository.go b/internal/group/mocks/mock_repository.go index db9d802..a5941a9 100644 --- a/internal/group/mocks/mock_repository.go +++ b/internal/group/mocks/mock_repository.go @@ -6,7 +6,7 @@ import ( context "context" ent "github.com/np-inprove/server/internal/ent" - group "github.com/np-inprove/server/internal/entity/group" + entitygroup "github.com/np-inprove/server/internal/entity/group" mock "github.com/stretchr/testify/mock" ) @@ -24,8 +24,51 @@ func (_m *MockRepository) EXPECT() *MockRepository_Expecter { return &MockRepository_Expecter{mock: &_m.Mock} } +// BulkDeleteGroupUsers provides a mock function with given fields: ctx, groupID +func (_m *MockRepository) BulkDeleteGroupUsers(ctx context.Context, groupID int) error { + ret := _m.Called(ctx, groupID) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, int) error); ok { + r0 = rf(ctx, groupID) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// MockRepository_BulkDeleteGroupUsers_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'BulkDeleteGroupUsers' +type MockRepository_BulkDeleteGroupUsers_Call struct { + *mock.Call +} + +// BulkDeleteGroupUsers is a helper method to define mock.On call +// - ctx context.Context +// - groupID int +func (_e *MockRepository_Expecter) BulkDeleteGroupUsers(ctx interface{}, groupID interface{}) *MockRepository_BulkDeleteGroupUsers_Call { + return &MockRepository_BulkDeleteGroupUsers_Call{Call: _e.mock.On("BulkDeleteGroupUsers", ctx, groupID)} +} + +func (_c *MockRepository_BulkDeleteGroupUsers_Call) Run(run func(ctx context.Context, groupID int)) *MockRepository_BulkDeleteGroupUsers_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(int)) + }) + return _c +} + +func (_c *MockRepository_BulkDeleteGroupUsers_Call) Return(_a0 error) *MockRepository_BulkDeleteGroupUsers_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockRepository_BulkDeleteGroupUsers_Call) RunAndReturn(run func(context.Context, int) error) *MockRepository_BulkDeleteGroupUsers_Call { + _c.Call.Return(run) + return _c +} + // CreateGroup provides a mock function with given fields: ctx, institutionID, opts -func (_m *MockRepository) CreateGroup(ctx context.Context, institutionID int, opts ...group.Option) (*ent.Group, error) { +func (_m *MockRepository) CreateGroup(ctx context.Context, institutionID int, opts ...entitygroup.Option) (*ent.Group, error) { _va := make([]interface{}, len(opts)) for _i := range opts { _va[_i] = opts[_i] @@ -37,10 +80,10 @@ func (_m *MockRepository) CreateGroup(ctx context.Context, institutionID int, op var r0 *ent.Group var r1 error - if rf, ok := ret.Get(0).(func(context.Context, int, ...group.Option) (*ent.Group, error)); ok { + if rf, ok := ret.Get(0).(func(context.Context, int, ...entitygroup.Option) (*ent.Group, error)); ok { return rf(ctx, institutionID, opts...) } - if rf, ok := ret.Get(0).(func(context.Context, int, ...group.Option) *ent.Group); ok { + if rf, ok := ret.Get(0).(func(context.Context, int, ...entitygroup.Option) *ent.Group); ok { r0 = rf(ctx, institutionID, opts...) } else { if ret.Get(0) != nil { @@ -48,7 +91,7 @@ func (_m *MockRepository) CreateGroup(ctx context.Context, institutionID int, op } } - if rf, ok := ret.Get(1).(func(context.Context, int, ...group.Option) error); ok { + if rf, ok := ret.Get(1).(func(context.Context, int, ...entitygroup.Option) error); ok { r1 = rf(ctx, institutionID, opts...) } else { r1 = ret.Error(1) @@ -65,18 +108,18 @@ type MockRepository_CreateGroup_Call struct { // CreateGroup is a helper method to define mock.On call // - ctx context.Context // - institutionID int -// - opts ...group.Option +// - opts ...entitygroup.Option func (_e *MockRepository_Expecter) CreateGroup(ctx interface{}, institutionID interface{}, opts ...interface{}) *MockRepository_CreateGroup_Call { return &MockRepository_CreateGroup_Call{Call: _e.mock.On("CreateGroup", append([]interface{}{ctx, institutionID}, opts...)...)} } -func (_c *MockRepository_CreateGroup_Call) Run(run func(ctx context.Context, institutionID int, opts ...group.Option)) *MockRepository_CreateGroup_Call { +func (_c *MockRepository_CreateGroup_Call) Run(run func(ctx context.Context, institutionID int, opts ...entitygroup.Option)) *MockRepository_CreateGroup_Call { _c.Call.Run(func(args mock.Arguments) { - variadicArgs := make([]group.Option, len(args)-2) + variadicArgs := make([]entitygroup.Option, len(args)-2) for i, a := range args[2:] { if a != nil { - variadicArgs[i] = a.(group.Option) + variadicArgs[i] = a.(entitygroup.Option) } } run(args[0].(context.Context), args[1].(int), variadicArgs...) @@ -89,7 +132,121 @@ func (_c *MockRepository_CreateGroup_Call) Return(_a0 *ent.Group, _a1 error) *Mo return _c } -func (_c *MockRepository_CreateGroup_Call) RunAndReturn(run func(context.Context, int, ...group.Option) (*ent.Group, error)) *MockRepository_CreateGroup_Call { +func (_c *MockRepository_CreateGroup_Call) RunAndReturn(run func(context.Context, int, ...entitygroup.Option) (*ent.Group, error)) *MockRepository_CreateGroup_Call { + _c.Call.Return(run) + return _c +} + +// CreateGroupUser provides a mock function with given fields: ctx, userID, groupID, role +func (_m *MockRepository) CreateGroupUser(ctx context.Context, userID int, groupID int, role entitygroup.Role) (*ent.GroupUser, error) { + ret := _m.Called(ctx, userID, groupID, role) + + var r0 *ent.GroupUser + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, int, int, entitygroup.Role) (*ent.GroupUser, error)); ok { + return rf(ctx, userID, groupID, role) + } + if rf, ok := ret.Get(0).(func(context.Context, int, int, entitygroup.Role) *ent.GroupUser); ok { + r0 = rf(ctx, userID, groupID, role) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*ent.GroupUser) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, int, int, entitygroup.Role) error); ok { + r1 = rf(ctx, userID, groupID, role) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockRepository_CreateGroupUser_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateGroupUser' +type MockRepository_CreateGroupUser_Call struct { + *mock.Call +} + +// CreateGroupUser is a helper method to define mock.On call +// - ctx context.Context +// - userID int +// - groupID int +// - role entitygroup.Role +func (_e *MockRepository_Expecter) CreateGroupUser(ctx interface{}, userID interface{}, groupID interface{}, role interface{}) *MockRepository_CreateGroupUser_Call { + return &MockRepository_CreateGroupUser_Call{Call: _e.mock.On("CreateGroupUser", ctx, userID, groupID, role)} +} + +func (_c *MockRepository_CreateGroupUser_Call) Run(run func(ctx context.Context, userID int, groupID int, role entitygroup.Role)) *MockRepository_CreateGroupUser_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(int), args[2].(int), args[3].(entitygroup.Role)) + }) + return _c +} + +func (_c *MockRepository_CreateGroupUser_Call) Return(_a0 *ent.GroupUser, _a1 error) *MockRepository_CreateGroupUser_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockRepository_CreateGroupUser_Call) RunAndReturn(run func(context.Context, int, int, entitygroup.Role) (*ent.GroupUser, error)) *MockRepository_CreateGroupUser_Call { + _c.Call.Return(run) + return _c +} + +// CreateInviteLink provides a mock function with given fields: ctx, id, code, role +func (_m *MockRepository) CreateInviteLink(ctx context.Context, id int, code string, role entitygroup.Role) (*ent.GroupInviteLink, error) { + ret := _m.Called(ctx, id, code, role) + + var r0 *ent.GroupInviteLink + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, int, string, entitygroup.Role) (*ent.GroupInviteLink, error)); ok { + return rf(ctx, id, code, role) + } + if rf, ok := ret.Get(0).(func(context.Context, int, string, entitygroup.Role) *ent.GroupInviteLink); ok { + r0 = rf(ctx, id, code, role) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*ent.GroupInviteLink) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, int, string, entitygroup.Role) error); ok { + r1 = rf(ctx, id, code, role) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockRepository_CreateInviteLink_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateInviteLink' +type MockRepository_CreateInviteLink_Call struct { + *mock.Call +} + +// CreateInviteLink is a helper method to define mock.On call +// - ctx context.Context +// - id int +// - code string +// - role entitygroup.Role +func (_e *MockRepository_Expecter) CreateInviteLink(ctx interface{}, id interface{}, code interface{}, role interface{}) *MockRepository_CreateInviteLink_Call { + return &MockRepository_CreateInviteLink_Call{Call: _e.mock.On("CreateInviteLink", ctx, id, code, role)} +} + +func (_c *MockRepository_CreateInviteLink_Call) Run(run func(ctx context.Context, id int, code string, role entitygroup.Role)) *MockRepository_CreateInviteLink_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(int), args[2].(string), args[3].(entitygroup.Role)) + }) + return _c +} + +func (_c *MockRepository_CreateInviteLink_Call) Return(_a0 *ent.GroupInviteLink, _a1 error) *MockRepository_CreateInviteLink_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockRepository_CreateInviteLink_Call) RunAndReturn(run func(context.Context, int, string, entitygroup.Role) (*ent.GroupInviteLink, error)) *MockRepository_CreateInviteLink_Call { _c.Call.Return(run) return _c } @@ -137,6 +294,104 @@ func (_c *MockRepository_DeleteGroup_Call) RunAndReturn(run func(context.Context return _c } +// DeleteInviteLink provides a mock function with given fields: ctx, id +func (_m *MockRepository) DeleteInviteLink(ctx context.Context, id int) error { + ret := _m.Called(ctx, id) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, int) error); ok { + r0 = rf(ctx, id) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// MockRepository_DeleteInviteLink_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeleteInviteLink' +type MockRepository_DeleteInviteLink_Call struct { + *mock.Call +} + +// DeleteInviteLink is a helper method to define mock.On call +// - ctx context.Context +// - id int +func (_e *MockRepository_Expecter) DeleteInviteLink(ctx interface{}, id interface{}) *MockRepository_DeleteInviteLink_Call { + return &MockRepository_DeleteInviteLink_Call{Call: _e.mock.On("DeleteInviteLink", ctx, id)} +} + +func (_c *MockRepository_DeleteInviteLink_Call) Run(run func(ctx context.Context, id int)) *MockRepository_DeleteInviteLink_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(int)) + }) + return _c +} + +func (_c *MockRepository_DeleteInviteLink_Call) Return(_a0 error) *MockRepository_DeleteInviteLink_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockRepository_DeleteInviteLink_Call) RunAndReturn(run func(context.Context, int) error) *MockRepository_DeleteInviteLink_Call { + _c.Call.Return(run) + return _c +} + +// FindGroup provides a mock function with given fields: ctx, shortName +func (_m *MockRepository) FindGroup(ctx context.Context, shortName string) (*ent.Group, error) { + ret := _m.Called(ctx, shortName) + + var r0 *ent.Group + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) (*ent.Group, error)); ok { + return rf(ctx, shortName) + } + if rf, ok := ret.Get(0).(func(context.Context, string) *ent.Group); ok { + r0 = rf(ctx, shortName) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*ent.Group) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, shortName) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockRepository_FindGroup_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FindGroup' +type MockRepository_FindGroup_Call struct { + *mock.Call +} + +// FindGroup is a helper method to define mock.On call +// - ctx context.Context +// - shortName string +func (_e *MockRepository_Expecter) FindGroup(ctx interface{}, shortName interface{}) *MockRepository_FindGroup_Call { + return &MockRepository_FindGroup_Call{Call: _e.mock.On("FindGroup", ctx, shortName)} +} + +func (_c *MockRepository_FindGroup_Call) Run(run func(ctx context.Context, shortName string)) *MockRepository_FindGroup_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string)) + }) + return _c +} + +func (_c *MockRepository_FindGroup_Call) Return(_a0 *ent.Group, _a1 error) *MockRepository_FindGroup_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockRepository_FindGroup_Call) RunAndReturn(run func(context.Context, string) (*ent.Group, error)) *MockRepository_FindGroup_Call { + _c.Call.Return(run) + return _c +} + // FindGroupByInstitutionIDAndShortName provides a mock function with given fields: ctx, institutionID, shortName func (_m *MockRepository) FindGroupByInstitutionIDAndShortName(ctx context.Context, institutionID int, shortName string) (*ent.Group, error) { ret := _m.Called(ctx, institutionID, shortName) @@ -249,6 +504,61 @@ func (_c *MockRepository_FindGroupUser_Call) RunAndReturn(run func(context.Conte return _c } +// FindGroupWithInvites provides a mock function with given fields: ctx, shortName +func (_m *MockRepository) FindGroupWithInvites(ctx context.Context, shortName string) (*ent.Group, error) { + ret := _m.Called(ctx, shortName) + + var r0 *ent.Group + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) (*ent.Group, error)); ok { + return rf(ctx, shortName) + } + if rf, ok := ret.Get(0).(func(context.Context, string) *ent.Group); ok { + r0 = rf(ctx, shortName) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*ent.Group) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, shortName) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockRepository_FindGroupWithInvites_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FindGroupWithInvites' +type MockRepository_FindGroupWithInvites_Call struct { + *mock.Call +} + +// FindGroupWithInvites is a helper method to define mock.On call +// - ctx context.Context +// - shortName string +func (_e *MockRepository_Expecter) FindGroupWithInvites(ctx interface{}, shortName interface{}) *MockRepository_FindGroupWithInvites_Call { + return &MockRepository_FindGroupWithInvites_Call{Call: _e.mock.On("FindGroupWithInvites", ctx, shortName)} +} + +func (_c *MockRepository_FindGroupWithInvites_Call) Run(run func(ctx context.Context, shortName string)) *MockRepository_FindGroupWithInvites_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string)) + }) + return _c +} + +func (_c *MockRepository_FindGroupWithInvites_Call) Return(_a0 *ent.Group, _a1 error) *MockRepository_FindGroupWithInvites_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockRepository_FindGroupWithInvites_Call) RunAndReturn(run func(context.Context, string) (*ent.Group, error)) *MockRepository_FindGroupWithInvites_Call { + _c.Call.Return(run) + return _c +} + // FindGroupsByUser provides a mock function with given fields: ctx, principal func (_m *MockRepository) FindGroupsByUser(ctx context.Context, principal string) ([]*ent.Group, error) { ret := _m.Called(ctx, principal) @@ -304,6 +614,61 @@ func (_c *MockRepository_FindGroupsByUser_Call) RunAndReturn(run func(context.Co return _c } +// FindInviteWithGroup provides a mock function with given fields: ctx, code +func (_m *MockRepository) FindInviteWithGroup(ctx context.Context, code string) (*ent.GroupInviteLink, error) { + ret := _m.Called(ctx, code) + + var r0 *ent.GroupInviteLink + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) (*ent.GroupInviteLink, error)); ok { + return rf(ctx, code) + } + if rf, ok := ret.Get(0).(func(context.Context, string) *ent.GroupInviteLink); ok { + r0 = rf(ctx, code) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*ent.GroupInviteLink) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, code) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockRepository_FindInviteWithGroup_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FindInviteWithGroup' +type MockRepository_FindInviteWithGroup_Call struct { + *mock.Call +} + +// FindInviteWithGroup is a helper method to define mock.On call +// - ctx context.Context +// - code string +func (_e *MockRepository_Expecter) FindInviteWithGroup(ctx interface{}, code interface{}) *MockRepository_FindInviteWithGroup_Call { + return &MockRepository_FindInviteWithGroup_Call{Call: _e.mock.On("FindInviteWithGroup", ctx, code)} +} + +func (_c *MockRepository_FindInviteWithGroup_Call) Run(run func(ctx context.Context, code string)) *MockRepository_FindInviteWithGroup_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string)) + }) + return _c +} + +func (_c *MockRepository_FindInviteWithGroup_Call) Return(_a0 *ent.GroupInviteLink, _a1 error) *MockRepository_FindInviteWithGroup_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockRepository_FindInviteWithGroup_Call) RunAndReturn(run func(context.Context, string) (*ent.GroupInviteLink, error)) *MockRepository_FindInviteWithGroup_Call { + _c.Call.Return(run) + return _c +} + // FindUserWithInstitution provides a mock function with given fields: ctx, principal func (_m *MockRepository) FindUserWithInstitution(ctx context.Context, principal string) (*ent.User, error) { ret := _m.Called(ctx, principal) @@ -360,7 +725,7 @@ func (_c *MockRepository_FindUserWithInstitution_Call) RunAndReturn(run func(con } // UpdateGroup provides a mock function with given fields: ctx, id, opts -func (_m *MockRepository) UpdateGroup(ctx context.Context, id int, opts ...group.Option) (*ent.Group, error) { +func (_m *MockRepository) UpdateGroup(ctx context.Context, id int, opts ...entitygroup.Option) (*ent.Group, error) { _va := make([]interface{}, len(opts)) for _i := range opts { _va[_i] = opts[_i] @@ -372,10 +737,10 @@ func (_m *MockRepository) UpdateGroup(ctx context.Context, id int, opts ...group var r0 *ent.Group var r1 error - if rf, ok := ret.Get(0).(func(context.Context, int, ...group.Option) (*ent.Group, error)); ok { + if rf, ok := ret.Get(0).(func(context.Context, int, ...entitygroup.Option) (*ent.Group, error)); ok { return rf(ctx, id, opts...) } - if rf, ok := ret.Get(0).(func(context.Context, int, ...group.Option) *ent.Group); ok { + if rf, ok := ret.Get(0).(func(context.Context, int, ...entitygroup.Option) *ent.Group); ok { r0 = rf(ctx, id, opts...) } else { if ret.Get(0) != nil { @@ -383,7 +748,7 @@ func (_m *MockRepository) UpdateGroup(ctx context.Context, id int, opts ...group } } - if rf, ok := ret.Get(1).(func(context.Context, int, ...group.Option) error); ok { + if rf, ok := ret.Get(1).(func(context.Context, int, ...entitygroup.Option) error); ok { r1 = rf(ctx, id, opts...) } else { r1 = ret.Error(1) @@ -400,18 +765,18 @@ type MockRepository_UpdateGroup_Call struct { // UpdateGroup is a helper method to define mock.On call // - ctx context.Context // - id int -// - opts ...group.Option +// - opts ...entitygroup.Option func (_e *MockRepository_Expecter) UpdateGroup(ctx interface{}, id interface{}, opts ...interface{}) *MockRepository_UpdateGroup_Call { return &MockRepository_UpdateGroup_Call{Call: _e.mock.On("UpdateGroup", append([]interface{}{ctx, id}, opts...)...)} } -func (_c *MockRepository_UpdateGroup_Call) Run(run func(ctx context.Context, id int, opts ...group.Option)) *MockRepository_UpdateGroup_Call { +func (_c *MockRepository_UpdateGroup_Call) Run(run func(ctx context.Context, id int, opts ...entitygroup.Option)) *MockRepository_UpdateGroup_Call { _c.Call.Run(func(args mock.Arguments) { - variadicArgs := make([]group.Option, len(args)-2) + variadicArgs := make([]entitygroup.Option, len(args)-2) for i, a := range args[2:] { if a != nil { - variadicArgs[i] = a.(group.Option) + variadicArgs[i] = a.(entitygroup.Option) } } run(args[0].(context.Context), args[1].(int), variadicArgs...) @@ -424,7 +789,62 @@ func (_c *MockRepository_UpdateGroup_Call) Return(_a0 *ent.Group, _a1 error) *Mo return _c } -func (_c *MockRepository_UpdateGroup_Call) RunAndReturn(run func(context.Context, int, ...group.Option) (*ent.Group, error)) *MockRepository_UpdateGroup_Call { +func (_c *MockRepository_UpdateGroup_Call) RunAndReturn(run func(context.Context, int, ...entitygroup.Option) (*ent.Group, error)) *MockRepository_UpdateGroup_Call { + _c.Call.Return(run) + return _c +} + +// WithTx provides a mock function with given fields: _a0, _a1 +func (_m *MockRepository) WithTx(_a0 context.Context, _a1 func(context.Context) (interface{}, error)) (interface{}, error) { + ret := _m.Called(_a0, _a1) + + var r0 interface{} + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, func(context.Context) (interface{}, error)) (interface{}, error)); ok { + return rf(_a0, _a1) + } + if rf, ok := ret.Get(0).(func(context.Context, func(context.Context) (interface{}, error)) interface{}); ok { + r0 = rf(_a0, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(interface{}) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, func(context.Context) (interface{}, error)) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockRepository_WithTx_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'WithTx' +type MockRepository_WithTx_Call struct { + *mock.Call +} + +// WithTx is a helper method to define mock.On call +// - _a0 context.Context +// - _a1 func(context.Context)(interface{} , error) +func (_e *MockRepository_Expecter) WithTx(_a0 interface{}, _a1 interface{}) *MockRepository_WithTx_Call { + return &MockRepository_WithTx_Call{Call: _e.mock.On("WithTx", _a0, _a1)} +} + +func (_c *MockRepository_WithTx_Call) Run(run func(_a0 context.Context, _a1 func(context.Context) (interface{}, error))) *MockRepository_WithTx_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(func(context.Context) (interface{}, error))) + }) + return _c +} + +func (_c *MockRepository_WithTx_Call) Return(_a0 interface{}, _a1 error) *MockRepository_WithTx_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockRepository_WithTx_Call) RunAndReturn(run func(context.Context, func(context.Context) (interface{}, error)) (interface{}, error)) *MockRepository_WithTx_Call { _c.Call.Return(run) return _c } diff --git a/internal/group/mocks/mock_use_case.go b/internal/group/mocks/mock_use_case.go index ec9330c..94c521a 100644 --- a/internal/group/mocks/mock_use_case.go +++ b/internal/group/mocks/mock_use_case.go @@ -94,6 +94,63 @@ func (_c *MockUseCase_CreateGroup_Call) RunAndReturn(run func(context.Context, s return _c } +// CreateInviteLink provides a mock function with given fields: ctx, principal, shortName, role +func (_m *MockUseCase) CreateInviteLink(ctx context.Context, principal string, shortName string, role group.Role) (*ent.GroupInviteLink, error) { + ret := _m.Called(ctx, principal, shortName, role) + + var r0 *ent.GroupInviteLink + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, string, group.Role) (*ent.GroupInviteLink, error)); ok { + return rf(ctx, principal, shortName, role) + } + if rf, ok := ret.Get(0).(func(context.Context, string, string, group.Role) *ent.GroupInviteLink); ok { + r0 = rf(ctx, principal, shortName, role) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*ent.GroupInviteLink) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string, string, group.Role) error); ok { + r1 = rf(ctx, principal, shortName, role) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockUseCase_CreateInviteLink_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateInviteLink' +type MockUseCase_CreateInviteLink_Call struct { + *mock.Call +} + +// CreateInviteLink is a helper method to define mock.On call +// - ctx context.Context +// - principal string +// - shortName string +// - role group.Role +func (_e *MockUseCase_Expecter) CreateInviteLink(ctx interface{}, principal interface{}, shortName interface{}, role interface{}) *MockUseCase_CreateInviteLink_Call { + return &MockUseCase_CreateInviteLink_Call{Call: _e.mock.On("CreateInviteLink", ctx, principal, shortName, role)} +} + +func (_c *MockUseCase_CreateInviteLink_Call) Run(run func(ctx context.Context, principal string, shortName string, role group.Role)) *MockUseCase_CreateInviteLink_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].(group.Role)) + }) + return _c +} + +func (_c *MockUseCase_CreateInviteLink_Call) Return(_a0 *ent.GroupInviteLink, _a1 error) *MockUseCase_CreateInviteLink_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockUseCase_CreateInviteLink_Call) RunAndReturn(run func(context.Context, string, string, group.Role) (*ent.GroupInviteLink, error)) *MockUseCase_CreateInviteLink_Call { + _c.Call.Return(run) + return _c +} + // DeleteGroup provides a mock function with given fields: ctx, principal, shortName func (_m *MockUseCase) DeleteGroup(ctx context.Context, principal string, shortName string) error { ret := _m.Called(ctx, principal, shortName) @@ -138,6 +195,163 @@ func (_c *MockUseCase_DeleteGroup_Call) RunAndReturn(run func(context.Context, s return _c } +// DeleteInviteLink provides a mock function with given fields: ctx, principal, shortName, code +func (_m *MockUseCase) DeleteInviteLink(ctx context.Context, principal string, shortName string, code string) error { + ret := _m.Called(ctx, principal, shortName, code) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, string, string) error); ok { + r0 = rf(ctx, principal, shortName, code) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// MockUseCase_DeleteInviteLink_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeleteInviteLink' +type MockUseCase_DeleteInviteLink_Call struct { + *mock.Call +} + +// DeleteInviteLink is a helper method to define mock.On call +// - ctx context.Context +// - principal string +// - shortName string +// - code string +func (_e *MockUseCase_Expecter) DeleteInviteLink(ctx interface{}, principal interface{}, shortName interface{}, code interface{}) *MockUseCase_DeleteInviteLink_Call { + return &MockUseCase_DeleteInviteLink_Call{Call: _e.mock.On("DeleteInviteLink", ctx, principal, shortName, code)} +} + +func (_c *MockUseCase_DeleteInviteLink_Call) Run(run func(ctx context.Context, principal string, shortName string, code string)) *MockUseCase_DeleteInviteLink_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].(string)) + }) + return _c +} + +func (_c *MockUseCase_DeleteInviteLink_Call) Return(_a0 error) *MockUseCase_DeleteInviteLink_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockUseCase_DeleteInviteLink_Call) RunAndReturn(run func(context.Context, string, string, string) error) *MockUseCase_DeleteInviteLink_Call { + _c.Call.Return(run) + return _c +} + +// JoinGroup provides a mock function with given fields: ctx, principal, code +func (_m *MockUseCase) JoinGroup(ctx context.Context, principal string, code string) (*ent.Group, error) { + ret := _m.Called(ctx, principal, code) + + var r0 *ent.Group + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, string) (*ent.Group, error)); ok { + return rf(ctx, principal, code) + } + if rf, ok := ret.Get(0).(func(context.Context, string, string) *ent.Group); ok { + r0 = rf(ctx, principal, code) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*ent.Group) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok { + r1 = rf(ctx, principal, code) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockUseCase_JoinGroup_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'JoinGroup' +type MockUseCase_JoinGroup_Call struct { + *mock.Call +} + +// JoinGroup is a helper method to define mock.On call +// - ctx context.Context +// - principal string +// - code string +func (_e *MockUseCase_Expecter) JoinGroup(ctx interface{}, principal interface{}, code interface{}) *MockUseCase_JoinGroup_Call { + return &MockUseCase_JoinGroup_Call{Call: _e.mock.On("JoinGroup", ctx, principal, code)} +} + +func (_c *MockUseCase_JoinGroup_Call) Run(run func(ctx context.Context, principal string, code string)) *MockUseCase_JoinGroup_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string), args[2].(string)) + }) + return _c +} + +func (_c *MockUseCase_JoinGroup_Call) Return(_a0 *ent.Group, _a1 error) *MockUseCase_JoinGroup_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockUseCase_JoinGroup_Call) RunAndReturn(run func(context.Context, string, string) (*ent.Group, error)) *MockUseCase_JoinGroup_Call { + _c.Call.Return(run) + return _c +} + +// ListInviteLinks provides a mock function with given fields: ctx, principal, shortName +func (_m *MockUseCase) ListInviteLinks(ctx context.Context, principal string, shortName string) ([]*ent.GroupInviteLink, error) { + ret := _m.Called(ctx, principal, shortName) + + var r0 []*ent.GroupInviteLink + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, string) ([]*ent.GroupInviteLink, error)); ok { + return rf(ctx, principal, shortName) + } + if rf, ok := ret.Get(0).(func(context.Context, string, string) []*ent.GroupInviteLink); ok { + r0 = rf(ctx, principal, shortName) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*ent.GroupInviteLink) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok { + r1 = rf(ctx, principal, shortName) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockUseCase_ListInviteLinks_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListInviteLinks' +type MockUseCase_ListInviteLinks_Call struct { + *mock.Call +} + +// ListInviteLinks is a helper method to define mock.On call +// - ctx context.Context +// - principal string +// - shortName string +func (_e *MockUseCase_Expecter) ListInviteLinks(ctx interface{}, principal interface{}, shortName interface{}) *MockUseCase_ListInviteLinks_Call { + return &MockUseCase_ListInviteLinks_Call{Call: _e.mock.On("ListInviteLinks", ctx, principal, shortName)} +} + +func (_c *MockUseCase_ListInviteLinks_Call) Run(run func(ctx context.Context, principal string, shortName string)) *MockUseCase_ListInviteLinks_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string), args[2].(string)) + }) + return _c +} + +func (_c *MockUseCase_ListInviteLinks_Call) Return(_a0 []*ent.GroupInviteLink, _a1 error) *MockUseCase_ListInviteLinks_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockUseCase_ListInviteLinks_Call) RunAndReturn(run func(context.Context, string, string) ([]*ent.GroupInviteLink, error)) *MockUseCase_ListInviteLinks_Call { + _c.Call.Return(run) + return _c +} + // ListPrincipalGroups provides a mock function with given fields: ctx, principal func (_m *MockUseCase) ListPrincipalGroups(ctx context.Context, principal string) ([]*ent.Group, error) { ret := _m.Called(ctx, principal) @@ -193,6 +407,77 @@ func (_c *MockUseCase_ListPrincipalGroups_Call) RunAndReturn(run func(context.Co return _c } +// UpdateGroup provides a mock function with given fields: ctx, principal, shortName, opts +func (_m *MockUseCase) UpdateGroup(ctx context.Context, principal string, shortName string, opts ...group.Option) (*ent.Group, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, principal, shortName) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *ent.Group + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, string, ...group.Option) (*ent.Group, error)); ok { + return rf(ctx, principal, shortName, opts...) + } + if rf, ok := ret.Get(0).(func(context.Context, string, string, ...group.Option) *ent.Group); ok { + r0 = rf(ctx, principal, shortName, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*ent.Group) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string, string, ...group.Option) error); ok { + r1 = rf(ctx, principal, shortName, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockUseCase_UpdateGroup_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateGroup' +type MockUseCase_UpdateGroup_Call struct { + *mock.Call +} + +// UpdateGroup is a helper method to define mock.On call +// - ctx context.Context +// - principal string +// - shortName string +// - opts ...group.Option +func (_e *MockUseCase_Expecter) UpdateGroup(ctx interface{}, principal interface{}, shortName interface{}, opts ...interface{}) *MockUseCase_UpdateGroup_Call { + return &MockUseCase_UpdateGroup_Call{Call: _e.mock.On("UpdateGroup", + append([]interface{}{ctx, principal, shortName}, opts...)...)} +} + +func (_c *MockUseCase_UpdateGroup_Call) Run(run func(ctx context.Context, principal string, shortName string, opts ...group.Option)) *MockUseCase_UpdateGroup_Call { + _c.Call.Run(func(args mock.Arguments) { + variadicArgs := make([]group.Option, len(args)-3) + for i, a := range args[3:] { + if a != nil { + variadicArgs[i] = a.(group.Option) + } + } + run(args[0].(context.Context), args[1].(string), args[2].(string), variadicArgs...) + }) + return _c +} + +func (_c *MockUseCase_UpdateGroup_Call) Return(_a0 *ent.Group, _a1 error) *MockUseCase_UpdateGroup_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockUseCase_UpdateGroup_Call) RunAndReturn(run func(context.Context, string, string, ...group.Option) (*ent.Group, error)) *MockUseCase_UpdateGroup_Call { + _c.Call.Return(run) + return _c +} + type mockConstructorTestingTNewMockUseCase interface { mock.TestingT Cleanup(func()) diff --git a/internal/group/mocks/mock_writer.go b/internal/group/mocks/mock_writer.go index 3807d54..6968c6d 100644 --- a/internal/group/mocks/mock_writer.go +++ b/internal/group/mocks/mock_writer.go @@ -6,7 +6,7 @@ import ( context "context" ent "github.com/np-inprove/server/internal/ent" - group "github.com/np-inprove/server/internal/entity/group" + entitygroup "github.com/np-inprove/server/internal/entity/group" mock "github.com/stretchr/testify/mock" ) @@ -24,8 +24,51 @@ func (_m *MockWriter) EXPECT() *MockWriter_Expecter { return &MockWriter_Expecter{mock: &_m.Mock} } +// BulkDeleteGroupUsers provides a mock function with given fields: ctx, groupID +func (_m *MockWriter) BulkDeleteGroupUsers(ctx context.Context, groupID int) error { + ret := _m.Called(ctx, groupID) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, int) error); ok { + r0 = rf(ctx, groupID) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// MockWriter_BulkDeleteGroupUsers_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'BulkDeleteGroupUsers' +type MockWriter_BulkDeleteGroupUsers_Call struct { + *mock.Call +} + +// BulkDeleteGroupUsers is a helper method to define mock.On call +// - ctx context.Context +// - groupID int +func (_e *MockWriter_Expecter) BulkDeleteGroupUsers(ctx interface{}, groupID interface{}) *MockWriter_BulkDeleteGroupUsers_Call { + return &MockWriter_BulkDeleteGroupUsers_Call{Call: _e.mock.On("BulkDeleteGroupUsers", ctx, groupID)} +} + +func (_c *MockWriter_BulkDeleteGroupUsers_Call) Run(run func(ctx context.Context, groupID int)) *MockWriter_BulkDeleteGroupUsers_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(int)) + }) + return _c +} + +func (_c *MockWriter_BulkDeleteGroupUsers_Call) Return(_a0 error) *MockWriter_BulkDeleteGroupUsers_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockWriter_BulkDeleteGroupUsers_Call) RunAndReturn(run func(context.Context, int) error) *MockWriter_BulkDeleteGroupUsers_Call { + _c.Call.Return(run) + return _c +} + // CreateGroup provides a mock function with given fields: ctx, institutionID, opts -func (_m *MockWriter) CreateGroup(ctx context.Context, institutionID int, opts ...group.Option) (*ent.Group, error) { +func (_m *MockWriter) CreateGroup(ctx context.Context, institutionID int, opts ...entitygroup.Option) (*ent.Group, error) { _va := make([]interface{}, len(opts)) for _i := range opts { _va[_i] = opts[_i] @@ -37,10 +80,10 @@ func (_m *MockWriter) CreateGroup(ctx context.Context, institutionID int, opts . var r0 *ent.Group var r1 error - if rf, ok := ret.Get(0).(func(context.Context, int, ...group.Option) (*ent.Group, error)); ok { + if rf, ok := ret.Get(0).(func(context.Context, int, ...entitygroup.Option) (*ent.Group, error)); ok { return rf(ctx, institutionID, opts...) } - if rf, ok := ret.Get(0).(func(context.Context, int, ...group.Option) *ent.Group); ok { + if rf, ok := ret.Get(0).(func(context.Context, int, ...entitygroup.Option) *ent.Group); ok { r0 = rf(ctx, institutionID, opts...) } else { if ret.Get(0) != nil { @@ -48,7 +91,7 @@ func (_m *MockWriter) CreateGroup(ctx context.Context, institutionID int, opts . } } - if rf, ok := ret.Get(1).(func(context.Context, int, ...group.Option) error); ok { + if rf, ok := ret.Get(1).(func(context.Context, int, ...entitygroup.Option) error); ok { r1 = rf(ctx, institutionID, opts...) } else { r1 = ret.Error(1) @@ -65,18 +108,18 @@ type MockWriter_CreateGroup_Call struct { // CreateGroup is a helper method to define mock.On call // - ctx context.Context // - institutionID int -// - opts ...group.Option +// - opts ...entitygroup.Option func (_e *MockWriter_Expecter) CreateGroup(ctx interface{}, institutionID interface{}, opts ...interface{}) *MockWriter_CreateGroup_Call { return &MockWriter_CreateGroup_Call{Call: _e.mock.On("CreateGroup", append([]interface{}{ctx, institutionID}, opts...)...)} } -func (_c *MockWriter_CreateGroup_Call) Run(run func(ctx context.Context, institutionID int, opts ...group.Option)) *MockWriter_CreateGroup_Call { +func (_c *MockWriter_CreateGroup_Call) Run(run func(ctx context.Context, institutionID int, opts ...entitygroup.Option)) *MockWriter_CreateGroup_Call { _c.Call.Run(func(args mock.Arguments) { - variadicArgs := make([]group.Option, len(args)-2) + variadicArgs := make([]entitygroup.Option, len(args)-2) for i, a := range args[2:] { if a != nil { - variadicArgs[i] = a.(group.Option) + variadicArgs[i] = a.(entitygroup.Option) } } run(args[0].(context.Context), args[1].(int), variadicArgs...) @@ -89,7 +132,121 @@ func (_c *MockWriter_CreateGroup_Call) Return(_a0 *ent.Group, _a1 error) *MockWr return _c } -func (_c *MockWriter_CreateGroup_Call) RunAndReturn(run func(context.Context, int, ...group.Option) (*ent.Group, error)) *MockWriter_CreateGroup_Call { +func (_c *MockWriter_CreateGroup_Call) RunAndReturn(run func(context.Context, int, ...entitygroup.Option) (*ent.Group, error)) *MockWriter_CreateGroup_Call { + _c.Call.Return(run) + return _c +} + +// CreateGroupUser provides a mock function with given fields: ctx, userID, groupID, role +func (_m *MockWriter) CreateGroupUser(ctx context.Context, userID int, groupID int, role entitygroup.Role) (*ent.GroupUser, error) { + ret := _m.Called(ctx, userID, groupID, role) + + var r0 *ent.GroupUser + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, int, int, entitygroup.Role) (*ent.GroupUser, error)); ok { + return rf(ctx, userID, groupID, role) + } + if rf, ok := ret.Get(0).(func(context.Context, int, int, entitygroup.Role) *ent.GroupUser); ok { + r0 = rf(ctx, userID, groupID, role) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*ent.GroupUser) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, int, int, entitygroup.Role) error); ok { + r1 = rf(ctx, userID, groupID, role) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockWriter_CreateGroupUser_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateGroupUser' +type MockWriter_CreateGroupUser_Call struct { + *mock.Call +} + +// CreateGroupUser is a helper method to define mock.On call +// - ctx context.Context +// - userID int +// - groupID int +// - role entitygroup.Role +func (_e *MockWriter_Expecter) CreateGroupUser(ctx interface{}, userID interface{}, groupID interface{}, role interface{}) *MockWriter_CreateGroupUser_Call { + return &MockWriter_CreateGroupUser_Call{Call: _e.mock.On("CreateGroupUser", ctx, userID, groupID, role)} +} + +func (_c *MockWriter_CreateGroupUser_Call) Run(run func(ctx context.Context, userID int, groupID int, role entitygroup.Role)) *MockWriter_CreateGroupUser_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(int), args[2].(int), args[3].(entitygroup.Role)) + }) + return _c +} + +func (_c *MockWriter_CreateGroupUser_Call) Return(_a0 *ent.GroupUser, _a1 error) *MockWriter_CreateGroupUser_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockWriter_CreateGroupUser_Call) RunAndReturn(run func(context.Context, int, int, entitygroup.Role) (*ent.GroupUser, error)) *MockWriter_CreateGroupUser_Call { + _c.Call.Return(run) + return _c +} + +// CreateInviteLink provides a mock function with given fields: ctx, id, code, role +func (_m *MockWriter) CreateInviteLink(ctx context.Context, id int, code string, role entitygroup.Role) (*ent.GroupInviteLink, error) { + ret := _m.Called(ctx, id, code, role) + + var r0 *ent.GroupInviteLink + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, int, string, entitygroup.Role) (*ent.GroupInviteLink, error)); ok { + return rf(ctx, id, code, role) + } + if rf, ok := ret.Get(0).(func(context.Context, int, string, entitygroup.Role) *ent.GroupInviteLink); ok { + r0 = rf(ctx, id, code, role) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*ent.GroupInviteLink) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, int, string, entitygroup.Role) error); ok { + r1 = rf(ctx, id, code, role) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockWriter_CreateInviteLink_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateInviteLink' +type MockWriter_CreateInviteLink_Call struct { + *mock.Call +} + +// CreateInviteLink is a helper method to define mock.On call +// - ctx context.Context +// - id int +// - code string +// - role entitygroup.Role +func (_e *MockWriter_Expecter) CreateInviteLink(ctx interface{}, id interface{}, code interface{}, role interface{}) *MockWriter_CreateInviteLink_Call { + return &MockWriter_CreateInviteLink_Call{Call: _e.mock.On("CreateInviteLink", ctx, id, code, role)} +} + +func (_c *MockWriter_CreateInviteLink_Call) Run(run func(ctx context.Context, id int, code string, role entitygroup.Role)) *MockWriter_CreateInviteLink_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(int), args[2].(string), args[3].(entitygroup.Role)) + }) + return _c +} + +func (_c *MockWriter_CreateInviteLink_Call) Return(_a0 *ent.GroupInviteLink, _a1 error) *MockWriter_CreateInviteLink_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockWriter_CreateInviteLink_Call) RunAndReturn(run func(context.Context, int, string, entitygroup.Role) (*ent.GroupInviteLink, error)) *MockWriter_CreateInviteLink_Call { _c.Call.Return(run) return _c } @@ -137,8 +294,51 @@ func (_c *MockWriter_DeleteGroup_Call) RunAndReturn(run func(context.Context, in return _c } +// DeleteInviteLink provides a mock function with given fields: ctx, id +func (_m *MockWriter) DeleteInviteLink(ctx context.Context, id int) error { + ret := _m.Called(ctx, id) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, int) error); ok { + r0 = rf(ctx, id) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// MockWriter_DeleteInviteLink_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeleteInviteLink' +type MockWriter_DeleteInviteLink_Call struct { + *mock.Call +} + +// DeleteInviteLink is a helper method to define mock.On call +// - ctx context.Context +// - id int +func (_e *MockWriter_Expecter) DeleteInviteLink(ctx interface{}, id interface{}) *MockWriter_DeleteInviteLink_Call { + return &MockWriter_DeleteInviteLink_Call{Call: _e.mock.On("DeleteInviteLink", ctx, id)} +} + +func (_c *MockWriter_DeleteInviteLink_Call) Run(run func(ctx context.Context, id int)) *MockWriter_DeleteInviteLink_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(int)) + }) + return _c +} + +func (_c *MockWriter_DeleteInviteLink_Call) Return(_a0 error) *MockWriter_DeleteInviteLink_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockWriter_DeleteInviteLink_Call) RunAndReturn(run func(context.Context, int) error) *MockWriter_DeleteInviteLink_Call { + _c.Call.Return(run) + return _c +} + // UpdateGroup provides a mock function with given fields: ctx, id, opts -func (_m *MockWriter) UpdateGroup(ctx context.Context, id int, opts ...group.Option) (*ent.Group, error) { +func (_m *MockWriter) UpdateGroup(ctx context.Context, id int, opts ...entitygroup.Option) (*ent.Group, error) { _va := make([]interface{}, len(opts)) for _i := range opts { _va[_i] = opts[_i] @@ -150,10 +350,10 @@ func (_m *MockWriter) UpdateGroup(ctx context.Context, id int, opts ...group.Opt var r0 *ent.Group var r1 error - if rf, ok := ret.Get(0).(func(context.Context, int, ...group.Option) (*ent.Group, error)); ok { + if rf, ok := ret.Get(0).(func(context.Context, int, ...entitygroup.Option) (*ent.Group, error)); ok { return rf(ctx, id, opts...) } - if rf, ok := ret.Get(0).(func(context.Context, int, ...group.Option) *ent.Group); ok { + if rf, ok := ret.Get(0).(func(context.Context, int, ...entitygroup.Option) *ent.Group); ok { r0 = rf(ctx, id, opts...) } else { if ret.Get(0) != nil { @@ -161,7 +361,7 @@ func (_m *MockWriter) UpdateGroup(ctx context.Context, id int, opts ...group.Opt } } - if rf, ok := ret.Get(1).(func(context.Context, int, ...group.Option) error); ok { + if rf, ok := ret.Get(1).(func(context.Context, int, ...entitygroup.Option) error); ok { r1 = rf(ctx, id, opts...) } else { r1 = ret.Error(1) @@ -178,18 +378,18 @@ type MockWriter_UpdateGroup_Call struct { // UpdateGroup is a helper method to define mock.On call // - ctx context.Context // - id int -// - opts ...group.Option +// - opts ...entitygroup.Option func (_e *MockWriter_Expecter) UpdateGroup(ctx interface{}, id interface{}, opts ...interface{}) *MockWriter_UpdateGroup_Call { return &MockWriter_UpdateGroup_Call{Call: _e.mock.On("UpdateGroup", append([]interface{}{ctx, id}, opts...)...)} } -func (_c *MockWriter_UpdateGroup_Call) Run(run func(ctx context.Context, id int, opts ...group.Option)) *MockWriter_UpdateGroup_Call { +func (_c *MockWriter_UpdateGroup_Call) Run(run func(ctx context.Context, id int, opts ...entitygroup.Option)) *MockWriter_UpdateGroup_Call { _c.Call.Run(func(args mock.Arguments) { - variadicArgs := make([]group.Option, len(args)-2) + variadicArgs := make([]entitygroup.Option, len(args)-2) for i, a := range args[2:] { if a != nil { - variadicArgs[i] = a.(group.Option) + variadicArgs[i] = a.(entitygroup.Option) } } run(args[0].(context.Context), args[1].(int), variadicArgs...) @@ -202,7 +402,7 @@ func (_c *MockWriter_UpdateGroup_Call) Return(_a0 *ent.Group, _a1 error) *MockWr return _c } -func (_c *MockWriter_UpdateGroup_Call) RunAndReturn(run func(context.Context, int, ...group.Option) (*ent.Group, error)) *MockWriter_UpdateGroup_Call { +func (_c *MockWriter_UpdateGroup_Call) RunAndReturn(run func(context.Context, int, ...entitygroup.Option) (*ent.Group, error)) *MockWriter_UpdateGroup_Call { _c.Call.Return(run) return _c } diff --git a/internal/group/repository.go b/internal/group/repository.go index b25b4cf..8f96c4e 100644 --- a/internal/group/repository.go +++ b/internal/group/repository.go @@ -4,12 +4,15 @@ import ( "context" "github.com/np-inprove/server/internal/entity" "github.com/np-inprove/server/internal/entity/group" + "github.com/np-inprove/server/internal/transactor" ) type Reader interface { + FindGroup(ctx context.Context, shortName string) (*entity.Group, error) FindGroupsByUser(ctx context.Context, principal string) ([]*entity.Group, error) FindGroupByInstitutionIDAndShortName(ctx context.Context, institutionID int, shortName string) (*entity.Group, error) - + FindGroupWithInvites(ctx context.Context, shortName string) (*entity.Group, error) + FindInviteWithGroup(ctx context.Context, code string) (*entity.GroupInviteLink, error) FindUserWithInstitution(ctx context.Context, principal string) (*entity.User, error) FindGroupUser(ctx context.Context, principal string, shortName string) (*entity.GroupUser, error) } @@ -18,9 +21,14 @@ type Writer interface { CreateGroup(ctx context.Context, institutionID int, opts ...group.Option) (*entity.Group, error) UpdateGroup(ctx context.Context, id int, opts ...group.Option) (*entity.Group, error) DeleteGroup(ctx context.Context, id int) error + CreateGroupUser(ctx context.Context, userID int, groupID int, role group.Role) (*entity.GroupUser, error) + BulkDeleteGroupUsers(ctx context.Context, groupID int) error + CreateInviteLink(ctx context.Context, id int, code string, role group.Role) (*entity.GroupInviteLink, error) + DeleteInviteLink(ctx context.Context, id int) error } type Repository interface { Reader Writer + transactor.Transactor } diff --git a/internal/group/repository_ent.go b/internal/group/repository_ent.go index 0029154..d82b3c1 100644 --- a/internal/group/repository_ent.go +++ b/internal/group/repository_ent.go @@ -3,21 +3,41 @@ package group import ( "context" "fmt" + "github.com/np-inprove/server/internal/apperror" "github.com/np-inprove/server/internal/ent" entgroup "github.com/np-inprove/server/internal/ent/group" + "github.com/np-inprove/server/internal/ent/groupinvitelink" "github.com/np-inprove/server/internal/ent/groupuser" entinstitution "github.com/np-inprove/server/internal/ent/institution" "github.com/np-inprove/server/internal/ent/user" "github.com/np-inprove/server/internal/entity" "github.com/np-inprove/server/internal/entity/group" + "github.com/np-inprove/server/internal/entutils" + "github.com/np-inprove/server/internal/logger" ) type entRepository struct { + log logger.AppLogger client *ent.Client } -func NewEntRepository(e *ent.Client) Repository { - return &entRepository{client: e} +func NewEntRepository(l logger.AppLogger, c *ent.Client) Repository { + return entRepository{l, c} +} + +func (e entRepository) FindGroupInvites(ctx context.Context, id int) ([]*entity.GroupInviteLink, error) { + invites, err := e.client.GroupInviteLink.Query(). + Where( + groupinvitelink.HasGroupWith( + entgroup.ID(id), + ), + ). + All(ctx) + if err != nil { + return nil, fmt.Errorf("failed to find group invites: %w", err) + } + + return invites, nil } func (e entRepository) FindGroupsByUser(ctx context.Context, principal string) ([]*entity.Group, error) { @@ -79,12 +99,18 @@ func (e entRepository) FindGroupUser(ctx context.Context, principal string, shor } func (e entRepository) CreateGroup(ctx context.Context, institutionID int, opts ...group.Option) (*entity.Group, error) { + + c := e.client + if cc, ok := entutils.ExtractTx(ctx); ok { + c = cc + } + var options group.Options for _, opt := range opts { opt(&options) } - g, err := e.client.Group. + g, err := c.Group. Create(). SetName(options.Name). SetShortName(options.ShortName). @@ -92,19 +118,48 @@ func (e entRepository) CreateGroup(ctx context.Context, institutionID int, opts SetInstitutionID(institutionID). Save(ctx) if err != nil { + if apperror.IsConflict(err) { + return nil, ErrGroupShortNameConflict + } return nil, fmt.Errorf("failed to create group: %w", err) } return g, nil } +func (e entRepository) CreateGroupUser(ctx context.Context, userID int, groupID int, role group.Role) (*entity.GroupUser, error) { + + c := e.client + if cc, ok := entutils.ExtractTx(ctx); ok { + c = cc + } + + groupUser, err := c.GroupUser. + Create(). + SetUserID(userID). + SetGroupID(groupID). + SetRole(role). + Save(ctx) + if err != nil { + return nil, fmt.Errorf("failed to add user to group: %w", err) + } + + return groupUser, nil +} + func (e entRepository) UpdateGroup(ctx context.Context, id int, opts ...group.Option) (*entity.Group, error) { + + c := e.client + if cc, ok := entutils.ExtractTx(ctx); ok { + c = cc + } + var options group.Options for _, opt := range opts { opt(&options) } - query := e.client.Group.UpdateOneID(id) + query := c.Group.UpdateOneID(id) if options.Name != "" { query.SetName(options.Name) @@ -120,16 +175,131 @@ func (e entRepository) UpdateGroup(ctx context.Context, id int, opts ...group.Op grp, err := query.Save(ctx) if err != nil { + if apperror.IsConflict(err) { + return nil, ErrGroupShortNameConflict + } return nil, fmt.Errorf("failed to update group: %w", err) } return grp, nil } +func (e entRepository) BulkDeleteGroupUsers(ctx context.Context, groupID int) error { + + c := e.client + if cc, ok := entutils.ExtractTx(ctx); ok { + c = cc + } + + _, err := c.GroupUser. + Delete().Where(groupuser.GroupID(groupID)). + Exec(ctx) + if err != nil { + return fmt.Errorf("failed to add user to group: %w", err) + } + + return nil +} + func (e entRepository) DeleteGroup(ctx context.Context, id int) error { - err := e.client.Group.DeleteOneID(id).Exec(ctx) + + c := e.client + if cc, ok := entutils.ExtractTx(ctx); ok { + c = cc + } + + err := c.Group.DeleteOneID(id).Exec(ctx) if err != nil { return fmt.Errorf("failed to delete group: %w", err) } return err } + +func (e entRepository) FindGroup(ctx context.Context, shortName string) (*entity.Group, error) { + grp, err := e.client.Group.Query().Where(entgroup.ShortName(shortName)).Only(ctx) + if err != nil { + if apperror.IsNotFound(err) { + return nil, ErrGroupNotFound + } + return nil, fmt.Errorf("failed to find group: %w", err) + } + return grp, err +} + +func (e entRepository) FindGroupWithInvites(ctx context.Context, shortName string) (*entity.Group, error) { + grp, err := e.client.Group.Query().Where(entgroup.ShortName(shortName)).WithInvites().Only(ctx) + if err != nil { + return nil, fmt.Errorf("failed to find group: %w", err) + } + return grp, nil +} + +func (e entRepository) FindInviteWithGroup(ctx context.Context, code string) (*entity.GroupInviteLink, error) { + link, err := e.client.GroupInviteLink.Query(). + Where( + groupinvitelink.Code(code), + ). + WithGroup(). + Only(ctx) + if err != nil { + return nil, fmt.Errorf("failed to find group invite link: %w", err) + } + return link, nil +} + +func (e entRepository) CreateInviteLink(ctx context.Context, id int, code string, role group.Role) (*entity.GroupInviteLink, error) { + link, err := e.client.GroupInviteLink.Create(). + SetGroupID(id). + SetCode(code). + SetRole(role). + Save(ctx) + if err != nil { + return nil, fmt.Errorf("failed to create group invite link: %w", err) + } + return link, nil +} + +func (e entRepository) DeleteInviteLink(ctx context.Context, id int) error { + err := e.client.GroupInviteLink.DeleteOneID(id).Exec(ctx) + if err != nil { + return fmt.Errorf("failed to delete group invite link: %w", err) + } + return nil +} + +func (e entRepository) WithTx( + ctx context.Context, + fn func(ctx context.Context) (interface{}, error), +) (interface{}, error) { + tx, err := e.client.Tx(ctx) + if err != nil { + return nil, fmt.Errorf("failed to start ent transaction: %w", err) + } + + txc := tx.Client() + ctx = context.WithValue(ctx, entutils.EntTxCtxKey, txc) + + ret, err := fn(ctx) + if err != nil { + e.log.Warn("failed database query during ent transaction", + logger.String("err", err.Error()), + logger.String("area", "group"), + ) + if err2 := tx.Rollback(); err2 != nil { + e.log.Error("failed ent transaction rollback", + logger.String("err", err.Error()), + logger.String("causer", err.Error()), + logger.String("area", "group"), + ) + return nil, fmt.Errorf("failed to rollback ent transaction: %w", err2) + } + return nil, err + } + + err = tx.Commit() + if err != nil { + return nil, err + } + + return ret, nil +} diff --git a/internal/group/usecase.go b/internal/group/usecase.go index cbc7874..646d4ac 100644 --- a/internal/group/usecase.go +++ b/internal/group/usecase.go @@ -3,19 +3,42 @@ package group import ( "context" "fmt" + "math/rand" + "github.com/np-inprove/server/internal/entity" "github.com/np-inprove/server/internal/entity/group" "github.com/np-inprove/server/internal/entity/institution" ) +var ( + letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890") +) + type UseCase interface { ListPrincipalGroups(ctx context.Context, principal string) ([]*entity.Group, error) - // CreateGroup should be an admin only function + // CreateGroup should be an educator and admin only feature. CreateGroup(ctx context.Context, principal string, opts ...group.Option) (*entity.Group, error) - // DeleteGroup should be an admin only function + // UpdateGroup should be an educator and admin only function. + UpdateGroup(ctx context.Context, principal string, shortName string, opts ...group.Option) (*entity.Group, error) + + // DeleteGroup should be an educator and admin only function. DeleteGroup(ctx context.Context, principal string, shortName string) error + + // JoinGroup adds a user identified by principal to a group. + // In the case that the group is not in the same institution the user belongs in, the user will still be added to the group. + JoinGroup(ctx context.Context, principal string, code string) (*entity.Group, error) + + // ListInviteLinks shows invite links for a group identified by shortName. + // This is an owner and educator only function. + ListInviteLinks(ctx context.Context, principal, shortName string) ([]*entity.GroupInviteLink, error) + + // CreateInviteLink should be an owner and educator only function. + CreateInviteLink(ctx context.Context, principal, shortName string, role group.Role) (*entity.GroupInviteLink, error) + + // DeleteInviteLink should be an owner and educator only function. + DeleteInviteLink(ctx context.Context, principal, shortName, code string) error } type useCase struct { @@ -27,7 +50,34 @@ func NewUseCase(r Repository) UseCase { } func (u useCase) ListPrincipalGroups(ctx context.Context, principal string) ([]*entity.Group, error) { - return u.repo.FindGroupsByUser(ctx, principal) + grps, err := u.repo.FindGroupsByUser(ctx, principal) + if err != nil { + return nil, fmt.Errorf("failed to find groups with user: %w", err) + } + return grps, nil +} + +func (u useCase) JoinGroup(ctx context.Context, principal string, code string) (*entity.Group, error) { + usr, err := u.repo.FindUserWithInstitution(ctx, principal) + if err != nil { + return nil, fmt.Errorf("failed to find user: %w", err) + } + + if usr.Edges.Institution == nil { + return nil, fmt.Errorf("user edges not loaded") + } + + invite, err := u.repo.FindInviteWithGroup(ctx, code) + if err != nil { + return nil, err + } + + _, err = u.repo.CreateGroupUser(ctx, usr.ID, invite.Edges.Group.ID, invite.Role) + if err != nil { + return nil, fmt.Errorf("failed to create group user: %w", err) + } + + return invite.Edges.Group, nil } func (u useCase) CreateGroup(ctx context.Context, principal string, opts ...group.Option) (*entity.Group, error) { @@ -40,7 +90,8 @@ func (u useCase) CreateGroup(ctx context.Context, principal string, opts ...grou return nil, fmt.Errorf("user edges not loaded") } - if usr.Role != institution.RoleAdmin { + // TODO use security module when implemented + if usr.Role != institution.RoleAdmin && usr.Role != institution.RoleEducator { return nil, ErrUnauthorized } @@ -54,9 +105,41 @@ func (u useCase) CreateGroup(ctx context.Context, principal string, opts ...grou return nil, ErrGroupShortNameConflict } - grp, err := u.repo.CreateGroup(ctx, inst.ID, opts...) + grp, err := u.repo.WithTx(ctx, func(ctx context.Context) (interface{}, error) { + grp, err := u.repo.CreateGroup(ctx, inst.ID, opts...) + if err != nil { + return nil, fmt.Errorf("failed to create group: %w", err) + } + + _, err = u.repo.CreateGroupUser(ctx, usr.ID, grp.ID, group.RoleOwner) + if err != nil { + return nil, fmt.Errorf("failed to assign group owner: %w", err) + } + + return grp, nil + }) + + if err != nil { + return nil, err + } + + return grp.(*entity.Group), nil +} + +func (u useCase) UpdateGroup(ctx context.Context, principal string, shortName string, opts ...group.Option) (*entity.Group, error) { + grpusr, err := u.repo.FindGroupUser(ctx, principal, shortName) if err != nil { - return nil, fmt.Errorf("failed to create group: %w", err) + return nil, fmt.Errorf("failed to find group user: %w", err) + } + + // TODO use security module when implemented + if grpusr.Role != group.RoleOwner && grpusr.Role != group.RoleEducator { + return nil, ErrUnauthorized + } + + grp, err := u.repo.UpdateGroup(ctx, grpusr.GroupID, opts...) + if err != nil { + return nil, fmt.Errorf("failed to update group: %w", err) } return grp, nil @@ -68,12 +151,96 @@ func (u useCase) DeleteGroup(ctx context.Context, principal string, shortName st return fmt.Errorf("failed to find group user: %w", err) } - if grpusr.Role != group.RoleOwner { + // TODO use security module when implemented + if grpusr.Role != group.RoleOwner && grpusr.Role != group.RoleEducator { return ErrUnauthorized } - if err := u.repo.DeleteGroup(ctx, grpusr.GroupID); err != nil { - return fmt.Errorf("failed to delete group: %w", err) + _, err = u.repo.WithTx(ctx, func(ctx context.Context) (interface{}, error) { + // This is because cascade delete does not work on edge schemas in ent. + if err := u.repo.BulkDeleteGroupUsers(ctx, grpusr.GroupID); err != nil { + return nil, fmt.Errorf("failed to delete group users: %w", err) + } + + if err := u.repo.DeleteGroup(ctx, grpusr.GroupID); err != nil { + return nil, fmt.Errorf("failed to delete group: %w", err) + } + + return nil, nil + }) + + if err != nil { + return fmt.Errorf("failed to execute delete group users txn: %w", err) + } + + return nil +} + +func (u useCase) ListInviteLinks(ctx context.Context, principal, shortName string) ([]*entity.GroupInviteLink, error) { + err := u.authorizedForInvite(ctx, principal, shortName) + if err != nil { + return nil, err + } + + grp, err := u.repo.FindGroupWithInvites(ctx, shortName) + if err != nil { + return nil, fmt.Errorf("failed to find group invites: %w", err) + } + + return grp.Edges.Invites, nil +} + +func (u useCase) CreateInviteLink(ctx context.Context, principal, shortName string, role group.Role) (*entity.GroupInviteLink, error) { + err := u.authorizedForInvite(ctx, principal, shortName) + if err != nil { + return nil, err + } + + grp, err := u.repo.FindGroup(ctx, shortName) + if err != nil { + return nil, fmt.Errorf("failed to find group") + } + + code := make([]rune, 8) + for i := range code { + code[i] = letters[rand.Intn(len(letters))] + } + + link, err := u.repo.CreateInviteLink(ctx, grp.ID, string(code), role) + if err != nil { + return nil, fmt.Errorf("failed to create invite link: %w", err) + } + + return link, nil +} + +func (u useCase) DeleteInviteLink(ctx context.Context, principal, shortName, code string) error { + err := u.authorizedForInvite(ctx, principal, shortName) + if err != nil { + return err + } + + link, err := u.repo.FindInviteWithGroup(ctx, code) + if err != nil { + return fmt.Errorf("failed to find invite link: %w", err) + } + + if err := u.repo.DeleteInviteLink(ctx, link.ID); err != nil { + return fmt.Errorf("failed to delete invite link: %w", err) + } + + return nil +} + +// TODO use security module when implemented +func (u useCase) authorizedForInvite(ctx context.Context, principal, shortName string) error { + usr, err := u.repo.FindGroupUser(ctx, principal, shortName) + if err != nil { + return fmt.Errorf("failed to find group: %w", err) + } + + if usr.Role != group.RoleOwner && usr.Role != group.RoleEducator { + return ErrUnauthorized } return nil diff --git a/internal/payload/auth.go b/internal/payload/auth.go index 272a13b..7a81a2a 100644 --- a/internal/payload/auth.go +++ b/internal/payload/auth.go @@ -17,6 +17,7 @@ type User struct { PointsAwardedResetTime time.Time `json:"pointsAwardedResetTime,omitempty"` GodMode bool `json:"godMode,omitempty"` Role institution.Role `json:"role,omitempty"` + Institution Institution `json:"institution,omitempty"` } func (u User) Render(_ http.ResponseWriter, _ *http.Request) error { diff --git a/internal/payload/group.go b/internal/payload/group.go index b3ed7b1..ad49513 100644 --- a/internal/payload/group.go +++ b/internal/payload/group.go @@ -2,13 +2,15 @@ package payload import ( "github.com/gookit/validate" + "github.com/np-inprove/server/internal/entity/group" + "golang.org/x/exp/slices" "net/http" ) type Group struct { ID int `json:"id,omitempty"` Name string `json:"name,omitempty"` - ShortName string `json:"path,omitempty"` + ShortName string `json:"shortName,omitempty"` Description string `json:"description,omitempty"` } @@ -25,3 +27,36 @@ type CreateGroupRequest struct { func (c CreateGroupRequest) Validate() *validate.Validation { return validate.Struct(c) } + +type UpdateGroupRequest struct { + Name string `json:"name,omitempty" validate:"required|minLen:3"` + ShortName string `json:"shortName,omitempty" validate:"required|alphaDash"` + Description string `json:"description,omitempty"` +} + +func (u UpdateGroupRequest) Validate() *validate.Validation { + return validate.Struct(u) +} + +type GroupInviteLink struct { + ID int `json:"id,omitempty"` + Code string `json:"code,omitempty"` + Role group.Role `json:"role,omitempty"` + Group Group `json:"group,omitempty"` +} + +func (g GroupInviteLink) Render(_ http.ResponseWriter, _ *http.Request) error { + return nil +} + +type CreateGroupInviteLinkRequest struct { + Role group.Role `json:"role,omitempty"` +} + +func (c CreateGroupInviteLinkRequest) Validate() *validate.Validation { + v := validate.Struct(c) + v.AddValidator("role", func(val interface{}) bool { + return slices.Contains(group.Roles, val.(group.Role)) + }) + return v +}