diff --git a/app/database/entity/approval_histories.entity.go b/app/database/entity/approval_histories.entity.go new file mode 100644 index 0000000..e2b02da --- /dev/null +++ b/app/database/entity/approval_histories.entity.go @@ -0,0 +1,19 @@ +package entity + +import ( + "time" +) + +// ApprovalHistories is a generic table to store approval/rejection history for all modules +type ApprovalHistories struct { + ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"` + ModuleType string `json:"module_type" gorm:"type:varchar"` // e.g., "banners", "galleries", "products", "sales_agents", "promotions" + ModuleId uint `json:"module_id" gorm:"type:int4"` // ID of the record in the respective module table + StatusId int `json:"status_id" gorm:"type:int4"` // 1: pending, 2: approved, 3: rejected + Action string `json:"action" gorm:"type:varchar"` // "approve" or "reject" + ApprovedBy *uint `json:"approved_by" gorm:"type:int4"` // User ID who performed the action + Message *string `json:"message" gorm:"type:text"` // Optional message/reason + CreatedAt time.Time `json:"created_at" gorm:"default:now()"` + UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"` +} + diff --git a/app/database/index.database.go b/app/database/index.database.go index 10397ae..492ef51 100644 --- a/app/database/index.database.go +++ b/app/database/index.database.go @@ -93,6 +93,7 @@ func Models() []interface{} { article_category_details.ArticleCategoryDetails{}, entity.ArticleFiles{}, entity.ArticleComments{}, + entity.ApprovalHistories{}, entity.AuditTrails{}, entity.Banners{}, entity.Clients{}, diff --git a/app/middleware/register.middleware.go b/app/middleware/register.middleware.go index 023a925..0ed4ce0 100644 --- a/app/middleware/register.middleware.go +++ b/app/middleware/register.middleware.go @@ -127,8 +127,9 @@ func (m *Middleware) Register(db *database.Database) { } //=============================== - m.App.Use(AuditTrailsMiddleware(db.DB)) - StartAuditTrailCleanup(db.DB, m.Cfg.Middleware.AuditTrails.Retention) + // Audit Trail disabled temporarily due to database connection issues + // m.App.Use(AuditTrailsMiddleware(db.DB)) + // StartAuditTrailCleanup(db.DB, m.Cfg.Middleware.AuditTrails.Retention) //m.App.Use(filesystem.New(filesystem.Config{ // Next: utils.IsEnabled(m.Cfg.Middleware.FileSystem.Enable), diff --git a/app/module/approval_histories/approval_histories.module.go b/app/module/approval_histories/approval_histories.module.go new file mode 100644 index 0000000..f3da7d3 --- /dev/null +++ b/app/module/approval_histories/approval_histories.module.go @@ -0,0 +1,52 @@ +package approval_histories + +import ( + "jaecoo-be/app/module/approval_histories/controller" + "jaecoo-be/app/module/approval_histories/repository" + "jaecoo-be/app/module/approval_histories/service" + + "github.com/gofiber/fiber/v2" + "go.uber.org/fx" +) + +// struct of ApprovalHistoriesRouter +type ApprovalHistoriesRouter struct { + App fiber.Router + Controller *controller.Controller +} + +// register bulky of ApprovalHistories module +var NewApprovalHistoriesModule = fx.Options( + // register repository of ApprovalHistories module + fx.Provide(repository.NewApprovalHistoriesRepository), + + // register service of ApprovalHistories module + fx.Provide(service.NewApprovalHistoriesService), + + // register controller of ApprovalHistories module + fx.Provide(controller.NewController), + + // register router of ApprovalHistories module + fx.Provide(NewApprovalHistoriesRouter), +) + +// init ApprovalHistoriesRouter +func NewApprovalHistoriesRouter(fiber *fiber.App, controller *controller.Controller) *ApprovalHistoriesRouter { + return &ApprovalHistoriesRouter{ + App: fiber, + Controller: controller, + } +} + +// register routes of ApprovalHistories module +func (_i *ApprovalHistoriesRouter) RegisterApprovalHistoriesRoutes() { + // define controllers + approvalHistoriesController := _i.Controller.ApprovalHistories + + // define routes + _i.App.Route("/approval-histories", func(router fiber.Router) { + router.Get("/", approvalHistoriesController.All) + router.Get("/:module_type/:module_id", approvalHistoriesController.GetByModule) + }) +} + diff --git a/app/module/approval_histories/controller/approval_histories.controller.go b/app/module/approval_histories/controller/approval_histories.controller.go new file mode 100644 index 0000000..a260ce6 --- /dev/null +++ b/app/module/approval_histories/controller/approval_histories.controller.go @@ -0,0 +1,130 @@ +package controller + +import ( + "jaecoo-be/app/module/approval_histories/request" + "jaecoo-be/app/module/approval_histories/service" + "jaecoo-be/utils/paginator" + "strconv" + + "github.com/gofiber/fiber/v2" + "github.com/rs/zerolog" + + utilRes "jaecoo-be/utils/response" +) + +type approvalHistoriesController struct { + approvalHistoriesService service.ApprovalHistoriesService + Log zerolog.Logger +} + +type ApprovalHistoriesController interface { + GetByModule(c *fiber.Ctx) error + All(c *fiber.Ctx) error +} + +func NewApprovalHistoriesController(approvalHistoriesService service.ApprovalHistoriesService, log zerolog.Logger) ApprovalHistoriesController { + return &approvalHistoriesController{ + approvalHistoriesService: approvalHistoriesService, + Log: log, + } +} + +// GetByModule get approval history by module type and module id +// @Summary Get approval history by module +// @Description API for getting approval history by module type and module id +// @Tags ApprovalHistories +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param module_type path string true "Module Type (banners, galleries, products, sales_agents, promotions)" +// @Param module_id path int true "Module ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /approval-histories/{module_type}/{module_id} [get] +func (_i *approvalHistoriesController) GetByModule(c *fiber.Ctx) error { + moduleType := c.Params("module_type") + moduleIdStr := c.Params("module_id") + + moduleId, err := strconv.ParseUint(moduleIdStr, 10, 0) + if err != nil { + return utilRes.Resp(c, utilRes.Response{ + Success: false, + Messages: utilRes.Messages{"Invalid module_id"}, + }) + } + + histories, err := _i.approvalHistoriesService.GetByModule(moduleType, uint(moduleId)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Approval histories successfully retrieved"}, + Data: histories, + }) +} + +// All get all ApprovalHistories +// @Summary Get all ApprovalHistories +// @Description API for getting all ApprovalHistories +// @Tags ApprovalHistories +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param req query request.ApprovalHistoriesQueryRequest 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 /approval-histories [get] +func (_i *approvalHistoriesController) All(c *fiber.Ctx) error { + paginate, err := paginator.Paginate(c) + if err != nil { + return err + } + + req := request.ApprovalHistoriesQueryRequest{ + Pagination: *paginate, + } + + if moduleType := c.Query("module_type"); moduleType != "" { + req.ModuleType = &moduleType + } + if moduleIdStr := c.Query("module_id"); moduleIdStr != "" { + moduleId, err := strconv.ParseUint(moduleIdStr, 10, 0) + if err == nil { + moduleIdUint := uint(moduleId) + req.ModuleId = &moduleIdUint + } + } + if statusIdStr := c.Query("status_id"); statusIdStr != "" { + statusId, err := strconv.Atoi(statusIdStr) + if err == nil { + req.StatusId = &statusId + } + } + if action := c.Query("action"); action != "" { + req.Action = &action + } + if approvedByStr := c.Query("approved_by"); approvedByStr != "" { + approvedBy, err := strconv.ParseUint(approvedByStr, 10, 0) + if err == nil { + approvedByUint := uint(approvedBy) + req.ApprovedBy = &approvedByUint + } + } + + histories, paging, err := _i.approvalHistoriesService.GetAll(req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Approval histories successfully retrieved"}, + Data: histories, + Meta: paging, + }) +} diff --git a/app/module/approval_histories/controller/controller.go b/app/module/approval_histories/controller/controller.go new file mode 100644 index 0000000..774236e --- /dev/null +++ b/app/module/approval_histories/controller/controller.go @@ -0,0 +1,17 @@ +package controller + +import ( + "jaecoo-be/app/module/approval_histories/service" + + "github.com/rs/zerolog" +) + +type Controller struct { + ApprovalHistories ApprovalHistoriesController +} + +func NewController(approvalHistoriesService service.ApprovalHistoriesService, log zerolog.Logger) *Controller { + return &Controller{ + ApprovalHistories: NewApprovalHistoriesController(approvalHistoriesService, log), + } +} diff --git a/app/module/approval_histories/mapper/approval_histories.mapper.go b/app/module/approval_histories/mapper/approval_histories.mapper.go new file mode 100644 index 0000000..5c9eb2d --- /dev/null +++ b/app/module/approval_histories/mapper/approval_histories.mapper.go @@ -0,0 +1,25 @@ +package mapper + +import ( + "jaecoo-be/app/database/entity" + "jaecoo-be/app/module/approval_histories/response" +) + +func ApprovalHistoriesResponseMapper(history *entity.ApprovalHistories) *response.ApprovalHistoriesResponse { + if history == nil { + return nil + } + + return &response.ApprovalHistoriesResponse{ + ID: history.ID, + ModuleType: history.ModuleType, + ModuleId: history.ModuleId, + StatusId: history.StatusId, + Action: history.Action, + ApprovedBy: history.ApprovedBy, + Message: history.Message, + CreatedAt: history.CreatedAt, + UpdatedAt: history.UpdatedAt, + } +} + diff --git a/app/module/approval_histories/repository/approval_histories.repository.go b/app/module/approval_histories/repository/approval_histories.repository.go new file mode 100644 index 0000000..ee4b947 --- /dev/null +++ b/app/module/approval_histories/repository/approval_histories.repository.go @@ -0,0 +1,95 @@ +package repository + +import ( + "jaecoo-be/app/database" + "jaecoo-be/app/database/entity" + "jaecoo-be/app/module/approval_histories/request" + "jaecoo-be/utils/paginator" + + "github.com/rs/zerolog" +) + +type approvalHistoriesRepository struct { + DB *database.Database + Log zerolog.Logger +} + +// ApprovalHistoriesRepository define interface +type ApprovalHistoriesRepository interface { + GetAll(req request.ApprovalHistoriesQueryRequest) (histories []*entity.ApprovalHistories, paging paginator.Pagination, err error) + FindOne(id uint) (history *entity.ApprovalHistories, err error) + Create(history *entity.ApprovalHistories) (historyReturn *entity.ApprovalHistories, err error) + GetByModule(moduleType string, moduleId uint) (histories []*entity.ApprovalHistories, err error) +} + +func NewApprovalHistoriesRepository(db *database.Database, logger zerolog.Logger) ApprovalHistoriesRepository { + return &approvalHistoriesRepository{ + DB: db, + Log: logger, + } +} + +// GetAll implement interface +func (_i *approvalHistoriesRepository) GetAll(req request.ApprovalHistoriesQueryRequest) (histories []*entity.ApprovalHistories, paging paginator.Pagination, err error) { + var count int64 + + query := _i.DB.DB.Model(&entity.ApprovalHistories{}) + + if req.ModuleType != nil && *req.ModuleType != "" { + query = query.Where("module_type = ?", *req.ModuleType) + } + if req.ModuleId != nil { + query = query.Where("module_id = ?", *req.ModuleId) + } + if req.StatusId != nil { + query = query.Where("status_id = ?", *req.StatusId) + } + if req.Action != nil && *req.Action != "" { + query = query.Where("action = ?", *req.Action) + } + if req.ApprovedBy != nil { + query = query.Where("approved_by = ?", *req.ApprovedBy) + } + + query.Count(&count) + + if req.Pagination.SortBy != "" { + direction := "ASC" + if req.Pagination.Sort == "desc" { + direction = "DESC" + } + query.Order("created_at " + direction) + } else { + query.Order("created_at DESC") + } + + req.Pagination.Count = count + pagingPtr := paginator.Paging(&req.Pagination) + + err = query.Offset(pagingPtr.Offset).Limit(pagingPtr.Limit).Find(&histories).Error + if err != nil { + return + } + + paging = *pagingPtr + return +} + +func (_i *approvalHistoriesRepository) FindOne(id uint) (history *entity.ApprovalHistories, err error) { + if err := _i.DB.DB.First(&history, id).Error; err != nil { + return nil, err + } + return history, nil +} + +func (_i *approvalHistoriesRepository) Create(history *entity.ApprovalHistories) (historyReturn *entity.ApprovalHistories, err error) { + result := _i.DB.DB.Create(history) + return history, result.Error +} + +func (_i *approvalHistoriesRepository) GetByModule(moduleType string, moduleId uint) (histories []*entity.ApprovalHistories, err error) { + err = _i.DB.DB.Where("module_type = ? AND module_id = ?", moduleType, moduleId). + Order("created_at DESC"). + Find(&histories).Error + return +} diff --git a/app/module/approval_histories/request/approval_histories.request.go b/app/module/approval_histories/request/approval_histories.request.go new file mode 100644 index 0000000..4bfd1f1 --- /dev/null +++ b/app/module/approval_histories/request/approval_histories.request.go @@ -0,0 +1,35 @@ +package request + +import ( + "jaecoo-be/app/database/entity" + "jaecoo-be/utils/paginator" +) + +type ApprovalHistoriesQueryRequest struct { + paginator.Pagination + ModuleType *string `json:"module_type" query:"module_type"` + ModuleId *uint `json:"module_id" query:"module_id"` + StatusId *int `json:"status_id" query:"status_id"` + Action *string `json:"action" query:"action"` + ApprovedBy *uint `json:"approved_by" query:"approved_by"` +} + +type ApprovalHistoriesCreateRequest struct { + ModuleType string `json:"module_type" validate:"required"` + ModuleId uint `json:"module_id" validate:"required"` + StatusId int `json:"status_id" validate:"required"` + Action string `json:"action" validate:"required"` + ApprovedBy *uint `json:"approved_by"` + Message *string `json:"message"` +} + +func (req *ApprovalHistoriesCreateRequest) ToEntity() *entity.ApprovalHistories { + return &entity.ApprovalHistories{ + ModuleType: req.ModuleType, + ModuleId: req.ModuleId, + StatusId: req.StatusId, + Action: req.Action, + ApprovedBy: req.ApprovedBy, + Message: req.Message, + } +} diff --git a/app/module/approval_histories/response/approval_histories.response.go b/app/module/approval_histories/response/approval_histories.response.go new file mode 100644 index 0000000..5de79cb --- /dev/null +++ b/app/module/approval_histories/response/approval_histories.response.go @@ -0,0 +1,16 @@ +package response + +import "time" + +type ApprovalHistoriesResponse struct { + ID uint `json:"id"` + ModuleType string `json:"module_type"` + ModuleId uint `json:"module_id"` + StatusId int `json:"status_id"` + Action string `json:"action"` + ApprovedBy *uint `json:"approved_by"` + Message *string `json:"message"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + diff --git a/app/module/approval_histories/service/approval_histories.service.go b/app/module/approval_histories/service/approval_histories.service.go new file mode 100644 index 0000000..daada67 --- /dev/null +++ b/app/module/approval_histories/service/approval_histories.service.go @@ -0,0 +1,70 @@ +package service + +import ( + "jaecoo-be/app/database/entity" + "jaecoo-be/app/module/approval_histories/mapper" + "jaecoo-be/app/module/approval_histories/repository" + "jaecoo-be/app/module/approval_histories/request" + "jaecoo-be/app/module/approval_histories/response" + "jaecoo-be/utils/paginator" + + "github.com/rs/zerolog" +) + +type approvalHistoriesService struct { + Repo repository.ApprovalHistoriesRepository + Log zerolog.Logger +} + +type ApprovalHistoriesService interface { + GetAll(req request.ApprovalHistoriesQueryRequest) (histories []*response.ApprovalHistoriesResponse, paging paginator.Pagination, err error) + GetByModule(moduleType string, moduleId uint) (histories []*response.ApprovalHistoriesResponse, err error) + CreateHistory(moduleType string, moduleId uint, statusId int, action string, approvedBy *uint, message *string) (err error) +} + +func NewApprovalHistoriesService(repo repository.ApprovalHistoriesRepository, log zerolog.Logger) ApprovalHistoriesService { + return &approvalHistoriesService{ + Repo: repo, + Log: log, + } +} + +func (_i *approvalHistoriesService) GetAll(req request.ApprovalHistoriesQueryRequest) (histories []*response.ApprovalHistoriesResponse, paging paginator.Pagination, err error) { + historiesEntity, paging, err := _i.Repo.GetAll(req) + if err != nil { + return + } + + for _, history := range historiesEntity { + histories = append(histories, mapper.ApprovalHistoriesResponseMapper(history)) + } + + return +} + +func (_i *approvalHistoriesService) GetByModule(moduleType string, moduleId uint) (histories []*response.ApprovalHistoriesResponse, err error) { + historiesEntity, err := _i.Repo.GetByModule(moduleType, moduleId) + if err != nil { + return + } + + for _, history := range historiesEntity { + histories = append(histories, mapper.ApprovalHistoriesResponseMapper(history)) + } + + return +} + +func (_i *approvalHistoriesService) CreateHistory(moduleType string, moduleId uint, statusId int, action string, approvedBy *uint, message *string) (err error) { + history := &entity.ApprovalHistories{ + ModuleType: moduleType, + ModuleId: moduleId, + StatusId: statusId, + Action: action, + ApprovedBy: approvedBy, + Message: message, + } + + _, err = _i.Repo.Create(history) + return +} diff --git a/app/module/banners/banners.module.go b/app/module/banners/banners.module.go index 687aeae..1e1dd2d 100644 --- a/app/module/banners/banners.module.go +++ b/app/module/banners/banners.module.go @@ -51,6 +51,7 @@ func (_i *BannersRouter) RegisterBannersRoutes() { router.Post("/", bannersController.Save) router.Put("/:id", bannersController.Update) router.Put("/:id/approve", bannersController.Approve) + router.Put("/:id/reject", bannersController.Reject) router.Delete("/:id", bannersController.Delete) }) } diff --git a/app/module/banners/controller/banners.controller.go b/app/module/banners/controller/banners.controller.go index 2195a87..5d2edc3 100644 --- a/app/module/banners/controller/banners.controller.go +++ b/app/module/banners/controller/banners.controller.go @@ -1,7 +1,6 @@ package controller import ( - "jaecoo-be/app/database/entity/users" "jaecoo-be/app/module/banners/request" "jaecoo-be/app/module/banners/service" "jaecoo-be/utils/paginator" @@ -23,6 +22,7 @@ type BannersController interface { Update(c *fiber.Ctx) error Delete(c *fiber.Ctx) error Approve(c *fiber.Ctx) error + Reject(c *fiber.Ctx) error Viewer(c *fiber.Ctx) error } @@ -291,22 +291,16 @@ func (_i *bannersController) Approve(c *fiber.Ctx) error { return err } - // Get user from context - user := c.Locals("user") - if user == nil { + // Get token from Authorization header + authToken := c.Get("Authorization") + if authToken == "" { return utilRes.Resp(c, utilRes.Response{ Success: false, - Messages: utilRes.Messages{"Unauthorized: user not found"}, + Messages: utilRes.Messages{"Unauthorized: token not found"}, }) } - // Type assert to get user role ID - userRoleId := uint(0) - if userData, ok := user.(*users.Users); ok { - userRoleId = userData.UserRoleId - } - - bannerData, err := _i.bannersService.Approve(uint(id), userRoleId) + bannerData, err := _i.bannersService.Approve(uint(id), authToken) if err != nil { return err } @@ -318,6 +312,55 @@ func (_i *bannersController) Approve(c *fiber.Ctx) error { }) } +// Reject Banner +// @Summary Reject Banner +// @Description API for rejecting Banner (only for admin with roleId = 1) +// @Tags Banners +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param id path int true "Banner ID" +// @Param message body string false "Rejection message" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /banners/{id}/reject [put] +func (_i *bannersController) Reject(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + // Get token from Authorization header + authToken := c.Get("Authorization") + if authToken == "" { + return utilRes.Resp(c, utilRes.Response{ + Success: false, + Messages: utilRes.Messages{"Unauthorized: token not found"}, + }) + } + + // Get optional message from request body + var body struct { + Message *string `json:"message"` + } + if err := c.BodyParser(&body); err != nil { + // Message is optional, so ignore parsing error + body.Message = nil + } + + bannerData, err := _i.bannersService.Reject(uint(id), authToken, body.Message) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Banner successfully rejected"}, + Data: bannerData, + }) +} + // Viewer Banner // @Summary Viewer Banner // @Description API for viewing Banner file diff --git a/app/module/banners/repository/banners.repository.go b/app/module/banners/repository/banners.repository.go index 8bf054d..312063f 100644 --- a/app/module/banners/repository/banners.repository.go +++ b/app/module/banners/repository/banners.repository.go @@ -23,6 +23,7 @@ type BannersRepository interface { Delete(id uint) (err error) FindByThumbnailPath(thumbnailPath string) (banner *entity.Banners, err error) Approve(id uint) (err error) + Reject(id uint) (err error) } func NewBannersRepository(db *database.Database, log zerolog.Logger) BannersRepository { @@ -104,3 +105,9 @@ func (_i *bannersRepository) Approve(id uint) (err error) { err = _i.DB.DB.Model(&entity.Banners{}).Where("id = ?", id).Update("status_id", statusId).Error return } + +func (_i *bannersRepository) Reject(id uint) (err error) { + statusId := 3 // Rejected status + err = _i.DB.DB.Model(&entity.Banners{}).Where("id = ?", id).Update("status_id", statusId).Error + return +} diff --git a/app/module/banners/service/banners.service.go b/app/module/banners/service/banners.service.go index 4cdea3d..a59c659 100644 --- a/app/module/banners/service/banners.service.go +++ b/app/module/banners/service/banners.service.go @@ -5,13 +5,16 @@ import ( "errors" "fmt" "io" + approvalHistoriesService "jaecoo-be/app/module/approval_histories/service" "jaecoo-be/app/module/banners/mapper" "jaecoo-be/app/module/banners/repository" "jaecoo-be/app/module/banners/request" "jaecoo-be/app/module/banners/response" + usersRepository "jaecoo-be/app/module/users/repository" "jaecoo-be/config/config" minioStorage "jaecoo-be/config/config" "jaecoo-be/utils/paginator" + utilSvc "jaecoo-be/utils/service" "math/rand" "mime" "path/filepath" @@ -25,10 +28,12 @@ import ( ) type bannersService struct { - Repo repository.BannersRepository - Log zerolog.Logger - Cfg *config.Config - MinioStorage *minioStorage.MinioStorage + Repo repository.BannersRepository + Log zerolog.Logger + Cfg *config.Config + MinioStorage *minioStorage.MinioStorage + UsersRepo usersRepository.UsersRepository + ApprovalHistoriesService approvalHistoriesService.ApprovalHistoriesService } type BannersService interface { @@ -37,17 +42,20 @@ type BannersService interface { Create(c *fiber.Ctx, req request.BannersCreateRequest) (banner *response.BannersResponse, err error) Update(c *fiber.Ctx, id uint, req request.BannersUpdateRequest) (banner *response.BannersResponse, err error) Delete(id uint) (err error) - Approve(id uint, userRoleId uint) (banner *response.BannersResponse, err error) + Approve(id uint, authToken string) (banner *response.BannersResponse, err error) + Reject(id uint, authToken string, message *string) (banner *response.BannersResponse, err error) UploadFileToMinio(c *fiber.Ctx, fileKey string) (filePath *string, err error) Viewer(c *fiber.Ctx) (err error) } -func NewBannersService(repo repository.BannersRepository, log zerolog.Logger, cfg *config.Config, minioStorage *minioStorage.MinioStorage) BannersService { +func NewBannersService(repo repository.BannersRepository, log zerolog.Logger, cfg *config.Config, minioStorage *minioStorage.MinioStorage, usersRepo usersRepository.UsersRepository, approvalHistoriesService approvalHistoriesService.ApprovalHistoriesService) BannersService { return &bannersService{ - Repo: repo, - Log: log, - Cfg: cfg, - MinioStorage: minioStorage, + Repo: repo, + Log: log, + Cfg: cfg, + MinioStorage: minioStorage, + UsersRepo: usersRepo, + ApprovalHistoriesService: approvalHistoriesService, } } @@ -198,9 +206,16 @@ func (_i *bannersService) Delete(id uint) (err error) { return } -func (_i *bannersService) Approve(id uint, userRoleId uint) (banner *response.BannersResponse, err error) { +func (_i *bannersService) Approve(id uint, authToken string) (banner *response.BannersResponse, err error) { + // Get user from token + user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + if user == nil { + err = errors.New("unauthorized: user not found") + return + } + // Check if user has admin role (roleId = 1) - if userRoleId != 1 { + if user.UserRoleId != 1 { err = errors.New("unauthorized: only admin can approve") return } @@ -211,6 +226,58 @@ func (_i *bannersService) Approve(id uint, userRoleId uint) (banner *response.Ba return } + // Save approval history + userID := user.ID + err = _i.ApprovalHistoriesService.CreateHistory("banners", id, 2, "approve", &userID, nil) + if err != nil { + _i.Log.Error().Err(err).Msg("Failed to save approval history") + // Don't return error, just log it + } + + // Get updated banner data + bannerEntity, err := _i.Repo.FindOne(id) + if err != nil { + return + } + + if bannerEntity == nil { + err = errors.New("banner not found") + return + } + + host := _i.Cfg.App.Domain + banner = mapper.BannersResponseMapper(bannerEntity, host) + return +} + +func (_i *bannersService) Reject(id uint, authToken string, message *string) (banner *response.BannersResponse, err error) { + // Get user from token + user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + if user == nil { + err = errors.New("unauthorized: user not found") + return + } + + // Check if user has admin role (roleId = 1) + if user.UserRoleId != 1 { + err = errors.New("unauthorized: only admin can reject") + return + } + + // Reject banner (update status_id to 3) + err = _i.Repo.Reject(id) + if err != nil { + return + } + + // Save rejection history + userID := user.ID + err = _i.ApprovalHistoriesService.CreateHistory("banners", id, 3, "reject", &userID, message) + if err != nil { + _i.Log.Error().Err(err).Msg("Failed to save rejection history") + // Don't return error, just log it + } + // Get updated banner data bannerEntity, err := _i.Repo.FindOne(id) if err != nil { diff --git a/app/module/galleries/controller/galleries.controller.go b/app/module/galleries/controller/galleries.controller.go index c80f5a4..668ee78 100644 --- a/app/module/galleries/controller/galleries.controller.go +++ b/app/module/galleries/controller/galleries.controller.go @@ -1,7 +1,6 @@ package controller import ( - "jaecoo-be/app/database/entity/users" "jaecoo-be/app/module/galleries/request" "jaecoo-be/app/module/galleries/service" "jaecoo-be/utils/paginator" @@ -24,6 +23,7 @@ type GalleriesController interface { Update(c *fiber.Ctx) error Delete(c *fiber.Ctx) error Approve(c *fiber.Ctx) error + Reject(c *fiber.Ctx) error } func NewGalleriesController(galleriesService service.GalleriesService) GalleriesController { @@ -213,22 +213,16 @@ func (_i *galleriesController) Approve(c *fiber.Ctx) error { return err } - // Get user from context - user := c.Locals("user") - if user == nil { + // Get token from Authorization header + authToken := c.Get("Authorization") + if authToken == "" { return utilRes.Resp(c, utilRes.Response{ Success: false, - Messages: utilRes.Messages{"Unauthorized: user not found"}, + Messages: utilRes.Messages{"Unauthorized: token not found"}, }) } - // Type assert to get user role ID - userRoleId := uint(0) - if userData, ok := user.(*users.Users); ok { - userRoleId = userData.UserRoleId - } - - galleryData, err := _i.galleriesService.Approve(uint(id), userRoleId) + galleryData, err := _i.galleriesService.Approve(uint(id), authToken) if err != nil { return err } @@ -239,3 +233,51 @@ func (_i *galleriesController) Approve(c *fiber.Ctx) error { Data: galleryData, }) } + +// Reject Gallery +// @Summary Reject Gallery +// @Description API for rejecting Gallery (only for admin with roleId = 1) +// @Tags Galleries +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param id path int true "Gallery ID" +// @Param message body string false "Rejection message" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /galleries/{id}/reject [put] +func (_i *galleriesController) Reject(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + // Get token from Authorization header + authToken := c.Get("Authorization") + if authToken == "" { + return utilRes.Resp(c, utilRes.Response{ + Success: false, + Messages: utilRes.Messages{"Unauthorized: token not found"}, + }) + } + + // Get optional message from request body + var body struct { + Message *string `json:"message"` + } + if err := c.BodyParser(&body); err != nil { + body.Message = nil + } + + galleryData, err := _i.galleriesService.Reject(uint(id), authToken, body.Message) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Gallery successfully rejected"}, + Data: galleryData, + }) +} diff --git a/app/module/galleries/galleries.module.go b/app/module/galleries/galleries.module.go index e871667..ee4a196 100644 --- a/app/module/galleries/galleries.module.go +++ b/app/module/galleries/galleries.module.go @@ -50,6 +50,7 @@ func (_i *GalleriesRouter) RegisterGalleriesRoutes() { router.Post("/", galleriesController.Save) router.Put("/:id", galleriesController.Update) router.Put("/:id/approve", galleriesController.Approve) + router.Put("/:id/reject", galleriesController.Reject) router.Delete("/:id", galleriesController.Delete) }) } diff --git a/app/module/galleries/repository/galleries.repository.go b/app/module/galleries/repository/galleries.repository.go index d0a7f81..19ec249 100644 --- a/app/module/galleries/repository/galleries.repository.go +++ b/app/module/galleries/repository/galleries.repository.go @@ -22,6 +22,7 @@ type GalleriesRepository interface { Update(id uint, gallery *entity.Galleries) (err error) Delete(id uint) (err error) Approve(id uint) (err error) + Reject(id uint) (err error) } func NewGalleriesRepository(db *database.Database, log zerolog.Logger) GalleriesRepository { @@ -89,3 +90,9 @@ func (_i *galleriesRepository) Approve(id uint) (err error) { err = _i.DB.DB.Model(&entity.Galleries{}).Where("id = ?", id).Update("status_id", statusId).Error return } + +func (_i *galleriesRepository) Reject(id uint) (err error) { + statusId := 3 // Rejected status + err = _i.DB.DB.Model(&entity.Galleries{}).Where("id = ?", id).Update("status_id", statusId).Error + return +} diff --git a/app/module/galleries/service/galleries.service.go b/app/module/galleries/service/galleries.service.go index 599738d..5644f16 100644 --- a/app/module/galleries/service/galleries.service.go +++ b/app/module/galleries/service/galleries.service.go @@ -2,20 +2,25 @@ package service import ( "errors" + approvalHistoriesService "jaecoo-be/app/module/approval_histories/service" "jaecoo-be/app/module/galleries/mapper" "jaecoo-be/app/module/galleries/repository" "jaecoo-be/app/module/galleries/request" "jaecoo-be/app/module/galleries/response" + usersRepository "jaecoo-be/app/module/users/repository" "jaecoo-be/config/config" "jaecoo-be/utils/paginator" + utilSvc "jaecoo-be/utils/service" "github.com/rs/zerolog" ) type galleriesService struct { - Repo repository.GalleriesRepository - Log zerolog.Logger - Cfg *config.Config + Repo repository.GalleriesRepository + Log zerolog.Logger + Cfg *config.Config + UsersRepo usersRepository.UsersRepository + ApprovalHistoriesService approvalHistoriesService.ApprovalHistoriesService } type GalleriesService interface { @@ -24,14 +29,17 @@ type GalleriesService interface { Create(req request.GalleriesCreateRequest) (gallery *response.GalleriesResponse, err error) Update(id uint, req request.GalleriesUpdateRequest) (gallery *response.GalleriesResponse, err error) Delete(id uint) (err error) - Approve(id uint, userRoleId uint) (gallery *response.GalleriesResponse, err error) + Approve(id uint, authToken string) (gallery *response.GalleriesResponse, err error) + Reject(id uint, authToken string, message *string) (gallery *response.GalleriesResponse, err error) } -func NewGalleriesService(repo repository.GalleriesRepository, log zerolog.Logger, cfg *config.Config) GalleriesService { +func NewGalleriesService(repo repository.GalleriesRepository, log zerolog.Logger, cfg *config.Config, usersRepo usersRepository.UsersRepository, approvalHistoriesService approvalHistoriesService.ApprovalHistoriesService) GalleriesService { return &galleriesService{ - Repo: repo, - Log: log, - Cfg: cfg, + Repo: repo, + Log: log, + Cfg: cfg, + UsersRepo: usersRepo, + ApprovalHistoriesService: approvalHistoriesService, } } @@ -107,9 +115,16 @@ func (_i *galleriesService) Delete(id uint) (err error) { return } -func (_i *galleriesService) Approve(id uint, userRoleId uint) (gallery *response.GalleriesResponse, err error) { +func (_i *galleriesService) Approve(id uint, authToken string) (gallery *response.GalleriesResponse, err error) { + // Get user from token + user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + if user == nil { + err = errors.New("unauthorized: user not found") + return + } + // Check if user has admin role (roleId = 1) - if userRoleId != 1 { + if user.UserRoleId != 1 { err = errors.New("unauthorized: only admin can approve") return } @@ -120,6 +135,56 @@ func (_i *galleriesService) Approve(id uint, userRoleId uint) (gallery *response return } + // Save approval history + userID := user.ID + err = _i.ApprovalHistoriesService.CreateHistory("galleries", id, 2, "approve", &userID, nil) + if err != nil { + _i.Log.Error().Err(err).Msg("Failed to save approval history") + } + + // Get updated gallery data + galleryEntity, err := _i.Repo.FindOne(id) + if err != nil { + return + } + + if galleryEntity == nil { + err = errors.New("gallery not found") + return + } + + host := _i.Cfg.App.Domain + gallery = mapper.GalleriesResponseMapper(galleryEntity, host) + return +} + +func (_i *galleriesService) Reject(id uint, authToken string, message *string) (gallery *response.GalleriesResponse, err error) { + // Get user from token + user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + if user == nil { + err = errors.New("unauthorized: user not found") + return + } + + // Check if user has admin role (roleId = 1) + if user.UserRoleId != 1 { + err = errors.New("unauthorized: only admin can reject") + return + } + + // Reject gallery (update status_id to 3) + err = _i.Repo.Reject(id) + if err != nil { + return + } + + // Save rejection history + userID := user.ID + err = _i.ApprovalHistoriesService.CreateHistory("galleries", id, 3, "reject", &userID, message) + if err != nil { + _i.Log.Error().Err(err).Msg("Failed to save rejection history") + } + // Get updated gallery data galleryEntity, err := _i.Repo.FindOne(id) if err != nil { diff --git a/app/module/products/controller/products.controller.go b/app/module/products/controller/products.controller.go index c747f72..1bded2c 100644 --- a/app/module/products/controller/products.controller.go +++ b/app/module/products/controller/products.controller.go @@ -2,7 +2,6 @@ package controller import ( "encoding/json" - "jaecoo-be/app/database/entity/users" "jaecoo-be/app/module/products/request" "jaecoo-be/app/module/products/service" "jaecoo-be/utils/paginator" @@ -24,6 +23,7 @@ type ProductsController interface { Update(c *fiber.Ctx) error Delete(c *fiber.Ctx) error Approve(c *fiber.Ctx) error + Reject(c *fiber.Ctx) error Viewer(c *fiber.Ctx) error } @@ -299,22 +299,16 @@ func (_i *productsController) Approve(c *fiber.Ctx) error { return err } - // Get user from context - user := c.Locals("user") - if user == nil { + // Get token from Authorization header + authToken := c.Get("Authorization") + if authToken == "" { return utilRes.Resp(c, utilRes.Response{ Success: false, - Messages: utilRes.Messages{"Unauthorized: user not found"}, + Messages: utilRes.Messages{"Unauthorized: token not found"}, }) } - // Type assert to get user role ID - userRoleId := uint(0) - if userData, ok := user.(*users.Users); ok { - userRoleId = userData.UserRoleId - } - - productData, err := _i.productsService.Approve(uint(id), userRoleId) + productData, err := _i.productsService.Approve(uint(id), authToken) if err != nil { return err } @@ -326,6 +320,54 @@ func (_i *productsController) Approve(c *fiber.Ctx) error { }) } +// Reject Product +// @Summary Reject Product +// @Description API for rejecting Product (only for admin with roleId = 1) +// @Tags Products +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param id path int true "Product ID" +// @Param message body string false "Rejection message" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /products/{id}/reject [put] +func (_i *productsController) Reject(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + // Get token from Authorization header + authToken := c.Get("Authorization") + if authToken == "" { + return utilRes.Resp(c, utilRes.Response{ + Success: false, + Messages: utilRes.Messages{"Unauthorized: token not found"}, + }) + } + + // Get optional message from request body + var body struct { + Message *string `json:"message"` + } + if err := c.BodyParser(&body); err != nil { + body.Message = nil + } + + productData, err := _i.productsService.Reject(uint(id), authToken, body.Message) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Product successfully rejected"}, + Data: productData, + }) +} + // Viewer Product // @Summary Viewer Product // @Description API for viewing Product file diff --git a/app/module/products/products.module.go b/app/module/products/products.module.go index 0953bce..1ea8b45 100644 --- a/app/module/products/products.module.go +++ b/app/module/products/products.module.go @@ -51,6 +51,7 @@ func (_i *ProductsRouter) RegisterProductsRoutes() { router.Post("/", productsController.Save) router.Put("/:id", productsController.Update) router.Put("/:id/approve", productsController.Approve) + router.Put("/:id/reject", productsController.Reject) router.Delete("/:id", productsController.Delete) }) } diff --git a/app/module/products/repository/products.repository.go b/app/module/products/repository/products.repository.go index 4414d27..b20c886 100644 --- a/app/module/products/repository/products.repository.go +++ b/app/module/products/repository/products.repository.go @@ -23,6 +23,7 @@ type ProductsRepository interface { Delete(id uint) (err error) FindByThumbnailPath(thumbnailPath string) (product *entity.Products, err error) Approve(id uint) (err error) + Reject(id uint) (err error) } func NewProductsRepository(db *database.Database, log zerolog.Logger) ProductsRepository { @@ -105,3 +106,9 @@ func (_i *productsRepository) Approve(id uint) (err error) { return } +func (_i *productsRepository) Reject(id uint) (err error) { + statusId := 3 // Rejected status + err = _i.DB.DB.Model(&entity.Products{}).Where("id = ?", id).Update("status_id", statusId).Error + return +} + diff --git a/app/module/products/service/products.service.go b/app/module/products/service/products.service.go index f615679..bf1cff5 100644 --- a/app/module/products/service/products.service.go +++ b/app/module/products/service/products.service.go @@ -5,13 +5,16 @@ import ( "errors" "fmt" "io" + approvalHistoriesService "jaecoo-be/app/module/approval_histories/service" "jaecoo-be/app/module/products/mapper" "jaecoo-be/app/module/products/repository" "jaecoo-be/app/module/products/request" "jaecoo-be/app/module/products/response" + usersRepository "jaecoo-be/app/module/users/repository" "jaecoo-be/config/config" minioStorage "jaecoo-be/config/config" "jaecoo-be/utils/paginator" + utilSvc "jaecoo-be/utils/service" "math/rand" "mime" "path/filepath" @@ -25,10 +28,12 @@ import ( ) type productsService struct { - Repo repository.ProductsRepository - Log zerolog.Logger - Cfg *config.Config - MinioStorage *minioStorage.MinioStorage + Repo repository.ProductsRepository + Log zerolog.Logger + Cfg *config.Config + MinioStorage *minioStorage.MinioStorage + UsersRepo usersRepository.UsersRepository + ApprovalHistoriesService approvalHistoriesService.ApprovalHistoriesService } type ProductsService interface { @@ -37,17 +42,20 @@ type ProductsService interface { Create(c *fiber.Ctx, req request.ProductsCreateRequest) (product *response.ProductsResponse, err error) Update(c *fiber.Ctx, id uint, req request.ProductsUpdateRequest) (product *response.ProductsResponse, err error) Delete(id uint) (err error) - Approve(id uint, userRoleId uint) (product *response.ProductsResponse, err error) + Approve(id uint, authToken string) (product *response.ProductsResponse, err error) + Reject(id uint, authToken string, message *string) (product *response.ProductsResponse, err error) UploadFileToMinio(c *fiber.Ctx, fileKey string) (filePath *string, err error) Viewer(c *fiber.Ctx) (err error) } -func NewProductsService(repo repository.ProductsRepository, log zerolog.Logger, cfg *config.Config, minioStorage *minioStorage.MinioStorage) ProductsService { +func NewProductsService(repo repository.ProductsRepository, log zerolog.Logger, cfg *config.Config, minioStorage *minioStorage.MinioStorage, usersRepo usersRepository.UsersRepository, approvalHistoriesService approvalHistoriesService.ApprovalHistoriesService) ProductsService { return &productsService{ - Repo: repo, - Log: log, - Cfg: cfg, - MinioStorage: minioStorage, + Repo: repo, + Log: log, + Cfg: cfg, + MinioStorage: minioStorage, + UsersRepo: usersRepo, + ApprovalHistoriesService: approvalHistoriesService, } } @@ -274,9 +282,16 @@ func getFileExtension(filename string) string { return parts[len(parts)-1] } -func (_i *productsService) Approve(id uint, userRoleId uint) (product *response.ProductsResponse, err error) { +func (_i *productsService) Approve(id uint, authToken string) (product *response.ProductsResponse, err error) { + // Get user from token + user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + if user == nil { + err = errors.New("unauthorized: user not found") + return + } + // Check if user has admin role (roleId = 1) - if userRoleId != 1 { + if user.UserRoleId != 1 { err = errors.New("unauthorized: only admin can approve") return } @@ -287,6 +302,56 @@ func (_i *productsService) Approve(id uint, userRoleId uint) (product *response. return } + // Save approval history + userID := user.ID + err = _i.ApprovalHistoriesService.CreateHistory("products", id, 2, "approve", &userID, nil) + if err != nil { + _i.Log.Error().Err(err).Msg("Failed to save approval history") + } + + // Get updated product data + productEntity, err := _i.Repo.FindOne(id) + if err != nil { + return + } + + if productEntity == nil { + err = errors.New("product not found") + return + } + + host := _i.Cfg.App.Domain + product = mapper.ProductsResponseMapper(productEntity, host) + return +} + +func (_i *productsService) Reject(id uint, authToken string, message *string) (product *response.ProductsResponse, err error) { + // Get user from token + user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + if user == nil { + err = errors.New("unauthorized: user not found") + return + } + + // Check if user has admin role (roleId = 1) + if user.UserRoleId != 1 { + err = errors.New("unauthorized: only admin can reject") + return + } + + // Reject product (update status_id to 3) + err = _i.Repo.Reject(id) + if err != nil { + return + } + + // Save rejection history + userID := user.ID + err = _i.ApprovalHistoriesService.CreateHistory("products", id, 3, "reject", &userID, message) + if err != nil { + _i.Log.Error().Err(err).Msg("Failed to save rejection history") + } + // Get updated product data productEntity, err := _i.Repo.FindOne(id) if err != nil { diff --git a/app/module/promotions/controller/promotions.controller.go b/app/module/promotions/controller/promotions.controller.go index 0bf7da4..cc9e670 100644 --- a/app/module/promotions/controller/promotions.controller.go +++ b/app/module/promotions/controller/promotions.controller.go @@ -1,7 +1,6 @@ package controller import ( - "jaecoo-be/app/database/entity/users" "jaecoo-be/app/module/promotions/request" "jaecoo-be/app/module/promotions/service" "jaecoo-be/utils/paginator" @@ -24,6 +23,7 @@ type PromotionsController interface { Update(c *fiber.Ctx) error Delete(c *fiber.Ctx) error Approve(c *fiber.Ctx) error + Reject(c *fiber.Ctx) error Viewer(c *fiber.Ctx) error } @@ -242,22 +242,16 @@ func (_i *promotionsController) Approve(c *fiber.Ctx) error { return err } - // Get user from context - user := c.Locals("user") - if user == nil { + // Get token from Authorization header + authToken := c.Get("Authorization") + if authToken == "" { return utilRes.Resp(c, utilRes.Response{ Success: false, - Messages: utilRes.Messages{"Unauthorized: user not found"}, + Messages: utilRes.Messages{"Unauthorized: token not found"}, }) } - // Type assert to get user role ID - userRoleId := uint(0) - if userData, ok := user.(*users.Users); ok { - userRoleId = userData.UserRoleId - } - - promotionData, err := _i.promotionsService.Approve(uint(id), userRoleId) + promotionData, err := _i.promotionsService.Approve(uint(id), authToken) if err != nil { return err } @@ -269,6 +263,54 @@ func (_i *promotionsController) Approve(c *fiber.Ctx) error { }) } +// Reject Promotion +// @Summary Reject Promotion +// @Description API for rejecting Promotion (only for admin with roleId = 1) +// @Tags Promotions +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param id path int true "Promotion ID" +// @Param message body string false "Rejection message" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /promotions/{id}/reject [put] +func (_i *promotionsController) Reject(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + // Get token from Authorization header + authToken := c.Get("Authorization") + if authToken == "" { + return utilRes.Resp(c, utilRes.Response{ + Success: false, + Messages: utilRes.Messages{"Unauthorized: token not found"}, + }) + } + + // Get optional message from request body + var body struct { + Message *string `json:"message"` + } + if err := c.BodyParser(&body); err != nil { + body.Message = nil + } + + promotionData, err := _i.promotionsService.Reject(uint(id), authToken, body.Message) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Promotion successfully rejected"}, + Data: promotionData, + }) +} + // Viewer Promotion // @Summary Viewer Promotion // @Description API for viewing Promotion file diff --git a/app/module/promotions/promotions.module.go b/app/module/promotions/promotions.module.go index d297672..d2f3a5c 100644 --- a/app/module/promotions/promotions.module.go +++ b/app/module/promotions/promotions.module.go @@ -51,6 +51,7 @@ func (_i *PromotionsRouter) RegisterPromotionsRoutes() { router.Post("/", promotionsController.Save) router.Put("/:id", promotionsController.Update) router.Put("/:id/approve", promotionsController.Approve) + router.Put("/:id/reject", promotionsController.Reject) router.Delete("/:id", promotionsController.Delete) }) } diff --git a/app/module/promotions/repository/promotions.repository.go b/app/module/promotions/repository/promotions.repository.go index a6ffbe3..153cb5c 100644 --- a/app/module/promotions/repository/promotions.repository.go +++ b/app/module/promotions/repository/promotions.repository.go @@ -23,6 +23,7 @@ type PromotionsRepository interface { Delete(id uint) (err error) FindByThumbnailPath(thumbnailPath string) (promotion *entity.Promotions, err error) Approve(id uint) (err error) + Reject(id uint) (err error) } func NewPromotionsRepository(db *database.Database, log zerolog.Logger) PromotionsRepository { @@ -96,3 +97,9 @@ func (_i *promotionsRepository) Approve(id uint) (err error) { err = _i.DB.DB.Model(&entity.Promotions{}).Where("id = ?", id).Update("status_id", statusId).Error return } + +func (_i *promotionsRepository) Reject(id uint) (err error) { + statusId := 3 // Rejected status + err = _i.DB.DB.Model(&entity.Promotions{}).Where("id = ?", id).Update("status_id", statusId).Error + return +} diff --git a/app/module/promotions/service/promotions.service.go b/app/module/promotions/service/promotions.service.go index 669554b..42761a7 100644 --- a/app/module/promotions/service/promotions.service.go +++ b/app/module/promotions/service/promotions.service.go @@ -5,13 +5,16 @@ import ( "errors" "fmt" "io" + approvalHistoriesService "jaecoo-be/app/module/approval_histories/service" "jaecoo-be/app/module/promotions/mapper" "jaecoo-be/app/module/promotions/repository" "jaecoo-be/app/module/promotions/request" "jaecoo-be/app/module/promotions/response" + usersRepository "jaecoo-be/app/module/users/repository" "jaecoo-be/config/config" minioStorage "jaecoo-be/config/config" "jaecoo-be/utils/paginator" + utilSvc "jaecoo-be/utils/service" "math/rand" "mime" "path/filepath" @@ -25,10 +28,12 @@ import ( ) type promotionsService struct { - Repo repository.PromotionsRepository - Log zerolog.Logger - Cfg *config.Config - MinioStorage *minioStorage.MinioStorage + Repo repository.PromotionsRepository + Log zerolog.Logger + Cfg *config.Config + MinioStorage *minioStorage.MinioStorage + UsersRepo usersRepository.UsersRepository + ApprovalHistoriesService approvalHistoriesService.ApprovalHistoriesService } type PromotionsService interface { @@ -37,17 +42,20 @@ type PromotionsService interface { Create(c *fiber.Ctx, req request.PromotionsCreateRequest) (promotion *response.PromotionsResponse, err error) Update(id uint, req request.PromotionsUpdateRequest) (promotion *response.PromotionsResponse, err error) Delete(id uint) (err error) - Approve(id uint, userRoleId uint) (promotion *response.PromotionsResponse, err error) + Approve(id uint, authToken string) (promotion *response.PromotionsResponse, err error) + Reject(id uint, authToken string, message *string) (promotion *response.PromotionsResponse, err error) UploadFileToMinio(c *fiber.Ctx, fileKey string) (filePath *string, err error) Viewer(c *fiber.Ctx) (err error) } -func NewPromotionsService(repo repository.PromotionsRepository, log zerolog.Logger, cfg *config.Config, minioStorage *minioStorage.MinioStorage) PromotionsService { +func NewPromotionsService(repo repository.PromotionsRepository, log zerolog.Logger, cfg *config.Config, minioStorage *minioStorage.MinioStorage, usersRepo usersRepository.UsersRepository, approvalHistoriesService approvalHistoriesService.ApprovalHistoriesService) PromotionsService { return &promotionsService{ - Repo: repo, - Log: log, - Cfg: cfg, - MinioStorage: minioStorage, + Repo: repo, + Log: log, + Cfg: cfg, + MinioStorage: minioStorage, + UsersRepo: usersRepo, + ApprovalHistoriesService: approvalHistoriesService, } } @@ -269,9 +277,16 @@ func getFileExtension(filename string) string { return parts[len(parts)-1] } -func (_i *promotionsService) Approve(id uint, userRoleId uint) (promotion *response.PromotionsResponse, err error) { +func (_i *promotionsService) Approve(id uint, authToken string) (promotion *response.PromotionsResponse, err error) { + // Get user from token + user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + if user == nil { + err = errors.New("unauthorized: user not found") + return + } + // Check if user has admin role (roleId = 1) - if userRoleId != 1 { + if user.UserRoleId != 1 { err = errors.New("unauthorized: only admin can approve") return } @@ -282,6 +297,56 @@ func (_i *promotionsService) Approve(id uint, userRoleId uint) (promotion *respo return } + // Save approval history + userID := user.ID + err = _i.ApprovalHistoriesService.CreateHistory("promotions", id, 2, "approve", &userID, nil) + if err != nil { + _i.Log.Error().Err(err).Msg("Failed to save approval history") + } + + // Get updated promotion data + promotionEntity, err := _i.Repo.FindOne(id) + if err != nil { + return + } + + if promotionEntity == nil { + err = errors.New("promotion not found") + return + } + + host := _i.Cfg.App.Domain + promotion = mapper.PromotionsResponseMapper(promotionEntity, host) + return +} + +func (_i *promotionsService) Reject(id uint, authToken string, message *string) (promotion *response.PromotionsResponse, err error) { + // Get user from token + user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + if user == nil { + err = errors.New("unauthorized: user not found") + return + } + + // Check if user has admin role (roleId = 1) + if user.UserRoleId != 1 { + err = errors.New("unauthorized: only admin can reject") + return + } + + // Reject promotion (update status_id to 3) + err = _i.Repo.Reject(id) + if err != nil { + return + } + + // Save rejection history + userID := user.ID + err = _i.ApprovalHistoriesService.CreateHistory("promotions", id, 3, "reject", &userID, message) + if err != nil { + _i.Log.Error().Err(err).Msg("Failed to save rejection history") + } + // Get updated promotion data promotionEntity, err := _i.Repo.FindOne(id) if err != nil { diff --git a/app/module/sales_agents/controller/sales_agents.controller.go b/app/module/sales_agents/controller/sales_agents.controller.go index 0bd2fe9..4d67707 100644 --- a/app/module/sales_agents/controller/sales_agents.controller.go +++ b/app/module/sales_agents/controller/sales_agents.controller.go @@ -2,7 +2,6 @@ package controller import ( "encoding/json" - "jaecoo-be/app/database/entity/users" "jaecoo-be/app/module/sales_agents/request" "jaecoo-be/app/module/sales_agents/service" "jaecoo-be/utils/paginator" @@ -24,6 +23,7 @@ type SalesAgentsController interface { Update(c *fiber.Ctx) error Delete(c *fiber.Ctx) error Approve(c *fiber.Ctx) error + Reject(c *fiber.Ctx) error Viewer(c *fiber.Ctx) error } @@ -300,22 +300,16 @@ func (_i *salesAgentsController) Approve(c *fiber.Ctx) error { return err } - // Get user from context - user := c.Locals("user") - if user == nil { + // Get token from Authorization header + authToken := c.Get("Authorization") + if authToken == "" { return utilRes.Resp(c, utilRes.Response{ Success: false, - Messages: utilRes.Messages{"Unauthorized: user not found"}, + Messages: utilRes.Messages{"Unauthorized: token not found"}, }) } - // Type assert to get user role ID - userRoleId := uint(0) - if userData, ok := user.(*users.Users); ok { - userRoleId = userData.UserRoleId - } - - agentData, err := _i.salesAgentsService.Approve(uint(id), userRoleId) + agentData, err := _i.salesAgentsService.Approve(uint(id), authToken) if err != nil { return err } @@ -327,6 +321,54 @@ func (_i *salesAgentsController) Approve(c *fiber.Ctx) error { }) } +// Reject SalesAgent +// @Summary Reject SalesAgent +// @Description API for rejecting SalesAgent (only for admin with roleId = 1) +// @Tags SalesAgents +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param id path int true "SalesAgent ID" +// @Param message body string false "Rejection message" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /sales-agents/{id}/reject [put] +func (_i *salesAgentsController) Reject(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + // Get token from Authorization header + authToken := c.Get("Authorization") + if authToken == "" { + return utilRes.Resp(c, utilRes.Response{ + Success: false, + Messages: utilRes.Messages{"Unauthorized: token not found"}, + }) + } + + // Get optional message from request body + var body struct { + Message *string `json:"message"` + } + if err := c.BodyParser(&body); err != nil { + body.Message = nil + } + + agentData, err := _i.salesAgentsService.Reject(uint(id), authToken, body.Message) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"SalesAgent successfully rejected"}, + Data: agentData, + }) +} + // Viewer SalesAgent // @Summary Viewer SalesAgent // @Description API for viewing SalesAgent profile picture file @@ -342,4 +384,3 @@ func (_i *salesAgentsController) Approve(c *fiber.Ctx) error { func (_i *salesAgentsController) Viewer(c *fiber.Ctx) error { return _i.salesAgentsService.Viewer(c) } - diff --git a/app/module/sales_agents/repository/sales_agents.repository.go b/app/module/sales_agents/repository/sales_agents.repository.go index f9477fa..8e13693 100644 --- a/app/module/sales_agents/repository/sales_agents.repository.go +++ b/app/module/sales_agents/repository/sales_agents.repository.go @@ -23,6 +23,7 @@ type SalesAgentsRepository interface { Delete(id uint) (err error) FindByProfilePicturePath(profilePicturePath string) (agent *entity.SalesAgents, err error) Approve(id uint) (err error) + Reject(id uint) (err error) } func NewSalesAgentsRepository(db *database.Database, log zerolog.Logger) SalesAgentsRepository { @@ -105,3 +106,9 @@ func (_i *salesAgentsRepository) Approve(id uint) (err error) { return } +func (_i *salesAgentsRepository) Reject(id uint) (err error) { + statusId := 3 // Rejected status + err = _i.DB.DB.Model(&entity.SalesAgents{}).Where("id = ?", id).Update("status_id", statusId).Error + return +} + diff --git a/app/module/sales_agents/sales_agents.module.go b/app/module/sales_agents/sales_agents.module.go index 3d948fc..35efc36 100644 --- a/app/module/sales_agents/sales_agents.module.go +++ b/app/module/sales_agents/sales_agents.module.go @@ -51,6 +51,7 @@ func (_i *SalesAgentsRouter) RegisterSalesAgentsRoutes() { router.Post("/", salesAgentsController.Save) router.Put("/:id", salesAgentsController.Update) router.Put("/:id/approve", salesAgentsController.Approve) + router.Put("/:id/reject", salesAgentsController.Reject) router.Delete("/:id", salesAgentsController.Delete) }) } diff --git a/app/module/sales_agents/service/sales_agents.service.go b/app/module/sales_agents/service/sales_agents.service.go index 2cfc51b..271e150 100644 --- a/app/module/sales_agents/service/sales_agents.service.go +++ b/app/module/sales_agents/service/sales_agents.service.go @@ -5,13 +5,16 @@ import ( "errors" "fmt" "io" + approvalHistoriesService "jaecoo-be/app/module/approval_histories/service" "jaecoo-be/app/module/sales_agents/mapper" "jaecoo-be/app/module/sales_agents/repository" "jaecoo-be/app/module/sales_agents/request" "jaecoo-be/app/module/sales_agents/response" + usersRepository "jaecoo-be/app/module/users/repository" "jaecoo-be/config/config" minioStorage "jaecoo-be/config/config" "jaecoo-be/utils/paginator" + utilSvc "jaecoo-be/utils/service" "math/rand" "mime" "path/filepath" @@ -25,10 +28,12 @@ import ( ) type salesAgentsService struct { - Repo repository.SalesAgentsRepository - Log zerolog.Logger - Cfg *config.Config - MinioStorage *minioStorage.MinioStorage + Repo repository.SalesAgentsRepository + Log zerolog.Logger + Cfg *config.Config + MinioStorage *minioStorage.MinioStorage + UsersRepo usersRepository.UsersRepository + ApprovalHistoriesService approvalHistoriesService.ApprovalHistoriesService } type SalesAgentsService interface { @@ -37,17 +42,20 @@ type SalesAgentsService interface { Create(c *fiber.Ctx, req request.SalesAgentsCreateRequest) (agent *response.SalesAgentsResponse, err error) Update(c *fiber.Ctx, id uint, req request.SalesAgentsUpdateRequest) (agent *response.SalesAgentsResponse, err error) Delete(id uint) (err error) - Approve(id uint, userRoleId uint) (agent *response.SalesAgentsResponse, err error) + Approve(id uint, authToken string) (agent *response.SalesAgentsResponse, err error) + Reject(id uint, authToken string, message *string) (agent *response.SalesAgentsResponse, err error) UploadFileToMinio(c *fiber.Ctx, fileKey string) (filePath *string, err error) Viewer(c *fiber.Ctx) (err error) } -func NewSalesAgentsService(repo repository.SalesAgentsRepository, log zerolog.Logger, cfg *config.Config, minioStorage *minioStorage.MinioStorage) SalesAgentsService { +func NewSalesAgentsService(repo repository.SalesAgentsRepository, log zerolog.Logger, cfg *config.Config, minioStorage *minioStorage.MinioStorage, usersRepo usersRepository.UsersRepository, approvalHistoriesService approvalHistoriesService.ApprovalHistoriesService) SalesAgentsService { return &salesAgentsService{ - Repo: repo, - Log: log, - Cfg: cfg, - MinioStorage: minioStorage, + Repo: repo, + Log: log, + Cfg: cfg, + MinioStorage: minioStorage, + UsersRepo: usersRepo, + ApprovalHistoriesService: approvalHistoriesService, } } @@ -274,9 +282,16 @@ func getFileExtension(filename string) string { return parts[len(parts)-1] } -func (_i *salesAgentsService) Approve(id uint, userRoleId uint) (agent *response.SalesAgentsResponse, err error) { +func (_i *salesAgentsService) Approve(id uint, authToken string) (agent *response.SalesAgentsResponse, err error) { + // Get user from token + user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + if user == nil { + err = errors.New("unauthorized: user not found") + return + } + // Check if user has admin role (roleId = 1) - if userRoleId != 1 { + if user.UserRoleId != 1 { err = errors.New("unauthorized: only admin can approve") return } @@ -287,6 +302,56 @@ func (_i *salesAgentsService) Approve(id uint, userRoleId uint) (agent *response return } + // Save approval history + userID := user.ID + err = _i.ApprovalHistoriesService.CreateHistory("sales_agents", id, 2, "approve", &userID, nil) + if err != nil { + _i.Log.Error().Err(err).Msg("Failed to save approval history") + } + + // Get updated sales agent data + agentEntity, err := _i.Repo.FindOne(id) + if err != nil { + return + } + + if agentEntity == nil { + err = errors.New("sales agent not found") + return + } + + host := _i.Cfg.App.Domain + agent = mapper.SalesAgentsResponseMapper(agentEntity, host) + return +} + +func (_i *salesAgentsService) Reject(id uint, authToken string, message *string) (agent *response.SalesAgentsResponse, err error) { + // Get user from token + user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + if user == nil { + err = errors.New("unauthorized: user not found") + return + } + + // Check if user has admin role (roleId = 1) + if user.UserRoleId != 1 { + err = errors.New("unauthorized: only admin can reject") + return + } + + // Reject sales agent (update status_id to 3) + err = _i.Repo.Reject(id) + if err != nil { + return + } + + // Save rejection history + userID := user.ID + err = _i.ApprovalHistoriesService.CreateHistory("sales_agents", id, 3, "reject", &userID, message) + if err != nil { + _i.Log.Error().Err(err).Msg("Failed to save rejection history") + } + // Get updated sales agent data agentEntity, err := _i.Repo.FindOne(id) if err != nil { diff --git a/app/router/api.go b/app/router/api.go index cc76a9e..c98642e 100644 --- a/app/router/api.go +++ b/app/router/api.go @@ -2,6 +2,7 @@ package router import ( "jaecoo-be/app/module/activity_logs" + "jaecoo-be/app/module/approval_histories" "jaecoo-be/app/module/article_approvals" "jaecoo-be/app/module/article_categories" "jaecoo-be/app/module/article_category_details" @@ -38,6 +39,7 @@ type Router struct { Cfg *config.Config ActivityLogsRouter *activity_logs.ActivityLogsRouter + ApprovalHistoriesRouter *approval_histories.ApprovalHistoriesRouter ArticleCategoriesRouter *article_categories.ArticleCategoriesRouter ArticleCategoryDetailsRouter *article_category_details.ArticleCategoryDetailsRouter ArticleFilesRouter *article_files.ArticleFilesRouter @@ -69,6 +71,7 @@ func NewRouter( cfg *config.Config, activityLogsRouter *activity_logs.ActivityLogsRouter, + approvalHistoriesRouter *approval_histories.ApprovalHistoriesRouter, articleCategoriesRouter *article_categories.ArticleCategoriesRouter, articleCategoryDetailsRouter *article_category_details.ArticleCategoryDetailsRouter, articleFilesRouter *article_files.ArticleFilesRouter, @@ -98,6 +101,7 @@ func NewRouter( App: fiber, Cfg: cfg, ActivityLogsRouter: activityLogsRouter, + ApprovalHistoriesRouter: approvalHistoriesRouter, ArticleCategoriesRouter: articleCategoriesRouter, ArticleCategoryDetailsRouter: articleCategoryDetailsRouter, ArticleFilesRouter: articleFilesRouter, @@ -137,6 +141,7 @@ func (r *Router) Register() { // Register routes of modules r.ActivityLogsRouter.RegisterActivityLogsRoutes() + r.ApprovalHistoriesRouter.RegisterApprovalHistoriesRoutes() r.ArticleCategoriesRouter.RegisterArticleCategoriesRoutes() r.ArticleCategoryDetailsRouter.RegisterArticleCategoryDetailsRoutes() r.ArticleFilesRouter.RegisterArticleFilesRoutes() diff --git a/jaecoo-be.exe b/jaecoo-be.exe new file mode 100644 index 0000000..c9e83f3 Binary files /dev/null and b/jaecoo-be.exe differ diff --git a/main.go b/main.go index e619d3c..79b45b8 100644 --- a/main.go +++ b/main.go @@ -4,6 +4,7 @@ import ( "jaecoo-be/app/database" "jaecoo-be/app/middleware" "jaecoo-be/app/module/activity_logs" + "jaecoo-be/app/module/approval_histories" "jaecoo-be/app/module/article_approvals" "jaecoo-be/app/module/article_categories" "jaecoo-be/app/module/article_category_details" @@ -64,6 +65,7 @@ func main() { // provide modules activity_logs.NewActivityLogsModule, + approval_histories.NewApprovalHistoriesModule, article_categories.NewArticleCategoriesModule, article_category_details.NewArticleCategoryDetailsModule, article_files.NewArticleFilesModule,