From 17d19d6be461b908fba8422d296a94fbd6a80432 Mon Sep 17 00:00:00 2001 From: hanif salafi Date: Fri, 31 Jan 2025 16:46:18 +0700 Subject: [PATCH] feat: add article comments module, and update users --- .../entity/article_comments.entity.go | 15 + .../entity/registration_otps.entity.go | 1 + app/database/index.database.go | 1 + .../article_comments.module.go | 53 +++ .../controller/article_comments.controller.go | 195 ++++++++++ .../article_comments/controller/controller.go | 16 + .../mapper/article_comments.mapper.go | 23 ++ .../repository/article_comments.repository.go | 98 +++++ .../request/article_comments.request.go | 96 +++++ .../response/article_comments.response.go | 15 + .../service/article_comments.service.go | 90 +++++ app/module/users/request/users.request.go | 3 +- app/module/users/service/users.service.go | 17 +- app/router/api.go | 5 + config/config/smtp.config.go | 4 + docs/swagger/docs.go | 368 ++++++++++++++++++ docs/swagger/swagger.json | 368 ++++++++++++++++++ docs/swagger/swagger.yaml | 234 +++++++++++ main.go | 2 + 19 files changed, 1601 insertions(+), 3 deletions(-) create mode 100644 app/database/entity/article_comments.entity.go create mode 100644 app/module/article_comments/article_comments.module.go create mode 100644 app/module/article_comments/controller/article_comments.controller.go create mode 100644 app/module/article_comments/controller/controller.go create mode 100644 app/module/article_comments/mapper/article_comments.mapper.go create mode 100644 app/module/article_comments/repository/article_comments.repository.go create mode 100644 app/module/article_comments/request/article_comments.request.go create mode 100644 app/module/article_comments/response/article_comments.response.go create mode 100644 app/module/article_comments/service/article_comments.service.go diff --git a/app/database/entity/article_comments.entity.go b/app/database/entity/article_comments.entity.go new file mode 100644 index 0000000..23c8450 --- /dev/null +++ b/app/database/entity/article_comments.entity.go @@ -0,0 +1,15 @@ +package entity + +import "time" + +type ArticleComments struct { + ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"` + Message string `json:"message" gorm:"type:varchar"` + ArticleId uint `json:"article_id" gorm:"type:int4"` + CommentFrom *uint `json:"comment_from" gorm:"type:int4"` + ParentId *int `json:"parent_id" gorm:"type:int4"` + IsPublic bool `json:"is_public" gorm:"type:bool"` + IsActive bool `json:"is_active" gorm:"type:bool"` + CreatedAt time.Time `json:"created_at" gorm:"default:now()"` + UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"` +} diff --git a/app/database/entity/registration_otps.entity.go b/app/database/entity/registration_otps.entity.go index 3161420..efff593 100644 --- a/app/database/entity/registration_otps.entity.go +++ b/app/database/entity/registration_otps.entity.go @@ -5,6 +5,7 @@ import "time" type RegistrationOtps struct { ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"` Email string `json:"email" gorm:"type:varchar"` + Name *string `json:"name" gorm:"type:varchar"` OtpCode string `json:"otp_code" gorm:"type:varchar"` ValidUntil time.Time `json:"valid_until" gorm:"default:(NOW() + INTERVAL '10 minutes')"` IsActive bool `json:"is_active" gorm:"type:bool"` diff --git a/app/database/index.database.go b/app/database/index.database.go index 755627d..efc0b23 100644 --- a/app/database/index.database.go +++ b/app/database/index.database.go @@ -72,6 +72,7 @@ func Models() []interface{} { entity.ArticleCategories{}, article_category_details.ArticleCategoryDetails{}, entity.ArticleFiles{}, + entity.ArticleComments{}, entity.ArticleNulisAI{}, entity.Cities{}, entity.CustomStaticPages{}, diff --git a/app/module/article_comments/article_comments.module.go b/app/module/article_comments/article_comments.module.go new file mode 100644 index 0000000..57e6035 --- /dev/null +++ b/app/module/article_comments/article_comments.module.go @@ -0,0 +1,53 @@ +package article_comments + +import ( + "github.com/gofiber/fiber/v2" + "go-humas-be/app/module/article_comments/controller" + "go-humas-be/app/module/article_comments/repository" + "go-humas-be/app/module/article_comments/service" + "go.uber.org/fx" +) + +// struct of ArticleCommentsRouter +type ArticleCommentsRouter struct { + App fiber.Router + Controller *controller.Controller +} + +// register bulky of ArticleComments module +var NewArticleCommentsModule = fx.Options( + // register repository of ArticleComments module + fx.Provide(repository.NewArticleCommentsRepository), + + // register service of ArticleComments module + fx.Provide(service.NewArticleCommentsService), + + // register controller of ArticleComments module + fx.Provide(controller.NewController), + + // register router of ArticleComments module + fx.Provide(NewArticleCommentsRouter), +) + +// init ArticleCommentsRouter +func NewArticleCommentsRouter(fiber *fiber.App, controller *controller.Controller) *ArticleCommentsRouter { + return &ArticleCommentsRouter{ + App: fiber, + Controller: controller, + } +} + +// register routes of ArticleComments module +func (_i *ArticleCommentsRouter) RegisterArticleCommentsRoutes() { + // define controllers + articleCommentsController := _i.Controller.ArticleComments + + // define routes + _i.App.Route("/article-comments", func(router fiber.Router) { + router.Get("/", articleCommentsController.All) + router.Get("/:id", articleCommentsController.Show) + router.Post("/", articleCommentsController.Save) + router.Put("/:id", articleCommentsController.Update) + router.Delete("/:id", articleCommentsController.Delete) + }) +} diff --git a/app/module/article_comments/controller/article_comments.controller.go b/app/module/article_comments/controller/article_comments.controller.go new file mode 100644 index 0000000..3afb056 --- /dev/null +++ b/app/module/article_comments/controller/article_comments.controller.go @@ -0,0 +1,195 @@ +package controller + +import ( + "github.com/gofiber/fiber/v2" + "github.com/rs/zerolog" + "go-humas-be/app/module/article_comments/request" + "go-humas-be/app/module/article_comments/service" + "go-humas-be/utils/paginator" + utilRes "go-humas-be/utils/response" + utilVal "go-humas-be/utils/validator" + "strconv" +) + +type articleCommentsController struct { + articleCommentsService service.ArticleCommentsService + Log zerolog.Logger +} + +type ArticleCommentsController interface { + All(c *fiber.Ctx) error + Show(c *fiber.Ctx) error + Save(c *fiber.Ctx) error + Update(c *fiber.Ctx) error + Delete(c *fiber.Ctx) error +} + +func NewArticleCommentsController(articleCommentsService service.ArticleCommentsService, log zerolog.Logger) ArticleCommentsController { + return &articleCommentsController{ + articleCommentsService: articleCommentsService, + Log: log, + } +} + +// All get all ArticleComments +// @Summary Get all ArticleComments +// @Description API for getting all ArticleComments +// @Tags ArticleComments +// @Security Bearer +// @Param req query request.ArticleCommentsQueryRequest false "query parameters" +// @Param req query paginator.Pagination false "pagination parameters" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /article-comments [get] +func (_i *articleCommentsController) All(c *fiber.Ctx) error { + paginate, err := paginator.Paginate(c) + if err != nil { + return err + } + + reqContext := request.ArticleCommentsQueryRequestContext{ + Message: c.Query("message"), + ArticleId: c.Query("articleId"), + CommentFrom: c.Query("commentFrom"), + ParentId: c.Query("parentId"), + IsPublic: c.Query("isPublic"), + } + req := reqContext.ToParamRequest() + req.Pagination = paginate + + articleCommentsData, paging, err := _i.articleCommentsService.All(req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ArticleComments list successfully retrieved"}, + Data: articleCommentsData, + Meta: paging, + }) +} + +// Show get one ArticleComments +// @Summary Get one ArticleComments +// @Description API for getting one ArticleComments +// @Tags ArticleComments +// @Security Bearer +// @Param id path int true "ArticleComments ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /article-comments/{id} [get] +func (_i *articleCommentsController) Show(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + articleCommentsData, err := _i.articleCommentsService.Show(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ArticleComments successfully retrieved"}, + Data: articleCommentsData, + }) +} + +// Save create ArticleComments +// @Summary Create ArticleComments +// @Description API for create ArticleComments +// @Tags ArticleComments +// @Security Bearer +// @Param Authorization header string true "Insert your access token" default(Bearer ) +// @Param payload body request.ArticleCommentsCreateRequest true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /article-comments [post] +func (_i *articleCommentsController) Save(c *fiber.Ctx) error { + req := new(request.ArticleCommentsCreateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + authToken := c.Get("Authorization") + dataResult, err := _i.articleCommentsService.Save(*req, authToken) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ArticleComments successfully created"}, + Data: dataResult, + }) +} + +// Update update ArticleComments +// @Summary update ArticleComments +// @Description API for update ArticleComments +// @Tags ArticleComments +// @Security Bearer +// @Param payload body request.ArticleCommentsUpdateRequest true "Required payload" +// @Param id path int true "ArticleComments ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /article-comments/{id} [put] +func (_i *articleCommentsController) Update(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + req := new(request.ArticleCommentsUpdateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + err = _i.articleCommentsService.Update(uint(id), *req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ArticleComments successfully updated"}, + }) +} + +// Delete delete ArticleComments +// @Summary delete ArticleComments +// @Description API for delete ArticleComments +// @Tags ArticleComments +// @Security Bearer +// @Param id path int true "ArticleComments ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /article-comments/{id} [delete] +func (_i *articleCommentsController) Delete(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + err = _i.articleCommentsService.Delete(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ArticleComments successfully deleted"}, + }) +} diff --git a/app/module/article_comments/controller/controller.go b/app/module/article_comments/controller/controller.go new file mode 100644 index 0000000..e0aff99 --- /dev/null +++ b/app/module/article_comments/controller/controller.go @@ -0,0 +1,16 @@ +package controller + +import ( + "github.com/rs/zerolog" + "go-humas-be/app/module/article_comments/service" +) + +type Controller struct { + ArticleComments ArticleCommentsController +} + +func NewController(ArticleCommentsService service.ArticleCommentsService, log zerolog.Logger) *Controller { + return &Controller{ + ArticleComments: NewArticleCommentsController(ArticleCommentsService, log), + } +} diff --git a/app/module/article_comments/mapper/article_comments.mapper.go b/app/module/article_comments/mapper/article_comments.mapper.go new file mode 100644 index 0000000..247238d --- /dev/null +++ b/app/module/article_comments/mapper/article_comments.mapper.go @@ -0,0 +1,23 @@ +package mapper + +import ( + "go-humas-be/app/database/entity" + res "go-humas-be/app/module/article_comments/response" +) + +func ArticleCommentsResponseMapper(articleCommentsReq *entity.ArticleComments) (articleCommentsRes *res.ArticleCommentsResponse) { + if articleCommentsReq != nil { + articleCommentsRes = &res.ArticleCommentsResponse{ + ID: articleCommentsReq.ID, + Message: articleCommentsReq.Message, + ArticleId: articleCommentsReq.ArticleId, + CommentFrom: articleCommentsReq.CommentFrom, + ParentId: articleCommentsReq.ParentId, + IsPublic: articleCommentsReq.IsPublic, + IsActive: articleCommentsReq.IsActive, + CreatedAt: articleCommentsReq.CreatedAt, + UpdatedAt: articleCommentsReq.UpdatedAt, + } + } + return articleCommentsRes +} diff --git a/app/module/article_comments/repository/article_comments.repository.go b/app/module/article_comments/repository/article_comments.repository.go new file mode 100644 index 0000000..3c78a16 --- /dev/null +++ b/app/module/article_comments/repository/article_comments.repository.go @@ -0,0 +1,98 @@ +package repository + +import ( + "fmt" + "github.com/rs/zerolog" + "go-humas-be/app/database" + "go-humas-be/app/database/entity" + "go-humas-be/app/module/article_comments/request" + "go-humas-be/utils/paginator" + "strings" +) + +type articleCommentsRepository struct { + DB *database.Database + Log zerolog.Logger +} + +// ArticleCommentsRepository define interface of IArticleCommentsRepository +type ArticleCommentsRepository interface { + GetAll(req request.ArticleCommentsQueryRequest) (articleCommentss []*entity.ArticleComments, paging paginator.Pagination, err error) + FindOne(id uint) (articleComments *entity.ArticleComments, err error) + Create(articleComments *entity.ArticleComments) (articleCommentsReturn *entity.ArticleComments, err error) + Update(id uint, articleComments *entity.ArticleComments) (err error) + Delete(id uint) (err error) +} + +func NewArticleCommentsRepository(db *database.Database, logger zerolog.Logger) ArticleCommentsRepository { + return &articleCommentsRepository{ + DB: db, + Log: logger, + } +} + +// implement interface of IArticleCommentsRepository +func (_i *articleCommentsRepository) GetAll(req request.ArticleCommentsQueryRequest) (articleCommentss []*entity.ArticleComments, paging paginator.Pagination, err error) { + var count int64 + + query := _i.DB.DB.Model(&entity.ArticleComments{}) + query = query.Where("is_active = ?", true) + + if req.Message != nil && *req.Message != "" { + message := strings.ToLower(*req.Message) + query = query.Where("LOWER(message) LIKE ?", "%"+strings.ToLower(message)+"%") + } + if req.ArticleId != nil { + query = query.Where("article_id = ?", req.ArticleId) + } + if req.CommentFrom != nil { + query = query.Where("comment_from = ?", req.CommentFrom) + } + if req.ParentId != nil { + query = query.Where("parent_id = ?", req.ParentId) + } + query.Count(&count) + + if req.Pagination.SortBy != "" { + direction := "ASC" + if req.Pagination.Sort == "desc" { + direction = "DESC" + } + query.Order(fmt.Sprintf("%s %s", req.Pagination.SortBy, direction)) + } + + req.Pagination.Count = count + req.Pagination = paginator.Paging(req.Pagination) + + err = query.Offset(req.Pagination.Offset).Limit(req.Pagination.Limit).Find(&articleCommentss).Error + if err != nil { + return + } + + paging = *req.Pagination + + return +} + +func (_i *articleCommentsRepository) FindOne(id uint) (articleComments *entity.ArticleComments, err error) { + if err := _i.DB.DB.First(&articleComments, id).Error; err != nil { + return nil, err + } + + return articleComments, nil +} + +func (_i *articleCommentsRepository) Create(articleComments *entity.ArticleComments) (articleCommentsReturn *entity.ArticleComments, err error) { + result := _i.DB.DB.Create(articleComments) + return articleComments, result.Error +} + +func (_i *articleCommentsRepository) Update(id uint, articleComments *entity.ArticleComments) (err error) { + return _i.DB.DB.Model(&entity.ArticleComments{}). + Where(&entity.ArticleComments{ID: id}). + Updates(articleComments).Error +} + +func (_i *articleCommentsRepository) Delete(id uint) error { + return _i.DB.DB.Delete(&entity.ArticleComments{}, id).Error +} diff --git a/app/module/article_comments/request/article_comments.request.go b/app/module/article_comments/request/article_comments.request.go new file mode 100644 index 0000000..7eb74b5 --- /dev/null +++ b/app/module/article_comments/request/article_comments.request.go @@ -0,0 +1,96 @@ +package request + +import ( + "go-humas-be/app/database/entity" + "go-humas-be/utils/paginator" + "strconv" + "time" +) + +type ArticleCommentsGeneric interface { + ToEntity() +} + +type ArticleCommentsQueryRequest struct { + Message *string `json:"message"` + ArticleId *int `json:"articleId"` + CommentFrom *int `json:"commentFrom"` + ParentId *int `json:"parentId"` + IsPublic *bool `json:"isPublic"` + Pagination *paginator.Pagination `json:"pagination"` +} + +type ArticleCommentsCreateRequest struct { + Message string `json:"message" validate:"required"` + ArticleId uint `json:"articleId" validate:"required"` + ParentId *int `json:"parentId"` + IsPublic *bool `json:"isPublic"` +} + +func (req ArticleCommentsCreateRequest) ToEntity() *entity.ArticleComments { + return &entity.ArticleComments{ + Message: req.Message, + ArticleId: req.ArticleId, + ParentId: req.ParentId, + } +} + +type ArticleCommentsUpdateRequest struct { + ID uint `json:"id" validate:"required"` + Message string `json:"message" validate:"required"` + ArticleId uint `json:"articleId" validate:"required"` + ParentId *int `json:"parentId"` + IsPublic *bool `json:"isPublic"` +} + +func (req ArticleCommentsUpdateRequest) ToEntity() *entity.ArticleComments { + return &entity.ArticleComments{ + ID: req.ID, + Message: req.Message, + ArticleId: req.ArticleId, + ParentId: req.ParentId, + UpdatedAt: time.Now(), + } +} + +type ArticleCommentsQueryRequestContext struct { + Message string `json:"message"` + ArticleId string `json:"articleId"` + CommentFrom string `json:"commentFrom"` + ParentId string `json:"parentId"` + IsPublic string `json:"isPublic"` +} + +func (req ArticleCommentsQueryRequestContext) ToParamRequest() ArticleCommentsQueryRequest { + var request ArticleCommentsQueryRequest + + if message := req.Message; message != "" { + request.Message = &message + } + if articleIdStr := req.ArticleId; articleIdStr != "" { + articleId, err := strconv.Atoi(articleIdStr) + if err == nil { + request.ArticleId = &articleId + } + } + if commentFromStr := req.CommentFrom; commentFromStr != "" { + commentFrom, err := strconv.Atoi(commentFromStr) + if err == nil { + request.CommentFrom = &commentFrom + } + } + if parentIdStr := req.ParentId; parentIdStr != "" { + parentId, err := strconv.Atoi(parentIdStr) + if err == nil { + request.ParentId = &parentId + } + } + if isPublicStr := req.IsPublic; isPublicStr != "" { + isPublic, err := strconv.ParseBool(isPublicStr) + if err == nil { + request.IsPublic = &isPublic + } + } + + return request +} diff --git a/app/module/article_comments/response/article_comments.response.go b/app/module/article_comments/response/article_comments.response.go new file mode 100644 index 0000000..3e1959b --- /dev/null +++ b/app/module/article_comments/response/article_comments.response.go @@ -0,0 +1,15 @@ +package response + +import "time" + +type ArticleCommentsResponse struct { + ID uint `json:"id"` + Message string `json:"message"` + ArticleId uint `json:"articleId"` + CommentFrom *uint `json:"commentFrom"` + ParentId *int `json:"parentId"` + IsPublic bool `json:"isPublic"` + IsActive bool `json:"isActive"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` +} diff --git a/app/module/article_comments/service/article_comments.service.go b/app/module/article_comments/service/article_comments.service.go new file mode 100644 index 0000000..75dc7e2 --- /dev/null +++ b/app/module/article_comments/service/article_comments.service.go @@ -0,0 +1,90 @@ +package service + +import ( + "github.com/rs/zerolog" + "go-humas-be/app/database/entity" + "go-humas-be/app/module/article_comments/mapper" + "go-humas-be/app/module/article_comments/repository" + "go-humas-be/app/module/article_comments/request" + "go-humas-be/app/module/article_comments/response" + usersRepository "go-humas-be/app/module/users/repository" + "go-humas-be/utils/paginator" + + utilSvc "go-humas-be/utils/service" +) + +// ArticleCommentsService +type articleCommentsService struct { + Repo repository.ArticleCommentsRepository + UsersRepo usersRepository.UsersRepository + Log zerolog.Logger +} + +// ArticleCommentsService define interface of IArticleCommentsService +type ArticleCommentsService interface { + All(req request.ArticleCommentsQueryRequest) (articleComments []*response.ArticleCommentsResponse, paging paginator.Pagination, err error) + Show(id uint) (articleComments *response.ArticleCommentsResponse, err error) + Save(req request.ArticleCommentsCreateRequest, authToken string) (articleComments *entity.ArticleComments, err error) + Update(id uint, req request.ArticleCommentsUpdateRequest) (err error) + Delete(id uint) error +} + +// NewArticleCommentsService init ArticleCommentsService +func NewArticleCommentsService(repo repository.ArticleCommentsRepository, log zerolog.Logger, usersRepo usersRepository.UsersRepository) ArticleCommentsService { + + return &articleCommentsService{ + Repo: repo, + Log: log, + UsersRepo: usersRepo, + } +} + +// All implement interface of ArticleCommentsService +func (_i *articleCommentsService) All(req request.ArticleCommentsQueryRequest) (articleCommentss []*response.ArticleCommentsResponse, paging paginator.Pagination, err error) { + results, paging, err := _i.Repo.GetAll(req) + if err != nil { + return + } + + for _, result := range results { + articleCommentss = append(articleCommentss, mapper.ArticleCommentsResponseMapper(result)) + } + + return +} + +func (_i *articleCommentsService) Show(id uint) (articleComments *response.ArticleCommentsResponse, err error) { + result, err := _i.Repo.FindOne(id) + if err != nil { + return nil, err + } + + return mapper.ArticleCommentsResponseMapper(result), nil +} + +func (_i *articleCommentsService) Save(req request.ArticleCommentsCreateRequest, authToken string) (articleComments *entity.ArticleComments, err error) { + _i.Log.Info().Interface("data", req).Msg("") + + newReq := req.ToEntity() + + createdBy := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + newReq.CommentFrom = &createdBy.ID + newReq.IsActive = true + + return _i.Repo.Create(newReq) +} + +func (_i *articleCommentsService) Update(id uint, req request.ArticleCommentsUpdateRequest) (err error) { + _i.Log.Info().Interface("data", req).Msg("") + return _i.Repo.Update(id, req.ToEntity()) +} + +func (_i *articleCommentsService) Delete(id uint) error { + result, err := _i.Repo.FindOne(id) + if err != nil { + return err + } + + result.IsActive = false + return _i.Repo.Update(id, result) +} diff --git a/app/module/users/request/users.request.go b/app/module/users/request/users.request.go index a96a81b..a8444a2 100644 --- a/app/module/users/request/users.request.go +++ b/app/module/users/request/users.request.go @@ -130,7 +130,8 @@ type UserResetPassword struct { } type UserOtpRequest struct { - Email string `json:"email"` + Email string `json:"email" validate:"required,email"` + Name *string `json:"name"` } type UserOtpValidation struct { diff --git a/app/module/users/service/users.service.go b/app/module/users/service/users.service.go index ac641a6..172dc51 100644 --- a/app/module/users/service/users.service.go +++ b/app/module/users/service/users.service.go @@ -311,6 +311,14 @@ func (_i *usersService) ForgotPassword(req request.UserForgotPassword) (err erro } // send email forgot password + url := fmt.Sprintf("https://kontenhumas.com/setup-password?userId=%s&code=%s", *user.KeycloakId, codeRequest) + + subject := "[HUMAS POLRI] Forgot Password" + htmlBody := "

Anda telah mengirimkan permintaan untuk melakukan reset password.

" + htmlBody += "

Silahkan buat password akun anda dengan menekan tombol di bawah ini, untuk membuat password baru

" + htmlBody += fmt.Sprintf("Reset Password", url) + htmlBody += "

Terimakasih.

" + err = _i.Smtp.SendEmail(subject, user.Email, user.Fullname, htmlBody) return nil } else { @@ -327,13 +335,18 @@ func (_i *usersService) OtpRequest(req request.UserOtpRequest) (err error) { } otpReq := entity.RegistrationOtps{ Email: req.Email, + Name: req.Name, OtpCode: codeRequest, IsActive: true, } - subject := "[OTP] Web Humas" - htmlBody := "

Testing

" + subject := "[HUMAS POLRI] Permintaan OTP" + htmlBody := fmt.Sprintf("

Hai %s !

Berikut kode OTP yang digunakan untuk verifikasi.

", *req.Name) + htmlBody += fmt.Sprintf("

%s

", codeRequest) + htmlBody += "

Kode diatas hanya berlaku selama 10 menit. Harap segera masukan kode tersebut pada aplikasi HUMAS POLRI.

" + htmlBody += "

Demi menjaga kerahasiaan data kamu, mohon jangan membagikan kode OTP ke siapapun.

" err = _i.Smtp.SendEmail(subject, req.Email, req.Email, htmlBody) + if err != nil { return err } diff --git a/app/router/api.go b/app/router/api.go index 8b482a4..775e1a7 100644 --- a/app/router/api.go +++ b/app/router/api.go @@ -5,6 +5,7 @@ import ( "github.com/gofiber/fiber/v2" "go-humas-be/app/module/article_categories" "go-humas-be/app/module/article_category_details" + "go-humas-be/app/module/article_comments" "go-humas-be/app/module/article_files" "go-humas-be/app/module/article_nulis_ai" "go-humas-be/app/module/articles" @@ -31,6 +32,7 @@ type Router struct { ArticleCategoriesRouter *article_categories.ArticleCategoriesRouter ArticleCategoryDetailsRouter *article_category_details.ArticleCategoryDetailsRouter ArticleFilesRouter *article_files.ArticleFilesRouter + ArticleCommentsRouter *article_comments.ArticleCommentsRouter ArticlesRouter *articles.ArticlesRouter ArticleNulisAIRouter *article_nulis_ai.ArticleNulisAIRouter CitiesRouter *cities.CitiesRouter @@ -54,6 +56,7 @@ func NewRouter( articleCategoriesRouter *article_categories.ArticleCategoriesRouter, articleCategoryDetailsRouter *article_category_details.ArticleCategoryDetailsRouter, articleFilesRouter *article_files.ArticleFilesRouter, + articleCommentsRouter *article_comments.ArticleCommentsRouter, articlesRouter *articles.ArticlesRouter, articleNulisRouter *article_nulis_ai.ArticleNulisAIRouter, citiesRouter *cities.CitiesRouter, @@ -75,6 +78,7 @@ func NewRouter( ArticleCategoriesRouter: articleCategoriesRouter, ArticleCategoryDetailsRouter: articleCategoryDetailsRouter, ArticleFilesRouter: articleFilesRouter, + ArticleCommentsRouter: articleCommentsRouter, ArticlesRouter: articlesRouter, ArticleNulisAIRouter: articleNulisRouter, CitiesRouter: citiesRouter, @@ -107,6 +111,7 @@ func (r *Router) Register() { r.ArticleCategoryDetailsRouter.RegisterArticleCategoryDetailsRoutes() r.ArticleFilesRouter.RegisterArticleFilesRoutes() r.ArticlesRouter.RegisterArticlesRoutes() + r.ArticleCommentsRouter.RegisterArticleCommentsRoutes() r.ArticleNulisAIRouter.RegisterArticleNulisAIRoutes() r.CitiesRouter.RegisterCitiesRoutes() r.CustomStaticPagesRouter.RegisterCustomStaticPagesRoutes() diff --git a/config/config/smtp.config.go b/config/config/smtp.config.go index da9daa4..a376074 100644 --- a/config/config/smtp.config.go +++ b/config/config/smtp.config.go @@ -24,6 +24,10 @@ func (_smtp *SmtpConfig) SendEmail(subject string, toAddress string, toName stri println(_smtp.Cfg.Smtp.Port) println(_smtp.Cfg.Smtp.Username) println(_smtp.Cfg.Smtp.Password) + println(subject) + println(toAddress) + println(toName) + println(htmlBody) client, err := mail.NewClient(_smtp.Cfg.Smtp.Host, mail.WithPort(_smtp.Cfg.Smtp.Port), mail.WithSSL(), mail.WithSMTPAuth(mail.SMTPAuthPlain), mail.WithUsername(_smtp.Cfg.Smtp.Username), mail.WithPassword(_smtp.Cfg.Smtp.Password)) diff --git a/docs/swagger/docs.go b/docs/swagger/docs.go index 7309e13..27283e9 100644 --- a/docs/swagger/docs.go +++ b/docs/swagger/docs.go @@ -690,6 +690,322 @@ const docTemplate = `{ } } }, + "/article-comments": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all ArticleComments", + "tags": [ + "ArticleComments" + ], + "summary": "Get all ArticleComments", + "parameters": [ + { + "type": "integer", + "name": "articleId", + "in": "query" + }, + { + "type": "integer", + "name": "commentFrom", + "in": "query" + }, + { + "type": "boolean", + "name": "isPublic", + "in": "query" + }, + { + "type": "string", + "name": "message", + "in": "query" + }, + { + "type": "integer", + "name": "parentId", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create ArticleComments", + "tags": [ + "ArticleComments" + ], + "summary": "Create ArticleComments", + "parameters": [ + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ArticleCommentsCreateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/article-comments/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one ArticleComments", + "tags": [ + "ArticleComments" + ], + "summary": "Get one ArticleComments", + "parameters": [ + { + "type": "integer", + "description": "ArticleComments ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update ArticleComments", + "tags": [ + "ArticleComments" + ], + "summary": "update ArticleComments", + "parameters": [ + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ArticleCommentsUpdateRequest" + } + }, + { + "type": "integer", + "description": "ArticleComments ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete ArticleComments", + "tags": [ + "ArticleComments" + ], + "summary": "delete ArticleComments", + "parameters": [ + { + "type": "integer", + "description": "ArticleComments ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, "/article-files": { "get": { "security": [ @@ -6635,6 +6951,52 @@ const docTemplate = `{ } } }, + "request.ArticleCommentsCreateRequest": { + "type": "object", + "required": [ + "articleId", + "message" + ], + "properties": { + "articleId": { + "type": "integer" + }, + "isPublic": { + "type": "boolean" + }, + "message": { + "type": "string" + }, + "parentId": { + "type": "integer" + } + } + }, + "request.ArticleCommentsUpdateRequest": { + "type": "object", + "required": [ + "articleId", + "id", + "message" + ], + "properties": { + "articleId": { + "type": "integer" + }, + "id": { + "type": "integer" + }, + "isPublic": { + "type": "boolean" + }, + "message": { + "type": "string" + }, + "parentId": { + "type": "integer" + } + } + }, "request.ArticleFilesUpdateRequest": { "type": "object", "required": [ @@ -7183,9 +7545,15 @@ const docTemplate = `{ }, "request.UserOtpRequest": { "type": "object", + "required": [ + "email" + ], "properties": { "email": { "type": "string" + }, + "name": { + "type": "string" } } }, diff --git a/docs/swagger/swagger.json b/docs/swagger/swagger.json index ada9435..2787c80 100644 --- a/docs/swagger/swagger.json +++ b/docs/swagger/swagger.json @@ -679,6 +679,322 @@ } } }, + "/article-comments": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all ArticleComments", + "tags": [ + "ArticleComments" + ], + "summary": "Get all ArticleComments", + "parameters": [ + { + "type": "integer", + "name": "articleId", + "in": "query" + }, + { + "type": "integer", + "name": "commentFrom", + "in": "query" + }, + { + "type": "boolean", + "name": "isPublic", + "in": "query" + }, + { + "type": "string", + "name": "message", + "in": "query" + }, + { + "type": "integer", + "name": "parentId", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create ArticleComments", + "tags": [ + "ArticleComments" + ], + "summary": "Create ArticleComments", + "parameters": [ + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ArticleCommentsCreateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/article-comments/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one ArticleComments", + "tags": [ + "ArticleComments" + ], + "summary": "Get one ArticleComments", + "parameters": [ + { + "type": "integer", + "description": "ArticleComments ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update ArticleComments", + "tags": [ + "ArticleComments" + ], + "summary": "update ArticleComments", + "parameters": [ + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ArticleCommentsUpdateRequest" + } + }, + { + "type": "integer", + "description": "ArticleComments ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete ArticleComments", + "tags": [ + "ArticleComments" + ], + "summary": "delete ArticleComments", + "parameters": [ + { + "type": "integer", + "description": "ArticleComments ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, "/article-files": { "get": { "security": [ @@ -6624,6 +6940,52 @@ } } }, + "request.ArticleCommentsCreateRequest": { + "type": "object", + "required": [ + "articleId", + "message" + ], + "properties": { + "articleId": { + "type": "integer" + }, + "isPublic": { + "type": "boolean" + }, + "message": { + "type": "string" + }, + "parentId": { + "type": "integer" + } + } + }, + "request.ArticleCommentsUpdateRequest": { + "type": "object", + "required": [ + "articleId", + "id", + "message" + ], + "properties": { + "articleId": { + "type": "integer" + }, + "id": { + "type": "integer" + }, + "isPublic": { + "type": "boolean" + }, + "message": { + "type": "string" + }, + "parentId": { + "type": "integer" + } + } + }, "request.ArticleFilesUpdateRequest": { "type": "object", "required": [ @@ -7172,9 +7534,15 @@ }, "request.UserOtpRequest": { "type": "object", + "required": [ + "email" + ], "properties": { "email": { "type": "string" + }, + "name": { + "type": "string" } } }, diff --git a/docs/swagger/swagger.yaml b/docs/swagger/swagger.yaml index d70f020..47bbb54 100644 --- a/docs/swagger/swagger.yaml +++ b/docs/swagger/swagger.yaml @@ -59,6 +59,37 @@ definitions: - statusId - title type: object + request.ArticleCommentsCreateRequest: + properties: + articleId: + type: integer + isPublic: + type: boolean + message: + type: string + parentId: + type: integer + required: + - articleId + - message + type: object + request.ArticleCommentsUpdateRequest: + properties: + articleId: + type: integer + id: + type: integer + isPublic: + type: boolean + message: + type: string + parentId: + type: integer + required: + - articleId + - id + - message + type: object request.ArticleFilesUpdateRequest: properties: articleId: @@ -442,6 +473,10 @@ definitions: properties: email: type: string + name: + type: string + required: + - email type: object request.UserOtpValidation: properties: @@ -1138,6 +1173,205 @@ paths: summary: update ArticleCategoryDetails tags: - Untags + /article-comments: + get: + description: API for getting all ArticleComments + parameters: + - in: query + name: articleId + type: integer + - in: query + name: commentFrom + type: integer + - in: query + name: isPublic + type: boolean + - in: query + name: message + type: string + - in: query + name: parentId + type: integer + - in: query + name: count + type: integer + - in: query + name: limit + type: integer + - in: query + name: nextPage + type: integer + - in: query + name: page + type: integer + - in: query + name: previousPage + type: integer + - in: query + name: sort + type: string + - in: query + name: sortBy + type: string + - in: query + name: totalPage + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get all ArticleComments + tags: + - ArticleComments + post: + description: API for create ArticleComments + parameters: + - default: Bearer + description: Insert your access token + in: header + name: Authorization + required: true + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: '#/definitions/request.ArticleCommentsCreateRequest' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Create ArticleComments + tags: + - ArticleComments + /article-comments/{id}: + delete: + description: API for delete ArticleComments + parameters: + - description: ArticleComments ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: delete ArticleComments + tags: + - ArticleComments + get: + description: API for getting one ArticleComments + parameters: + - description: ArticleComments ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get one ArticleComments + tags: + - ArticleComments + put: + description: API for update ArticleComments + parameters: + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: '#/definitions/request.ArticleCommentsUpdateRequest' + - description: ArticleComments ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: update ArticleComments + tags: + - ArticleComments /article-files: get: description: API for getting all ArticleFiles diff --git a/main.go b/main.go index 04cdf3b..df77011 100644 --- a/main.go +++ b/main.go @@ -6,6 +6,7 @@ import ( "go-humas-be/app/middleware" "go-humas-be/app/module/article_categories" "go-humas-be/app/module/article_category_details" + "go-humas-be/app/module/article_comments" "go-humas-be/app/module/article_files" "go-humas-be/app/module/article_nulis_ai" "go-humas-be/app/module/articles" @@ -57,6 +58,7 @@ func main() { article_category_details.NewArticleCategoryDetailsModule, article_files.NewArticleFilesModule, articles.NewArticlesModule, + article_comments.NewArticleCommentsModule, article_nulis_ai.NewArticleNulisAIModule, cities.NewCitiesModule, custom_static_pages.NewCustomStaticPagesModule,