From dbc1f79a36512cc800dff183edd4dfc19ef884c6 Mon Sep 17 00:00:00 2001 From: "Hafizh A. Y" Date: Tue, 30 Sep 2025 16:48:05 +0700 Subject: [PATCH] chore: update port so it doesn't conflict with sso --- .env.example | 6 +- Dockerfile.local | 2 +- docker-compose.local.yml | 8 +- ...250925040409_create_master_tables.down.sql | 4 +- ...20250925040409_create_master_tables.up.sql | 26 +-- internal/modules/master/module.go | 13 ++ internal/modules/master/route.go | 25 +++ .../master/uoms/controllers/uom.controller.go | 140 ++++++++++++++ internal/modules/master/uoms/dto/uom.dto.go | 49 +++++ .../modules/master/uoms/models/uom.model.go | 19 ++ internal/modules/master/uoms/module.go | 26 +++ .../uoms/repositories/uom.repository.go | 21 +++ internal/modules/master/uoms/route.go | 28 +++ .../master/uoms/services/uom.service.go | 120 ++++++++++++ .../master/uoms/validations/uom.validation.go | 15 ++ internal/route/route.go | 2 + tools/gen.go | 171 +++++++++++++++++- tools/templates/model.tmpl | 12 +- tools/templates/route.tmpl | 18 +- 19 files changed, 668 insertions(+), 37 deletions(-) create mode 100644 internal/modules/master/module.go create mode 100644 internal/modules/master/route.go create mode 100644 internal/modules/master/uoms/controllers/uom.controller.go create mode 100644 internal/modules/master/uoms/dto/uom.dto.go create mode 100644 internal/modules/master/uoms/models/uom.model.go create mode 100644 internal/modules/master/uoms/module.go create mode 100644 internal/modules/master/uoms/repositories/uom.repository.go create mode 100644 internal/modules/master/uoms/route.go create mode 100644 internal/modules/master/uoms/services/uom.service.go create mode 100644 internal/modules/master/uoms/validations/uom.validation.go diff --git a/.env.example b/.env.example index a729d17f..02810734 100644 --- a/.env.example +++ b/.env.example @@ -3,8 +3,8 @@ VERSION=0.0.1 APP_ENV=dev APP_HOST=0.0.0.0 -APP_PORT=8080 -APP_URL=http://localhost:8080 +APP_PORT=8081 +APP_URL=http://localhost:8081 # database configuration DB_HOST=postgresdb @@ -12,6 +12,7 @@ DB_USER=postgres DB_PASSWORD=changeme DB_NAME=db_lti_erp DB_PORT=5432 +DB_PORT_HOST=5542 # JWT JWT_SECRET=changeme @@ -30,3 +31,4 @@ CORS_MAX_AGE=600 # Redis REDIS_URL=redis://redis:6379/0 +REDIS_PORT_HOST=6381 diff --git a/Dockerfile.local b/Dockerfile.local index 8fda5df1..87781228 100644 --- a/Dockerfile.local +++ b/Dockerfile.local @@ -15,6 +15,6 @@ RUN go mod download # Copy source code COPY . . -EXPOSE 8080 +EXPOSE 8081 CMD ["air", "-c", ".air.toml"] diff --git a/docker-compose.local.yml b/docker-compose.local.yml index c37c633f..cdc4652d 100644 --- a/docker-compose.local.yml +++ b/docker-compose.local.yml @@ -3,7 +3,7 @@ services: image: postgres:alpine restart: always ports: - - "${DB_PORT:-5432}:5432" + - "${DB_PORT_HOST:-5542}:5432" environment: POSTGRES_USER: ${DB_USER:-postgres} POSTGRES_PASSWORD: ${DB_PASSWORD:-postgres} @@ -25,7 +25,7 @@ services: image: redis:7-alpine restart: unless-stopped ports: - - "6379:6379" + - "${REDIS_PORT_HOST:-6381}:6379" healthcheck: test: ["CMD-SHELL", "redis-cli ping | grep PONG"] interval: 5s @@ -54,13 +54,13 @@ services: DB_NAME: ${DB_NAME:-db_lti_erp} REDIS_URL: ${REDIS_URL:-redis://redis:6379/0} ports: - - "${APP_PORT:-8080}:8080" + - "${APP_PORT:-8081}:8081" depends_on: postgresdb: condition: service_healthy networks: [go-network] healthcheck: - test: ["CMD-SHELL", "wget -qO- http://localhost:8080/healthz || exit 1"] + test: ["CMD-SHELL", "wget -qO- http://localhost:8081/healthz || exit 1"] interval: 10s timeout: 3s retries: 10 diff --git a/internal/database/migrations/20250925040409_create_master_tables.down.sql b/internal/database/migrations/20250925040409_create_master_tables.down.sql index df78e492..e0cd201a 100644 --- a/internal/database/migrations/20250925040409_create_master_tables.down.sql +++ b/internal/database/migrations/20250925040409_create_master_tables.down.sql @@ -13,9 +13,9 @@ DROP TABLE IF EXISTS warehouses; DROP TABLE IF EXISTS kandangs; DROP TABLE IF EXISTS locations; DROP TABLE IF EXISTS areas; -DROP TABLE IF EXISTS uom; +DROP TABLE IF EXISTS uoms; DROP TABLE IF EXISTS suppliers; -DROP TABLE IF EXISTS fcr; +DROP TABLE IF EXISTS fcrs; DROP TABLE IF EXISTS projects; DROP INDEX IF EXISTS users_id_user_unique; DROP INDEX IF EXISTS users_email_unique; diff --git a/internal/database/migrations/20250925040409_create_master_tables.up.sql b/internal/database/migrations/20250925040409_create_master_tables.up.sql index 2f6856b4..ec75e077 100644 --- a/internal/database/migrations/20250925040409_create_master_tables.up.sql +++ b/internal/database/migrations/20250925040409_create_master_tables.up.sql @@ -36,7 +36,7 @@ CREATE TABLE product_categories ( CREATE UNIQUE INDEX product_categories_code_unique ON product_categories (code) WHERE deleted_at IS NULL; -- UOM -CREATE TABLE uom ( +CREATE TABLE uoms ( id BIGSERIAL PRIMARY KEY, name VARCHAR NOT NULL, created_at TIMESTAMPTZ DEFAULT NOW(), @@ -51,11 +51,11 @@ CREATE TABLE products ( name VARCHAR NOT NULL, brand VARCHAR NOT NULL, sku VARCHAR(100), - uom_id BIGINT NOT NULL REFERENCES uom(id), + uom_id BIGINT NOT NULL REFERENCES uoms(id), product_category_id BIGINT NOT NULL REFERENCES product_categories(id), - product_price NUMERIC(15,2) NOT NULL, - selling_price NUMERIC(15,2), - tax NUMERIC(15,2), + product_price NUMERIC(15,3) NOT NULL, + selling_price NUMERIC(15,3), + tax NUMERIC(15,3), expiry_period INT, created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW(), @@ -134,7 +134,7 @@ CREATE TABLE customers ( phone VARCHAR(20) NOT NULL, email VARCHAR NOT NULL, account_number VARCHAR(50) NOT NULL, - balance NUMERIC(15,2) DEFAULT 0, + balance NUMERIC(15,3) DEFAULT 0, created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW(), deleted_at TIMESTAMPTZ, @@ -145,7 +145,7 @@ CREATE TABLE customers ( CREATE TABLE nonstocks ( id BIGSERIAL PRIMARY KEY, name VARCHAR NOT NULL, - uom_id BIGINT NOT NULL REFERENCES uom(id), + uom_id BIGINT NOT NULL REFERENCES uoms(id), created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW(), deleted_at TIMESTAMPTZ, @@ -153,7 +153,7 @@ CREATE TABLE nonstocks ( ); -- FCR -CREATE TABLE fcr ( +CREATE TABLE fcrs ( id BIGSERIAL PRIMARY KEY, name VARCHAR NOT NULL, created_at TIMESTAMPTZ DEFAULT NOW(), @@ -164,10 +164,10 @@ CREATE TABLE fcr ( CREATE TABLE fcr_standards ( id BIGSERIAL PRIMARY KEY, - fcr_id BIGINT NOT NULL REFERENCES fcr(id) ON DELETE CASCADE ON UPDATE CASCADE, - weight NUMERIC(15,2) NOT NULL, - fcr_number NUMERIC(15,2) NOT NULL, - mortality NUMERIC(15,2) NOT NULL, + fcr_id BIGINT NOT NULL REFERENCES fcrs(id) ON DELETE CASCADE ON UPDATE CASCADE, + weight NUMERIC(15,3) NOT NULL, + fcr_number NUMERIC(15,3) NOT NULL, + mortality NUMERIC(15,3) NOT NULL, created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW(), deleted_at TIMESTAMPTZ @@ -186,7 +186,7 @@ CREATE TABLE suppliers ( address TEXT NOT NULL, npwp VARCHAR(50), account_number VARCHAR(50), - balance NUMERIC(15,2) DEFAULT 0, + balance NUMERIC(15,3) DEFAULT 0, due_date INT NOT NULL, created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW(), diff --git a/internal/modules/master/module.go b/internal/modules/master/module.go new file mode 100644 index 00000000..f0fed701 --- /dev/null +++ b/internal/modules/master/module.go @@ -0,0 +1,13 @@ +package master + +import ( + "github.com/go-playground/validator/v10" + "github.com/gofiber/fiber/v2" + "gorm.io/gorm" +) + +type MasterModule struct{} + +func (MasterModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate *validator.Validate) { + RegisterRoutes(router, db, validate) +} diff --git a/internal/modules/master/route.go b/internal/modules/master/route.go new file mode 100644 index 00000000..c407033b --- /dev/null +++ b/internal/modules/master/route.go @@ -0,0 +1,25 @@ +package master + +import ( + "gitlab.com/mbugroup/lti-api.git/internal/modules" + + "github.com/go-playground/validator/v10" + "github.com/gofiber/fiber/v2" + "gorm.io/gorm" + + uoms "gitlab.com/mbugroup/lti-api.git/internal/modules/master/uoms" + // MODULE IMPORTS +) + +func RegisterRoutes(router fiber.Router, db *gorm.DB, validate *validator.Validate) { + group := router.Group("/master") + + allModules := []modules.Module{ + uoms.UomModule{}, + // MODULE REGISTRY + } + + for _, m := range allModules { + m.RegisterRoutes(group, db, validate) + } +} diff --git a/internal/modules/master/uoms/controllers/uom.controller.go b/internal/modules/master/uoms/controllers/uom.controller.go new file mode 100644 index 00000000..0bd3a382 --- /dev/null +++ b/internal/modules/master/uoms/controllers/uom.controller.go @@ -0,0 +1,140 @@ +package controller + +import ( + "math" + "strconv" + + "gitlab.com/mbugroup/lti-api.git/internal/modules/master/uoms/dto" + service "gitlab.com/mbugroup/lti-api.git/internal/modules/master/uoms/services" + validation "gitlab.com/mbugroup/lti-api.git/internal/modules/master/uoms/validations" + "gitlab.com/mbugroup/lti-api.git/internal/response" + + "github.com/gofiber/fiber/v2" +) + +type UomController struct { + UomService service.UomService +} + +func NewUomController(uomService service.UomService) *UomController { + return &UomController{ + UomService: uomService, + } +} + +func (u *UomController) GetAll(c *fiber.Ctx) error { + query := &validation.Query{ + Page: c.QueryInt("page", 1), + Limit: c.QueryInt("limit", 10), + Search: c.Query("search", ""), + } + + result, totalResults, err := u.UomService.GetAll(c, query) + if err != nil { + return err + } + + return c.Status(fiber.StatusOK). + JSON(response.SuccessWithPaginate[dto.UomListDTO]{ + Code: fiber.StatusOK, + Status: "success", + Message: "Get all uoms successfully", + Meta: response.Meta{ + Page: query.Page, + Limit: query.Limit, + TotalPages: int64(math.Ceil(float64(totalResults) / float64(query.Limit))), + TotalResults: totalResults, + }, + Data: dto.ToUomListDTOs(result), + }) +} + +func (u *UomController) 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.UomService.GetOne(c, uint(id)) + if err != nil { + return err + } + + return c.Status(fiber.StatusOK). + JSON(response.Success{ + Code: fiber.StatusOK, + Status: "success", + Message: "Get uom successfully", + Data: dto.ToUomListDTO(*result), + }) +} + +func (u *UomController) CreateOne(c *fiber.Ctx) error { + req := new(validation.Create) + + if err := c.BodyParser(req); err != nil { + return fiber.NewError(fiber.StatusBadRequest, "Invalid request body") + } + + result, err := u.UomService.CreateOne(c, req) + if err != nil { + return err + } + + return c.Status(fiber.StatusCreated). + JSON(response.Success{ + Code: fiber.StatusCreated, + Status: "success", + Message: "Create uom successfully", + Data: dto.ToUomListDTO(*result), + }) +} + +func (u *UomController) UpdateOne(c *fiber.Ctx) error { + req := new(validation.Update) + param := c.Params("id") + + id, err := strconv.Atoi(param) + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, "Invalid Id") + } + + if err := c.BodyParser(req); err != nil { + return fiber.NewError(fiber.StatusBadRequest, "Invalid request body") + } + + result, err := u.UomService.UpdateOne(c, req, uint(id)) + if err != nil { + return err + } + + return c.Status(fiber.StatusOK). + JSON(response.Success{ + Code: fiber.StatusOK, + Status: "success", + Message: "Update uom successfully", + Data: dto.ToUomListDTO(*result), + }) +} + +func (u *UomController) DeleteOne(c *fiber.Ctx) error { + param := c.Params("id") + + id, err := strconv.Atoi(param) + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, "Invalid Id") + } + + if err := u.UomService.DeleteOne(c, uint(id)); err != nil { + return err + } + + return c.Status(fiber.StatusOK). + JSON(response.Common{ + Code: fiber.StatusOK, + Status: "success", + Message: "Delete uom successfully", + }) +} diff --git a/internal/modules/master/uoms/dto/uom.dto.go b/internal/modules/master/uoms/dto/uom.dto.go new file mode 100644 index 00000000..305f6e05 --- /dev/null +++ b/internal/modules/master/uoms/dto/uom.dto.go @@ -0,0 +1,49 @@ +package dto + +import ( + "time" + + model "gitlab.com/mbugroup/lti-api.git/internal/modules/master/uoms/models" +) + +// === DTO Structs === + +type UomBaseDTO struct { + Id uint `json:"id"` + Name string `json:"name"` +} + +type UomListDTO struct { + UomBaseDTO + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +type UomDetailDTO struct { + UomListDTO +} + +// === Mapper Functions === + +func ToUomBaseDTO(m model.Uom) UomBaseDTO { + return UomBaseDTO{ + Id: m.Id, + Name: m.Name, + } +} + +func ToUomListDTO(m model.Uom) UomListDTO { + return UomListDTO{ + UomBaseDTO: ToUomBaseDTO(m), + CreatedAt: m.CreatedAt, + UpdatedAt: m.UpdatedAt, + } +} + +func ToUomListDTOs(m []model.Uom) []UomListDTO { + result := make([]UomListDTO, len(m)) + for i, r := range m { + result[i] = ToUomListDTO(r) + } + return result +} diff --git a/internal/modules/master/uoms/models/uom.model.go b/internal/modules/master/uoms/models/uom.model.go new file mode 100644 index 00000000..b6ba141e --- /dev/null +++ b/internal/modules/master/uoms/models/uom.model.go @@ -0,0 +1,19 @@ +package model + +import ( + "time" + + model "gitlab.com/mbugroup/lti-api.git/internal/modules/users/models" + "gorm.io/gorm" +) + +type Uom struct { + Id uint `gorm:"primaryKey"` + Name string `gorm:"not null"` + CreatedBy int64 `gorm:"not null"` + CreatedAt time.Time + UpdatedAt time.Time + DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` + + CreatedUser model.User `gorm:"foreignKey:CreatedBy;references:Id"` +} diff --git a/internal/modules/master/uoms/module.go b/internal/modules/master/uoms/module.go new file mode 100644 index 00000000..25919045 --- /dev/null +++ b/internal/modules/master/uoms/module.go @@ -0,0 +1,26 @@ +package uoms + +import ( + "github.com/go-playground/validator/v10" + "github.com/gofiber/fiber/v2" + "gorm.io/gorm" + + rUom "gitlab.com/mbugroup/lti-api.git/internal/modules/master/uoms/repositories" + sUom "gitlab.com/mbugroup/lti-api.git/internal/modules/master/uoms/services" + + rUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/repositories" + sUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services" +) + +type UomModule struct{} + +func (UomModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate *validator.Validate) { + uomRepo := rUom.NewUomRepository(db) + userRepo := rUser.NewUserRepository(db) + + uomService := sUom.NewUomService(uomRepo, validate) + userService := sUser.NewUserService(userRepo, validate) + + UomRoutes(router, userService, uomService) +} + diff --git a/internal/modules/master/uoms/repositories/uom.repository.go b/internal/modules/master/uoms/repositories/uom.repository.go new file mode 100644 index 00000000..8554250e --- /dev/null +++ b/internal/modules/master/uoms/repositories/uom.repository.go @@ -0,0 +1,21 @@ +package repository + +import ( + model "gitlab.com/mbugroup/lti-api.git/internal/modules/master/uoms/models" + "gitlab.com/mbugroup/lti-api.git/internal/repository" + "gorm.io/gorm" +) + +type UomRepository interface { + repository.BaseRepository[model.Uom] +} + +type UomRepositoryImpl struct { + *repository.BaseRepositoryImpl[model.Uom] +} + +func NewUomRepository(db *gorm.DB) UomRepository { + return &UomRepositoryImpl{ + BaseRepositoryImpl: repository.NewBaseRepository[model.Uom](db), + } +} diff --git a/internal/modules/master/uoms/route.go b/internal/modules/master/uoms/route.go new file mode 100644 index 00000000..6c8b29cc --- /dev/null +++ b/internal/modules/master/uoms/route.go @@ -0,0 +1,28 @@ +package uoms + +import ( + // m "gitlab.com/mbugroup/lti-api.git/internal/middleware" + controller "gitlab.com/mbugroup/lti-api.git/internal/modules/master/uoms/controllers" + uom "gitlab.com/mbugroup/lti-api.git/internal/modules/master/uoms/services" + user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services" + + "github.com/gofiber/fiber/v2" +) + +func UomRoutes(v1 fiber.Router, u user.UserService, s uom.UomService) { + ctrl := controller.NewUomController(s) + + route := v1.Group("/uoms") + + // 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.Post("/", ctrl.CreateOne) + route.Get("/:id", ctrl.GetOne) + route.Patch("/:id", ctrl.UpdateOne) + route.Delete("/:id", ctrl.DeleteOne) +} diff --git a/internal/modules/master/uoms/services/uom.service.go b/internal/modules/master/uoms/services/uom.service.go new file mode 100644 index 00000000..41460b3b --- /dev/null +++ b/internal/modules/master/uoms/services/uom.service.go @@ -0,0 +1,120 @@ +package service + +import ( + "errors" + + model "gitlab.com/mbugroup/lti-api.git/internal/modules/master/uoms/models" + repository "gitlab.com/mbugroup/lti-api.git/internal/modules/master/uoms/repositories" + validation "gitlab.com/mbugroup/lti-api.git/internal/modules/master/uoms/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 UomService interface { + GetAll(ctx *fiber.Ctx, params *validation.Query) ([]model.Uom, int64, error) + GetOne(ctx *fiber.Ctx, id uint) (*model.Uom, error) + CreateOne(ctx *fiber.Ctx, req *validation.Create) (*model.Uom, error) + UpdateOne(ctx *fiber.Ctx, req *validation.Update, id uint) (*model.Uom, error) + DeleteOne(ctx *fiber.Ctx, id uint) error +} + +type uomService struct { + Log *logrus.Logger + Validate *validator.Validate + Repository repository.UomRepository +} + +func NewUomService(repo repository.UomRepository, validate *validator.Validate) UomService { + return &uomService{ + Log: utils.Log, + Validate: validate, + Repository: repo, + } +} +func (s uomService) GetAll(c *fiber.Ctx, params *validation.Query) ([]model.Uom, int64, error) { + if err := s.Validate.Struct(params); err != nil { + return nil, 0, err + } + + offset := (params.Page - 1) * params.Limit + + uoms, total, err := s.Repository.GetAll(c.Context(), offset, params.Limit, func(db *gorm.DB) *gorm.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 uoms: %+v", err) + return nil, 0, err + } + return uoms, total, nil +} + +func (s uomService) GetOne(c *fiber.Ctx, id uint) (*model.Uom, error) { + uom, err := s.Repository.GetByID(c.Context(), id, nil) + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, fiber.NewError(fiber.StatusNotFound, "Uom not found") + } + if err != nil { + s.Log.Errorf("Failed get uom by id: %+v", err) + return nil, err + } + return uom, nil +} + +func (s *uomService) CreateOne(c *fiber.Ctx, req *validation.Create) (*model.Uom, error) { + if err := s.Validate.Struct(req); err != nil { + return nil, err + } + + createBody := &model.Uom{ + Name: req.Name, + CreatedBy: 1, + } + + if err := s.Repository.CreateOne(c.Context(), createBody, nil); err != nil { + s.Log.Errorf("Failed to create uom: %+v", err) + return nil, err + } + + return createBody, nil +} + +func (s uomService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint) (*model.Uom, error) { + if err := s.Validate.Struct(req); err != nil { + return nil, err + } + + updateBody := make(map[string]any) + + if req.Name != nil { + updateBody["name"] = *req.Name + } + + if err := s.Repository.PatchOne(c.Context(), id, updateBody, nil); err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, fiber.NewError(fiber.StatusNotFound, "Uom not found") + } + s.Log.Errorf("Failed to update uom: %+v", err) + return nil, err + } + + return s.GetOne(c, id) +} + +func (s uomService) DeleteOne(c *fiber.Ctx, id uint) error { + if err := s.Repository.DeleteOne(c.Context(), id); err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return fiber.NewError(fiber.StatusNotFound, "Uom not found") + } + s.Log.Errorf("Failed to delete uom: %+v", err) + return err + } + return nil +} diff --git a/internal/modules/master/uoms/validations/uom.validation.go b/internal/modules/master/uoms/validations/uom.validation.go new file mode 100644 index 00000000..e423aa8c --- /dev/null +++ b/internal/modules/master/uoms/validations/uom.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,max=50"` +} + +type Query struct { + Page int `query:"page" validate:"omitempty,number,min=1"` + Limit int `query:"limit" validate:"omitempty,number,min=1,max=100"` + Search string `query:"search" validate:"omitempty,max=50"` +} diff --git a/internal/route/route.go b/internal/route/route.go index a3b12c0e..b5769041 100644 --- a/internal/route/route.go +++ b/internal/route/route.go @@ -7,6 +7,7 @@ import ( "github.com/gofiber/fiber/v2" "gorm.io/gorm" + master "gitlab.com/mbugroup/lti-api.git/internal/modules/master" users "gitlab.com/mbugroup/lti-api.git/internal/modules/users" // MODULE IMPORTS ) @@ -18,6 +19,7 @@ func Routes(app *fiber.App, db *gorm.DB) { // root modules di sini allModules := []modules.Module{ users.UserModule{}, + master.MasterModule{}, // MODULE REGISTRY } diff --git a/tools/gen.go b/tools/gen.go index e56c3cf4..6bae2a0d 100644 --- a/tools/gen.go +++ b/tools/gen.go @@ -1,6 +1,7 @@ package main import ( + "errors" "fmt" "log" "os" @@ -15,6 +16,11 @@ type Data struct { Entity string // last ("area") } +const ( + repoBase = "gitlab.com/mbugroup/lti-api.git" + modulesBase = repoBase + "/internal/modules" +) + func main() { if len(os.Args) < 2 { log.Fatal("usage: make gen feat= (ex: customer | master/area)") @@ -139,6 +145,7 @@ func main() { log.Println("Generated:", outFile) } + updateParentModules(d) updateMainRoute(d) } @@ -150,13 +157,22 @@ func updateMainRoute(d Data) { return } - // entity & path - modPath := filepath.Join(append(toCamelParts(d.Parts[:len(d.Parts)-1]), toCamelCase(d.Entity)+"s")...) - modName := toCamelCase(d.Entity) + "s" - pkgName := toPascalCase(d.Entity) + "Module" + var importPath string + var modName string + var pkgName string + if len(d.Parts) > 1 { + top := d.Parts[0] + importPath = toKebab(top) + modName = toCamelCase(top) + pkgName = toPascalCase(top) + "Module" + } else { + importPath = toKebab(d.Entity) + "s" + modName = toCamelCase(d.Entity) + "s" + pkgName = toPascalCase(d.Entity) + "Module" + } // Inject import - importLine := fmt.Sprintf("\t%[1]s \"%s/internal/modules/%s\"", modName, "gitlab.com/mbugroup/lti-api.git", modPath) + importLine := fmt.Sprintf("\t%[1]s \"%s/%s\"", modName, modulesBase, importPath) if !strings.Contains(string(content), importLine) { content = []byte(strings.Replace(string(content), "// MODULE IMPORTS", @@ -179,6 +195,151 @@ func updateMainRoute(d Data) { log.Println("Updated:", routeFile) } +func updateParentModules(d Data) { + if len(d.Parts) < 2 { + return + } + + fullLen := len(d.Parts) + for i := 1; i < len(d.Parts); i++ { + parentParts := d.Parts[:i] + ensureParentModuleFile(parentParts) + + childParts := d.Parts[:i+1] + ensureParentRoute(parentParts, childParts, fullLen) + } +} + +func ensureParentModuleFile(parentParts []string) { + dir := filepath.Join(append([]string{"internal", "modules"}, toKebabParts(parentParts)...)...) + if err := os.MkdirAll(dir, 0o755); err != nil { + log.Fatalf("make parent dir: %v", err) + } + + moduleFile := filepath.Join(dir, "module.go") + if _, err := os.Stat(moduleFile); err == nil { + return + } else if !errors.Is(err, os.ErrNotExist) { + log.Fatalf("check parent module: %v", err) + } + + pkgName := toPackageName(parentParts[len(parentParts)-1]) + structName := toPascalCase(parentParts[len(parentParts)-1]) + "Module" + + content := fmt.Sprintf(`package %s + +import ( + "github.com/go-playground/validator/v10" + "github.com/gofiber/fiber/v2" + "gorm.io/gorm" +) + +type %s struct{} + +func (%s) RegisterRoutes(router fiber.Router, db *gorm.DB, validate *validator.Validate) { + RegisterRoutes(router, db, validate) +} +`, pkgName, structName, structName) + + if err := os.WriteFile(moduleFile, []byte(content), 0o644); err != nil { + log.Fatalf("write parent module: %v", err) + } + log.Println("Generated:", moduleFile) +} + +func ensureParentRoute(parentParts, childParts []string, fullLen int) { + dir := filepath.Join(append([]string{"internal", "modules"}, toKebabParts(parentParts)...)...) + if err := os.MkdirAll(dir, 0o755); err != nil { + log.Fatalf("make parent dir: %v", err) + } + + routeFile := filepath.Join(dir, "route.go") + isLeaf := len(childParts) == fullLen + childName := childParts[len(childParts)-1] + childAlias := toCamelCase(childName) + if isLeaf { + childAlias += "s" + } + childStruct := toPascalCase(childName) + "Module" + childImportParts := toKebabParts(childParts[:len(childParts)-1]) + childLast := childParts[len(childParts)-1] + if isLeaf { + childImportParts = append(childImportParts, toKebab(childLast)+"s") + } else { + childImportParts = append(childImportParts, toKebab(childLast)) + } + childImportPath := modulesBase + "/" + strings.Join(childImportParts, "/") + + if _, err := os.Stat(routeFile); errors.Is(err, os.ErrNotExist) { + pkgName := toPackageName(parentParts[len(parentParts)-1]) + segment := toKebab(parentParts[len(parentParts)-1]) + content := fmt.Sprintf(`package %s + +import ( + "%s" + + "github.com/go-playground/validator/v10" + "github.com/gofiber/fiber/v2" + "gorm.io/gorm" + + %s "%s" + // MODULE IMPORTS +) + +func RegisterRoutes(router fiber.Router, db *gorm.DB, validate *validator.Validate) { + group := router.Group("/%s") + + allModules := []modules.Module{ + %s.%s{}, + // MODULE REGISTRY + } + + for _, m := range allModules { + m.RegisterRoutes(group, db, validate) + } +} +`, pkgName, modulesBase, childAlias, childImportPath, segment, childAlias, childStruct) + + if err := os.WriteFile(routeFile, []byte(content), 0o644); err != nil { + log.Fatalf("write parent route: %v", err) + } + log.Println("Generated:", routeFile) + return + } else if err != nil { + log.Fatalf("check parent route: %v", err) + } + + content, err := os.ReadFile(routeFile) + if err != nil { + log.Fatalf("read parent route: %v", err) + } + + importLine := fmt.Sprintf("\t%s \"%s\"", childAlias, childImportPath) + if !strings.Contains(string(content), importLine) { + content = []byte(strings.Replace(string(content), + "\t// MODULE IMPORTS", + importLine+"\n\t// MODULE IMPORTS", + 1)) + } + + registryLine := fmt.Sprintf("\t\t%s.%s{},", childAlias, childStruct) + if !strings.Contains(string(content), registryLine) { + content = []byte(strings.Replace(string(content), + "\t\t// MODULE REGISTRY", + registryLine+"\n\t\t// MODULE REGISTRY", + 1)) + } + + if err := os.WriteFile(routeFile, content, 0o644); err != nil { + log.Fatalf("write parent route: %v", err) + } + log.Println("Updated:", routeFile) +} + +func toPackageName(s string) string { + return strings.ToLower(toPascalCase(s)) +} + func toPascalCase(s string) string { sep := func(r rune) bool { return r == '_' || r == '-' || r == ' ' || r == '/' } parts := strings.FieldsFunc(s, sep) diff --git a/tools/templates/model.tmpl b/tools/templates/model.tmpl index c6070f2c..efc2af69 100644 --- a/tools/templates/model.tmpl +++ b/tools/templates/model.tmpl @@ -5,9 +5,13 @@ import ( ) type {{Pascal .Entity}} struct { - Id uint `gorm:"primaryKey"` - Name string `gorm:"not null"` - CreatedAt time.Time - UpdatedAt time.Time + Id uint `gorm:"primaryKey"` + Name string `gorm:"not null"` + CreatedBy int64 `gorm:"not null"` + CreatedAt time.Time + UpdatedAt time.Time + DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` + + CreatedUser model.User `gorm:"foreignKey:CreatedBy;references:Id"` } {{end}} diff --git a/tools/templates/route.tmpl b/tools/templates/route.tmpl index 1d80ff03..26958deb 100644 --- a/tools/templates/route.tmpl +++ b/tools/templates/route.tmpl @@ -1,7 +1,7 @@ {{define "route"}}package {{Kebab .Entity}}s import ( - m "gitlab.com/mbugroup/lti-api.git/internal/middleware" + // m "gitlab.com/mbugroup/lti-api.git/internal/middleware" controller "gitlab.com/mbugroup/lti-api.git/internal/modules/{{Kebab .FeatName}}s/controllers" {{Camel .Entity}} "gitlab.com/mbugroup/lti-api.git/internal/modules/{{Kebab .FeatName}}s/services" user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services" @@ -14,10 +14,16 @@ func {{Pascal .Entity}}Routes(v1 fiber.Router, u user.UserService, s {{Camel .En route := v1.Group("/{{Kebab .Entity}}s") - 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("/", 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.Post("/", ctrl.CreateOne) + route.Get("/:id", ctrl.GetOne) + route.Patch("/:id", ctrl.UpdateOne) + route.Delete("/:id", ctrl.DeleteOne) } {{end}}