diff --git a/application/dependency.go b/application/dependency.go index cd4d512..97c4c89 100644 --- a/application/dependency.go +++ b/application/dependency.go @@ -1,6 +1,8 @@ package application import ( + "sync" + "github.com/PickHD/pickablog/controller" "github.com/PickHD/pickablog/repository" "github.com/PickHD/pickablog/requester" @@ -146,6 +148,9 @@ func setupUserDependency(app *App) *controller.UserController { // setupBlogDependency is a function to set up dependencies to be used inside blog controller layer func setupBlogDependency(app *App) *controller.BlogController { + //init mutex + mutex := &sync.RWMutex{} + blogRepo := &repository.BlogRepository{ Context: app.Context, Config: app.Config, @@ -153,11 +158,36 @@ func setupBlogDependency(app *App) *controller.BlogController { DB: app.DB, } + commentRepo := &repository.CommentRepository{ + Context: app.Context, + Config: app.Config, + Logger: app.Logger, + DB: app.DB, + } + + likeRepo := &repository.LikeRepository{ + Context: app.Context, + Config: app.Config, + Logger: app.Logger, + DB: app.DB, + } + + userRepo := &repository.UserRepository{ + Context: app.Context, + Config: app.Config, + Logger: app.Logger, + DB: app.DB, + } + blogSvc := &service.BlogService{ Context: app.Context, Config: app.Config, Logger: app.Logger, BlogRepo: blogRepo, + CommentRepo: commentRepo, + LikeRepo: likeRepo, + UserRepo: userRepo, + Mutex: mutex, } blogCtrl := &controller.BlogController{ diff --git a/controller/blog.go b/controller/blog.go index 037594a..f0e7e70 100644 --- a/controller/blog.go +++ b/controller/blog.go @@ -23,6 +23,14 @@ type ( DetailBlog(ctx *fiber.Ctx) error UpdateBlog(ctx *fiber.Ctx) error DeleteBlog(ctx *fiber.Ctx) error + + CreateComment(ctx *fiber.Ctx) error + UpdateComment(ctx *fiber.Ctx) error + ListComment(ctx *fiber.Ctx) error + DeleteComment(ctx *fiber.Ctx) error + + Like(ctx *fiber.Ctx) error + UnLike(ctx *fiber.Ctx) error } // BlogController is an app blog struct that consists of all the dependencies needed for blog controller @@ -163,6 +171,10 @@ func (bc *BlogController) UpdateBlog(ctx *fiber.Ctx) error { err = bc.BlogSvc.UpdateBlogSvc(id,blogReq,extData.FullName) if err != nil { + if errors.Is(err,model.ErrForbiddenUpdate) { + return helper.ResponseFormatter[any](ctx,fiber.StatusForbidden,err,err.Error(),nil,nil) + } + if errors.Is(err,model.ErrBlogNotFound) { return helper.ResponseFormatter[any](ctx,fiber.StatusNotFound,err,err.Error(),nil,nil) } @@ -188,8 +200,18 @@ func (bc *BlogController) DeleteBlog(ctx *fiber.Ctx) error { return helper.ResponseFormatter[any](ctx,fiber.StatusBadRequest,nil,model.ErrBlogNotFound.Error(),nil,nil) } - err = bc.BlogSvc.DeleteBlogSvc(id) + data := ctx.Locals(model.KeyJWTValidAccess) + extData,err := util.ExtractPayloadJWT(data) + if err != nil { + return helper.ResponseFormatter[any](ctx,fiber.StatusInternalServerError,err,err.Error(),nil,nil) + } + + err = bc.BlogSvc.DeleteBlogSvc(id,extData.UserID) if err != nil { + if errors.Is(err,model.ErrForbiddenDelete) { + return helper.ResponseFormatter[any](ctx,fiber.StatusForbidden,err,err.Error(),nil,nil) + } + if errors.Is(err,model.ErrBlogNotFound) { return helper.ResponseFormatter[any](ctx,fiber.StatusNotFound,err,err.Error(),nil,nil) } @@ -198,4 +220,262 @@ func (bc *BlogController) DeleteBlog(ctx *fiber.Ctx) error { } return helper.ResponseFormatter[any](ctx,fiber.StatusOK,nil,"Success Delete Blog",nil,nil) -} \ No newline at end of file +} + +// CreateComment responsible to creating a comment from controller layer +func (bc *BlogController) CreateComment(ctx *fiber.Ctx) error { + var commentReq model.CommentRequest + + id,err := ctx.ParamsInt("id",0) + if err != nil { + return helper.ResponseFormatter[any](ctx,fiber.StatusBadRequest,err,err.Error(),nil,nil) + } + + if id == 0 { + return helper.ResponseFormatter[any](ctx,fiber.StatusBadRequest,nil,model.ErrBlogNotFound.Error(),nil,nil) + } + + data := ctx.Locals(model.KeyJWTValidAccess) + extData,err := util.ExtractPayloadJWT(data) + if err != nil { + return helper.ResponseFormatter[any](ctx,fiber.StatusInternalServerError,err,err.Error(),nil,nil) + } + + if err := ctx.BodyParser(&commentReq); err != nil { + return helper.ResponseFormatter[any](ctx,fiber.StatusBadRequest,err,err.Error(),nil,nil) + } + + commentReq.UserID = extData.UserID + + err = bc.BlogSvc.CreateCommentSvc(id,commentReq,extData.FullName) + if err != nil { + + if errors.Is(err,model.ErrUserNotFound) || errors.Is(err,model.ErrBlogNotFound) { + return helper.ResponseFormatter[any](ctx,fiber.StatusNotFound,err,err.Error(),nil,nil) + } + + if errors.Is(err,model.ErrInvalidRequest) { + return helper.ResponseFormatter[any](ctx,fiber.StatusBadRequest,err,err.Error(),nil,nil) + } + + return helper.ResponseFormatter[any](ctx,fiber.StatusInternalServerError,err,err.Error(),nil,nil) + } + + return helper.ResponseFormatter[any](ctx,fiber.StatusCreated,nil,"Success Create Comment",nil,nil) +} + +// UpdateComment responsible to updating a comment by id from controller layer +func (bc *BlogController) UpdateComment(ctx *fiber.Ctx) error { + var commentReq model.CommentRequest + + commentId,err := ctx.ParamsInt("comment_id",0) + if err != nil { + return helper.ResponseFormatter[any](ctx,fiber.StatusBadRequest,err,err.Error(),nil,nil) + } + + if commentId == 0 { + return helper.ResponseFormatter[any](ctx,fiber.StatusBadRequest,nil,model.ErrCommentNotFound.Error(),nil,nil) + } + + data := ctx.Locals(model.KeyJWTValidAccess) + extData,err := util.ExtractPayloadJWT(data) + if err != nil { + return helper.ResponseFormatter[any](ctx,fiber.StatusInternalServerError,err,err.Error(),nil,nil) + } + + if err := ctx.BodyParser(&commentReq); err != nil { + return helper.ResponseFormatter[any](ctx,fiber.StatusBadRequest,err,err.Error(),nil,nil) + } + + commentReq.UserID = extData.UserID + + err = bc.BlogSvc.UpdateCommentSvc(commentId,commentReq,extData.FullName) + if err != nil { + if errors.Is(err,model.ErrForbiddenUpdate) { + return helper.ResponseFormatter[any](ctx,fiber.StatusForbidden,err,err.Error(),nil,nil) + } + + if errors.Is(err,model.ErrUserNotFound) || errors.Is(err,model.ErrCommentNotFound) { + return helper.ResponseFormatter[any](ctx,fiber.StatusNotFound,err,err.Error(),nil,nil) + } + + if errors.Is(err,model.ErrInvalidRequest) { + return helper.ResponseFormatter[any](ctx,fiber.StatusBadRequest,err,err.Error(),nil,nil) + } + + return helper.ResponseFormatter[any](ctx,fiber.StatusInternalServerError,err,err.Error(),nil,nil) + } + + return helper.ResponseFormatter[any](ctx,fiber.StatusOK,nil,"Success Update Comment",nil,nil) +} + +// ListComment responsible to getting comments by blog id from controller layer +func (bc *BlogController) ListComment(ctx *fiber.Ctx) error { + var ( + page = 1 + size = 10 + order = "ASC" + field = "id" + ) + + id,err := ctx.ParamsInt("id",0) + if err != nil { + return helper.ResponseFormatter[any](ctx,fiber.StatusBadRequest,err,err.Error(),nil,nil) + } + + if id == 0 { + return helper.ResponseFormatter[any](ctx,fiber.StatusBadRequest,nil,model.ErrBlogNotFound.Error(),nil,nil) + } + + if p := ctx.Query("page",""); p != "" { + pNum,err := strconv.Atoi(p) + if err == nil && pNum > 0 { + page = pNum + } + } + + if s := ctx.Query("size",""); s != "" { + sNum, err := strconv.Atoi(s) + if err == nil && sNum > 0 { + size = sNum + } + } + + if o := ctx.Query("order",""); o != "" { + if len(o) > 0 { + order = o + } + } + + if f := ctx.Query("field",""); f != "" { + if len(f) > 0 { + field = f + } + } + + data,meta,err := bc.BlogSvc.GetCommentsByBlogSvc(id,page,size,order,field) + if err != nil { + return helper.ResponseFormatter[any](ctx,fiber.StatusInternalServerError,err,err.Error(),nil,nil) + } + + return helper.ResponseFormatter[any](ctx,fiber.StatusOK,nil,"Success Getting all Comments",data,meta) +} + +// DeleteComment responsible to deleting a comment by id from controller layer +func (bc *BlogController) DeleteComment(ctx *fiber.Ctx) error { + blogID,err := ctx.ParamsInt("id",0) + if err != nil { + return helper.ResponseFormatter[any](ctx,fiber.StatusBadRequest,err,err.Error(),nil,nil) + } + + if blogID == 0 { + return helper.ResponseFormatter[any](ctx,fiber.StatusBadRequest,nil,model.ErrBlogNotFound.Error(),nil,nil) + } + + commentID,err := ctx.ParamsInt("comment_id",0) + if err != nil { + return helper.ResponseFormatter[any](ctx,fiber.StatusBadRequest,err,err.Error(),nil,nil) + } + + if commentID == 0 { + return helper.ResponseFormatter[any](ctx,fiber.StatusBadRequest,nil,model.ErrCommentNotFound.Error(),nil,nil) + } + + data := ctx.Locals(model.KeyJWTValidAccess) + extData,err := util.ExtractPayloadJWT(data) + if err != nil { + return helper.ResponseFormatter[any](ctx,fiber.StatusInternalServerError,err,err.Error(),nil,nil) + } + + err = bc.BlogSvc.DeleteCommentSvc(blogID,commentID,extData.UserID) + if err != nil { + if errors.Is(err,model.ErrForbiddenDelete) { + return helper.ResponseFormatter[any](ctx,fiber.StatusForbidden,err,err.Error(),nil,nil) + } + + if errors.Is(err,model.ErrBlogNotFound) || errors.Is(err,model.ErrCommentNotFound) { + return helper.ResponseFormatter[any](ctx,fiber.StatusNotFound,err,err.Error(),nil,nil) + } + + return helper.ResponseFormatter[any](ctx,fiber.StatusInternalServerError,err,err.Error(),nil,nil) + } + + return helper.ResponseFormatter[any](ctx,fiber.StatusOK,nil,"Success Delete Comment",nil,nil) +} + +// Like responsible to creating a like / liking a blog +func (bc *BlogController) Like(ctx *fiber.Ctx) error { + var likeReq model.LikeRequest + + id,err := ctx.ParamsInt("id",0) + if err != nil { + return helper.ResponseFormatter[any](ctx,fiber.StatusBadRequest,err,err.Error(),nil,nil) + } + + if id == 0 { + return helper.ResponseFormatter[any](ctx,fiber.StatusBadRequest,nil,model.ErrBlogNotFound.Error(),nil,nil) + } + + data := ctx.Locals(model.KeyJWTValidAccess) + extData,err := util.ExtractPayloadJWT(data) + if err != nil { + return helper.ResponseFormatter[any](ctx,fiber.StatusInternalServerError,err,err.Error(),nil,nil) + } + + likeReq.Like = 1 + likeReq.UserID = extData.UserID + + err = bc.BlogSvc.CreateLikeSvc(id,likeReq,extData.FullName) + if err != nil { + + if errors.Is(err,model.ErrUserNotFound) || errors.Is(err,model.ErrBlogNotFound) { + return helper.ResponseFormatter[any](ctx,fiber.StatusNotFound,err,err.Error(),nil,nil) + } + + if errors.Is(err,model.ErrInvalidRequest) || errors.Is(err,model.ErrAlreadyLikeBlog) { + return helper.ResponseFormatter[any](ctx,fiber.StatusBadRequest,err,err.Error(),nil,nil) + } + + return helper.ResponseFormatter[any](ctx,fiber.StatusInternalServerError,err,err.Error(),nil,nil) + } + + return helper.ResponseFormatter[any](ctx,fiber.StatusCreated,nil,"Success Like",nil,nil) +} + +// UnLike responsible to deleting a like by id / unliking a blog +func (bc *BlogController) UnLike(ctx *fiber.Ctx) error { + blogID,err := ctx.ParamsInt("id",0) + if err != nil { + return helper.ResponseFormatter[any](ctx,fiber.StatusBadRequest,err,err.Error(),nil,nil) + } + + if blogID == 0 { + return helper.ResponseFormatter[any](ctx,fiber.StatusBadRequest,nil,model.ErrBlogNotFound.Error(),nil,nil) + } + + likeID,err := ctx.ParamsInt("like_id",0) + if err != nil { + return helper.ResponseFormatter[any](ctx,fiber.StatusBadRequest,err,err.Error(),nil,nil) + } + + if likeID == 0 { + return helper.ResponseFormatter[any](ctx,fiber.StatusBadRequest,nil,model.ErrLikeNotFound.Error(),nil,nil) + } + + data := ctx.Locals(model.KeyJWTValidAccess) + extData,err := util.ExtractPayloadJWT(data) + if err != nil { + return helper.ResponseFormatter[any](ctx,fiber.StatusInternalServerError,err,err.Error(),nil,nil) + } + + err = bc.BlogSvc.DeleteLikeSvc(blogID,likeID,extData.UserID) + if err != nil { + if errors.Is(err,model.ErrBlogNotFound) || errors.Is(err,model.ErrLikeNotFound) { + return helper.ResponseFormatter[any](ctx,fiber.StatusNotFound,err,err.Error(),nil,nil) + } + + return helper.ResponseFormatter[any](ctx,fiber.StatusInternalServerError,err,err.Error(),nil,nil) + } + + return helper.ResponseFormatter[any](ctx,fiber.StatusOK,nil,"Success UnLike",nil,nil) +} diff --git a/infrastructure/http.go b/infrastructure/http.go index c4341d8..1cd80a5 100644 --- a/infrastructure/http.go +++ b/infrastructure/http.go @@ -1,10 +1,13 @@ package infrastructure import ( + "time" + "github.com/PickHD/pickablog/application" "github.com/PickHD/pickablog/helper" m "github.com/PickHD/pickablog/middleware" "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v2/middleware/limiter" ) // ServeHTTP is wrapper function to start the apps infra in HTTP mode @@ -34,7 +37,13 @@ func setupRouter(app *application.App) { v1.Post("/auth/register",dep.AuthController.RegisterAuthor) v1.Get("/auth/google/login",dep.AuthController.GoogleLogin) v1.Get("/auth/google/callback",dep.AuthController.GoogleLoginCallback) - v1.Post("/auth/login",dep.AuthController.Login) + v1.Post("/auth/login",limiter.New(limiter.Config{ + Expiration: 15 * time.Minute, + Max: 5, + LimitReached: func (ctx *fiber.Ctx) error { + return helper.ResponseFormatter[any](ctx,fiber.StatusTooManyRequests,nil,"Login Attempts already reached the limit, tell our super admin about resetting a password, Thank you",nil,nil) + }, + }),dep.AuthController.Login) } // TAG SECTION @@ -60,6 +69,16 @@ func setupRouter(app *application.App) { v1.Get("/blog/:slug",dep.BlogController.DetailBlog) v1.Put("/blog/:id",m.ValidateJWTMiddleware,m.AuthorOnlyMiddleware,dep.BlogController.UpdateBlog) v1.Delete("/blog/:id",m.ValidateJWTMiddleware,m.AuthorOnlyMiddleware,dep.BlogController.DeleteBlog) + + // BLOG COMMENT SECTION + v1.Post("/blog/:id/comment",m.ValidateJWTMiddleware,dep.BlogController.CreateComment) + v1.Put("/blog/:id/comment/:comment_id",m.ValidateJWTMiddleware,dep.BlogController.UpdateComment) + v1.Get("/blog/:id/comment",m.ValidateJWTMiddleware,dep.BlogController.ListComment) + v1.Delete("/blog/:id/comment/:comment_id",m.ValidateJWTMiddleware,dep.BlogController.DeleteComment) + + // BLOG LIKE SECTION + v1.Get("/blog/:id/like",m.ValidateJWTMiddleware,dep.BlogController.Like) + v1.Delete("/blog/:id/like/:like_id",m.ValidateJWTMiddleware,dep.BlogController.UnLike) } } diff --git a/model/blog.go b/model/blog.go index ad74d90..5556a6f 100644 --- a/model/blog.go +++ b/model/blog.go @@ -2,15 +2,9 @@ package model import ( "database/sql" - "regexp" "time" ) -var ( - // NoSpecialChar is regex validation where str is not contains a special characters - NoSpecialChar *regexp.Regexp -) - type ( // CreateBlogRequest consist data for creating a blog CreateBlogRequest struct { @@ -42,6 +36,7 @@ type ( ViewBlogResponse struct { ID int `db:"id" json:"id"` Title string `db:"title" json:"title"` + Slug string `db:"slug" json:"slug"` Body string `db:"body" json:"body"` Footer string `db:"footer" json:"footer"` UserID int `db:"user_id" json:"user_id"` @@ -57,6 +52,7 @@ type ( BlogResponse struct { ID int `db:"id" json:"id"` Title string `db:"title" json:"title"` + Slug string `db:"slug" json:"slug"` Body string `db:"body" json:"body"` Footer string `db:"footer" json:"footer"` UserID int `db:"user_id" json:"user_id"` @@ -68,8 +64,4 @@ type ( UpdatedAt time.Time `db:"updated_at" json:"updated_at,omitempty"` UpdatedBy *string `db:"updated_by" json:"updated_by,omitempty"` } -) - -func init() { - NoSpecialChar = regexp.MustCompile(`[^\w]`) -} \ No newline at end of file +) \ No newline at end of file diff --git a/model/comment.go b/model/comment.go new file mode 100644 index 0000000..98f312e --- /dev/null +++ b/model/comment.go @@ -0,0 +1,24 @@ +package model + +import "time" + +type ( + + // CommentRequest consist data for requesting create/update comment + CommentRequest struct { + Comment string `json:"comment"` + UserID int `json:"user_id"` + } + + // ViewCommentResponse consist data of comments + ViewCommentResponse struct { + ID int `db:"id" json:"id"` + Comment string `db:"comment" json:"comment"` + BlogID int `db:"article_id" json:"blog_id"` + UserID int `db:"user_id" json:"user_id"` + CreatedAt time.Time `db:"created_at" json:"created_at"` + CreatedBy string `db:"created_by" json:"created_by"` + UpdatedAt time.Time `db:"updated_at" json:"updated_at,omitempty"` + UpdatedBy *string `db:"updated_by" json:"updated_by,omitempty"` + } +) \ No newline at end of file diff --git a/model/error.go b/model/error.go index 0f32519..0db720d 100644 --- a/model/error.go +++ b/model/error.go @@ -31,11 +31,17 @@ var ( ErrTagNotFound = errors.New("tag is not found") // ErrBlogNotFound occurs when blog is not found in database ErrBlogNotFound = errors.New("blog is not found") + // ErrCommentNotFound occurs when comment is not found in database + ErrCommentNotFound = errors.New("comment is not found") + // ErrLikeNotFound occurs when like is not found in database + ErrLikeNotFound = errors.New("like is not found") // ErrInvalidPassword occurs when password user inputed is invalid ErrInvalidPassword = errors.New("invalid password") // ErrMismatchLogin occurs when user trying mismatch login method ErrMismatchLogin = errors.New("mismatch login, please use endpoint /api/v1/auth/google/login") + // ErrAlreadyLikeBlog occurs when user trying to liking a blog more than 1 times + ErrAlreadyLikeBlog = errors.New("already liking this blog") // ErrRedisKeyNotExisted occurs when key provided is not existed ErrRedisKeyNotExisted = errors.New("keys not existed") @@ -50,4 +56,8 @@ var ( // ErrForbiddenDeleteSelf occurs when user trying deleting their account by self ErrForbiddenDeleteSelf = errors.New("forbidden delete account self, make sure the id is corrent") + // ErrForbiddenUpdate occurs when user trying updating forbidden resource + ErrForbiddenUpdate = errors.New("forbidden updating data") + // ErrForbiddenDelete occurs when user trying deleting forbidden resource + ErrForbiddenDelete = errors.New("forbidden deleteing data") ) \ No newline at end of file diff --git a/model/like.go b/model/like.go new file mode 100644 index 0000000..785f859 --- /dev/null +++ b/model/like.go @@ -0,0 +1,18 @@ +package model + +type ( + + // LikeRequest consist data for requesting create like + LikeRequest struct { + Like int `json:"like"` + UserID int `json:"user_id"` + } + + // ViewLikeResponse consist data of like response + ViewLikeResponse struct { + ID int `db:"id" json:"id"` + Like int `db:"like_count" json:"like"` + UserID int `db:"user_id" json:"user_id"` + BlogID int `db:"article_id" json:"blog_id"` + } +) \ No newline at end of file diff --git a/repository/blog.go b/repository/blog.go index 2deacdd..ce12c47 100644 --- a/repository/blog.go +++ b/repository/blog.go @@ -52,6 +52,7 @@ func (br *BlogRepository) GetAll(page int, size int, order string, field string, SELECT id, title, + slug, body, footer, user_id, @@ -152,6 +153,7 @@ func (br *BlogRepository) GetAll(page int, size int, order string, field string, err := rows.Scan( &data.ID, &data.Title, + &data.Slug, &data.Body, &data.Footer, &data.UserID, @@ -183,6 +185,7 @@ func (br *BlogRepository) GetBySlug(slug string) (*model.ViewBlogResponse,error) SELECT id, title, + slug, body, footer, user_id, @@ -201,6 +204,7 @@ func (br *BlogRepository) GetBySlug(slug string) (*model.ViewBlogResponse,error) err := row.Scan( &blog.ID, &blog.Title, + &blog.Slug, &blog.Body, &blog.Footer, &blog.UserID, @@ -228,6 +232,7 @@ func (br *BlogRepository) GetByID(id int) (*model.ViewBlogResponse,error) { SELECT id, title, + slug, body, footer, user_id, @@ -246,6 +251,7 @@ func (br *BlogRepository) GetByID(id int) (*model.ViewBlogResponse,error) { err := row.Scan( &blog.ID, &blog.Title, + &blog.Slug, &blog.Body, &blog.Footer, &blog.UserID, diff --git a/repository/comment.go b/repository/comment.go new file mode 100644 index 0000000..ed599a5 --- /dev/null +++ b/repository/comment.go @@ -0,0 +1,255 @@ +package repository + +import ( + "context" + "fmt" + + "github.com/PickHD/pickablog/config" + "github.com/PickHD/pickablog/helper" + "github.com/PickHD/pickablog/model" + "github.com/jackc/pgx/v4" + "github.com/sirupsen/logrus" +) + +type ( + // ICommentRepository is an interface that has all the function to be implemented inside comment repository + ICommentRepository interface { + Create(blogID int,req model.CommentRequest,createdBy string) error + GetByID(id int) (*model.ViewCommentResponse,error) + UpdateByID(id int,req map[string]interface{}, updatedBy string) error + GetAllByBlogID(blogID int,page int, size int, order string, field string) ([]model.ViewCommentResponse,int,error) + DeleteByID(blogID,commentID int) error + } + + // CommentRepository is an app comment struct that consists of all the dependencies needed for comment repository + CommentRepository struct { + Context context.Context + Config *config.Configuration + Logger *logrus.Logger + DB *pgx.Conn + } +) + +// Create repository layer for executing command to creating comments +func (cr *CommentRepository) Create(blogID int, req model.CommentRequest,createdBy string) error { + tx,err := cr.DB.Begin(cr.Context) + if err != nil { + cr.Logger.Error(fmt.Errorf("CommentRepository.Create BeginTX ERROR %v MSG %s",err,err.Error())) + return err + } + + qInsert := `INSERT INTO comments (id,comment,article_id,user_id,created_by) VALUES (nextval('comment_seq'),$1,$2,$3,$4) RETURNING id` + + var commentID int + err = tx.QueryRow(cr.Context,qInsert,req.Comment,blogID,req.UserID,createdBy).Scan(&commentID) + if err != nil { + err = tx.Rollback(cr.Context) + if err != nil { + cr.Logger.Error(fmt.Errorf("CommentRepository.Create.QueryRow.Scan Rollback ERROR %v MSG %s",err,err.Error())) + return err + } + + cr.Logger.Error(fmt.Errorf("CommentRepository.Create.QueryRow Scan ERROR %v MSG %s",err,err.Error())) + return err + } + + qUpdate := `UPDATE article SET comments = ARRAY_APPEND(comments,$1) WHERE id = $2` + + _,err = tx.Exec(cr.Context,qUpdate,commentID,blogID) + if err != nil { + err = tx.Rollback(cr.Context) + if err != nil { + cr.Logger.Error(fmt.Errorf("CommentRepository.Create.Exec Rollback ERROR %v MSG %s",err,err.Error())) + return err + } + + cr.Logger.Error(fmt.Errorf("CommentRepository.Create Exec ERROR %v MSG %s",err,err.Error())) + return err + } + + err = tx.Commit(cr.Context) + if err != nil { + cr.Logger.Error(fmt.Errorf("CommentRepository.Create Commit ERROR %v MSG %s",err,err.Error())) + return err + } + + return nil +} + +// GetByID repository layer for querying command to getting detail comment by id +func (cr *CommentRepository) GetByID(id int) (*model.ViewCommentResponse,error) { + var comment model.ViewCommentResponse + + q := ` + SELECT + id, + comment, + article_id, + user_id, + created_at, + created_by, + updated_at, + updated_by + FROM comments + WHERE id = $1` + + row := cr.DB.QueryRow(cr.Context,q,id) + err := row.Scan(&comment.ID,&comment.Comment,&comment.BlogID,&comment.UserID,&comment.CreatedAt,&comment.CreatedBy,&comment.UpdatedAt,&comment.UpdatedBy) + if err != nil { + if err == pgx.ErrNoRows { + cr.Logger.Info(fmt.Errorf("CommentRepository.GetByID INFO %v MSG %s",err,err.Error())) + } else { + cr.Logger.Error(fmt.Errorf("CommentRepository.GetByID ERROR %v MSG %s",err,err.Error())) + } + return nil,err + } + + return &comment,nil +} + +// UpdateByID repository layer for executing command to updating a comment by id +func (cr *CommentRepository) UpdateByID(id int, req map[string]interface{}, updatedBy string) error { + req["updated_by"] = updatedBy + req["id"] = id + + tx,err := cr.DB.Begin(cr.Context) + if err != nil { + cr.Logger.Error(fmt.Errorf("CommentRepository.UpdateByID BeginTX ERROR %v MSG %s",err,err.Error())) + return err + } + + q,args,err := helper.QueryUpdateBuilder("comments",req,[]string{"id"}) + if err != nil { + err = tx.Rollback(cr.Context) + if err != nil { + cr.Logger.Error(fmt.Errorf("CommentRepository.UpdateByID.QueryUpdateBuilder Rollback ERROR %v MSG %s",err,err.Error())) + return err + } + + cr.Logger.Error(fmt.Errorf("CommentRepository.UpdateByID QueryUpdateBuilder ERROR %v MSG %s",err,err.Error())) + return err + } + + cr.Logger.Info(fmt.Sprintf("Query : %s Args : %v",q,args)) + + _,err = tx.Exec(cr.Context,q,args...) + if err != nil { + err = tx.Rollback(cr.Context) + if err != nil { + cr.Logger.Error(fmt.Errorf("CommentRepository.UpdateByID.Exec Rollback ERROR %v MSG %s",err,err.Error())) + return err + } + + + cr.Logger.Error(fmt.Errorf("CommentRepository.UpdateByID Exec ERROR %v MSG %s",err,err.Error())) + return err + } + + err = tx.Commit(cr.Context) + if err != nil { + cr.Logger.Error(fmt.Errorf("CommentRepository.UpdateByID Commit ERROR %v MSG %s",err,err.Error())) + return err + } + + return nil +} + +// GetAllByBlogID repository layer for querying command to getting all comments +func (cr *CommentRepository) GetAllByBlogID(blogID int,page int, size int, order string, field string) ([]model.ViewCommentResponse,int,error) { + q :=fmt.Sprintf(` + SELECT + id, + comment, + article_id, + user_id, + created_at, + created_by, + updated_at, + updated_by + FROM comments + WHERE article_id = %d + `,blogID) + qCount := fmt.Sprintf(`SELECT 1 FROM comments WHERE article_id = %d`,blogID) + + limit := size + 1 + offset := (page - 1) * size + orderBy := fmt.Sprintf(" ORDER BY %s %s LIMIT %d OFFSET %d ",field, order, limit, offset) + + query := fmt.Sprintf("%s %s", q, orderBy) + queryCount := fmt.Sprintf("SELECT COUNT (*) FROM ( %s ) AS article_count ",qCount) + + cr.Logger.Info(fmt.Sprintf("Query : %s",query)) + cr.Logger.Info(fmt.Sprintf("Query Count : %s",queryCount)) + + var totalData int + err := cr.DB.QueryRow(cr.Context,queryCount).Scan(&totalData) + if err != nil { + cr.Logger.Error(fmt.Errorf("CommentRepository.GetAllByBlogID Scan ERROR %v MSG %s",err,err.Error())) + return nil,0,err + } + + rows,err := cr.DB.Query(cr.Context,query) + if err != nil { + cr.Logger.Error(fmt.Errorf("CommentRepository.GetAllByBlogID Query ERROR %v MSG %s",err,err.Error())) + return nil,0,err + } + + var listData []model.ViewCommentResponse + for rows.Next() { + data := &model.ViewCommentResponse{} + err := rows.Scan(&data.ID,&data.Comment,&data.BlogID,&data.UserID,&data.CreatedAt,&data.CreatedBy,&data.UpdatedAt,&data.UpdatedBy) + if err != nil { + cr.Logger.Error(fmt.Errorf("CommentRepository.GetAllByBlogID rows.Next Scan ERROR %v MSG %s",err,err.Error())) + return nil,0,err + } + + listData = append(listData, *data) + } + + return listData,totalData,nil +} + +// DeleteByID repository layer for executing command to deleting a comment by id +func (cr *CommentRepository) DeleteByID(blogID int,commentID int) error { + tx,err := cr.DB.Begin(cr.Context) + if err != nil { + cr.Logger.Error(fmt.Errorf("CommentRepository.DeleteByID BeginTX ERROR %v MSG %s",err,err.Error())) + return err + } + + qDelete := `DELETE FROM comments WHERE id = $1` + + _,err = tx.Exec(cr.Context,qDelete,commentID) + if err != nil { + err = tx.Rollback(cr.Context) + if err != nil { + cr.Logger.Error(fmt.Errorf("CommentRepository.DeleteByID.Exec Rollback ERROR %v MSG %s",err,err.Error())) + return err + } + + cr.Logger.Error(fmt.Errorf("CommentRepository.DeleteByID Exec ERROR %v MSG %s",err,err.Error())) + return err + } + + qUpdate := `UPDATE article SET comments = ARRAY_REMOVE(comments,$1) WHERE id = $2` + + _,err = tx.Exec(cr.Context,qUpdate,commentID,blogID) + if err != nil { + err = tx.Rollback(cr.Context) + if err != nil { + cr.Logger.Error(fmt.Errorf("CommentRepository.DeleteByID.Exec Rollback ERROR %v MSG %s",err,err.Error())) + return err + } + + cr.Logger.Error(fmt.Errorf("CommentRepository.DeleteByID Exec ERROR %v MSG %s",err,err.Error())) + return err + } + + err = tx.Commit(cr.Context) + if err != nil { + cr.Logger.Error(fmt.Errorf("CommentRepository.DeleteByID Commit ERROR %v MSG %s",err,err.Error())) + return err + } + + return nil +} \ No newline at end of file diff --git a/repository/like.go b/repository/like.go new file mode 100644 index 0000000..31c3435 --- /dev/null +++ b/repository/like.go @@ -0,0 +1,178 @@ +package repository + +import ( + "context" + "fmt" + + "github.com/PickHD/pickablog/config" + "github.com/PickHD/pickablog/model" + "github.com/jackc/pgx/v4" + "github.com/sirupsen/logrus" +) + +type ( + // ILikeRepository is an interface that has all the function to be implemented inside like repository + ILikeRepository interface { + Create(blogID int,req model.LikeRequest,createdBy string) error + GetByID(id int) (*model.ViewLikeResponse,error) + GetByUserID(userID int) (*model.ViewLikeResponse,error) + DeleteByID(blogID int,likeID int) error + } + + // LikeRepository is an app like struct that consists of all the dependencies needed for like repository + LikeRepository struct { + Context context.Context + Config *config.Configuration + Logger *logrus.Logger + DB *pgx.Conn + } +) + +// Create repository layer for executing command creating like +func (lr *LikeRepository) Create(blogID int,req model.LikeRequest,createdBy string) error { + tx,err := lr.DB.Begin(lr.Context) + if err != nil { + lr.Logger.Error(fmt.Errorf("LikeRepository.Create BeginTX ERROR %v MSG %s",err,err.Error())) + return err + } + + qInsert := `INSERT INTO likes (id,like_count,article_id,user_id,created_by) VALUES (nextval('like_seq'),$1,$2,$3,$4) RETURNING id` + + var likeID int + err = tx.QueryRow(lr.Context,qInsert,req.Like,blogID,req.UserID,createdBy).Scan(&likeID) + if err != nil { + err = tx.Rollback(lr.Context) + if err != nil { + lr.Logger.Error(fmt.Errorf("LikeRepository.Create.QueryRow.Scan Rollback ERROR %v MSG %s",err,err.Error())) + return err + } + + lr.Logger.Error(fmt.Errorf("LikeRepository.Create.QueryRow Scan ERROR %v MSG %s",err,err.Error())) + return err + } + + qUpdate := `UPDATE article SET likes = ARRAY_APPEND(likes,$1) WHERE id = $2` + + _,err = tx.Exec(lr.Context,qUpdate,likeID,blogID) + if err != nil { + err = tx.Rollback(lr.Context) + if err != nil { + lr.Logger.Error(fmt.Errorf("LikeRepository.Create.Exec Rollback ERROR %v MSG %s",err,err.Error())) + return err + } + + lr.Logger.Error(fmt.Errorf("LikeRepository.Create Exec ERROR %v MSG %s",err,err.Error())) + return err + } + + err = tx.Commit(lr.Context) + if err != nil { + lr.Logger.Error(fmt.Errorf("LikeRepository.Create Commit ERROR %v MSG %s",err,err.Error())) + return err + } + + return nil +} + +// GetByID repository layer for querying command getting detail like by id +func (lr *LikeRepository) GetByID(id int) (*model.ViewLikeResponse,error) { + var like model.ViewLikeResponse + + q := ` + SELECT + id, + like_count, + user_id, + article_id + FROM likes + WHERE id = $1 + ` + + row := lr.DB.QueryRow(lr.Context,q,id) + err := row.Scan(&like.ID,&like.Like,&like.UserID,&like.BlogID) + if err != nil { + if err == pgx.ErrNoRows { + lr.Logger.Info(fmt.Errorf("LikeRepository.GetByID Scan INFO %v MSG %s",err,err.Error())) + } else { + lr.Logger.Error(fmt.Errorf("LikeRepository.GetByID Scan ERROR %v MSG %s",err,err.Error())) + } + + return nil,err + } + + return &like,nil +} + +// GetByUserID repository layer for querying command getting detail like by userID +func (lr *LikeRepository) GetByUserID(userID int) (*model.ViewLikeResponse,error) { + var like model.ViewLikeResponse + + q := ` + SELECT + id, + like_count, + user_id, + article_id + FROM likes + WHERE user_id = $1 + ` + + row := lr.DB.QueryRow(lr.Context,q,userID) + err := row.Scan(&like.ID,&like.Like,&like.UserID,&like.BlogID) + if err != nil { + if err == pgx.ErrNoRows { + lr.Logger.Info(fmt.Errorf("LikeRepository.GetByID Scan INFO %v MSG %s",err,err.Error())) + } else { + lr.Logger.Error(fmt.Errorf("LikeRepository.GetByID Scan ERROR %v MSG %s",err,err.Error())) + } + + return nil,err + } + + return &like,nil +} + +// DeleteByID repository layer for executing command deleting like by id +func (lr *LikeRepository) DeleteByID(blogID int,likeID int) error { + tx,err := lr.DB.Begin(lr.Context) + if err != nil { + lr.Logger.Error(fmt.Errorf("LikeRepository.DeleteByID BeginTX ERROR %v MSG %s",err,err.Error())) + return err + } + + qDelete := `DELETE FROM likes WHERE id = $1` + + _,err = tx.Exec(lr.Context,qDelete,likeID) + if err != nil { + err = tx.Rollback(lr.Context) + if err != nil { + lr.Logger.Error(fmt.Errorf("LikeRepository.DeleteByID.Exec Rollback ERROR %v MSG %s",err,err.Error())) + return err + } + + lr.Logger.Error(fmt.Errorf("LikeRepository.DeleteByID Exec ERROR %v MSG %s",err,err.Error())) + return err + } + + qUpdate := `UPDATE article SET likes = ARRAY_REMOVE(likes,$1) WHERE id = $2` + + _,err = tx.Exec(lr.Context,qUpdate,likeID,blogID) + if err != nil { + err = tx.Rollback(lr.Context) + if err != nil { + lr.Logger.Error(fmt.Errorf("LikeRepository.DeleteByID.Exec Rollback ERROR %v MSG %s",err,err.Error())) + return err + } + + lr.Logger.Error(fmt.Errorf("LikeRepository.DeleteByID Exec ERROR %v MSG %s",err,err.Error())) + return err + } + + err = tx.Commit(lr.Context) + if err != nil { + lr.Logger.Error(fmt.Errorf("LikeRepository.DeleteByID Commit ERROR %v MSG %s",err,err.Error())) + return err + } + + return nil +} \ No newline at end of file diff --git a/service/blog.go b/service/blog.go index 2f4475f..83d9367 100644 --- a/service/blog.go +++ b/service/blog.go @@ -2,14 +2,15 @@ package service import ( "context" + "sync" "github.com/PickHD/pickablog/config" "github.com/PickHD/pickablog/helper" "github.com/PickHD/pickablog/model" "github.com/PickHD/pickablog/repository" + "github.com/gosimple/slug" "github.com/jackc/pgx/v4" "github.com/sirupsen/logrus" - "github.com/gosimple/slug" ) type ( @@ -19,7 +20,15 @@ type ( GetAllBlogSvc(page int, size int, order string, field string, search string, filter model.FilterBlogRequest) ([]model.BlogResponse,*model.Metadata,error) GetBlogBySlugSvc(slug string) (*model.BlogResponse,error) UpdateBlogSvc(id int, req model.UpdateBlogRequest, updatedBy string) error - DeleteBlogSvc(id int) error + DeleteBlogSvc(id int,userID int) error + + CreateCommentSvc(blogID int,req model.CommentRequest,createdBy string) error + UpdateCommentSvc(id int, req model.CommentRequest, updatedBy string) error + GetCommentsByBlogSvc(blogID int,page int, size int, order string, field string) ([]model.ViewCommentResponse,*model.Metadata,error) + DeleteCommentSvc(blogID int, commentID int, userID int) error + + CreateLikeSvc(blogID int,req model.LikeRequest,createdBy string) error + DeleteLikeSvc(blogID int,likeID int, userID int) error } // BlogRepository is an app blog struct that consists of all the dependencies needed for blog service @@ -28,6 +37,10 @@ type ( Config *config.Configuration Logger *logrus.Logger BlogRepo repository.IBlogRepository + CommentRepo repository.ICommentRepository + LikeRepo repository.ILikeRepository + UserRepo repository.IUserRepository + Mutex *sync.RWMutex } ) @@ -75,6 +88,7 @@ func (bs *BlogService) GetAllBlogSvc(page int, size int, order string, field str d.ID= r.ID d.Title = r.Title + d.Slug = r.Slug d.Body = r.Body d.Footer = r.Footer d.UserID = r.UserID @@ -83,7 +97,7 @@ func (bs *BlogService) GetAllBlogSvc(page int, size int, order string, field str d.UpdatedAt = r.UpdatedAt d.UpdatedBy = r.UpdatedBy - if len(r.Comments) > 1 { + if len(r.Comments) > 0 { for c := range r.Comments{ rc := r.Comments[c] if rc.Valid { @@ -93,7 +107,7 @@ func (bs *BlogService) GetAllBlogSvc(page int, size int, order string, field str } - if len(r.Tags) > 1 { + if len(r.Tags) > 0 { for t := range r.Tags{ rt := r.Tags[t] if rt.Valid { @@ -102,7 +116,7 @@ func (bs *BlogService) GetAllBlogSvc(page int, size int, order string, field str } } - if len(r.Likes) > 1 { + if len(r.Likes) > 0 { for l := range r.Likes{ rl := r.Likes[l] if rl.Valid { @@ -133,6 +147,7 @@ func (bs *BlogService) GetBlogBySlugSvc(slug string) (*model.BlogResponse,error) data.ID= r.ID data.Title = r.Title + data.Slug = r.Slug data.Body = r.Body data.Footer = r.Footer data.UserID = r.UserID @@ -141,7 +156,7 @@ func (bs *BlogService) GetBlogBySlugSvc(slug string) (*model.BlogResponse,error) data.UpdatedAt = r.UpdatedAt data.UpdatedBy = r.UpdatedBy - if len(r.Comments) > 1 { + if len(r.Comments) > 0 { for c := range r.Comments{ rc := r.Comments[c] if rc.Valid { @@ -151,7 +166,7 @@ func (bs *BlogService) GetBlogBySlugSvc(slug string) (*model.BlogResponse,error) } - if len(r.Tags) > 1 { + if len(r.Tags) > 0 { for t := range r.Tags{ rt := r.Tags[t] if rt.Valid { @@ -160,7 +175,7 @@ func (bs *BlogService) GetBlogBySlugSvc(slug string) (*model.BlogResponse,error) } } - if len(r.Likes) > 1 { + if len(r.Likes) > 0 { for l := range r.Likes{ rl := r.Likes[l] if rl.Valid { @@ -174,14 +189,14 @@ func (bs *BlogService) GetBlogBySlugSvc(slug string) (*model.BlogResponse,error) // UpdateBlogSvc service layer for handling updating a blog by ID func (bs *BlogService) UpdateBlogSvc(id int, req model.UpdateBlogRequest, updatedBy string) error { - _,err := bs.BlogRepo.GetByID(id) + currentBlog,err := bs.BlogRepo.GetByID(id) if err != nil { if err == pgx.ErrNoRows { return model.ErrBlogNotFound } } - blogMap,err := validateUpdateBlogRequest(&req) + blogMap,err := validateUpdateBlogRequest(currentBlog,&req) if err != nil { return err } @@ -195,14 +210,18 @@ func (bs *BlogService) UpdateBlogSvc(id int, req model.UpdateBlogRequest, update } // DeleteBlogSvc service layer for handling deleting a blog by ID -func (bs *BlogService) DeleteBlogSvc(id int) error { - _,err := bs.BlogRepo.GetByID(id) +func (bs *BlogService) DeleteBlogSvc(id int,userID int) error { + currentBlog,err := bs.BlogRepo.GetByID(id) if err != nil { if err == pgx.ErrNoRows { return model.ErrBlogNotFound } } + if currentBlog.UserID != userID { + return model.ErrForbiddenDelete + } + err = bs.BlogRepo.DeleteByID(id) if err != nil { return err @@ -211,6 +230,206 @@ func (bs *BlogService) DeleteBlogSvc(id int) error { return nil } +// CreateCommentSvc service layer for handling creating comments +func (bs *BlogService) CreateCommentSvc(blogID int, req model.CommentRequest,createdBy string) error { + _,err := bs.BlogRepo.GetByID(blogID) + if err != nil { + if err == pgx.ErrNoRows { + return model.ErrBlogNotFound + } + + return err + } + + err = validateCreateCommentRequest(&req) + if err != nil { + return err + } + + _,err = bs.UserRepo.GetByID(req.UserID) + if err != nil { + if err == pgx.ErrNoRows { + return model.ErrUserNotFound + } + + return err + } + + bs.Mutex.Lock() + err = bs.CommentRepo.Create(blogID,req,createdBy) + if err != nil { + return err + } + defer bs.Mutex.Unlock() + + return nil +} + +// UpdateCommentSvc service layer for handling updating comment by id +func (bs *BlogService) UpdateCommentSvc(id int, req model.CommentRequest,updatedBy string) error { + currentComment,err := bs.CommentRepo.GetByID(id) + if err != nil { + if err == pgx.ErrNoRows { + return model.ErrCommentNotFound + } + + return err + } + + commentMap,err := validateUpdateCommentRequest(currentComment,&req) + if err != nil { + return err + } + + _,err = bs.UserRepo.GetByID(req.UserID) + if err != nil { + if err == pgx.ErrNoRows { + return model.ErrUserNotFound + } + + return err + } + + bs.Mutex.Lock() + err = bs.CommentRepo.UpdateByID(id,commentMap,updatedBy) + if err != nil { + return err + } + defer bs.Mutex.Unlock() + + return nil +} + +// GetCommentsByBlogSvc service layer for handling getting comments with filter +func (bs *BlogService) GetCommentsByBlogSvc(blogID int, page int, size int, order string, field string) ([]model.ViewCommentResponse,*model.Metadata,error) { + res,totalData,err := bs.CommentRepo.GetAllByBlogID(blogID,page,size,order,field) + if err != nil { + return nil,nil,err + } + + if totalData < 1 { + return []model.ViewCommentResponse{},nil,nil + } + + totalPage := (int(totalData) + size - 1) / size + + if len(res) > size { + res = res[:len(res)-1] + } + + meta := helper.BuildMetaData(page,size,order,totalData,totalPage) + + return res,meta,nil +} + +// DeleteCommentSvc service layer for handling deleting comment with id +func (bs *BlogService) DeleteCommentSvc(blogID int, commentID int, userID int) error { + _,err := bs.BlogRepo.GetByID(blogID) + if err != nil { + if err == pgx.ErrNoRows { + return model.ErrBlogNotFound + } + + return err + } + + currentComment,err := bs.CommentRepo.GetByID(commentID) + if err != nil { + if err == pgx.ErrNoRows { + return model.ErrCommentNotFound + } + + return err + } + + if currentComment.UserID != userID { + return model.ErrForbiddenDelete + } + + bs.Mutex.Lock() + err = bs.CommentRepo.DeleteByID(blogID,commentID) + if err != nil { + return err + } + defer bs.Mutex.Unlock() + + return nil +} + +// CreateLikeSvc service layer for handling creating likes +func (bs *BlogService) CreateLikeSvc(blogID int, req model.LikeRequest,createdBy string) error { + _,err := bs.BlogRepo.GetByID(blogID) + if err != nil { + if err == pgx.ErrNoRows { + return model.ErrBlogNotFound + } + + return err + } + + _,err = bs.UserRepo.GetByID(req.UserID) + if err != nil { + if err == pgx.ErrNoRows { + return model.ErrUserNotFound + } + + return err + } + + _,err = bs.LikeRepo.GetByUserID(req.UserID) + if err != nil { + if err == pgx.ErrNoRows { + + bs.Mutex.Lock() + err = bs.LikeRepo.Create(blogID,req,createdBy) + if err != nil { + return err + } + defer bs.Mutex.Unlock() + + return nil + } + + return err + } + + return model.ErrAlreadyLikeBlog +} + +// DeleteLikeSvc service layer for handling deleting like with id +func (bs *BlogService) DeleteLikeSvc(blogID int,likeID int, userID int) error { + _,err := bs.BlogRepo.GetByID(blogID) + if err != nil { + if err == pgx.ErrNoRows { + return model.ErrBlogNotFound + } + + return err + } + + currentLike,err := bs.LikeRepo.GetByID(likeID) + if err != nil { + if err == pgx.ErrNoRows { + return model.ErrLikeNotFound + } + + return err + } + + if currentLike.UserID != userID { + return model.ErrForbiddenDelete + } + + bs.Mutex.Lock() + err = bs.LikeRepo.DeleteByID(blogID,likeID) + if err != nil { + return err + } + defer bs.Mutex.Unlock() + + return nil +} + // validateCreateBlogRequest responsible to validating create blog request func validateCreateBlogRequest(req *model.CreateBlogRequest) error { if req.Title == "" || req.Body == "" || req.Footer == "" || len(req.Tags) < 1 { @@ -233,7 +452,7 @@ func validateCreateBlogRequest(req *model.CreateBlogRequest) error { } // validateCreateBlogRequest responsible to validating update blog request -func validateUpdateBlogRequest(req *model.UpdateBlogRequest) (map[string]interface{},error) { +func validateUpdateBlogRequest(blog *model.ViewBlogResponse,req *model.UpdateBlogRequest) (map[string]interface{},error) { blogMap := make(map[string]interface{}) if req.Title == "" || req.Body == "" || req.Footer == "" { @@ -252,6 +471,10 @@ func validateUpdateBlogRequest(req *model.UpdateBlogRequest) (map[string]interfa return nil,model.ErrInvalidRequest } + if req.UserID != blog.UserID { + return nil,model.ErrForbiddenUpdate + } + req.Slug = slug.Make(req.Title) blogMap["title"] = req.Title @@ -261,4 +484,31 @@ func validateUpdateBlogRequest(req *model.UpdateBlogRequest) (map[string]interfa blogMap["user_id"] = req.UserID return blogMap,nil +} + +// validateCreateCommentRequest responsible to validating create comment request +func validateCreateCommentRequest(req *model.CommentRequest) error { + if len(req.Comment) < 5 { + return model.ErrInvalidRequest + } + + return nil +} + +// validateUpdateCommentRequest responsible to validating update comment request +func validateUpdateCommentRequest(comment *model.ViewCommentResponse,req *model.CommentRequest)(map[string]interface{},error) { + commentMap := make(map[string]interface{}) + + if len(req.Comment) < 5 { + return nil,model.ErrInvalidRequest + } + + if comment.UserID != req.UserID { + return nil,model.ErrForbiddenUpdate + } + + commentMap["comment"] = req.Comment + commentMap["user_id"] = req.UserID + + return commentMap,nil } \ No newline at end of file