Skip to content
This repository has been archived by the owner on Aug 11, 2023. It is now read-only.

feat: Forums CRUD #27

Open
wants to merge 19 commits into
base: main
Choose a base branch
from
3 changes: 3 additions & 0 deletions internal/entity/entity.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
55 changes: 55 additions & 0 deletions internal/entity/forum/forum.go
Original file line number Diff line number Diff line change
@@ -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"
)
tan-yun-e marked this conversation as resolved.
Show resolved Hide resolved

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
}
}
46 changes: 46 additions & 0 deletions internal/group/forum/error.go
Original file line number Diff line number Diff line change
@@ -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,
tan-yun-e marked this conversation as resolved.
Show resolved Hide resolved
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)
}
139 changes: 139 additions & 0 deletions internal/group/forum/httphandler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
package forum

import (
"github.com/go-chi/chi/v5"
"github.com/go-chi/jwtauth/v5"

"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/config"
"github.com/np-inprove/server/internal/middleware"
"github.com/np-inprove/server/internal/payload"
)

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{u, 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("/{shortName}", a.DeleteForum)
r.Put("/{shortName}", a.UpdateForum)
})

return r
}

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)
tan-yun-e marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
_ = render.Render(w, r, mapDomainErr(err))
return
}

res := make([]render.Renderer, len(forums))
for i, item := range forums {
res[i] = payload.Forum{
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,
})
}
}
28 changes: 28 additions & 0 deletions internal/group/forum/repository.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
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, 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, forumID int, name, shortName, description string) (*entity.Forum, error)
DeleteForum(ctx context.Context, id int) error
}

type Repository interface {
Reader
Writer
}
Loading