From c82a1984e0a172eb40babc25fabf44eba2f9bd3c Mon Sep 17 00:00:00 2001 From: Yun-E Date: Thu, 29 Jun 2023 16:53:50 +0800 Subject: [PATCH 01/19] feat: forums wip --- internal/entity/entity.go | 3 + internal/entity/forum/forum.go | 55 ++++++++++++++++ internal/group/forum/httphandler.go | 62 ++++++++++++++++++ internal/group/forum/repository.go | 26 ++++++++ internal/group/forum/repository_ent.go | 87 ++++++++++++++++++++++++++ internal/group/repository.go | 3 +- 6 files changed, 235 insertions(+), 1 deletion(-) create mode 100644 internal/entity/forum/forum.go create mode 100644 internal/group/forum/httphandler.go create mode 100644 internal/group/forum/repository.go create mode 100644 internal/group/forum/repository_ent.go diff --git a/internal/entity/entity.go b/internal/entity/entity.go index 86a7e60..98a61bf 100644 --- a/internal/entity/entity.go +++ b/internal/entity/entity.go @@ -18,5 +18,8 @@ type GroupEdges = ent.GroupEdges type GroupUser = ent.GroupUser type GroupUserEdges = ent.GroupUserEdges +type Forum = ent.Forum +type ForumPost = ent.ForumPost + type InstitutionInviteLink = ent.InstitutionInviteLink type InstitutionInviteLinkEdges = ent.InstitutionInviteLinkEdges diff --git a/internal/entity/forum/forum.go b/internal/entity/forum/forum.go new file mode 100644 index 0000000..bf9f153 --- /dev/null +++ b/internal/entity/forum/forum.go @@ -0,0 +1,55 @@ +package forum + +import "errors" + +type Role string + +func (Role) Values() (kinds []string) { + for _, s := range Roles { + kinds = append(kinds, string(s)) + } + return +} + +func (fr Role) Validate() error { + switch fr { + case RoleOwner, RoleEducator, RoleMember: + return nil + default: + return errors.New("group role is not valid") + } +} + +var ( + Roles = []Role{RoleOwner, RoleEducator, RoleMember} + + RoleOwner Role = "owner" + RoleEducator Role = "educator" + RoleMember Role = "member" +) + +type Options struct { + Name string + ShortName string + Description string +} + +type Option func(*Options) + +func Name(n string) Option { + return func(opts *Options) { + opts.Name = n + } +} + +func ShortName(n string) Option { + return func(opts *Options) { + opts.ShortName = n + } +} + +func Description(d string) Option { + return func(opts *Options) { + opts.Description = d + } +} diff --git a/internal/group/forum/httphandler.go b/internal/group/forum/httphandler.go new file mode 100644 index 0000000..94c1dca --- /dev/null +++ b/internal/group/forum/httphandler.go @@ -0,0 +1,62 @@ +package forum + +import ( + "github.com/go-chi/chi/v5" + "github.com/go-chi/jwtauth/v5" + + /* "github.com/go-chi/render" + "github.com/lestrrat-go/jwx/v2/jwt" + "github.com/np-inprove/server/internal/apperror" */ + "github.com/np-inprove/server/internal/config" + /* "github.com/np-inprove/server/internal/entity/group" */ + "github.com/np-inprove/server/internal/middleware" + /* "github.com/np-inprove/server/internal/payload" */ + "net/http" +) + +type httpHandler struct { + //service UseCase + cfg *config.Config + jwt *jwtauth.JWTAuth +} + +func NewHTTPHandler( /* u UseCase, */ c *config.Config, j *jwtauth.JWTAuth) chi.Router { + a := &httpHandler{c, j} + r := chi.NewRouter() + + // Authenticated routes + r.Group(func(r chi.Router) { + r.Use(jwtauth.Verify(j, func(r *http.Request) string { + c, err := r.Cookie(c.AppJWTCookieName()) + if err != nil { + return "" + } + return c.Value + })) + + r.Use(middleware.Authenticator) + + r.Get("/", a.ListForums) + r.Post("/", a.CreateForum) + r.Delete("/{path}", a.DeleteForum) + r.Put("/{path}", a.EditForum) + }) + + return r +} + +func (h httpHandler) ListForums(w http.ResponseWriter, r *http.Request) { + +} + +func (h httpHandler) CreateForum(w http.ResponseWriter, r *http.Request) { + +} + +func (h httpHandler) DeleteForum(w http.ResponseWriter, r *http.Request) { + +} + +func (h httpHandler) EditForum(w http.ResponseWriter, r *http.Request) { + +} diff --git a/internal/group/forum/repository.go b/internal/group/forum/repository.go new file mode 100644 index 0000000..70aec07 --- /dev/null +++ b/internal/group/forum/repository.go @@ -0,0 +1,26 @@ +package forum + +import ( + "context" + + "github.com/np-inprove/server/internal/entity" + "github.com/np-inprove/server/internal/entity/forum" +) + +type Reader interface { + FindForumsByUser(ctx context.Context, principal string) ([]*entity.Forum, error) + FindForumByGroupIDAndShortName(ctx context.Context, institutionID int, shortName string) (*entity.Forum, error) + + FindUserWithForum(ctx context.Context, principal string) (*entity.User, error) +} + +type Writer interface { + CreateForum(ctx context.Context, forumID int, opts ...forum.Options) (*entity.Forum, error) + EditForum(ctx context.Context, id int, opts ...forum.Options) (*entity.Forum, error) + DeleteForum(ctx context.Context, id int) error +} + +type Repository interface { + Reader + Writer +} diff --git a/internal/group/forum/repository_ent.go b/internal/group/forum/repository_ent.go new file mode 100644 index 0000000..745ef96 --- /dev/null +++ b/internal/group/forum/repository_ent.go @@ -0,0 +1,87 @@ +package forum + +import ( + "context" + "fmt" + + "github.com/np-inprove/server/internal/apperror" + "github.com/np-inprove/server/internal/ent" + entforum "github.com/np-inprove/server/internal/ent/forum" + "github.com/np-inprove/server/internal/entity" + "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(l logger.AppLogger, c *ent.Client) Repository { + return entRepository{l, c} +} + +func (e entRepository) FindForums(ctx context.Context) ([]*entity.Forum, error) { + forum, err := e.client.Forum.Query().All(ctx) + if err != nil { + return nil, fmt.Errorf("failed to find all forums: %w", err) + } + return forum, nil +} + +func (e entRepository) FindForum(ctx context.Context, shortName string) (*entity.Forum, error) { + inst, err := e.client.Forum.Query().Where(entforum.ShortName(shortName)).Only(ctx) + if err != nil { + return nil, fmt.Errorf("failed to find forum: %w", err) + } + return inst, nil +} + +func (e entRepository) CreateForum(ctx context.Context, name, shortName, description string) (*entity.Forum, error) { + c := e.client + if cc, ok := entutils.ExtractTx(ctx); ok { + c = cc + } + + forum, err := c.Forum.Create(). + SetName(name). + SetShortName(shortName). + SetDescription(description). + Save(ctx) + if err != nil { + if apperror.IsConflict(err) { + return nil, ErrForumShortNameConflict //create error.go file + } + return nil, fmt.Errorf("failed to save forum: %w", err) + } + + return forum, nil +} + +func (e entRepository) EditForum(ctx context.Context, id int, name, shortName, description string) (*entity.Forum, error) { + c := e.client + if cc, ok := entutils.ExtractTx(ctx); ok { + c = cc + } + + forum, err := c.Forum.UpdateOneID(id). + SetName(name). + SetShortName(shortName). + SetDescription(description). + Save(ctx) + if err != nil { + return nil, fmt.Errorf("failed to update forum: %w", err) + } + + return forum, nil +} + +func (e entRepository) DeleteForum(ctx context.Context, id int) error { + err := e.client.Forum.DeleteOneID(id).Exec(ctx) + if err != nil { + return fmt.Errorf("failed to delete forum: %w", err) + } + return err +} + +func (e entRepository) FindUserWithForum() diff --git a/internal/group/repository.go b/internal/group/repository.go index b25b4cf..f547454 100644 --- a/internal/group/repository.go +++ b/internal/group/repository.go @@ -2,6 +2,7 @@ package group import ( "context" + "github.com/np-inprove/server/internal/entity" "github.com/np-inprove/server/internal/entity/group" ) @@ -11,7 +12,7 @@ type Reader interface { FindGroupByInstitutionIDAndShortName(ctx context.Context, institutionID int, shortName string) (*entity.Group, error) FindUserWithInstitution(ctx context.Context, principal string) (*entity.User, error) - FindGroupUser(ctx context.Context, principal string, shortName string) (*entity.GroupUser, error) + FindGroupUser(ctx context.Context, principal string, shortName string) (*entity.Group, error) } type Writer interface { From 2f89e87fe31975823aa1b38a56ac3b683b0a9cb4 Mon Sep 17 00:00:00 2001 From: Yun-E Date: Thu, 29 Jun 2023 17:02:52 +0800 Subject: [PATCH 02/19] feat: forums errors --- internal/group/forum/error.go | 46 +++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 internal/group/forum/error.go diff --git a/internal/group/forum/error.go b/internal/group/forum/error.go new file mode 100644 index 0000000..5a2c3cb --- /dev/null +++ b/internal/group/forum/error.go @@ -0,0 +1,46 @@ +package forum + +import ( + "errors" + "net/http" + + "github.com/go-chi/render" + "github.com/np-inprove/server/internal/apperror" +) + +var ( + ErrGroupNotFound = errors.New("group does not exist") + ErrForumShortNameConflict = errors.New("a forum with the desired short name already exists") + ErrUnauthorized = errors.New("not authorized") +) + +func mapDomainErr(err error) render.Renderer { + if errors.Is(err, ErrGroupNotFound) { + return &apperror.ErrResponse{ + Err: err, + HTTPStatusCode: http.StatusNotFound, + AppErrCode: 40401, + AppErrMessage: "The group specified does not exist", + } + } + + if errors.Is(err, ErrForumShortNameConflict) { + return &apperror.ErrResponse{ + Err: err, + HTTPStatusCode: http.StatusConflict, + AppErrCode: 40901, + AppErrMessage: "This forum short name is unavailable", + } + } + + if errors.Is(err, ErrUnauthorized) { + return &apperror.ErrResponse{ + Err: err, + HTTPStatusCode: http.StatusUnauthorized, + AppErrCode: 40301, + AppErrMessage: "Unauthorized", + } + } + + return apperror.ErrInternal(err) +} From cf1088e37955e8dad1d2414809845c4313e3beb2 Mon Sep 17 00:00:00 2001 From: Yun-E Date: Fri, 30 Jun 2023 01:35:41 +0800 Subject: [PATCH 03/19] feat: forums usecase --- internal/group/forum/repository.go | 11 +-- internal/group/forum/repository_ent.go | 2 +- internal/group/forum/usecase.go | 98 ++++++++++++++++++++++++++ internal/group/repository.go | 2 +- 4 files changed, 106 insertions(+), 7 deletions(-) create mode 100644 internal/group/forum/usecase.go diff --git a/internal/group/forum/repository.go b/internal/group/forum/repository.go index 70aec07..1860aae 100644 --- a/internal/group/forum/repository.go +++ b/internal/group/forum/repository.go @@ -8,15 +8,16 @@ import ( ) type Reader interface { - FindForumsByUser(ctx context.Context, principal string) ([]*entity.Forum, error) - FindForumByGroupIDAndShortName(ctx context.Context, institutionID int, shortName string) (*entity.Forum, error) + FindForumsByGroup(ctx context.Context, principal string) ([]*entity.Forum, error) + FindForumByGroupIDAndShortName(ctx context.Context, groupID int, shortName string) (*entity.Forum, error) + FindForum(ctx context.Context, shortName string) (*entity.Forum, error) - FindUserWithForum(ctx context.Context, principal string) (*entity.User, error) + FindGroupUser(ctx context.Context, principal, shortName string) (*entity.GroupUser, error) } type Writer interface { - CreateForum(ctx context.Context, forumID int, opts ...forum.Options) (*entity.Forum, error) - EditForum(ctx context.Context, id int, opts ...forum.Options) (*entity.Forum, error) + CreateForum(ctx context.Context, forumID int, opts ...forum.Option) (*entity.Forum, error) + UpdateForum(ctx context.Context, id int, opts ...forum.Option) (*entity.Forum, error) DeleteForum(ctx context.Context, id int) error } diff --git a/internal/group/forum/repository_ent.go b/internal/group/forum/repository_ent.go index 745ef96..a3fe1b4 100644 --- a/internal/group/forum/repository_ent.go +++ b/internal/group/forum/repository_ent.go @@ -58,7 +58,7 @@ func (e entRepository) CreateForum(ctx context.Context, name, shortName, descrip return forum, nil } -func (e entRepository) EditForum(ctx context.Context, id int, name, shortName, description string) (*entity.Forum, error) { +func (e entRepository) UpdateForum(ctx context.Context, id int, name, shortName, description string) (*entity.Forum, error) { c := e.client if cc, ok := entutils.ExtractTx(ctx); ok { c = cc diff --git a/internal/group/forum/usecase.go b/internal/group/forum/usecase.go new file mode 100644 index 0000000..0b6fd6e --- /dev/null +++ b/internal/group/forum/usecase.go @@ -0,0 +1,98 @@ +package forum + +import ( + "context" + "fmt" + + "github.com/np-inprove/server/internal/entity" + "github.com/np-inprove/server/internal/entity/forum" + "github.com/np-inprove/server/internal/entity/group" +) + +type UseCase interface { + ListPrincipalForums(ctx context.Context, principal string) ([]*entity.Forum, error) + + // CreateGroup should be an admin only function + CreateForum(ctx context.Context, principal, shortName string, opts ...forum.Option) (*entity.Forum, error) + + // DeleteGroup should be an admin only function + DeleteForum(ctx context.Context, principal string, shortName string) error + + // UpdateForum should be an admin only function + UpdateForum(ctx context.Context, principal string, opts ...forum.Option) (*entity.Forum, error) +} + +type useCase struct { + repo Repository +} + +func NewUseCase(r Repository) UseCase { + return useCase{repo: r} +} + +func (u useCase) ListPrincipalForums(ctx context.Context, principal string) ([]*entity.Forum, error) { + return u.repo.FindForumsByGroup(ctx, principal) +} + +func (u useCase) CreateForum(ctx context.Context, principal, shortName string, opts ...forum.Option) (*entity.Forum, error) { + usr, err := u.repo.FindGroupUser(ctx, principal, shortName) + if err != nil { + return nil, fmt.Errorf("failed to find user: %w", err) + } + + if usr.Edges.Group == nil { + return nil, fmt.Errorf("user edges not loaded") + } + + if usr.Role != group.RoleOwner && usr.Role != group.RoleEducator { + return nil, ErrUnauthorized + } + + var options forum.Options + for _, opt := range opts { + opt(&options) + } + + grp := usr.Edges.Group + if _, err := u.repo.FindForumByGroupIDAndShortName(ctx, grp.ID, options.ShortName); err == nil { + return nil, ErrForumShortNameConflict + } + + forum, err := u.repo.CreateForum(ctx, grp.ID, opts...) + if err != nil { + return nil, fmt.Errorf("failed to create forum: %w", err) + } + + return forum, nil +} + +func (u useCase) DeleteForum(ctx context.Context, principal string, shortName string) error { + usr, err := u.repo.FindGroupUser(ctx, principal, shortName) + if err != nil { + return fmt.Errorf("failed to find user: %w", err) + } + + if usr.Role != group.RoleOwner && usr.Role != group.RoleEducator { + return ErrUnauthorized + } + + if err := u.repo.DeleteForum(ctx, usr.GroupID); err != nil { + return fmt.Errorf("failed to delete forum: w%") + } + + return nil +} + +func (u useCase) UpdateForum(ctx context.Context, principal string, opts ...forum.Option) (*entity.Forum, error) { + forum, err := u.repo.FindForum(ctx, principal) + if err != nil { + return nil, err + } + + forum, err = u.repo.UpdateForum(ctx, forum.ID, opts...) + if err != nil { + return nil, fmt.Errorf("failed to update institution: %w", err) + } + + return forum, nil +} diff --git a/internal/group/repository.go b/internal/group/repository.go index f547454..cf55a2c 100644 --- a/internal/group/repository.go +++ b/internal/group/repository.go @@ -12,7 +12,7 @@ type Reader interface { FindGroupByInstitutionIDAndShortName(ctx context.Context, institutionID int, shortName string) (*entity.Group, error) FindUserWithInstitution(ctx context.Context, principal string) (*entity.User, error) - FindGroupUser(ctx context.Context, principal string, shortName string) (*entity.Group, error) + FindGroupUser(ctx context.Context, principal string, shortName string) (*entity.GroupUser, error) } type Writer interface { From f016f4fd12f67d02572010869a321fdf6285e35d Mon Sep 17 00:00:00 2001 From: Yun-E Date: Fri, 30 Jun 2023 11:44:50 +0800 Subject: [PATCH 04/19] feat: forums repo and httphandler --- internal/group/forum/httphandler.go | 6 +- internal/group/forum/repository.go | 4 +- internal/group/forum/repository_ent.go | 96 +++++++++++++++++--------- internal/group/forum/usecase.go | 4 +- 4 files changed, 71 insertions(+), 39 deletions(-) diff --git a/internal/group/forum/httphandler.go b/internal/group/forum/httphandler.go index 94c1dca..c805674 100644 --- a/internal/group/forum/httphandler.go +++ b/internal/group/forum/httphandler.go @@ -38,8 +38,8 @@ func NewHTTPHandler( /* u UseCase, */ c *config.Config, j *jwtauth.JWTAuth) chi. r.Get("/", a.ListForums) r.Post("/", a.CreateForum) - r.Delete("/{path}", a.DeleteForum) - r.Put("/{path}", a.EditForum) + r.Delete("/{shortName}", a.DeleteForum) + r.Put("/{shortName}", a.UpdateForum) }) return r @@ -57,6 +57,6 @@ func (h httpHandler) DeleteForum(w http.ResponseWriter, r *http.Request) { } -func (h httpHandler) EditForum(w http.ResponseWriter, r *http.Request) { +func (h httpHandler) UpdateForum(w http.ResponseWriter, r *http.Request) { } diff --git a/internal/group/forum/repository.go b/internal/group/forum/repository.go index 1860aae..65ab92c 100644 --- a/internal/group/forum/repository.go +++ b/internal/group/forum/repository.go @@ -8,7 +8,7 @@ import ( ) type Reader interface { - FindForumsByGroup(ctx context.Context, principal string) ([]*entity.Forum, error) + FindForumsByGroup(ctx context.Context, shortName string) ([]*entity.Forum, error) FindForumByGroupIDAndShortName(ctx context.Context, groupID int, shortName string) (*entity.Forum, error) FindForum(ctx context.Context, shortName string) (*entity.Forum, error) @@ -16,7 +16,7 @@ type Reader interface { } type Writer interface { - CreateForum(ctx context.Context, forumID int, opts ...forum.Option) (*entity.Forum, error) + CreateForum(ctx context.Context, groupID int, opts ...forum.Option) (*entity.Forum, error) UpdateForum(ctx context.Context, id int, opts ...forum.Option) (*entity.Forum, error) DeleteForum(ctx context.Context, id int) error } diff --git a/internal/group/forum/repository_ent.go b/internal/group/forum/repository_ent.go index a3fe1b4..47dedeb 100644 --- a/internal/group/forum/repository_ent.go +++ b/internal/group/forum/repository_ent.go @@ -4,28 +4,51 @@ import ( "context" "fmt" - "github.com/np-inprove/server/internal/apperror" "github.com/np-inprove/server/internal/ent" entforum "github.com/np-inprove/server/internal/ent/forum" + entgroup "github.com/np-inprove/server/internal/ent/group" + "github.com/np-inprove/server/internal/ent/groupuser" + "github.com/np-inprove/server/internal/ent/predicate" + "github.com/np-inprove/server/internal/ent/user" "github.com/np-inprove/server/internal/entity" - "github.com/np-inprove/server/internal/entutils" - "github.com/np-inprove/server/internal/logger" + "github.com/np-inprove/server/internal/entity/forum" ) type entRepository struct { - log logger.AppLogger client *ent.Client } -func NewEntRepository(l logger.AppLogger, c *ent.Client) Repository { - return entRepository{l, c} +func NewEntRepository(e *ent.Client) Repository { + return &entRepository{client: e} } -func (e entRepository) FindForums(ctx context.Context) ([]*entity.Forum, error) { - forum, err := e.client.Forum.Query().All(ctx) +func (e entRepository) FindForumsByUser(ctx context.Context, principal string) ([]*entity.Forum, error) { + forum, err := e.client.Forum.Query(). + Where( + entforum.HasGroupWith( + predicate.Group(user.Email(principal)), + )). + All(ctx) if err != nil { - return nil, fmt.Errorf("failed to find all forums: %w", err) + return nil, fmt.Errorf("failed to find forums by user email: %w", err) } + + return forum, nil +} + +func (e entRepository) FindForumByGroupIDAndShortName(ctx context.Context, groupID int, shortName string) (*entity.Forum, error) { + forum, err := e.client.Forum.Query(). + Where( + entforum.HasGroupWith( + entgroup.ID(groupID), + ), + entforum.ShortName(shortName), + ). + Only(ctx) + if err != nil { + return nil, fmt.Errorf("failed to find forums by user email: %w", err) + } + return forum, nil } @@ -37,37 +60,48 @@ func (e entRepository) FindForum(ctx context.Context, shortName string) (*entity return inst, nil } -func (e entRepository) CreateForum(ctx context.Context, name, shortName, description string) (*entity.Forum, error) { - c := e.client - if cc, ok := entutils.ExtractTx(ctx); ok { - c = cc +func (e entRepository) FindGroupUser(ctx context.Context, principal string, shortName string) (*entity.GroupUser, error) { + grpusr, err := e.client.GroupUser.Query(). + Where( + groupuser.HasUserWith(user.Email(principal)), + groupuser.HasGroupWith(entgroup.ShortName(shortName)), + ).Only(ctx) + if err != nil { + return nil, fmt.Errorf("failed to find group user: %w", err) } - forum, err := c.Forum.Create(). - SetName(name). - SetShortName(shortName). - SetDescription(description). + return grpusr, nil +} + +func (e entRepository) CreateForum(ctx context.Context, groupID int, opts ...forum.Option) (*entity.Forum, error) { + var options forum.Options + for _, opt := range opts { + opt(&options) + } + + forum, err := e.client.Forum. + Create(). + SetName(options.Name). + SetShortName(options.ShortName). + SetDescription(options.Description). + SetGroupID(groupID). Save(ctx) if err != nil { - if apperror.IsConflict(err) { - return nil, ErrForumShortNameConflict //create error.go file - } - return nil, fmt.Errorf("failed to save forum: %w", err) + return nil, fmt.Errorf("failed to create group: %w", err) } - return forum, nil } -func (e entRepository) UpdateForum(ctx context.Context, id int, name, shortName, description string) (*entity.Forum, error) { - c := e.client - if cc, ok := entutils.ExtractTx(ctx); ok { - c = cc +func (e entRepository) UpdateForum(ctx context.Context, id int, opts ...forum.Option) (*entity.Forum, error) { + var options forum.Options + for _, opt := range opts { + opt(&options) } - forum, err := c.Forum.UpdateOneID(id). - SetName(name). - SetShortName(shortName). - SetDescription(description). + forum, err := e.client.Forum.UpdateOneID(id). + SetName(options.Name). + SetShortName(options.ShortName). + SetDescription(options.Description). Save(ctx) if err != nil { return nil, fmt.Errorf("failed to update forum: %w", err) @@ -83,5 +117,3 @@ func (e entRepository) DeleteForum(ctx context.Context, id int) error { } return err } - -func (e entRepository) FindUserWithForum() diff --git a/internal/group/forum/usecase.go b/internal/group/forum/usecase.go index 0b6fd6e..b4f6df5 100644 --- a/internal/group/forum/usecase.go +++ b/internal/group/forum/usecase.go @@ -31,7 +31,7 @@ func NewUseCase(r Repository) UseCase { } func (u useCase) ListPrincipalForums(ctx context.Context, principal string) ([]*entity.Forum, error) { - return u.repo.FindForumsByGroup(ctx, principal) + return u.repo.FindForumsByUser(ctx, principal) } func (u useCase) CreateForum(ctx context.Context, principal, shortName string, opts ...forum.Option) (*entity.Forum, error) { @@ -77,7 +77,7 @@ func (u useCase) DeleteForum(ctx context.Context, principal string, shortName st } if err := u.repo.DeleteForum(ctx, usr.GroupID); err != nil { - return fmt.Errorf("failed to delete forum: w%") + return fmt.Errorf("failed to delete forum: %w", err) } return nil From c456a60c74a196e4d54ea48339f2e2ae7343c7d2 Mon Sep 17 00:00:00 2001 From: Yun-E Date: Sat, 1 Jul 2023 10:34:25 +0800 Subject: [PATCH 05/19] feat: forum httphandler --- internal/group/forum/httphandler.go | 103 +++++++++++++++++++++---- internal/group/forum/repository.go | 5 +- internal/group/forum/repository_ent.go | 26 ++++--- internal/group/forum/usecase.go | 34 ++++---- internal/payload/forum.go | 38 +++++++++ 5 files changed, 169 insertions(+), 37 deletions(-) create mode 100644 internal/payload/forum.go diff --git a/internal/group/forum/httphandler.go b/internal/group/forum/httphandler.go index c805674..fc1aeac 100644 --- a/internal/group/forum/httphandler.go +++ b/internal/group/forum/httphandler.go @@ -4,24 +4,24 @@ import ( "github.com/go-chi/chi/v5" "github.com/go-chi/jwtauth/v5" - /* "github.com/go-chi/render" + "net/http" + + "github.com/go-chi/render" "github.com/lestrrat-go/jwx/v2/jwt" - "github.com/np-inprove/server/internal/apperror" */ + "github.com/np-inprove/server/internal/apperror" "github.com/np-inprove/server/internal/config" - /* "github.com/np-inprove/server/internal/entity/group" */ "github.com/np-inprove/server/internal/middleware" - /* "github.com/np-inprove/server/internal/payload" */ - "net/http" + "github.com/np-inprove/server/internal/payload" ) type httpHandler struct { - //service UseCase - cfg *config.Config - jwt *jwtauth.JWTAuth + service UseCase + cfg *config.Config + jwt *jwtauth.JWTAuth } -func NewHTTPHandler( /* u UseCase, */ c *config.Config, j *jwtauth.JWTAuth) chi.Router { - a := &httpHandler{c, j} +func NewHTTPHandler(u UseCase, c *config.Config, j *jwtauth.JWTAuth) chi.Router { + a := &httpHandler{u, c, j} r := chi.NewRouter() // Authenticated routes @@ -46,17 +46,94 @@ func NewHTTPHandler( /* u UseCase, */ c *config.Config, j *jwtauth.JWTAuth) chi. } func (h httpHandler) ListForums(w http.ResponseWriter, r *http.Request) { - + token := r.Context().Value(jwtauth.TokenCtxKey) + email := token.(jwt.Token).Subject() + + forums, err := h.service.ListPrincipalForums(r.Context(), email) + if err != nil { + _ = render.Render(w, r, mapDomainErr(err)) + return + } + + res := make([]render.Renderer, len(forums)) + for i, item := range forums { + res[i] = payload.Group{ + ID: item.ID, + ShortName: item.ShortName, + Name: item.Name, + Description: item.Description, + } + } + + _ = render.RenderList(w, r, res) } func (h httpHandler) CreateForum(w http.ResponseWriter, r *http.Request) { - + p := &payload.CreateForumRequest{} + 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 + } + + token := r.Context().Value(jwtauth.TokenCtxKey) + email := token.(jwt.Token).Subject() + + res, err := h.service.CreateForum(r.Context(), email, + p.Name, + p.ShortName, + p.Description, + ) + + if err != nil { + _ = render.Render(w, r, mapDomainErr(err)) + return + } + + _ = render.Render(w, r, payload.Forum{ + ID: res.ID, + ShortName: res.ShortName, + Name: res.Name, + Description: res.Description, + }) } func (h httpHandler) DeleteForum(w http.ResponseWriter, r *http.Request) { + path := chi.URLParam(r, "shortName") + token := r.Context().Value(jwtauth.TokenCtxKey) + email := token.(jwt.Token).Subject() + if err := h.service.DeleteForum(r.Context(), email, path); err != nil { + _ = render.Render(w, r, mapDomainErr(err)) + return + } + + render.NoContent(w, r) } func (h httpHandler) UpdateForum(w http.ResponseWriter, r *http.Request) { - + shortName := chi.URLParam(r, "shortName") + p := payload.UpdateForumRequest{} + if err := render.Decode(r, p); err != nil { + _ = render.Render(w, r, apperror.ErrBadRequest(err)) + return + } + + forum, err := h.service.UpdateForum(r.Context(), shortName, + p.Name, + p.ShortName, + p.Description, + ) + if err != nil { + _ = render.Render(w, r, payload.Forum{ + ID: forum.ID, + Name: forum.Name, + ShortName: forum.ShortName, + Description: forum.Description, + }) + } } diff --git a/internal/group/forum/repository.go b/internal/group/forum/repository.go index 65ab92c..6b1afeb 100644 --- a/internal/group/forum/repository.go +++ b/internal/group/forum/repository.go @@ -8,16 +8,17 @@ import ( ) type Reader interface { - FindForumsByGroup(ctx context.Context, shortName string) ([]*entity.Forum, error) + FindForumsByUser(ctx context.Context, principal string) ([]*entity.Forum, error) FindForumByGroupIDAndShortName(ctx context.Context, groupID int, shortName string) (*entity.Forum, error) FindForum(ctx context.Context, shortName string) (*entity.Forum, error) + FindUserWithGroups(ctx context.Context, principal string) (*entity.User, error) FindGroupUser(ctx context.Context, principal, shortName string) (*entity.GroupUser, error) } type Writer interface { CreateForum(ctx context.Context, groupID int, opts ...forum.Option) (*entity.Forum, error) - UpdateForum(ctx context.Context, id int, opts ...forum.Option) (*entity.Forum, error) + UpdateForum(ctx context.Context, principal, name, shortName, description string) (*entity.Forum, error) DeleteForum(ctx context.Context, id int) error } diff --git a/internal/group/forum/repository_ent.go b/internal/group/forum/repository_ent.go index 47dedeb..449bf5b 100644 --- a/internal/group/forum/repository_ent.go +++ b/internal/group/forum/repository_ent.go @@ -60,6 +60,19 @@ func (e entRepository) FindForum(ctx context.Context, shortName string) (*entity return inst, nil } +func (e entRepository) FindUserWithGroups(ctx context.Context, principal string) (*entity.User, error) { + usr, err := e.client.User.Query(). + Where( + user.Email(principal), + ).WithGroups(). + Only(ctx) + if err != nil { + return nil, fmt.Errorf("failed to find user with group: %w", err) + } + + return usr, nil +} + func (e entRepository) FindGroupUser(ctx context.Context, principal string, shortName string) (*entity.GroupUser, error) { grpusr, err := e.client.GroupUser.Query(). Where( @@ -92,16 +105,11 @@ func (e entRepository) CreateForum(ctx context.Context, groupID int, opts ...for return forum, nil } -func (e entRepository) UpdateForum(ctx context.Context, id int, opts ...forum.Option) (*entity.Forum, error) { - var options forum.Options - for _, opt := range opts { - opt(&options) - } - +func (e entRepository) UpdateForum(ctx context.Context, id int, name, shortName, description string) (*entity.Forum, error) { forum, err := e.client.Forum.UpdateOneID(id). - SetName(options.Name). - SetShortName(options.ShortName). - SetDescription(options.Description). + SetName(name). + SetShortName(shortName). + SetDescription(description). Save(ctx) if err != nil { return nil, fmt.Errorf("failed to update forum: %w", err) diff --git a/internal/group/forum/usecase.go b/internal/group/forum/usecase.go index b4f6df5..c2e3402 100644 --- a/internal/group/forum/usecase.go +++ b/internal/group/forum/usecase.go @@ -13,13 +13,13 @@ type UseCase interface { ListPrincipalForums(ctx context.Context, principal string) ([]*entity.Forum, error) // CreateGroup should be an admin only function - CreateForum(ctx context.Context, principal, shortName string, opts ...forum.Option) (*entity.Forum, error) + CreateForum(ctx context.Context, principal, name, shortName, description string) (*entity.Forum, error) // DeleteGroup should be an admin only function DeleteForum(ctx context.Context, principal string, shortName string) error // UpdateForum should be an admin only function - UpdateForum(ctx context.Context, principal string, opts ...forum.Option) (*entity.Forum, error) + UpdateForum(ctx context.Context, principal string, shortName string, opts ...forum.Option) (*entity.Forum, error) } type useCase struct { @@ -34,7 +34,7 @@ func (u useCase) ListPrincipalForums(ctx context.Context, principal string) ([]* return u.repo.FindForumsByUser(ctx, principal) } -func (u useCase) CreateForum(ctx context.Context, principal, shortName string, opts ...forum.Option) (*entity.Forum, error) { +func (u useCase) CreateForum(ctx context.Context, principal, name, shortName, description string) (*entity.Forum, error) { usr, err := u.repo.FindGroupUser(ctx, principal, shortName) if err != nil { return nil, fmt.Errorf("failed to find user: %w", err) @@ -44,21 +44,16 @@ func (u useCase) CreateForum(ctx context.Context, principal, shortName string, o return nil, fmt.Errorf("user edges not loaded") } - if usr.Role != group.RoleOwner && usr.Role != group.RoleEducator { + if usr.Role == group.RoleMember { return nil, ErrUnauthorized } - var options forum.Options - for _, opt := range opts { - opt(&options) - } - grp := usr.Edges.Group - if _, err := u.repo.FindForumByGroupIDAndShortName(ctx, grp.ID, options.ShortName); err == nil { + if _, err := u.repo.FindForumByGroupIDAndShortName(ctx, grp.ID, shortName); err == nil { return nil, ErrForumShortNameConflict } - forum, err := u.repo.CreateForum(ctx, grp.ID, opts...) + forum, err := u.repo.CreateForum(ctx, grp.ID, forum.Name(name), forum.ShortName(shortName), forum.Description(description)) if err != nil { return nil, fmt.Errorf("failed to create forum: %w", err) } @@ -72,7 +67,7 @@ func (u useCase) DeleteForum(ctx context.Context, principal string, shortName st return fmt.Errorf("failed to find user: %w", err) } - if usr.Role != group.RoleOwner && usr.Role != group.RoleEducator { + if usr.Role == group.RoleMember { return ErrUnauthorized } @@ -83,7 +78,20 @@ func (u useCase) DeleteForum(ctx context.Context, principal string, shortName st return nil } -func (u useCase) UpdateForum(ctx context.Context, principal string, opts ...forum.Option) (*entity.Forum, error) { +func (u useCase) UpdateForum(ctx context.Context, principal string, shortName string, opts ...forum.Option) (*entity.Forum, error) { + usr, err := u.repo.FindGroupUser(ctx, principal, shortName) + if err != nil { + return nil, fmt.Errorf("failed to find user: %w", err) + } + + if usr.Edges.Group == nil { + return nil, fmt.Errorf("user edges not loaded") + } + + if usr.Role == group.RoleMember { + return nil, ErrUnauthorized + } + forum, err := u.repo.FindForum(ctx, principal) if err != nil { return nil, err diff --git a/internal/payload/forum.go b/internal/payload/forum.go new file mode 100644 index 0000000..c7be0fb --- /dev/null +++ b/internal/payload/forum.go @@ -0,0 +1,38 @@ +package payload + +import ( + "net/http" + + "github.com/gookit/validate" +) + +type Forum struct { + ID int `json:"id,omitempty"` + Name string `json:"name,omitempty"` + ShortName string `json:"path,omitempty"` + Description string `json:"description,omitempty"` +} + +func (f Forum) Render(_ http.ResponseWriter, _ *http.Request) error { + return nil +} + +type CreateForumRequest struct { + Name string `json:"name,omitempty" validate:"required|minLen:3"` + ShortName string `json:"shortName,omitempty" validate:"required|alphaDash"` + Description string `json:"description,omitempty"` +} + +func (c CreateForumRequest) Validate() *validate.Validation { + return validate.Struct(c) +} + +type UpdateForumRequest 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 UpdateForumRequest) Validate() *validate.Validation { + return validate.Struct(u) +} From f4344a0723643752c5b5a7684dd1bfcbf42da37f Mon Sep 17 00:00:00 2001 From: Yun-E Date: Sat, 1 Jul 2023 11:32:31 +0800 Subject: [PATCH 06/19] feat: forums usecase --- internal/group/forum/repository.go | 2 +- internal/group/forum/usecase.go | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/group/forum/repository.go b/internal/group/forum/repository.go index 6b1afeb..710c8cf 100644 --- a/internal/group/forum/repository.go +++ b/internal/group/forum/repository.go @@ -18,7 +18,7 @@ type Reader interface { type Writer interface { CreateForum(ctx context.Context, groupID int, opts ...forum.Option) (*entity.Forum, error) - UpdateForum(ctx context.Context, principal, name, shortName, description string) (*entity.Forum, error) + UpdateForum(ctx context.Context, forumID int, name, shortName, description string) (*entity.Forum, error) DeleteForum(ctx context.Context, id int) error } diff --git a/internal/group/forum/usecase.go b/internal/group/forum/usecase.go index c2e3402..e541163 100644 --- a/internal/group/forum/usecase.go +++ b/internal/group/forum/usecase.go @@ -19,7 +19,7 @@ type UseCase interface { DeleteForum(ctx context.Context, principal string, shortName string) error // UpdateForum should be an admin only function - UpdateForum(ctx context.Context, principal string, shortName string, opts ...forum.Option) (*entity.Forum, error) + UpdateForum(ctx context.Context, principal string, name, shortName, description string) (*entity.Forum, error) } type useCase struct { @@ -78,7 +78,7 @@ func (u useCase) DeleteForum(ctx context.Context, principal string, shortName st return nil } -func (u useCase) UpdateForum(ctx context.Context, principal string, shortName string, opts ...forum.Option) (*entity.Forum, error) { +func (u useCase) UpdateForum(ctx context.Context, principal string, name, shortName, description string) (*entity.Forum, error) { usr, err := u.repo.FindGroupUser(ctx, principal, shortName) if err != nil { return nil, fmt.Errorf("failed to find user: %w", err) @@ -97,7 +97,7 @@ func (u useCase) UpdateForum(ctx context.Context, principal string, shortName st return nil, err } - forum, err = u.repo.UpdateForum(ctx, forum.ID, opts...) + forum, err = u.repo.UpdateForum(ctx, forum.ID, name, shortName, description) if err != nil { return nil, fmt.Errorf("failed to update institution: %w", err) } From 46670ad577ec0c76ca4d624907f5c3a914f931f3 Mon Sep 17 00:00:00 2001 From: Yun-E Date: Sat, 1 Jul 2023 11:47:11 +0800 Subject: [PATCH 07/19] fix: payload forum --- internal/group/forum/httphandler.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/group/forum/httphandler.go b/internal/group/forum/httphandler.go index fc1aeac..d5cb731 100644 --- a/internal/group/forum/httphandler.go +++ b/internal/group/forum/httphandler.go @@ -57,7 +57,7 @@ func (h httpHandler) ListForums(w http.ResponseWriter, r *http.Request) { res := make([]render.Renderer, len(forums)) for i, item := range forums { - res[i] = payload.Group{ + res[i] = payload.Forum{ ID: item.ID, ShortName: item.ShortName, Name: item.Name, From 8c2924e474378da7a20108825af29b833aebfdbd Mon Sep 17 00:00:00 2001 From: Tan Yun-E <93076628+tan-yun-e@users.noreply.github.com> Date: Sat, 1 Jul 2023 15:04:56 +0800 Subject: [PATCH 08/19] Update internal/payload/forum.go Co-authored-by: Qin Guan --- internal/payload/forum.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/payload/forum.go b/internal/payload/forum.go index c7be0fb..5fe6341 100644 --- a/internal/payload/forum.go +++ b/internal/payload/forum.go @@ -9,7 +9,7 @@ import ( type Forum 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"` } From b11a072e63697854f6c22d85d91e7d4aedd93a5e Mon Sep 17 00:00:00 2001 From: Yun-E Date: Sat, 1 Jul 2023 17:38:51 +0800 Subject: [PATCH 09/19] feat: edits --- internal/entity/forumpost/forumpost.go | 48 ++++++++++++++++++++ internal/group/forum/error.go | 4 +- internal/group/forum/forumpost/repository.go | 24 ++++++++++ internal/group/forum/repository.go | 2 +- internal/group/forum/repository_ent.go | 2 +- internal/group/forum/usecase.go | 37 +++++++-------- internal/payload/forum.go | 3 +- internal/payload/forumpost.go | 38 ++++++++++++++++ 8 files changed, 135 insertions(+), 23 deletions(-) create mode 100644 internal/entity/forumpost/forumpost.go create mode 100644 internal/group/forum/forumpost/repository.go create mode 100644 internal/payload/forumpost.go diff --git a/internal/entity/forumpost/forumpost.go b/internal/entity/forumpost/forumpost.go new file mode 100644 index 0000000..a5d75ca --- /dev/null +++ b/internal/entity/forumpost/forumpost.go @@ -0,0 +1,48 @@ +package forumpost + +import "errors" + +type Role string + +func (Role) Values() (kinds []string) { + for _, s := range Roles { + kinds = append(kinds, string(s)) + } + return +} + +func (fr Role) Validate() error { + switch fr { + case RoleOwner, RoleEducator, RoleMember: + return nil + default: + return errors.New("group role is not valid") + } +} + +var ( + Roles = []Role{RoleOwner, RoleEducator, RoleMember} + + RoleOwner Role = "owner" + RoleEducator Role = "educator" + RoleMember Role = "member" +) + +type Options struct { + Title string + Content string +} + +type Option func(*Options) + +func Name(n string) Option { + return func(opts *Options) { + opts.Title = n + } +} + +func ShortName(n string) Option { + return func(opts *Options) { + opts.Content = n + } +} diff --git a/internal/group/forum/error.go b/internal/group/forum/error.go index 5a2c3cb..609634b 100644 --- a/internal/group/forum/error.go +++ b/internal/group/forum/error.go @@ -19,7 +19,7 @@ func mapDomainErr(err error) render.Renderer { return &apperror.ErrResponse{ Err: err, HTTPStatusCode: http.StatusNotFound, - AppErrCode: 40401, + AppErrCode: 40402, AppErrMessage: "The group specified does not exist", } } @@ -28,7 +28,7 @@ func mapDomainErr(err error) render.Renderer { return &apperror.ErrResponse{ Err: err, HTTPStatusCode: http.StatusConflict, - AppErrCode: 40901, + AppErrCode: 40902, AppErrMessage: "This forum short name is unavailable", } } diff --git a/internal/group/forum/forumpost/repository.go b/internal/group/forum/forumpost/repository.go new file mode 100644 index 0000000..3b4b523 --- /dev/null +++ b/internal/group/forum/forumpost/repository.go @@ -0,0 +1,24 @@ +package forumpost + +import ( + "context" + + "github.com/np-inprove/server/internal/entity" + "github.com/np-inprove/server/internal/entity/forumpost" +) + +type Reader interface { + FindForumPostsByUser() ([]*entity.ForumPost, error) + FindForumPost() (*entity.ForumPost, error) + + FindGroupUser(ctx context.Context, principal, shortName string) (*entity.GroupUser, error) +} + +type Writer interface { + CreateForumPost(ctx context.Context, forumID int, opts ...forumpost.Option) (*entity.ForumPost, error) +} + +type Repository interface { + Reader + Writer +} diff --git a/internal/group/forum/repository.go b/internal/group/forum/repository.go index 710c8cf..3bfb9ff 100644 --- a/internal/group/forum/repository.go +++ b/internal/group/forum/repository.go @@ -8,7 +8,7 @@ import ( ) type Reader interface { - FindForumsByUser(ctx context.Context, principal string) ([]*entity.Forum, error) + FindForumsByGroup(ctx context.Context, principal string) ([]*entity.Forum, error) FindForumByGroupIDAndShortName(ctx context.Context, groupID int, shortName string) (*entity.Forum, error) FindForum(ctx context.Context, shortName string) (*entity.Forum, error) diff --git a/internal/group/forum/repository_ent.go b/internal/group/forum/repository_ent.go index 449bf5b..d1fd8b0 100644 --- a/internal/group/forum/repository_ent.go +++ b/internal/group/forum/repository_ent.go @@ -22,7 +22,7 @@ func NewEntRepository(e *ent.Client) Repository { return &entRepository{client: e} } -func (e entRepository) FindForumsByUser(ctx context.Context, principal string) ([]*entity.Forum, error) { +func (e entRepository) FindForumsByGroup(ctx context.Context, principal string) ([]*entity.Forum, error) { forum, err := e.client.Forum.Query(). Where( entforum.HasGroupWith( diff --git a/internal/group/forum/usecase.go b/internal/group/forum/usecase.go index e541163..43515d5 100644 --- a/internal/group/forum/usecase.go +++ b/internal/group/forum/usecase.go @@ -10,16 +10,17 @@ import ( ) type UseCase interface { - ListPrincipalForums(ctx context.Context, principal string) ([]*entity.Forum, error) + // groupShortName is the shortname used by groups, shortName us the shortname used by forums + ListForums(ctx context.Context, principal string, groupShortName string) ([]*entity.Forum, error) - // CreateGroup should be an admin only function - CreateForum(ctx context.Context, principal, name, shortName, description string) (*entity.Forum, error) + // CreateGroup should be an educator + owner only function + CreateForum(ctx context.Context, principal, groupShortName, name, shortName, description string) (*entity.Forum, error) - // DeleteGroup should be an admin only function - DeleteForum(ctx context.Context, principal string, shortName string) error + // DeleteGroup should be an educator + owner only function + DeleteForum(ctx context.Context, principal string, groupShortName string) error - // UpdateForum should be an admin only function - UpdateForum(ctx context.Context, principal string, name, shortName, description string) (*entity.Forum, error) + // UpdateForum should be an educator + owner only function + UpdateForum(ctx context.Context, principal string, groupShortName, name, shortName, description string) (*entity.Forum, error) } type useCase struct { @@ -30,12 +31,12 @@ func NewUseCase(r Repository) UseCase { return useCase{repo: r} } -func (u useCase) ListPrincipalForums(ctx context.Context, principal string) ([]*entity.Forum, error) { - return u.repo.FindForumsByUser(ctx, principal) +func (u useCase) ListForums(ctx context.Context, principal string, groupShortName string) ([]*entity.Forum, error) { + return u.repo.FindForumsByGroup(ctx, principal) } -func (u useCase) CreateForum(ctx context.Context, principal, name, shortName, description string) (*entity.Forum, error) { - usr, err := u.repo.FindGroupUser(ctx, principal, shortName) +func (u useCase) CreateForum(ctx context.Context, principal, groupShortName, name, shortName, description string) (*entity.Forum, error) { + usr, err := u.repo.FindGroupUser(ctx, principal, groupShortName) if err != nil { return nil, fmt.Errorf("failed to find user: %w", err) } @@ -44,7 +45,7 @@ func (u useCase) CreateForum(ctx context.Context, principal, name, shortName, de return nil, fmt.Errorf("user edges not loaded") } - if usr.Role == group.RoleMember { + if usr.Role != group.RoleEducator || usr.Role != group.RoleOwner { return nil, ErrUnauthorized } @@ -61,13 +62,13 @@ func (u useCase) CreateForum(ctx context.Context, principal, name, shortName, de return forum, nil } -func (u useCase) DeleteForum(ctx context.Context, principal string, shortName string) error { - usr, err := u.repo.FindGroupUser(ctx, principal, shortName) +func (u useCase) DeleteForum(ctx context.Context, principal string, groupShortName string) error { + usr, err := u.repo.FindGroupUser(ctx, principal, groupShortName) if err != nil { return fmt.Errorf("failed to find user: %w", err) } - if usr.Role == group.RoleMember { + if usr.Role != group.RoleEducator || usr.Role != group.RoleOwner { return ErrUnauthorized } @@ -78,8 +79,8 @@ func (u useCase) DeleteForum(ctx context.Context, principal string, shortName st return nil } -func (u useCase) UpdateForum(ctx context.Context, principal string, name, shortName, description string) (*entity.Forum, error) { - usr, err := u.repo.FindGroupUser(ctx, principal, shortName) +func (u useCase) UpdateForum(ctx context.Context, principal string, groupShortName, name, shortName, description string) (*entity.Forum, error) { + usr, err := u.repo.FindGroupUser(ctx, principal, groupShortName) if err != nil { return nil, fmt.Errorf("failed to find user: %w", err) } @@ -88,7 +89,7 @@ func (u useCase) UpdateForum(ctx context.Context, principal string, name, shortN return nil, fmt.Errorf("user edges not loaded") } - if usr.Role == group.RoleMember { + if usr.Role != group.RoleEducator || usr.Role != group.RoleOwner { return nil, ErrUnauthorized } diff --git a/internal/payload/forum.go b/internal/payload/forum.go index 5fe6341..44e1339 100644 --- a/internal/payload/forum.go +++ b/internal/payload/forum.go @@ -7,7 +7,8 @@ import ( ) type Forum struct { - ID int `json:"id,omitempty"` + GroupID int `json:"groupid,omitempty"` + ID int `json:"forumid,omitempty"` Name string `json:"name,omitempty"` ShortName string `json:"shortName,omitempty"` Description string `json:"description,omitempty"` diff --git a/internal/payload/forumpost.go b/internal/payload/forumpost.go new file mode 100644 index 0000000..92cfe00 --- /dev/null +++ b/internal/payload/forumpost.go @@ -0,0 +1,38 @@ +package payload + +import ( + "net/http" + + "github.com/gookit/validate" +) + +type ForumPost struct { + ID int `json:"id,omitempty"` + Title string `json:"title,omitempty"` + Content string `json:"content,omitempty"` + TimeDate string `json:"description,omitempty"` +} + +func (fp ForumPost) Render(_ http.ResponseWriter, _ *http.Request) error { + return nil +} + +type CreateForumPostRequest struct { + Name string `json:"name,omitempty" validate:"required|minLen:3"` + ShortName string `json:"shortName,omitempty" validate:"required|alphaDash"` + Description string `json:"description,omitempty"` +} + +func (c CreateForumPostRequest) Validate() *validate.Validation { + return validate.Struct(c) +} + +type UpdateForumPostRequest 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 UpdateForumPostRequest) Validate() *validate.Validation { + return validate.Struct(u) +} From ef53933fcf8da65e5d8a83663f4eed4738287507 Mon Sep 17 00:00:00 2001 From: Yun-E Date: Sat, 1 Jul 2023 23:21:07 +0800 Subject: [PATCH 10/19] feat: more edits --- internal/group/forum/repository_ent.go | 8 +++++++- internal/payload/forum.go | 1 - 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/internal/group/forum/repository_ent.go b/internal/group/forum/repository_ent.go index d1fd8b0..5fff73a 100644 --- a/internal/group/forum/repository_ent.go +++ b/internal/group/forum/repository_ent.go @@ -53,7 +53,13 @@ func (e entRepository) FindForumByGroupIDAndShortName(ctx context.Context, group } func (e entRepository) FindForum(ctx context.Context, shortName string) (*entity.Forum, error) { - inst, err := e.client.Forum.Query().Where(entforum.ShortName(shortName)).Only(ctx) + inst, err := e.client.Forum.Query().Where( + predicate.Forum( + entgroup.HasForumsWith( + entforum.ShortName(shortName), + ), + ), + ).Only(ctx) if err != nil { return nil, fmt.Errorf("failed to find forum: %w", err) } diff --git a/internal/payload/forum.go b/internal/payload/forum.go index 44e1339..3ea54e4 100644 --- a/internal/payload/forum.go +++ b/internal/payload/forum.go @@ -7,7 +7,6 @@ import ( ) type Forum struct { - GroupID int `json:"groupid,omitempty"` ID int `json:"forumid,omitempty"` Name string `json:"name,omitempty"` ShortName string `json:"shortName,omitempty"` From a0bfaa964dc649e3882c383210b89c5b7d6f7ab6 Mon Sep 17 00:00:00 2001 From: Yun-E Date: Sun, 2 Jul 2023 12:50:18 +0800 Subject: [PATCH 11/19] feat: remove forum roles --- internal/entity/forum/forum.go | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/internal/entity/forum/forum.go b/internal/entity/forum/forum.go index bf9f153..cca95de 100644 --- a/internal/entity/forum/forum.go +++ b/internal/entity/forum/forum.go @@ -1,33 +1,5 @@ package forum -import "errors" - -type Role string - -func (Role) Values() (kinds []string) { - for _, s := range Roles { - kinds = append(kinds, string(s)) - } - return -} - -func (fr Role) Validate() error { - switch fr { - case RoleOwner, RoleEducator, RoleMember: - return nil - default: - return errors.New("group role is not valid") - } -} - -var ( - Roles = []Role{RoleOwner, RoleEducator, RoleMember} - - RoleOwner Role = "owner" - RoleEducator Role = "educator" - RoleMember Role = "member" -) - type Options struct { Name string ShortName string From 512df91ca1387a9660f7ed8b973cc0bc0b42fd00 Mon Sep 17 00:00:00 2001 From: Yun-E Date: Tue, 4 Jul 2023 08:44:54 +0800 Subject: [PATCH 12/19] feat: more edits --- internal/group/forum/httphandler.go | 11 ++++++++--- internal/group/forum/usecase.go | 3 ++- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/internal/group/forum/httphandler.go b/internal/group/forum/httphandler.go index d5cb731..966d9e9 100644 --- a/internal/group/forum/httphandler.go +++ b/internal/group/forum/httphandler.go @@ -49,7 +49,9 @@ func (h httpHandler) ListForums(w http.ResponseWriter, r *http.Request) { token := r.Context().Value(jwtauth.TokenCtxKey) email := token.(jwt.Token).Subject() - forums, err := h.service.ListPrincipalForums(r.Context(), email) + shortName := chi.URLParam(r, "shortName") + + forums, err := h.service.ListForums(r.Context(), email, shortName) if err != nil { _ = render.Render(w, r, mapDomainErr(err)) return @@ -82,8 +84,9 @@ func (h httpHandler) CreateForum(w http.ResponseWriter, r *http.Request) { token := r.Context().Value(jwtauth.TokenCtxKey) email := token.(jwt.Token).Subject() + shortName := chi.URLParam(r, "shortName") - res, err := h.service.CreateForum(r.Context(), email, + res, err := h.service.CreateForum(r.Context(), email, shortName, p.Name, p.ShortName, p.Description, @@ -117,13 +120,15 @@ func (h httpHandler) DeleteForum(w http.ResponseWriter, r *http.Request) { func (h httpHandler) UpdateForum(w http.ResponseWriter, r *http.Request) { shortName := chi.URLParam(r, "shortName") + token := r.Context().Value(jwtauth.TokenCtxKey) + email := token.(jwt.Token).Subject() p := payload.UpdateForumRequest{} if err := render.Decode(r, p); err != nil { _ = render.Render(w, r, apperror.ErrBadRequest(err)) return } - forum, err := h.service.UpdateForum(r.Context(), shortName, + forum, err := h.service.UpdateForum(r.Context(), email, shortName, p.Name, p.ShortName, p.Description, diff --git a/internal/group/forum/usecase.go b/internal/group/forum/usecase.go index 43515d5..d0ebca7 100644 --- a/internal/group/forum/usecase.go +++ b/internal/group/forum/usecase.go @@ -20,7 +20,7 @@ type UseCase interface { DeleteForum(ctx context.Context, principal string, groupShortName string) error // UpdateForum should be an educator + owner only function - UpdateForum(ctx context.Context, principal string, groupShortName, name, shortName, description string) (*entity.Forum, error) + UpdateForum(ctx context.Context, principal string, originalShortName, name, shortName, description string) (*entity.Forum, error) } type useCase struct { @@ -37,6 +37,7 @@ func (u useCase) ListForums(ctx context.Context, principal string, groupShortNam func (u useCase) CreateForum(ctx context.Context, principal, groupShortName, name, shortName, description string) (*entity.Forum, error) { usr, err := u.repo.FindGroupUser(ctx, principal, groupShortName) + if err != nil { return nil, fmt.Errorf("failed to find user: %w", err) } From 2ad92e09c4046ca7c78a4be0ccba21ea51b6fdfc Mon Sep 17 00:00:00 2001 From: Yun-E Date: Tue, 4 Jul 2023 10:59:45 +0800 Subject: [PATCH 13/19] feat: rename --- internal/group/forum/repository.go | 2 +- internal/group/forum/repository_ent.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/group/forum/repository.go b/internal/group/forum/repository.go index 3bfb9ff..f4e06e2 100644 --- a/internal/group/forum/repository.go +++ b/internal/group/forum/repository.go @@ -13,7 +13,7 @@ type Reader interface { FindForum(ctx context.Context, shortName string) (*entity.Forum, error) FindUserWithGroups(ctx context.Context, principal string) (*entity.User, error) - FindGroupUser(ctx context.Context, principal, shortName string) (*entity.GroupUser, error) + FindGroupUserWithGroup(ctx context.Context, principal, shortName string) (*entity.GroupUser, error) } type Writer interface { diff --git a/internal/group/forum/repository_ent.go b/internal/group/forum/repository_ent.go index 5fff73a..e35da67 100644 --- a/internal/group/forum/repository_ent.go +++ b/internal/group/forum/repository_ent.go @@ -79,7 +79,7 @@ func (e entRepository) FindUserWithGroups(ctx context.Context, principal string) return usr, nil } -func (e entRepository) FindGroupUser(ctx context.Context, principal string, shortName string) (*entity.GroupUser, error) { +func (e entRepository) FindGroupUserWithGroup(ctx context.Context, principal string, shortName string) (*entity.GroupUser, error) { grpusr, err := e.client.GroupUser.Query(). Where( groupuser.HasUserWith(user.Email(principal)), From 70f06c175ddc304e7f0971255ae07b629a98710a Mon Sep 17 00:00:00 2001 From: Yun-E Date: Wed, 5 Jul 2023 22:41:33 +0800 Subject: [PATCH 14/19] feat: rename --- internal/group/forum/usecase.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/group/forum/usecase.go b/internal/group/forum/usecase.go index d0ebca7..ca3d22a 100644 --- a/internal/group/forum/usecase.go +++ b/internal/group/forum/usecase.go @@ -36,7 +36,7 @@ func (u useCase) ListForums(ctx context.Context, principal string, groupShortNam } func (u useCase) CreateForum(ctx context.Context, principal, groupShortName, name, shortName, description string) (*entity.Forum, error) { - usr, err := u.repo.FindGroupUser(ctx, principal, groupShortName) + usr, err := u.repo.FindGroupUserWithGroup(ctx, principal, groupShortName) if err != nil { return nil, fmt.Errorf("failed to find user: %w", err) @@ -64,7 +64,7 @@ func (u useCase) CreateForum(ctx context.Context, principal, groupShortName, nam } func (u useCase) DeleteForum(ctx context.Context, principal string, groupShortName string) error { - usr, err := u.repo.FindGroupUser(ctx, principal, groupShortName) + usr, err := u.repo.FindGroupUserWithGroup(ctx, principal, groupShortName) if err != nil { return fmt.Errorf("failed to find user: %w", err) } @@ -81,7 +81,7 @@ func (u useCase) DeleteForum(ctx context.Context, principal string, groupShortNa } func (u useCase) UpdateForum(ctx context.Context, principal string, groupShortName, name, shortName, description string) (*entity.Forum, error) { - usr, err := u.repo.FindGroupUser(ctx, principal, groupShortName) + usr, err := u.repo.FindGroupUserWithGroup(ctx, principal, groupShortName) if err != nil { return nil, fmt.Errorf("failed to find user: %w", err) } From ee553f9a848372592ecdf7ab4acb31060c43ca42 Mon Sep 17 00:00:00 2001 From: Yun-E Date: Thu, 6 Jul 2023 08:46:06 +0800 Subject: [PATCH 15/19] fix: logic --- internal/group/forum/usecase.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/group/forum/usecase.go b/internal/group/forum/usecase.go index ca3d22a..4021f21 100644 --- a/internal/group/forum/usecase.go +++ b/internal/group/forum/usecase.go @@ -46,7 +46,7 @@ func (u useCase) CreateForum(ctx context.Context, principal, groupShortName, nam return nil, fmt.Errorf("user edges not loaded") } - if usr.Role != group.RoleEducator || usr.Role != group.RoleOwner { + if usr.Role != group.RoleEducator && usr.Role != group.RoleOwner { return nil, ErrUnauthorized } @@ -69,7 +69,7 @@ func (u useCase) DeleteForum(ctx context.Context, principal string, groupShortNa return fmt.Errorf("failed to find user: %w", err) } - if usr.Role != group.RoleEducator || usr.Role != group.RoleOwner { + if usr.Role != group.RoleEducator && usr.Role != group.RoleOwner { return ErrUnauthorized } @@ -90,7 +90,7 @@ func (u useCase) UpdateForum(ctx context.Context, principal string, groupShortNa return nil, fmt.Errorf("user edges not loaded") } - if usr.Role != group.RoleEducator || usr.Role != group.RoleOwner { + if usr.Role != group.RoleEducator && usr.Role != group.RoleOwner { return nil, ErrUnauthorized } From 95d512200f99af1f5e0387b4eec87380e6920264 Mon Sep 17 00:00:00 2001 From: Qin Guan Date: Thu, 6 Jul 2023 12:14:45 +0800 Subject: [PATCH 16/19] feat: security rbac pkg --- internal/security/security.go | 70 +++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 internal/security/security.go diff --git a/internal/security/security.go b/internal/security/security.go new file mode 100644 index 0000000..2048034 --- /dev/null +++ b/internal/security/security.go @@ -0,0 +1,70 @@ +package security + +import ( + "context" + "fmt" + "github.com/np-inprove/server/internal/ent" + entgroup "github.com/np-inprove/server/internal/ent/group" + "github.com/np-inprove/server/internal/ent/user" + "github.com/np-inprove/server/internal/entity/group" + "github.com/np-inprove/server/internal/entity/institution" +) + +type RBAC interface { + UserInInstitution(ctx context.Context, principal string, id int) (*institution.Role, error) + UserInGroup(ctx context.Context, principal string, id int) (*group.Role, error) + UserHasGroupRole(role group.Role, allowedRoles ...group.Role) bool + UserHasInstitutionRole(role institution.Role, allowedRoles ...institution.Role) bool +} + +type rbac struct { + client *ent.Client +} + +func NewRBAC(client *ent.Client) RBAC { + return &rbac{client} +} + +func (s rbac) UserInInstitution(ctx context.Context, principal string, id int) (*institution.Role, error) { + usr, err := s.client.User.Query().Where(user.Email(principal)).WithInstitution().Only(ctx) + if err != nil { + return nil, fmt.Errorf("failed to query user: %w", err) + } + + if usr.Edges.Institution.ID != id { + return nil, fmt.Errorf("insufficient permissions: %w", err) + } + + return &usr.Role, nil +} + +func (s rbac) UserInGroup(ctx context.Context, principal string, id int) (*group.Role, error) { + grpusr, err := s.client.User.Query().Where(user.Email(principal), user.HasGroupsWith(entgroup.ID(id))).QueryGroupUsers().Only(ctx) + if err != nil { + return nil, fmt.Errorf("failed to query user: %w", err) + } + + return &grpusr.Role, nil +} + +func (s rbac) UserHasGroupRole(role group.Role, allowedRoles ...group.Role) bool { + res := false + for _, allowed := range allowedRoles { + if role == allowed { + res = true + break + } + } + return res +} + +func (s rbac) UserHasInstitutionRole(role institution.Role, allowedRoles ...institution.Role) bool { + res := false + for _, allowed := range allowedRoles { + if role == allowed { + res = true + break + } + } + return res +} From 65a590d54069967eaf54924c780b14c29f17bfa0 Mon Sep 17 00:00:00 2001 From: Yun-E Date: Thu, 6 Jul 2023 20:54:16 +0800 Subject: [PATCH 17/19] rename: forumShortName --- internal/group/forum/httphandler.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/internal/group/forum/httphandler.go b/internal/group/forum/httphandler.go index 966d9e9..8d1b165 100644 --- a/internal/group/forum/httphandler.go +++ b/internal/group/forum/httphandler.go @@ -38,8 +38,8 @@ func NewHTTPHandler(u UseCase, c *config.Config, j *jwtauth.JWTAuth) chi.Router r.Get("/", a.ListForums) r.Post("/", a.CreateForum) - r.Delete("/{shortName}", a.DeleteForum) - r.Put("/{shortName}", a.UpdateForum) + r.Delete("/{forumShortName}", a.DeleteForum) + r.Put("/{forumShortName}", a.UpdateForum) }) return r @@ -49,7 +49,7 @@ func (h httpHandler) ListForums(w http.ResponseWriter, r *http.Request) { token := r.Context().Value(jwtauth.TokenCtxKey) email := token.(jwt.Token).Subject() - shortName := chi.URLParam(r, "shortName") + shortName := chi.URLParam(r, "forumShortName") forums, err := h.service.ListForums(r.Context(), email, shortName) if err != nil { @@ -84,7 +84,7 @@ func (h httpHandler) CreateForum(w http.ResponseWriter, r *http.Request) { token := r.Context().Value(jwtauth.TokenCtxKey) email := token.(jwt.Token).Subject() - shortName := chi.URLParam(r, "shortName") + shortName := chi.URLParam(r, "forumShortName") res, err := h.service.CreateForum(r.Context(), email, shortName, p.Name, @@ -106,7 +106,7 @@ func (h httpHandler) CreateForum(w http.ResponseWriter, r *http.Request) { } func (h httpHandler) DeleteForum(w http.ResponseWriter, r *http.Request) { - path := chi.URLParam(r, "shortName") + path := chi.URLParam(r, "forumShortName") token := r.Context().Value(jwtauth.TokenCtxKey) email := token.(jwt.Token).Subject() @@ -119,7 +119,7 @@ func (h httpHandler) DeleteForum(w http.ResponseWriter, r *http.Request) { } func (h httpHandler) UpdateForum(w http.ResponseWriter, r *http.Request) { - shortName := chi.URLParam(r, "shortName") + shortName := chi.URLParam(r, "forumShortName") token := r.Context().Value(jwtauth.TokenCtxKey) email := token.(jwt.Token).Subject() p := payload.UpdateForumRequest{} From 9cb9417d14fe677f7dca68fc0912dd6e3ade2545 Mon Sep 17 00:00:00 2001 From: Yun-E Date: Thu, 6 Jul 2023 21:07:19 +0800 Subject: [PATCH 18/19] feat: more edits --- internal/group/forum/repository.go | 2 +- internal/group/forum/repository_ent.go | 12 ++++++++---- internal/group/forum/usecase.go | 12 +++++++----- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/internal/group/forum/repository.go b/internal/group/forum/repository.go index f4e06e2..f666594 100644 --- a/internal/group/forum/repository.go +++ b/internal/group/forum/repository.go @@ -8,7 +8,7 @@ import ( ) type Reader interface { - FindForumsByGroup(ctx context.Context, principal string) ([]*entity.Forum, error) + FindForumsByGroupAndInstitution(ctx context.Context, principal string, instShortName string) ([]*entity.Forum, error) FindForumByGroupIDAndShortName(ctx context.Context, groupID int, shortName string) (*entity.Forum, error) FindForum(ctx context.Context, shortName string) (*entity.Forum, error) diff --git a/internal/group/forum/repository_ent.go b/internal/group/forum/repository_ent.go index e35da67..cbba4bd 100644 --- a/internal/group/forum/repository_ent.go +++ b/internal/group/forum/repository_ent.go @@ -8,6 +8,7 @@ import ( entforum "github.com/np-inprove/server/internal/ent/forum" entgroup "github.com/np-inprove/server/internal/ent/group" "github.com/np-inprove/server/internal/ent/groupuser" + entinstitution "github.com/np-inprove/server/internal/ent/institution" "github.com/np-inprove/server/internal/ent/predicate" "github.com/np-inprove/server/internal/ent/user" "github.com/np-inprove/server/internal/entity" @@ -22,15 +23,18 @@ func NewEntRepository(e *ent.Client) Repository { return &entRepository{client: e} } -func (e entRepository) FindForumsByGroup(ctx context.Context, principal string) ([]*entity.Forum, error) { +func (e entRepository) FindForumsByGroupAndInstitution(ctx context.Context, principal string, instShortname string) ([]*entity.Forum, error) { forum, err := e.client.Forum.Query(). Where( entforum.HasGroupWith( - predicate.Group(user.Email(principal)), - )). + entgroup.HasInstitutionWith( + entinstitution.ShortName(instShortname), //is there such a thing as too much nesting + ), + ), + ). All(ctx) if err != nil { - return nil, fmt.Errorf("failed to find forums by user email: %w", err) + return nil, fmt.Errorf("failed to find forums by group and institution: %w", err) } return forum, nil diff --git a/internal/group/forum/usecase.go b/internal/group/forum/usecase.go index 4021f21..f11b74a 100644 --- a/internal/group/forum/usecase.go +++ b/internal/group/forum/usecase.go @@ -7,6 +7,7 @@ import ( "github.com/np-inprove/server/internal/entity" "github.com/np-inprove/server/internal/entity/forum" "github.com/np-inprove/server/internal/entity/group" + "github.com/np-inprove/server/internal/security" ) type UseCase interface { @@ -25,14 +26,15 @@ type UseCase interface { type useCase struct { repo Repository + rbac security.RBAC } func NewUseCase(r Repository) UseCase { return useCase{repo: r} } -func (u useCase) ListForums(ctx context.Context, principal string, groupShortName string) ([]*entity.Forum, error) { - return u.repo.FindForumsByGroup(ctx, principal) +func (u useCase) ListForums(ctx context.Context, principal string, instShortName string) ([]*entity.Forum, error) { + return u.repo.FindForumsByGroupAndInstitution(ctx, principal, instShortName) } func (u useCase) CreateForum(ctx context.Context, principal, groupShortName, name, shortName, description string) (*entity.Forum, error) { @@ -46,7 +48,7 @@ func (u useCase) CreateForum(ctx context.Context, principal, groupShortName, nam return nil, fmt.Errorf("user edges not loaded") } - if usr.Role != group.RoleEducator && usr.Role != group.RoleOwner { + if ok := u.rbac.UserHasGroupRole(usr.Role, group.RoleEducator, group.RoleOwner); !ok { return nil, ErrUnauthorized } @@ -69,7 +71,7 @@ func (u useCase) DeleteForum(ctx context.Context, principal string, groupShortNa return fmt.Errorf("failed to find user: %w", err) } - if usr.Role != group.RoleEducator && usr.Role != group.RoleOwner { + if ok := u.rbac.UserHasGroupRole(usr.Role, group.RoleEducator, group.RoleOwner); !ok { return ErrUnauthorized } @@ -90,7 +92,7 @@ func (u useCase) UpdateForum(ctx context.Context, principal string, groupShortNa return nil, fmt.Errorf("user edges not loaded") } - if usr.Role != group.RoleEducator && usr.Role != group.RoleOwner { + if ok := u.rbac.UserHasGroupRole(usr.Role, group.RoleEducator, group.RoleOwner); !ok { return nil, ErrUnauthorized } From 2e5687baea2559444aabe65d8b64625ba099c2e6 Mon Sep 17 00:00:00 2001 From: Yun-E Date: Thu, 6 Jul 2023 21:14:23 +0800 Subject: [PATCH 19/19] feat: remove forum post files --- internal/entity/forumpost/forumpost.go | 48 -------------------- internal/group/forum/forumpost/repository.go | 24 ---------- internal/group/forum/repository_ent.go | 2 +- 3 files changed, 1 insertion(+), 73 deletions(-) delete mode 100644 internal/entity/forumpost/forumpost.go delete mode 100644 internal/group/forum/forumpost/repository.go diff --git a/internal/entity/forumpost/forumpost.go b/internal/entity/forumpost/forumpost.go deleted file mode 100644 index a5d75ca..0000000 --- a/internal/entity/forumpost/forumpost.go +++ /dev/null @@ -1,48 +0,0 @@ -package forumpost - -import "errors" - -type Role string - -func (Role) Values() (kinds []string) { - for _, s := range Roles { - kinds = append(kinds, string(s)) - } - return -} - -func (fr Role) Validate() error { - switch fr { - case RoleOwner, RoleEducator, RoleMember: - return nil - default: - return errors.New("group role is not valid") - } -} - -var ( - Roles = []Role{RoleOwner, RoleEducator, RoleMember} - - RoleOwner Role = "owner" - RoleEducator Role = "educator" - RoleMember Role = "member" -) - -type Options struct { - Title string - Content string -} - -type Option func(*Options) - -func Name(n string) Option { - return func(opts *Options) { - opts.Title = n - } -} - -func ShortName(n string) Option { - return func(opts *Options) { - opts.Content = n - } -} diff --git a/internal/group/forum/forumpost/repository.go b/internal/group/forum/forumpost/repository.go deleted file mode 100644 index 3b4b523..0000000 --- a/internal/group/forum/forumpost/repository.go +++ /dev/null @@ -1,24 +0,0 @@ -package forumpost - -import ( - "context" - - "github.com/np-inprove/server/internal/entity" - "github.com/np-inprove/server/internal/entity/forumpost" -) - -type Reader interface { - FindForumPostsByUser() ([]*entity.ForumPost, error) - FindForumPost() (*entity.ForumPost, error) - - FindGroupUser(ctx context.Context, principal, shortName string) (*entity.GroupUser, error) -} - -type Writer interface { - CreateForumPost(ctx context.Context, forumID int, opts ...forumpost.Option) (*entity.ForumPost, error) -} - -type Repository interface { - Reader - Writer -} diff --git a/internal/group/forum/repository_ent.go b/internal/group/forum/repository_ent.go index cbba4bd..b4d5a4b 100644 --- a/internal/group/forum/repository_ent.go +++ b/internal/group/forum/repository_ent.go @@ -28,7 +28,7 @@ func (e entRepository) FindForumsByGroupAndInstitution(ctx context.Context, prin Where( entforum.HasGroupWith( entgroup.HasInstitutionWith( - entinstitution.ShortName(instShortname), //is there such a thing as too much nesting + entinstitution.ShortName(instShortname), ), ), ).