diff --git a/internal/modules/closings/controllers/closing.controller.go b/internal/modules/closings/controllers/closing.controller.go new file mode 100644 index 00000000..4918c28f --- /dev/null +++ b/internal/modules/closings/controllers/closing.controller.go @@ -0,0 +1,76 @@ +package controller + +import ( + "math" + "strconv" + + "gitlab.com/mbugroup/lti-api.git/internal/modules/closings/dto" + service "gitlab.com/mbugroup/lti-api.git/internal/modules/closings/services" + validation "gitlab.com/mbugroup/lti-api.git/internal/modules/closings/validations" + "gitlab.com/mbugroup/lti-api.git/internal/response" + + "github.com/gofiber/fiber/v2" +) + +type ClosingController struct { + ClosingService service.ClosingService +} + +func NewClosingController(closingService service.ClosingService) *ClosingController { + return &ClosingController{ + ClosingService: closingService, + } +} + +func (u *ClosingController) GetAll(c *fiber.Ctx) error { + query := &validation.Query{ + Page: c.QueryInt("page", 1), + Limit: c.QueryInt("limit", 10), + Search: c.Query("search", ""), + } + + if query.Page < 1 || query.Limit < 1 { + return fiber.NewError(fiber.StatusBadRequest, "page and limit must be greater than 0") + } + + result, totalResults, err := u.ClosingService.GetAll(c, query) + if err != nil { + return err + } + + return c.Status(fiber.StatusOK). + JSON(response.SuccessWithPaginate[dto.ClosingListDTO]{ + Code: fiber.StatusOK, + Status: "success", + Message: "Get all closings successfully", + Meta: response.Meta{ + Page: query.Page, + Limit: query.Limit, + TotalPages: int64(math.Ceil(float64(totalResults) / float64(query.Limit))), + TotalResults: totalResults, + }, + Data: dto.ToClosingListDTOs(result), + }) +} + +func (u *ClosingController) GetOne(c *fiber.Ctx) error { + param := c.Params("id") + + id, err := strconv.Atoi(param) + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, "Invalid Id") + } + + result, err := u.ClosingService.GetOne(c, uint(id)) + if err != nil { + return err + } + + return c.Status(fiber.StatusOK). + JSON(response.Success{ + Code: fiber.StatusOK, + Status: "success", + Message: "Get closing successfully", + Data: dto.ToClosingListDTO(*result), + }) +} diff --git a/internal/modules/closings/dto/closing.dto.go b/internal/modules/closings/dto/closing.dto.go new file mode 100644 index 00000000..ccb014e6 --- /dev/null +++ b/internal/modules/closings/dto/closing.dto.go @@ -0,0 +1,64 @@ +package dto + +import ( + "time" + + entity "gitlab.com/mbugroup/lti-api.git/internal/entities" + userDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/users/dto" +) + +// === DTO Structs === + +type ClosingRelationDTO struct { + Id uint `json:"id"` + Name string `json:"name"` +} + +type ClosingListDTO struct { + Id uint `json:"id"` + Name string `json:"name"` + CreatedUser *userDTO.UserRelationDTO `json:"created_user"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +type ClosingDetailDTO struct { + ClosingListDTO +} + +// === Mapper Functions === + +func ToClosingRelationDTO(e entity.ProjectFlock) ClosingRelationDTO { + return ClosingRelationDTO{ + Id: e.Id, + } +} + +func ToClosingListDTO(e entity.ProjectFlock) ClosingListDTO { + var createdUser *userDTO.UserRelationDTO + if e.CreatedUser.Id != 0 { + mapped := userDTO.ToUserRelationDTO(e.CreatedUser) + createdUser = &mapped + } + + return ClosingListDTO{ + Id: e.Id, + CreatedAt: e.CreatedAt, + UpdatedAt: e.UpdatedAt, + CreatedUser: createdUser, + } +} + +func ToClosingListDTOs(e []entity.ProjectFlock) []ClosingListDTO { + result := make([]ClosingListDTO, len(e)) + for i, r := range e { + result[i] = ToClosingListDTO(r) + } + return result +} + +func ToClosingDetailDTO(e entity.ProjectFlock) ClosingDetailDTO { + return ClosingDetailDTO{ + ClosingListDTO: ToClosingListDTO(e), + } +} diff --git a/internal/modules/closings/module.go b/internal/modules/closings/module.go new file mode 100644 index 00000000..d831195c --- /dev/null +++ b/internal/modules/closings/module.go @@ -0,0 +1,26 @@ +package closings + +import ( + "github.com/go-playground/validator/v10" + "github.com/gofiber/fiber/v2" + "gorm.io/gorm" + + rClosing "gitlab.com/mbugroup/lti-api.git/internal/modules/closings/repositories" + sClosing "gitlab.com/mbugroup/lti-api.git/internal/modules/closings/services" + + rUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/repositories" + sUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services" +) + +type ClosingModule struct{} + +func (ClosingModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate *validator.Validate) { + closingRepo := rClosing.NewClosingRepository(db) + userRepo := rUser.NewUserRepository(db) + + closingService := sClosing.NewClosingService(closingRepo, validate) + userService := sUser.NewUserService(userRepo, validate) + + ClosingRoutes(router, userService, closingService) +} + diff --git a/internal/modules/closings/repositories/closing.repository.go b/internal/modules/closings/repositories/closing.repository.go new file mode 100644 index 00000000..946797fd --- /dev/null +++ b/internal/modules/closings/repositories/closing.repository.go @@ -0,0 +1,21 @@ +package repository + +import ( + "gitlab.com/mbugroup/lti-api.git/internal/common/repository" + entity "gitlab.com/mbugroup/lti-api.git/internal/entities" + "gorm.io/gorm" +) + +type ClosingRepository interface { + repository.BaseRepository[entity.ProjectFlock] +} + +type ClosingRepositoryImpl struct { + *repository.BaseRepositoryImpl[entity.ProjectFlock] +} + +func NewClosingRepository(db *gorm.DB) ClosingRepository { + return &ClosingRepositoryImpl{ + BaseRepositoryImpl: repository.NewBaseRepository[entity.ProjectFlock](db), + } +} diff --git a/internal/modules/closings/route.go b/internal/modules/closings/route.go new file mode 100644 index 00000000..6570a17d --- /dev/null +++ b/internal/modules/closings/route.go @@ -0,0 +1,25 @@ +package closings + +import ( + // m "gitlab.com/mbugroup/lti-api.git/internal/middleware" + controller "gitlab.com/mbugroup/lti-api.git/internal/modules/closings/controllers" + closing "gitlab.com/mbugroup/lti-api.git/internal/modules/closings/services" + user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services" + + "github.com/gofiber/fiber/v2" +) + +func ClosingRoutes(v1 fiber.Router, u user.UserService, s closing.ClosingService) { + ctrl := controller.NewClosingController(s) + + route := v1.Group("/closings") + + // route.Get("/", m.Auth(u), ctrl.GetAll) + // route.Post("/", m.Auth(u), ctrl.CreateOne) + // route.Get("/:id", m.Auth(u), ctrl.GetOne) + // route.Patch("/:id", m.Auth(u), ctrl.UpdateOne) + // route.Delete("/:id", m.Auth(u), ctrl.DeleteOne) + + route.Get("/", ctrl.GetAll) + route.Get("/:id", ctrl.GetOne) +} diff --git a/internal/modules/closings/services/closing.service.go b/internal/modules/closings/services/closing.service.go new file mode 100644 index 00000000..fd1b42eb --- /dev/null +++ b/internal/modules/closings/services/closing.service.go @@ -0,0 +1,72 @@ +package service + +import ( + "errors" + + entity "gitlab.com/mbugroup/lti-api.git/internal/entities" + repository "gitlab.com/mbugroup/lti-api.git/internal/modules/closings/repositories" + validation "gitlab.com/mbugroup/lti-api.git/internal/modules/closings/validations" + "gitlab.com/mbugroup/lti-api.git/internal/utils" + + "github.com/go-playground/validator/v10" + "github.com/gofiber/fiber/v2" + "github.com/sirupsen/logrus" + "gorm.io/gorm" +) + +type ClosingService interface { + GetAll(ctx *fiber.Ctx, params *validation.Query) ([]entity.ProjectFlock, int64, error) + GetOne(ctx *fiber.Ctx, id uint) (*entity.ProjectFlock, error) +} + +type closingService struct { + Log *logrus.Logger + Validate *validator.Validate + Repository repository.ClosingRepository +} + +func NewClosingService(repo repository.ClosingRepository, validate *validator.Validate) ClosingService { + return &closingService{ + Log: utils.Log, + Validate: validate, + Repository: repo, + } +} + +func (s closingService) withRelations(db *gorm.DB) *gorm.DB { + return db.Preload("CreatedUser") +} + +func (s closingService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entity.ProjectFlock, int64, error) { + if err := s.Validate.Struct(params); err != nil { + return nil, 0, err + } + + offset := (params.Page - 1) * params.Limit + + closings, total, err := s.Repository.GetAll(c.Context(), offset, params.Limit, func(db *gorm.DB) *gorm.DB { + db = s.withRelations(db) + if params.Search != "" { + return db.Where("name LIKE ?", "%"+params.Search+"%") + } + return db.Order("created_at DESC").Order("updated_at DESC") + }) + + if err != nil { + s.Log.Errorf("Failed to get closings: %+v", err) + return nil, 0, err + } + return closings, total, nil +} + +func (s closingService) GetOne(c *fiber.Ctx, id uint) (*entity.ProjectFlock, error) { + closing, err := s.Repository.GetByID(c.Context(), id, s.withRelations) + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, fiber.NewError(fiber.StatusNotFound, "Closing not found") + } + if err != nil { + s.Log.Errorf("Failed get closing by id: %+v", err) + return nil, err + } + return closing, nil +} diff --git a/internal/modules/closings/validations/closing.validation.go b/internal/modules/closings/validations/closing.validation.go new file mode 100644 index 00000000..7d16d3ee --- /dev/null +++ b/internal/modules/closings/validations/closing.validation.go @@ -0,0 +1,15 @@ +package validation + +type Create struct { + Name string `json:"name" validate:"required_strict,min=3"` +} + +type Update struct { + Name *string `json:"name,omitempty" validate:"omitempty"` +} + +type Query struct { + Page int `query:"page" validate:"omitempty,number,min=1,gt=0"` + Limit int `query:"limit" validate:"omitempty,number,min=1,max=100,gt=0"` + Search string `query:"search" validate:"omitempty,max=50"` +} diff --git a/internal/route/route.go b/internal/route/route.go index ac7fb486..4d1c1bae 100644 --- a/internal/route/route.go +++ b/internal/route/route.go @@ -9,6 +9,7 @@ import ( "gorm.io/gorm" approvals "gitlab.com/mbugroup/lti-api.git/internal/modules/approvals" + closings "gitlab.com/mbugroup/lti-api.git/internal/modules/closings" constants "gitlab.com/mbugroup/lti-api.git/internal/modules/constants" expenses "gitlab.com/mbugroup/lti-api.git/internal/modules/expenses" inventory "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory" @@ -40,6 +41,7 @@ func Routes(app *fiber.App, db *gorm.DB) { ssoModule.Module{}, expenses.ExpenseModule{}, ssoModule.Module{}, + closings.ClosingModule{}, // MODULE REGISTRY }