mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-20 05:21:57 +00:00
Feat(BE-36,37,38,39): master area, customer, kandang, location, warehouse
This commit is contained in:
@@ -27,7 +27,7 @@ WAIT_DB := docker run --rm --network $(NETWORK) postgres:alpine \
|
||||
.DEFAULT_GOAL := start
|
||||
|
||||
# --- Daftar phony targets ---
|
||||
.PHONY: start build lint gen \
|
||||
.PHONY: start build test lint gen \
|
||||
db-up wait-db \
|
||||
migration-% migrate-up migrate-down migrate-fresh \
|
||||
seed \
|
||||
@@ -40,6 +40,9 @@ start:
|
||||
build:
|
||||
@go build -o tmp/app ./cmd/api
|
||||
|
||||
test:
|
||||
@go test ./test/...
|
||||
|
||||
lint:
|
||||
@golangci-lint run
|
||||
|
||||
|
||||
@@ -24,8 +24,11 @@ require (
|
||||
github.com/cloudwego/base64x v0.1.4 // indirect
|
||||
github.com/cloudwego/iasm v0.2.0 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
|
||||
github.com/glebarez/go-sqlite v1.21.2 // indirect
|
||||
github.com/glebarez/sqlite v1.11.0 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/google/go-cmp v0.6.0 // indirect
|
||||
@@ -38,15 +41,17 @@ require (
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/klauspost/compress v1.17.9 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.0.9 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.3 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.17 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
||||
github.com/philhofer/fwd v1.1.2 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/rogpeppe/go-internal v1.11.0 // indirect
|
||||
github.com/sagikazarmark/locafero v0.4.0 // indirect
|
||||
@@ -71,4 +76,9 @@ require (
|
||||
golang.org/x/text v0.22.0 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
gorm.io/driver/sqlite v1.5.5 // indirect
|
||||
modernc.org/libc v1.22.5 // indirect
|
||||
modernc.org/mathutil v1.5.0 // indirect
|
||||
modernc.org/memory v1.5.0 // indirect
|
||||
modernc.org/sqlite v1.23.1 // indirect
|
||||
)
|
||||
|
||||
@@ -23,12 +23,18 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
|
||||
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
|
||||
github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo=
|
||||
github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k=
|
||||
github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
|
||||
github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ=
|
||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||
@@ -65,6 +71,7 @@ github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2
|
||||
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.3/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
|
||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
@@ -81,6 +88,8 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
|
||||
github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
|
||||
@@ -92,6 +101,9 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/redis/go-redis/v9 v9.14.0 h1:u4tNCjXOyzfgeLN+vAZaW1xUooqWDqVEsZN0U01jfAE=
|
||||
github.com/redis/go-redis/v9 v9.14.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
@@ -167,6 +179,7 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
@@ -198,6 +211,16 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gorm.io/driver/postgres v1.5.9 h1:DkegyItji119OlcaLjqN11kHoUgZ/j13E0jkJZgD6A8=
|
||||
gorm.io/driver/postgres v1.5.9/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI=
|
||||
gorm.io/driver/sqlite v1.5.5 h1:7MDMtUZhV065SilG62E0MquljeArQZNfJnjd9i9gx3E=
|
||||
gorm.io/driver/sqlite v1.5.5/go.mod h1:6NgQ7sQWAIFsPrJJl1lSNSu2TABh0ZZ/zm5fosATavE=
|
||||
gorm.io/gorm v1.25.11 h1:/Wfyg1B/je1hnDx3sMkX+gAlxrlZpn6X0BXRlwXlvHg=
|
||||
gorm.io/gorm v1.25.11/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
|
||||
modernc.org/libc v1.22.5 h1:91BNch/e5B0uPbJFgqbxXuOnxBQjlS//icfQEGmvyjE=
|
||||
modernc.org/libc v1.22.5/go.mod h1:jj+Z7dTNX8fBScMVNRAYZ/jF91K8fdT2hYMThc3YjBY=
|
||||
modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
|
||||
modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
||||
modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds=
|
||||
modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
|
||||
modernc.org/sqlite v1.23.1 h1:nrSBg4aRQQwq59JpvGEQ15tNxoO5pX/kUjcRNwSAGQM=
|
||||
modernc.org/sqlite v1.23.1/go.mod h1:OrDj17Mggn6MhE+iPbBNf7RGKODDE9NFT0f3EwDzJqk=
|
||||
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// Exists reports whether a record with the given ID exists for type T.
|
||||
func Exists[T any](ctx context.Context, db *gorm.DB, id uint) (bool, error) {
|
||||
var count int64
|
||||
if err := db.WithContext(ctx).
|
||||
Model(new(T)).
|
||||
Where("id = ?", id).
|
||||
Count(&count).Error; err != nil {
|
||||
return false, err
|
||||
}
|
||||
return count > 0, nil
|
||||
}
|
||||
|
||||
func ExistsByName[T any](ctx context.Context, db *gorm.DB, name string, excludeID *uint) (bool, error) {
|
||||
var count int64
|
||||
q := db.WithContext(ctx).
|
||||
Model(new(T)).
|
||||
Where("name = ?", name).
|
||||
Where("deleted_at IS NULL")
|
||||
if excludeID != nil {
|
||||
q = q.Where("id <> ?", *excludeID)
|
||||
}
|
||||
if err := q.Count(&count).Error; err != nil {
|
||||
return false, err
|
||||
}
|
||||
return count > 0, nil
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
// RelationCheck describes a foreign-key style dependency that must exist before processing.
|
||||
type RelationCheck struct {
|
||||
Name string
|
||||
ID *uint
|
||||
Exists func(context.Context, uint) (bool, error)
|
||||
}
|
||||
|
||||
// EnsureRelations validates that each RelationCheck is satisfied, returning consistent Fiber errors.
|
||||
func EnsureRelations(ctx context.Context, checks ...RelationCheck) error {
|
||||
for _, check := range checks {
|
||||
if check.ID == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
exists, err := check.Exists(ctx, *check.ID)
|
||||
if err != nil {
|
||||
return fiber.NewError(
|
||||
fiber.StatusInternalServerError,
|
||||
fmt.Sprintf("Failed to check %s", strings.ToLower(check.Name)),
|
||||
)
|
||||
}
|
||||
if !exists {
|
||||
return fiber.NewError(
|
||||
fiber.StatusNotFound,
|
||||
fmt.Sprintf("%s with id %d not found", check.Name, *check.ID),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -70,6 +70,5 @@ func Validator() *validator.Validate {
|
||||
if err := validate.RegisterValidation("omitempty_strict", OmitemptyStrict); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return validate
|
||||
}
|
||||
@@ -1,20 +1,32 @@
|
||||
|
||||
DROP TABLE IF EXISTS fcr_standards;
|
||||
DROP INDEX IF EXISTS suppliers_name_unique;
|
||||
DROP INDEX IF EXISTS products_sku_unique;
|
||||
DROP INDEX IF EXISTS products_name_unique;
|
||||
DROP TABLE IF EXISTS products;
|
||||
DROP TABLE IF EXISTS flags;
|
||||
DROP INDEX IF EXISTS customers_name_unique;
|
||||
DROP INDEX IF EXISTS customers_email_unique;
|
||||
DROP TABLE IF EXISTS customers;
|
||||
DROP INDEX IF EXISTS warehouses_name_unique;
|
||||
DROP INDEX IF EXISTS product_categories_code_unique;
|
||||
DROP INDEX IF EXISTS product_categories_name_unique;
|
||||
DROP TABLE IF EXISTS product_categories;
|
||||
DROP INDEX IF EXISTS nonstocks_name_unique;
|
||||
DROP TABLE IF EXISTS nonstocks;
|
||||
DROP INDEX IF EXISTS banks_name_unique;
|
||||
DROP TABLE IF EXISTS banks;
|
||||
DROP INDEX IF EXISTS kandangs_name_unique;
|
||||
DROP TABLE IF EXISTS warehouses;
|
||||
DROP TABLE IF EXISTS kandangs;
|
||||
DROP INDEX IF EXISTS locations_name_unique;
|
||||
DROP TABLE IF EXISTS locations;
|
||||
DROP INDEX IF EXISTS areas_name_unique;
|
||||
DROP TABLE IF EXISTS areas;
|
||||
DROP INDEX IF EXISTS uoms_name_unique;
|
||||
DROP TABLE IF EXISTS uoms;
|
||||
DROP TABLE IF EXISTS suppliers;
|
||||
DROP INDEX IF EXISTS fcrs_name_unique;
|
||||
DROP TABLE IF EXISTS fcrs;
|
||||
DROP TABLE IF EXISTS projects;
|
||||
DROP INDEX IF EXISTS users_id_user_unique;
|
||||
|
||||
@@ -31,8 +31,9 @@ CREATE TABLE product_categories (
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
deleted_at TIMESTAMPTZ,
|
||||
created_by BIGINT NOT NULL REFERENCES users(id)
|
||||
created_by BIGINT REFERENCES users(id) ON DELETE SET NULL ON UPDATE CASCADE
|
||||
);
|
||||
CREATE UNIQUE INDEX product_categories_name_unique ON product_categories (name) WHERE deleted_at IS NULL;
|
||||
CREATE UNIQUE INDEX product_categories_code_unique ON product_categories (code) WHERE deleted_at IS NULL;
|
||||
|
||||
-- UOM
|
||||
@@ -42,8 +43,9 @@ CREATE TABLE uoms (
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
deleted_at TIMESTAMPTZ,
|
||||
created_by BIGINT NOT NULL REFERENCES users(id)
|
||||
created_by BIGINT REFERENCES users(id) ON DELETE SET NULL ON UPDATE CASCADE
|
||||
);
|
||||
CREATE UNIQUE INDEX uoms_name_unique ON uoms (name) WHERE deleted_at IS NULL;
|
||||
|
||||
-- PRODUCTS
|
||||
CREATE TABLE products (
|
||||
@@ -51,8 +53,8 @@ CREATE TABLE products (
|
||||
name VARCHAR NOT NULL,
|
||||
brand VARCHAR NOT NULL,
|
||||
sku VARCHAR(100),
|
||||
uom_id BIGINT NOT NULL REFERENCES uoms(id),
|
||||
product_category_id BIGINT NOT NULL REFERENCES product_categories(id),
|
||||
uom_id BIGINT NOT NULL REFERENCES uoms(id) ON DELETE RESTRICT ON UPDATE CASCADE,
|
||||
product_category_id BIGINT NOT NULL REFERENCES product_categories(id) ON DELETE RESTRICT ON UPDATE CASCADE,
|
||||
product_price NUMERIC(15,3) NOT NULL,
|
||||
selling_price NUMERIC(15,3),
|
||||
tax NUMERIC(15,3),
|
||||
@@ -60,8 +62,9 @@ CREATE TABLE products (
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
deleted_at TIMESTAMPTZ,
|
||||
created_by BIGINT NOT NULL REFERENCES users(id)
|
||||
created_by BIGINT REFERENCES users(id) ON DELETE SET NULL ON UPDATE CASCADE
|
||||
);
|
||||
CREATE UNIQUE INDEX products_name_unique ON products (name) WHERE deleted_at IS NULL;
|
||||
CREATE UNIQUE INDEX products_sku_unique ON products (sku) WHERE deleted_at IS NULL;
|
||||
|
||||
-- BANKS
|
||||
@@ -74,8 +77,9 @@ CREATE TABLE banks (
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
deleted_at TIMESTAMPTZ,
|
||||
created_by BIGINT NOT NULL REFERENCES users(id)
|
||||
created_by BIGINT REFERENCES users(id) ON DELETE SET NULL ON UPDATE CASCADE
|
||||
);
|
||||
CREATE UNIQUE INDEX banks_name_unique ON banks (name) WHERE deleted_at IS NULL;
|
||||
|
||||
-- AREAS
|
||||
CREATE TABLE areas (
|
||||
@@ -84,51 +88,56 @@ CREATE TABLE areas (
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
deleted_at TIMESTAMPTZ,
|
||||
created_by BIGINT NOT NULL REFERENCES users(id)
|
||||
created_by BIGINT REFERENCES users(id) ON DELETE SET NULL ON UPDATE CASCADE
|
||||
);
|
||||
CREATE UNIQUE INDEX areas_name_unique ON areas (name) WHERE deleted_at IS NULL;
|
||||
|
||||
-- LOCATIONS
|
||||
CREATE TABLE locations (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
name VARCHAR NOT NULL,
|
||||
address TEXT NOT NULL,
|
||||
area_id BIGINT NOT NULL REFERENCES areas(id),
|
||||
area_id BIGINT NOT NULL REFERENCES areas(id) ON DELETE RESTRICT ON UPDATE CASCADE,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
deleted_at TIMESTAMPTZ,
|
||||
created_by BIGINT NOT NULL REFERENCES users(id)
|
||||
created_by BIGINT REFERENCES users(id) ON DELETE SET NULL ON UPDATE CASCADE
|
||||
);
|
||||
CREATE UNIQUE INDEX locations_name_unique ON locations (name) WHERE deleted_at IS NULL;
|
||||
|
||||
-- KANDANG
|
||||
CREATE TABLE kandangs (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
name VARCHAR(191) NOT NULL,
|
||||
location_id BIGINT NOT NULL REFERENCES locations(id),
|
||||
pic_id BIGINT NOT NULL REFERENCES users(id),
|
||||
name VARCHAR NOT NULL,
|
||||
location_id BIGINT NOT NULL REFERENCES locations(id) ON DELETE RESTRICT ON UPDATE CASCADE,
|
||||
pic_id BIGINT REFERENCES users(id) ON DELETE SET NULL ON UPDATE CASCADE,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
deleted_at TIMESTAMPTZ,
|
||||
created_by BIGINT NOT NULL REFERENCES users(id)
|
||||
created_by BIGINT REFERENCES users(id) ON DELETE SET NULL ON UPDATE CASCADE
|
||||
);
|
||||
CREATE UNIQUE INDEX kandangs_name_unique ON kandangs (name) WHERE deleted_at IS NULL;
|
||||
|
||||
-- WAREHOUSES
|
||||
CREATE TABLE warehouses (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
name VARCHAR NOT NULL,
|
||||
type VARCHAR(50) NOT NULL,
|
||||
location_id BIGINT NOT NULL REFERENCES locations(id),
|
||||
kandang_id BIGINT REFERENCES kandangs(id),
|
||||
area_id BIGINT NOT NULL REFERENCES areas(id) ON DELETE RESTRICT ON UPDATE CASCADE,
|
||||
location_id BIGINT REFERENCES locations(id) ON DELETE SET NULL ON UPDATE CASCADE,
|
||||
kandang_id BIGINT REFERENCES kandangs(id) ON DELETE SET NULL ON UPDATE CASCADE,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
deleted_at TIMESTAMPTZ,
|
||||
created_by BIGINT NOT NULL REFERENCES users(id)
|
||||
created_by BIGINT REFERENCES users(id) ON DELETE SET NULL ON UPDATE CASCADE
|
||||
);
|
||||
CREATE UNIQUE INDEX warehouses_name_unique ON warehouses (name) WHERE deleted_at IS NULL;
|
||||
|
||||
-- CUSTOMERS
|
||||
CREATE TABLE customers (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
name VARCHAR NOT NULL,
|
||||
pic_id BIGINT REFERENCES users(id),
|
||||
pic_id BIGINT REFERENCES users(id) ON DELETE SET NULL ON UPDATE CASCADE,
|
||||
type VARCHAR(50) NOT NULL,
|
||||
address TEXT NOT NULL,
|
||||
phone VARCHAR(20) NOT NULL,
|
||||
@@ -138,19 +147,21 @@ CREATE TABLE customers (
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
deleted_at TIMESTAMPTZ,
|
||||
created_by BIGINT NOT NULL REFERENCES users(id)
|
||||
created_by BIGINT REFERENCES users(id) ON DELETE SET NULL ON UPDATE CASCADE
|
||||
);
|
||||
CREATE UNIQUE INDEX customers_name_unique ON customers (name) WHERE deleted_at IS NULL;
|
||||
|
||||
-- NONSTOCK
|
||||
CREATE TABLE nonstocks (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
name VARCHAR NOT NULL,
|
||||
uom_id BIGINT NOT NULL REFERENCES uoms(id),
|
||||
uom_id BIGINT NOT NULL REFERENCES uoms(id) ON DELETE RESTRICT ON UPDATE CASCADE,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
deleted_at TIMESTAMPTZ,
|
||||
created_by BIGINT NOT NULL REFERENCES users(id)
|
||||
created_by BIGINT REFERENCES users(id) ON DELETE SET NULL ON UPDATE CASCADE
|
||||
);
|
||||
CREATE UNIQUE INDEX nonstocks_name_unique ON nonstocks (name) WHERE deleted_at IS NULL;
|
||||
|
||||
-- FCR
|
||||
CREATE TABLE fcrs (
|
||||
@@ -159,8 +170,9 @@ CREATE TABLE fcrs (
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
deleted_at TIMESTAMPTZ,
|
||||
created_by BIGINT NOT NULL REFERENCES users(id)
|
||||
created_by BIGINT REFERENCES users(id) ON DELETE SET NULL ON UPDATE CASCADE
|
||||
);
|
||||
CREATE UNIQUE INDEX fcrs_name_unique ON fcrs (name) WHERE deleted_at IS NULL;
|
||||
|
||||
CREATE TABLE fcr_standards (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
@@ -191,8 +203,9 @@ CREATE TABLE suppliers (
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
deleted_at TIMESTAMPTZ,
|
||||
created_by BIGINT NOT NULL REFERENCES users(id)
|
||||
created_by BIGINT REFERENCES users(id) ON DELETE SET NULL ON UPDATE CASCADE
|
||||
);
|
||||
CREATE UNIQUE INDEX suppliers_name_unique ON suppliers (name) WHERE deleted_at IS NULL;
|
||||
|
||||
-- PROJECTS
|
||||
CREATE TABLE projects (
|
||||
@@ -200,5 +213,5 @@ CREATE TABLE projects (
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
deleted_at TIMESTAMPTZ,
|
||||
created_by BIGINT NOT NULL REFERENCES users(id)
|
||||
created_by BIGINT REFERENCES users(id) ON DELETE SET NULL ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
@@ -3,7 +3,7 @@ package seed
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
mUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/models"
|
||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
func Run(db *gorm.DB) error {
|
||||
return db.Transaction(func(tx *gorm.DB) error {
|
||||
// ===== Users (user) =====
|
||||
user := mUser.User{
|
||||
user := entity.User{
|
||||
Email: "admin@mbugroup.id",
|
||||
IdUser: 1,
|
||||
Name: "Super Admin",
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package entities
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type Area struct {
|
||||
Id uint `gorm:"primaryKey"`
|
||||
Name string `gorm:"not null"`
|
||||
CreatedBy uint `gorm:"not null"`
|
||||
CreatedAt time.Time `gorm:"autoCreateTime"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||
|
||||
CreatedUser User `gorm:"foreignKey:CreatedBy;references:Id"`
|
||||
Locations []Location `gorm:"foreignKey:AreaId;references:Id"`
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package entities
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type Customer struct {
|
||||
Id uint `gorm:"primaryKey"`
|
||||
Name string `gorm:"not null"`
|
||||
PicId uint `gorm:"not null"`
|
||||
Type string `gorm:"not null"`
|
||||
Address string `gorm:"not null"`
|
||||
Phone string `gorm:"not null"`
|
||||
Email string `gorm:"not null"`
|
||||
AccountNumber string `gorm:"not null"`
|
||||
Balance float64 `gorm:"default:0"`
|
||||
CreatedBy uint `gorm:"not null"`
|
||||
CreatedAt time.Time `gorm:"autoCreateTime"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||
|
||||
CreatedUser User `gorm:"foreignKey:CreatedBy;references:Id"`
|
||||
Pic User `gorm:"foreignKey:PicId;references:Id"`
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package entities
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type Kandang struct {
|
||||
Id uint `gorm:"primaryKey"`
|
||||
Name string `gorm:"not null"`
|
||||
LocationId uint `gorm:"not null"`
|
||||
PicId uint `gorm:"not null"`
|
||||
CreatedBy uint `gorm:"not null"`
|
||||
CreatedAt time.Time `gorm:"autoCreateTime"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||
|
||||
CreatedUser User `gorm:"foreignKey:CreatedBy;references:Id"`
|
||||
Location Location `gorm:"foreignKey:LocationId;references:Id"`
|
||||
Pic User `gorm:"foreignKey:PicId;references:Id"`
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package entities
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type Location struct {
|
||||
Id uint `gorm:"primaryKey"`
|
||||
Name string `gorm:"not null"`
|
||||
Address string `gorm:"not null"`
|
||||
AreaId uint `gorm:"not null"`
|
||||
CreatedBy uint `gorm:"not null"`
|
||||
CreatedAt time.Time `gorm:"autoCreateTime"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||
|
||||
CreatedUser User `gorm:"foreignKey:CreatedBy;references:Id"`
|
||||
Area Area `gorm:"foreignKey:AreaId;references:Id"`
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package entities
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type Uom struct {
|
||||
Id uint `gorm:"primaryKey"`
|
||||
Name string `gorm:"not null"`
|
||||
CreatedBy uint `gorm:"not null"`
|
||||
CreatedAt time.Time `gorm:"autoCreateTime"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||
|
||||
CreatedUser User `gorm:"foreignKey:CreatedBy;references:Id"`
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package entities
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
Id uint `gorm:"primaryKey"`
|
||||
IdUser int64 `gorm:"uniqueIndex"`
|
||||
Email string `gorm:"uniqueIndex"`
|
||||
Name string `gorm:"not null"`
|
||||
CreatedAt time.Time `gorm:"autoCreateTime"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package entities
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type Warehouse struct {
|
||||
Id uint `gorm:"primaryKey"`
|
||||
Name string `gorm:"not null"`
|
||||
Type string `gorm:"not null"`
|
||||
AreaId uint `gorm:"not null"`
|
||||
LocationId *uint
|
||||
KandangId *uint
|
||||
CreatedBy uint `gorm:"not null"`
|
||||
CreatedAt time.Time `gorm:"autoCreateTime"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||
|
||||
CreatedUser User `gorm:"foreignKey:CreatedBy;references:Id"`
|
||||
Area Area `gorm:"foreignKey:AreaId;references:Id"`
|
||||
Location *Location `gorm:"foreignKey:LocationId;references:Id"`
|
||||
Kandang *Kandang `gorm:"foreignKey:KandangId;references:Id"`
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package trim
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
// JSONBody trims whitespace from string fields in JSON request bodies.
|
||||
func JSONBody() fiber.Handler {
|
||||
return func(c *fiber.Ctx) error {
|
||||
contentType := c.Get(fiber.HeaderContentType)
|
||||
if !strings.Contains(contentType, fiber.MIMEApplicationJSON) {
|
||||
return c.Next()
|
||||
}
|
||||
|
||||
body := c.Body()
|
||||
if len(body) == 0 {
|
||||
return c.Next()
|
||||
}
|
||||
|
||||
var payload any
|
||||
if err := json.Unmarshal(body, &payload); err != nil {
|
||||
return c.Next()
|
||||
}
|
||||
|
||||
trimStrings(payload)
|
||||
|
||||
buf := bytes.Buffer{}
|
||||
encoder := json.NewEncoder(&buf)
|
||||
encoder.SetEscapeHTML(false)
|
||||
if err := encoder.Encode(payload); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
trimmedBody := bytes.TrimSpace(buf.Bytes())
|
||||
c.Request().SetBody(trimmedBody)
|
||||
|
||||
return c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func trimStrings(value any) {
|
||||
switch v := value.(type) {
|
||||
case map[string]any:
|
||||
for key, val := range v {
|
||||
if str, ok := val.(string); ok {
|
||||
v[key] = strings.TrimSpace(str)
|
||||
continue
|
||||
}
|
||||
trimStrings(val)
|
||||
}
|
||||
case []any:
|
||||
for i, elem := range v {
|
||||
if str, ok := elem.(string); ok {
|
||||
v[i] = strings.TrimSpace(str)
|
||||
continue
|
||||
}
|
||||
trimStrings(elem)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"math"
|
||||
"strconv"
|
||||
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/modules/master/areas/dto"
|
||||
service "gitlab.com/mbugroup/lti-api.git/internal/modules/master/areas/services"
|
||||
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/master/areas/validations"
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/response"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
type AreaController struct {
|
||||
AreaService service.AreaService
|
||||
}
|
||||
|
||||
func NewAreaController(areaService service.AreaService) *AreaController {
|
||||
return &AreaController{
|
||||
AreaService: areaService,
|
||||
}
|
||||
}
|
||||
|
||||
func (u *AreaController) 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.AreaService.GetAll(c, query)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusOK).
|
||||
JSON(response.SuccessWithPaginate[dto.AreaListDTO]{
|
||||
Code: fiber.StatusOK,
|
||||
Status: "success",
|
||||
Message: "Get all areas successfully",
|
||||
Meta: response.Meta{
|
||||
Page: query.Page,
|
||||
Limit: query.Limit,
|
||||
TotalPages: int64(math.Ceil(float64(totalResults) / float64(query.Limit))),
|
||||
TotalResults: totalResults,
|
||||
},
|
||||
Data: dto.ToAreaListDTOs(result),
|
||||
})
|
||||
}
|
||||
|
||||
func (u *AreaController) 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.AreaService.GetOne(c, uint(id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusOK).
|
||||
JSON(response.Success{
|
||||
Code: fiber.StatusOK,
|
||||
Status: "success",
|
||||
Message: "Get area successfully",
|
||||
Data: dto.ToAreaListDTO(*result),
|
||||
})
|
||||
}
|
||||
|
||||
func (u *AreaController) 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.AreaService.CreateOne(c, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusCreated).
|
||||
JSON(response.Success{
|
||||
Code: fiber.StatusCreated,
|
||||
Status: "success",
|
||||
Message: "Create area successfully",
|
||||
Data: dto.ToAreaListDTO(*result),
|
||||
})
|
||||
}
|
||||
|
||||
func (u *AreaController) 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.AreaService.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 area successfully",
|
||||
Data: dto.ToAreaListDTO(*result),
|
||||
})
|
||||
}
|
||||
|
||||
func (u *AreaController) 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.AreaService.DeleteOne(c, uint(id)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusOK).
|
||||
JSON(response.Common{
|
||||
Code: fiber.StatusOK,
|
||||
Status: "success",
|
||||
Message: "Delete area successfully",
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
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 AreaBaseDTO struct {
|
||||
Id uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type AreaListDTO struct {
|
||||
AreaBaseDTO
|
||||
CreatedUser *userDTO.UserBaseDTO `json:"created_user"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
type AreaDetailDTO struct {
|
||||
AreaListDTO
|
||||
}
|
||||
|
||||
// === Mapper Functions ===
|
||||
|
||||
func ToAreaBaseDTO(e entity.Area) AreaBaseDTO {
|
||||
return AreaBaseDTO{
|
||||
Id: e.Id,
|
||||
Name: e.Name,
|
||||
}
|
||||
}
|
||||
|
||||
func ToAreaListDTO(e entity.Area) AreaListDTO {
|
||||
var createdUser *userDTO.UserBaseDTO
|
||||
if e.CreatedUser.Id != 0 {
|
||||
mapped := userDTO.ToUserBaseDTO(e.CreatedUser)
|
||||
createdUser = &mapped
|
||||
}
|
||||
|
||||
return AreaListDTO{
|
||||
AreaBaseDTO: ToAreaBaseDTO(e),
|
||||
CreatedUser: createdUser,
|
||||
CreatedAt: e.CreatedAt,
|
||||
UpdatedAt: e.UpdatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
func ToAreaListDTOs(e []entity.Area) []AreaListDTO {
|
||||
result := make([]AreaListDTO, len(e))
|
||||
for i, r := range e {
|
||||
result[i] = ToAreaListDTO(r)
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package areas
|
||||
|
||||
import (
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"gorm.io/gorm"
|
||||
|
||||
rArea "gitlab.com/mbugroup/lti-api.git/internal/modules/master/areas/repositories"
|
||||
sArea "gitlab.com/mbugroup/lti-api.git/internal/modules/master/areas/services"
|
||||
|
||||
rUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/repositories"
|
||||
sUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
||||
)
|
||||
|
||||
type AreaModule struct{}
|
||||
|
||||
func (AreaModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate *validator.Validate) {
|
||||
areaRepo := rArea.NewAreaRepository(db)
|
||||
userRepo := rUser.NewUserRepository(db)
|
||||
|
||||
areaService := sArea.NewAreaService(areaRepo, validate)
|
||||
userService := sUser.NewUserService(userRepo, validate)
|
||||
|
||||
AreaRoutes(router, userService, areaService)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type AreaRepository interface {
|
||||
repository.BaseRepository[entity.Area]
|
||||
NameExists(ctx context.Context, name string, excludeID *uint) (bool, error)
|
||||
}
|
||||
|
||||
type AreaRepositoryImpl struct {
|
||||
*repository.BaseRepositoryImpl[entity.Area]
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewAreaRepository(db *gorm.DB) AreaRepository {
|
||||
return &AreaRepositoryImpl{
|
||||
BaseRepositoryImpl: repository.NewBaseRepository[entity.Area](db),
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *AreaRepositoryImpl) NameExists(ctx context.Context, name string, excludeID *uint) (bool, error) {
|
||||
return repository.ExistsByName[entity.Area](ctx, r.db, name, excludeID)
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package areas
|
||||
|
||||
import (
|
||||
// m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
||||
controller "gitlab.com/mbugroup/lti-api.git/internal/modules/master/areas/controllers"
|
||||
area "gitlab.com/mbugroup/lti-api.git/internal/modules/master/areas/services"
|
||||
user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func AreaRoutes(v1 fiber.Router, u user.UserService, s area.AreaService) {
|
||||
ctrl := controller.NewAreaController(s)
|
||||
|
||||
route := v1.Group("/areas")
|
||||
|
||||
// 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)
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||
repository "gitlab.com/mbugroup/lti-api.git/internal/modules/master/areas/repositories"
|
||||
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/master/areas/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 AreaService interface {
|
||||
GetAll(ctx *fiber.Ctx, params *validation.Query) ([]entity.Area, int64, error)
|
||||
GetOne(ctx *fiber.Ctx, id uint) (*entity.Area, error)
|
||||
CreateOne(ctx *fiber.Ctx, req *validation.Create) (*entity.Area, error)
|
||||
UpdateOne(ctx *fiber.Ctx, req *validation.Update, id uint) (*entity.Area, error)
|
||||
DeleteOne(ctx *fiber.Ctx, id uint) error
|
||||
}
|
||||
|
||||
type areaService struct {
|
||||
Log *logrus.Logger
|
||||
Validate *validator.Validate
|
||||
Repository repository.AreaRepository
|
||||
}
|
||||
|
||||
func NewAreaService(repo repository.AreaRepository, validate *validator.Validate) AreaService {
|
||||
return &areaService{
|
||||
Log: utils.Log,
|
||||
Validate: validate,
|
||||
Repository: repo,
|
||||
}
|
||||
}
|
||||
|
||||
func (s areaService) withRelations(db *gorm.DB) *gorm.DB {
|
||||
return db.Preload("CreatedUser")
|
||||
}
|
||||
|
||||
func (s areaService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entity.Area, int64, error) {
|
||||
if err := s.Validate.Struct(params); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
offset := (params.Page - 1) * params.Limit
|
||||
|
||||
areas, 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 areas: %+v", err)
|
||||
return nil, 0, err
|
||||
}
|
||||
return areas, total, nil
|
||||
}
|
||||
|
||||
func (s areaService) GetOne(c *fiber.Ctx, id uint) (*entity.Area, error) {
|
||||
area, err := s.Repository.GetByID(c.Context(), id, s.withRelations)
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, fiber.NewError(fiber.StatusNotFound, "Area not found")
|
||||
}
|
||||
if err != nil {
|
||||
s.Log.Errorf("Failed get area by id: %+v", err)
|
||||
return nil, err
|
||||
}
|
||||
return area, nil
|
||||
}
|
||||
|
||||
func (s *areaService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entity.Area, error) {
|
||||
if err := s.Validate.Struct(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if exists, err := s.Repository.NameExists(c.Context(), req.Name, nil); err != nil {
|
||||
s.Log.Errorf("Failed to check area name: %+v", err)
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to check area name")
|
||||
} else if exists {
|
||||
return nil, fiber.NewError(fiber.StatusConflict, fmt.Sprintf("Area with name %s already exists", req.Name))
|
||||
}
|
||||
|
||||
//TODO: created by dummy
|
||||
createBody := &entity.Area{
|
||||
Name: req.Name,
|
||||
CreatedBy: 1,
|
||||
}
|
||||
|
||||
if err := s.Repository.CreateOne(c.Context(), createBody, nil); err != nil {
|
||||
s.Log.Errorf("Failed to create area: %+v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s.Repository.GetByID(c.Context(), createBody.Id, s.withRelations)
|
||||
}
|
||||
|
||||
func (s areaService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint) (*entity.Area, error) {
|
||||
if err := s.Validate.Struct(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
updateBody := make(map[string]any)
|
||||
|
||||
if req.Name != nil {
|
||||
if exists, err := s.Repository.NameExists(c.Context(), *req.Name, &id); err != nil {
|
||||
s.Log.Errorf("Failed to check area name: %+v", err)
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to check area name")
|
||||
} else if exists {
|
||||
return nil, fiber.NewError(fiber.StatusConflict, fmt.Sprintf("Area with name %s already exists", *req.Name))
|
||||
}
|
||||
updateBody["name"] = *req.Name
|
||||
}
|
||||
|
||||
if len(updateBody) == 0 {
|
||||
return s.GetOne(c, id)
|
||||
}
|
||||
|
||||
if err := s.Repository.PatchOne(c.Context(), id, updateBody, nil); err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, fiber.NewError(fiber.StatusNotFound, "Area not found")
|
||||
}
|
||||
s.Log.Errorf("Failed to update area: %+v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s.GetOne(c, id)
|
||||
}
|
||||
|
||||
func (s areaService) 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, "Area not found")
|
||||
}
|
||||
s.Log.Errorf("Failed to delete area: %+v", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -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"`
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"math"
|
||||
"strconv"
|
||||
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/modules/master/customers/dto"
|
||||
service "gitlab.com/mbugroup/lti-api.git/internal/modules/master/customers/services"
|
||||
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/master/customers/validations"
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/response"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
type CustomerController struct {
|
||||
CustomerService service.CustomerService
|
||||
}
|
||||
|
||||
func NewCustomerController(customerService service.CustomerService) *CustomerController {
|
||||
return &CustomerController{
|
||||
CustomerService: customerService,
|
||||
}
|
||||
}
|
||||
|
||||
func (u *CustomerController) 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.CustomerService.GetAll(c, query)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusOK).
|
||||
JSON(response.SuccessWithPaginate[dto.CustomerListDTO]{
|
||||
Code: fiber.StatusOK,
|
||||
Status: "success",
|
||||
Message: "Get all customers successfully",
|
||||
Meta: response.Meta{
|
||||
Page: query.Page,
|
||||
Limit: query.Limit,
|
||||
TotalPages: int64(math.Ceil(float64(totalResults) / float64(query.Limit))),
|
||||
TotalResults: totalResults,
|
||||
},
|
||||
Data: dto.ToCustomerListDTOs(result),
|
||||
})
|
||||
}
|
||||
|
||||
func (u *CustomerController) 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.CustomerService.GetOne(c, uint(id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusOK).
|
||||
JSON(response.Success{
|
||||
Code: fiber.StatusOK,
|
||||
Status: "success",
|
||||
Message: "Get customer successfully",
|
||||
Data: dto.ToCustomerListDTO(*result),
|
||||
})
|
||||
}
|
||||
|
||||
func (u *CustomerController) 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.CustomerService.CreateOne(c, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusCreated).
|
||||
JSON(response.Success{
|
||||
Code: fiber.StatusCreated,
|
||||
Status: "success",
|
||||
Message: "Create customer successfully",
|
||||
Data: dto.ToCustomerListDTO(*result),
|
||||
})
|
||||
}
|
||||
|
||||
func (u *CustomerController) 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.CustomerService.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 customer successfully",
|
||||
Data: dto.ToCustomerListDTO(*result),
|
||||
})
|
||||
}
|
||||
|
||||
func (u *CustomerController) 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.CustomerService.DeleteOne(c, uint(id)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusOK).
|
||||
JSON(response.Common{
|
||||
Code: fiber.StatusOK,
|
||||
Status: "success",
|
||||
Message: "Delete customer successfully",
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
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 CustomerBaseDTO struct {
|
||||
Id uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
PicId uint `json:"pic_id"`
|
||||
Type string `json:"type"`
|
||||
Address string `json:"address"`
|
||||
Phone string `json:"phone"`
|
||||
Email string `json:"email"`
|
||||
AccountNumber string `json:"account_number"`
|
||||
|
||||
Pic *userDTO.UserBaseDTO `json:"pic"`
|
||||
}
|
||||
|
||||
type CustomerListDTO struct {
|
||||
CustomerBaseDTO
|
||||
CreatedUser *userDTO.UserBaseDTO `json:"created_user"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
type CustomerDetailDTO struct {
|
||||
CustomerListDTO
|
||||
}
|
||||
|
||||
// === Mapper Functions ===
|
||||
|
||||
func ToCustomerBaseDTO(e entity.Customer) CustomerBaseDTO {
|
||||
var pic *userDTO.UserBaseDTO
|
||||
if e.Pic.Id != 0 {
|
||||
mapped := userDTO.ToUserBaseDTO(e.Pic)
|
||||
pic = &mapped
|
||||
}
|
||||
|
||||
return CustomerBaseDTO{
|
||||
Id: e.Id,
|
||||
Name: e.Name,
|
||||
PicId: e.PicId,
|
||||
Type: e.Type,
|
||||
Address: e.Address,
|
||||
Phone: e.Phone,
|
||||
Email: e.Email,
|
||||
AccountNumber: e.AccountNumber,
|
||||
Pic: pic,
|
||||
}
|
||||
}
|
||||
|
||||
func ToCustomerListDTO(e entity.Customer) CustomerListDTO {
|
||||
var createdUser *userDTO.UserBaseDTO
|
||||
if e.CreatedUser.Id != 0 {
|
||||
mapped := userDTO.ToUserBaseDTO(e.CreatedUser)
|
||||
createdUser = &mapped
|
||||
}
|
||||
|
||||
return CustomerListDTO{
|
||||
CustomerBaseDTO: ToCustomerBaseDTO(e),
|
||||
CreatedAt: e.CreatedAt,
|
||||
UpdatedAt: e.UpdatedAt,
|
||||
CreatedUser: createdUser,
|
||||
}
|
||||
}
|
||||
|
||||
func ToCustomerListDTOs(e []entity.Customer) []CustomerListDTO {
|
||||
result := make([]CustomerListDTO, len(e))
|
||||
for i, r := range e {
|
||||
result[i] = ToCustomerListDTO(r)
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package customers
|
||||
|
||||
import (
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"gorm.io/gorm"
|
||||
|
||||
rCustomer "gitlab.com/mbugroup/lti-api.git/internal/modules/master/customers/repositories"
|
||||
sCustomer "gitlab.com/mbugroup/lti-api.git/internal/modules/master/customers/services"
|
||||
|
||||
rUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/repositories"
|
||||
sUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
||||
)
|
||||
|
||||
type CustomerModule struct{}
|
||||
|
||||
func (CustomerModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate *validator.Validate) {
|
||||
customerRepo := rCustomer.NewCustomerRepository(db)
|
||||
userRepo := rUser.NewUserRepository(db)
|
||||
|
||||
customerService := sCustomer.NewCustomerService(customerRepo, validate)
|
||||
userService := sUser.NewUserService(userRepo, validate)
|
||||
|
||||
CustomerRoutes(router, userService, customerService)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type CustomerRepository interface {
|
||||
repository.BaseRepository[entity.Customer]
|
||||
PicExists(ctx context.Context, areaId uint) (bool, error)
|
||||
NameExists(ctx context.Context, name string, excludeID *uint) (bool, error)
|
||||
}
|
||||
|
||||
type CustomerRepositoryImpl struct {
|
||||
*repository.BaseRepositoryImpl[entity.Customer]
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewCustomerRepository(db *gorm.DB) CustomerRepository {
|
||||
return &CustomerRepositoryImpl{
|
||||
BaseRepositoryImpl: repository.NewBaseRepository[entity.Customer](db),
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *CustomerRepositoryImpl) PicExists(ctx context.Context, picId uint) (bool, error) {
|
||||
return repository.Exists[entity.User](ctx, r.db, picId)
|
||||
}
|
||||
|
||||
func (r *CustomerRepositoryImpl) NameExists(ctx context.Context, name string, excludeID *uint) (bool, error) {
|
||||
return repository.ExistsByName[entity.Customer](ctx, r.db, name, excludeID)
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package customers
|
||||
|
||||
import (
|
||||
// m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
||||
controller "gitlab.com/mbugroup/lti-api.git/internal/modules/master/customers/controllers"
|
||||
customer "gitlab.com/mbugroup/lti-api.git/internal/modules/master/customers/services"
|
||||
user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func CustomerRoutes(v1 fiber.Router, u user.UserService, s customer.CustomerService) {
|
||||
ctrl := controller.NewCustomerController(s)
|
||||
|
||||
route := v1.Group("/customers")
|
||||
|
||||
// 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)
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
common "gitlab.com/mbugroup/lti-api.git/internal/common/service"
|
||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||
repository "gitlab.com/mbugroup/lti-api.git/internal/modules/master/customers/repositories"
|
||||
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/master/customers/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 CustomerService interface {
|
||||
GetAll(ctx *fiber.Ctx, params *validation.Query) ([]entity.Customer, int64, error)
|
||||
GetOne(ctx *fiber.Ctx, id uint) (*entity.Customer, error)
|
||||
CreateOne(ctx *fiber.Ctx, req *validation.Create) (*entity.Customer, error)
|
||||
UpdateOne(ctx *fiber.Ctx, req *validation.Update, id uint) (*entity.Customer, error)
|
||||
DeleteOne(ctx *fiber.Ctx, id uint) error
|
||||
}
|
||||
|
||||
type customerService struct {
|
||||
Log *logrus.Logger
|
||||
Validate *validator.Validate
|
||||
Repository repository.CustomerRepository
|
||||
}
|
||||
|
||||
func NewCustomerService(repo repository.CustomerRepository, validate *validator.Validate) CustomerService {
|
||||
return &customerService{
|
||||
Log: utils.Log,
|
||||
Validate: validate,
|
||||
Repository: repo,
|
||||
}
|
||||
}
|
||||
|
||||
func (s customerService) withRelations(db *gorm.DB) *gorm.DB {
|
||||
return db.Preload("CreatedUser").Preload("Pic")
|
||||
}
|
||||
|
||||
func (s customerService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entity.Customer, int64, error) {
|
||||
if err := s.Validate.Struct(params); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
offset := (params.Page - 1) * params.Limit
|
||||
|
||||
customers, 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 customers: %+v", err)
|
||||
return nil, 0, err
|
||||
}
|
||||
return customers, total, nil
|
||||
}
|
||||
|
||||
func (s customerService) GetOne(c *fiber.Ctx, id uint) (*entity.Customer, error) {
|
||||
customer, err := s.Repository.GetByID(c.Context(), id, s.withRelations)
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, fiber.NewError(fiber.StatusNotFound, "Customer not found")
|
||||
}
|
||||
if err != nil {
|
||||
s.Log.Errorf("Failed get customer by id: %+v", err)
|
||||
return nil, err
|
||||
}
|
||||
return customer, nil
|
||||
}
|
||||
|
||||
func (s *customerService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entity.Customer, error) {
|
||||
if err := s.Validate.Struct(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if exists, err := s.Repository.NameExists(c.Context(), req.Name, nil); err != nil {
|
||||
s.Log.Errorf("Failed to check customer name: %+v", err)
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to check customer name")
|
||||
} else if exists {
|
||||
return nil, fiber.NewError(fiber.StatusConflict, fmt.Sprintf("Customer with name %s already exists", req.Name))
|
||||
}
|
||||
|
||||
typ := strings.ToUpper(req.Type)
|
||||
if !utils.IsValidCustomerSupplierType(typ) {
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid customer type")
|
||||
}
|
||||
|
||||
if err := common.EnsureRelations(c.Context(),
|
||||
common.RelationCheck{Name: "Pic", ID: &req.PicId, Exists: s.Repository.PicExists},
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//TODO: created by dummy
|
||||
createBody := &entity.Customer{
|
||||
Name: req.Name,
|
||||
PicId: req.PicId,
|
||||
Type: typ,
|
||||
Address: req.Address,
|
||||
Phone: req.Phone,
|
||||
Email: req.Email,
|
||||
AccountNumber: req.AccountNumber,
|
||||
CreatedBy: 1,
|
||||
}
|
||||
|
||||
if err := s.Repository.CreateOne(c.Context(), createBody, nil); err != nil {
|
||||
s.Log.Errorf("Failed to create customer: %+v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return createBody, nil
|
||||
}
|
||||
|
||||
func (s customerService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint) (*entity.Customer, error) {
|
||||
if err := s.Validate.Struct(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
updateBody := make(map[string]any)
|
||||
|
||||
if req.Name != nil {
|
||||
if exists, err := s.Repository.NameExists(c.Context(), *req.Name, &id); err != nil {
|
||||
s.Log.Errorf("Failed to check customer name: %+v", err)
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to check customer name")
|
||||
} else if exists {
|
||||
return nil, fiber.NewError(fiber.StatusConflict, fmt.Sprintf("Customer with name %s already exists", *req.Name))
|
||||
}
|
||||
updateBody["name"] = *req.Name
|
||||
}
|
||||
|
||||
if req.PicId != nil {
|
||||
if err := common.EnsureRelations(c.Context(), common.RelationCheck{Name: "Pic", ID: req.PicId, Exists: s.Repository.PicExists}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
updateBody["pic_id"] = *req.PicId
|
||||
}
|
||||
|
||||
if req.Type != nil {
|
||||
typ := strings.ToUpper(*req.Type)
|
||||
if !utils.IsValidCustomerSupplierType(typ) {
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, "invalid customer type")
|
||||
}
|
||||
updateBody["type"] = typ
|
||||
}
|
||||
|
||||
if len(updateBody) == 0 {
|
||||
return s.GetOne(c, id)
|
||||
}
|
||||
|
||||
if err := s.Repository.PatchOne(c.Context(), id, updateBody, nil); err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, fiber.NewError(fiber.StatusNotFound, "Customer not found")
|
||||
}
|
||||
s.Log.Errorf("Failed to update customer: %+v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s.GetOne(c, id)
|
||||
}
|
||||
|
||||
func (s customerService) 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, "Customer not found")
|
||||
}
|
||||
s.Log.Errorf("Failed to delete customer: %+v", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package validation
|
||||
|
||||
type Create struct {
|
||||
Name string `json:"name" validate:"required_strict,min=3"`
|
||||
PicId uint `json:"pic_id" validate:"required_strict,number,gt=0"`
|
||||
Type string `json:"type" validate:"required_strict"`
|
||||
Address string `json:"address" validate:"required_strict"`
|
||||
Phone string `json:"phone" validate:"required_strict,max=20"`
|
||||
Email string `json:"email" validate:"required_strict,email"`
|
||||
AccountNumber string `json:"account_number" validate:"required_strict"`
|
||||
}
|
||||
|
||||
type Update struct {
|
||||
Name *string `json:"name,omitempty" validate:"omitempty,max=50"`
|
||||
PicId *uint `json:"pic_id,omitempty" validate:"omitempty,number,gt=0"`
|
||||
Type *string `json:"type,omitempty" validate:"omitempty"`
|
||||
Address *string `json:"address,omitempty" validate:"omitempty"`
|
||||
Phone *string `json:"phone,omitempty" validate:"omitempty"`
|
||||
Email *string `json:"email,omitempty" validate:"omitempty"`
|
||||
AccountNumber *string `json:"account_number,omitempty" validate:"omitempty"`
|
||||
}
|
||||
|
||||
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"`
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"math"
|
||||
"strconv"
|
||||
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/dto"
|
||||
service "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/services"
|
||||
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/validations"
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/response"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
type KandangController struct {
|
||||
KandangService service.KandangService
|
||||
}
|
||||
|
||||
func NewKandangController(kandangService service.KandangService) *KandangController {
|
||||
return &KandangController{
|
||||
KandangService: kandangService,
|
||||
}
|
||||
}
|
||||
|
||||
func (u *KandangController) 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.KandangService.GetAll(c, query)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusOK).
|
||||
JSON(response.SuccessWithPaginate[dto.KandangListDTO]{
|
||||
Code: fiber.StatusOK,
|
||||
Status: "success",
|
||||
Message: "Get all kandangs successfully",
|
||||
Meta: response.Meta{
|
||||
Page: query.Page,
|
||||
Limit: query.Limit,
|
||||
TotalPages: int64(math.Ceil(float64(totalResults) / float64(query.Limit))),
|
||||
TotalResults: totalResults,
|
||||
},
|
||||
Data: dto.ToKandangListDTOs(result),
|
||||
})
|
||||
}
|
||||
|
||||
func (u *KandangController) 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.KandangService.GetOne(c, uint(id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusOK).
|
||||
JSON(response.Success{
|
||||
Code: fiber.StatusOK,
|
||||
Status: "success",
|
||||
Message: "Get kandang successfully",
|
||||
Data: dto.ToKandangListDTO(*result),
|
||||
})
|
||||
}
|
||||
|
||||
func (u *KandangController) 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.KandangService.CreateOne(c, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusCreated).
|
||||
JSON(response.Success{
|
||||
Code: fiber.StatusCreated,
|
||||
Status: "success",
|
||||
Message: "Create kandang successfully",
|
||||
Data: dto.ToKandangListDTO(*result),
|
||||
})
|
||||
}
|
||||
|
||||
func (u *KandangController) 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.KandangService.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 kandang successfully",
|
||||
Data: dto.ToKandangListDTO(*result),
|
||||
})
|
||||
}
|
||||
|
||||
func (u *KandangController) 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.KandangService.DeleteOne(c, uint(id)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusOK).
|
||||
JSON(response.Common{
|
||||
Code: fiber.StatusOK,
|
||||
Status: "success",
|
||||
Message: "Delete kandang successfully",
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package dto
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||
locationDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/locations/dto"
|
||||
userDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/users/dto"
|
||||
)
|
||||
|
||||
// === DTO Structs ===
|
||||
|
||||
type KandangBaseDTO struct {
|
||||
Id uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Location *locationDTO.LocationBaseDTO `json:"location"`
|
||||
Pic *userDTO.UserBaseDTO `json:"pic"`
|
||||
}
|
||||
|
||||
type KandangListDTO struct {
|
||||
KandangBaseDTO
|
||||
CreatedUser *userDTO.UserBaseDTO `json:"created_user"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
type KandangDetailDTO struct {
|
||||
KandangListDTO
|
||||
}
|
||||
|
||||
// === Mapper Functions ===
|
||||
|
||||
func ToKandangBaseDTO(e entity.Kandang) KandangBaseDTO {
|
||||
var location *locationDTO.LocationBaseDTO
|
||||
if e.Location.Id != 0 {
|
||||
mapped := locationDTO.ToLocationBaseDTO(e.Location)
|
||||
location = &mapped
|
||||
}
|
||||
|
||||
var pic *userDTO.UserBaseDTO
|
||||
if e.Pic.Id != 0 {
|
||||
mapped := userDTO.ToUserBaseDTO(e.Pic)
|
||||
pic = &mapped
|
||||
}
|
||||
|
||||
return KandangBaseDTO{
|
||||
Id: e.Id,
|
||||
Name: e.Name,
|
||||
Location: location,
|
||||
Pic: pic,
|
||||
}
|
||||
}
|
||||
|
||||
func ToKandangListDTO(e entity.Kandang) KandangListDTO {
|
||||
var createdUser *userDTO.UserBaseDTO
|
||||
if e.CreatedUser.Id != 0 {
|
||||
mapped := userDTO.ToUserBaseDTO(e.CreatedUser)
|
||||
createdUser = &mapped
|
||||
}
|
||||
|
||||
return KandangListDTO{
|
||||
KandangBaseDTO: ToKandangBaseDTO(e),
|
||||
CreatedAt: e.CreatedAt,
|
||||
UpdatedAt: e.UpdatedAt,
|
||||
CreatedUser: createdUser,
|
||||
}
|
||||
}
|
||||
|
||||
func ToKandangListDTOs(e []entity.Kandang) []KandangListDTO {
|
||||
result := make([]KandangListDTO, len(e))
|
||||
for i, r := range e {
|
||||
result[i] = ToKandangListDTO(r)
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package kandangs
|
||||
|
||||
import (
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"gorm.io/gorm"
|
||||
|
||||
rKandang "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/repositories"
|
||||
sKandang "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/services"
|
||||
|
||||
rUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/repositories"
|
||||
sUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
||||
)
|
||||
|
||||
type KandangModule struct{}
|
||||
|
||||
func (KandangModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate *validator.Validate) {
|
||||
kandangRepo := rKandang.NewKandangRepository(db)
|
||||
userRepo := rUser.NewUserRepository(db)
|
||||
|
||||
kandangService := sKandang.NewKandangService(kandangRepo, validate)
|
||||
userService := sUser.NewUserService(userRepo, validate)
|
||||
|
||||
KandangRoutes(router, userService, kandangService)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type KandangRepository interface {
|
||||
repository.BaseRepository[entity.Kandang]
|
||||
LocationExists(ctx context.Context, areaId uint) (bool, error)
|
||||
PicExists(ctx context.Context, areaId uint) (bool, error)
|
||||
NameExists(ctx context.Context, name string, excludeID *uint) (bool, error)
|
||||
}
|
||||
|
||||
type KandangRepositoryImpl struct {
|
||||
*repository.BaseRepositoryImpl[entity.Kandang]
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewKandangRepository(db *gorm.DB) KandangRepository {
|
||||
return &KandangRepositoryImpl{
|
||||
BaseRepositoryImpl: repository.NewBaseRepository[entity.Kandang](db),
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *KandangRepositoryImpl) LocationExists(ctx context.Context, locationId uint) (bool, error) {
|
||||
return repository.Exists[entity.Location](ctx, r.db, locationId)
|
||||
}
|
||||
|
||||
func (r *KandangRepositoryImpl) PicExists(ctx context.Context, picId uint) (bool, error) {
|
||||
return repository.Exists[entity.User](ctx, r.db, picId)
|
||||
}
|
||||
|
||||
func (r *KandangRepositoryImpl) NameExists(ctx context.Context, name string, excludeID *uint) (bool, error) {
|
||||
return repository.ExistsByName[entity.Kandang](ctx, r.db, name, excludeID)
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package kandangs
|
||||
|
||||
import (
|
||||
// m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
||||
controller "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/controllers"
|
||||
kandang "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/services"
|
||||
user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func KandangRoutes(v1 fiber.Router, u user.UserService, s kandang.KandangService) {
|
||||
ctrl := controller.NewKandangController(s)
|
||||
|
||||
route := v1.Group("/kandangs")
|
||||
|
||||
// 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)
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
common "gitlab.com/mbugroup/lti-api.git/internal/common/service"
|
||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||
repository "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/repositories"
|
||||
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/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 KandangService interface {
|
||||
GetAll(ctx *fiber.Ctx, params *validation.Query) ([]entity.Kandang, int64, error)
|
||||
GetOne(ctx *fiber.Ctx, id uint) (*entity.Kandang, error)
|
||||
CreateOne(ctx *fiber.Ctx, req *validation.Create) (*entity.Kandang, error)
|
||||
UpdateOne(ctx *fiber.Ctx, req *validation.Update, id uint) (*entity.Kandang, error)
|
||||
DeleteOne(ctx *fiber.Ctx, id uint) error
|
||||
}
|
||||
|
||||
type kandangService struct {
|
||||
Log *logrus.Logger
|
||||
Validate *validator.Validate
|
||||
Repository repository.KandangRepository
|
||||
}
|
||||
|
||||
func NewKandangService(repo repository.KandangRepository, validate *validator.Validate) KandangService {
|
||||
return &kandangService{
|
||||
Log: utils.Log,
|
||||
Validate: validate,
|
||||
Repository: repo,
|
||||
}
|
||||
}
|
||||
|
||||
func (s kandangService) withRelations(db *gorm.DB) *gorm.DB {
|
||||
return db.Preload("CreatedUser").Preload("Location").Preload("Pic")
|
||||
}
|
||||
|
||||
func (s kandangService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entity.Kandang, int64, error) {
|
||||
if err := s.Validate.Struct(params); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
offset := (params.Page - 1) * params.Limit
|
||||
|
||||
kandangs, 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 kandangs: %+v", err)
|
||||
return nil, 0, err
|
||||
}
|
||||
return kandangs, total, nil
|
||||
}
|
||||
|
||||
func (s kandangService) GetOne(c *fiber.Ctx, id uint) (*entity.Kandang, error) {
|
||||
kandang, err := s.Repository.GetByID(c.Context(), id, s.withRelations)
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, fiber.NewError(fiber.StatusNotFound, "Kandang not found")
|
||||
}
|
||||
if err != nil {
|
||||
s.Log.Errorf("Failed get kandang by id: %+v", err)
|
||||
return nil, err
|
||||
}
|
||||
return kandang, nil
|
||||
}
|
||||
|
||||
func (s *kandangService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entity.Kandang, error) {
|
||||
if err := s.Validate.Struct(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if exists, err := s.Repository.NameExists(c.Context(), req.Name, nil); err != nil {
|
||||
s.Log.Errorf("Failed to check kandang name: %+v", err)
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to check kandang name")
|
||||
} else if exists {
|
||||
return nil, fiber.NewError(fiber.StatusConflict, fmt.Sprintf("Kandang with name %s already exists", req.Name))
|
||||
}
|
||||
|
||||
if err := common.EnsureRelations(c.Context(),
|
||||
common.RelationCheck{Name: "Location", ID: &req.LocationId, Exists: s.Repository.LocationExists},
|
||||
common.RelationCheck{Name: "Pic", ID: &req.PicId, Exists: s.Repository.PicExists},
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//TODO: created by dummy
|
||||
createBody := &entity.Kandang{
|
||||
Name: req.Name,
|
||||
LocationId: req.LocationId,
|
||||
PicId: req.PicId,
|
||||
CreatedBy: 1,
|
||||
}
|
||||
|
||||
if err := s.Repository.CreateOne(c.Context(), createBody, nil); err != nil {
|
||||
s.Log.Errorf("Failed to create kandang: %+v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s.Repository.GetByID(c.Context(), createBody.Id, s.withRelations)
|
||||
}
|
||||
|
||||
func (s kandangService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint) (*entity.Kandang, error) {
|
||||
if err := s.Validate.Struct(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
updateBody := make(map[string]any)
|
||||
|
||||
if req.Name != nil {
|
||||
if exists, err := s.Repository.NameExists(c.Context(), *req.Name, &id); err != nil {
|
||||
s.Log.Errorf("Failed to check kandang name: %+v", err)
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to check kandang name")
|
||||
} else if exists {
|
||||
return nil, fiber.NewError(fiber.StatusConflict, fmt.Sprintf("Kandang with name %s already exists", *req.Name))
|
||||
}
|
||||
updateBody["name"] = *req.Name
|
||||
}
|
||||
|
||||
if req.LocationId != nil {
|
||||
if err := common.EnsureRelations(c.Context(), common.RelationCheck{Name: "Location", ID: req.LocationId, Exists: s.Repository.LocationExists}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
updateBody["location_id"] = *req.LocationId
|
||||
}
|
||||
|
||||
if req.PicId != nil {
|
||||
if err := common.EnsureRelations(c.Context(), common.RelationCheck{Name: "Pic", ID: req.PicId, Exists: s.Repository.PicExists}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
updateBody["pic_id"] = *req.PicId
|
||||
}
|
||||
|
||||
if len(updateBody) == 0 {
|
||||
return s.GetOne(c, id)
|
||||
}
|
||||
|
||||
if err := s.Repository.PatchOne(c.Context(), id, updateBody, nil); err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, fiber.NewError(fiber.StatusNotFound, "Kandang not found")
|
||||
}
|
||||
s.Log.Errorf("Failed to update kandang: %+v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s.GetOne(c, id)
|
||||
}
|
||||
|
||||
func (s kandangService) 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, "Kandang not found")
|
||||
}
|
||||
s.Log.Errorf("Failed to delete kandang: %+v", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package validation
|
||||
|
||||
type Create struct {
|
||||
Name string `json:"name" validate:"required_strict,min=3"`
|
||||
LocationId uint `json:"location_id" validate:"required_strict,number,gt=0"`
|
||||
PicId uint `json:"pic_id" validate:"required_strict,number,gt=0"`
|
||||
}
|
||||
|
||||
type Update struct {
|
||||
Name *string `json:"name,omitempty" validate:"omitempty,max=50"`
|
||||
LocationId *uint `json:"location_id,omitempty" validate:"omitempty,number,gt=0"`
|
||||
PicId *uint `json:"pic_id,omitempty" validate:"omitempty,number,gt=0"`
|
||||
}
|
||||
|
||||
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"`
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"math"
|
||||
"strconv"
|
||||
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/modules/master/locations/dto"
|
||||
service "gitlab.com/mbugroup/lti-api.git/internal/modules/master/locations/services"
|
||||
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/master/locations/validations"
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/response"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
type LocationController struct {
|
||||
LocationService service.LocationService
|
||||
}
|
||||
|
||||
func NewLocationController(locationService service.LocationService) *LocationController {
|
||||
return &LocationController{
|
||||
LocationService: locationService,
|
||||
}
|
||||
}
|
||||
|
||||
func (u *LocationController) 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.LocationService.GetAll(c, query)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusOK).
|
||||
JSON(response.SuccessWithPaginate[dto.LocationListDTO]{
|
||||
Code: fiber.StatusOK,
|
||||
Status: "success",
|
||||
Message: "Get all locations successfully",
|
||||
Meta: response.Meta{
|
||||
Page: query.Page,
|
||||
Limit: query.Limit,
|
||||
TotalPages: int64(math.Ceil(float64(totalResults) / float64(query.Limit))),
|
||||
TotalResults: totalResults,
|
||||
},
|
||||
Data: dto.ToLocationListDTOs(result),
|
||||
})
|
||||
}
|
||||
|
||||
func (u *LocationController) 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.LocationService.GetOne(c, uint(id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusOK).
|
||||
JSON(response.Success{
|
||||
Code: fiber.StatusOK,
|
||||
Status: "success",
|
||||
Message: "Get location successfully",
|
||||
Data: dto.ToLocationListDTO(*result),
|
||||
})
|
||||
}
|
||||
|
||||
func (u *LocationController) 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.LocationService.CreateOne(c, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusCreated).
|
||||
JSON(response.Success{
|
||||
Code: fiber.StatusCreated,
|
||||
Status: "success",
|
||||
Message: "Create location successfully",
|
||||
Data: dto.ToLocationListDTO(*result),
|
||||
})
|
||||
}
|
||||
|
||||
func (u *LocationController) 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.LocationService.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 location successfully",
|
||||
Data: dto.ToLocationListDTO(*result),
|
||||
})
|
||||
}
|
||||
|
||||
func (u *LocationController) 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.LocationService.DeleteOne(c, uint(id)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusOK).
|
||||
JSON(response.Common{
|
||||
Code: fiber.StatusOK,
|
||||
Status: "success",
|
||||
Message: "Delete location successfully",
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package dto
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||
areaDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/areas/dto"
|
||||
userDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/users/dto"
|
||||
)
|
||||
|
||||
// === DTO Structs ===
|
||||
|
||||
type LocationBaseDTO struct {
|
||||
Id uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Address string `json:"address"`
|
||||
Area *areaDTO.AreaBaseDTO `json:"area"`
|
||||
}
|
||||
|
||||
type LocationListDTO struct {
|
||||
LocationBaseDTO
|
||||
CreatedUser *userDTO.UserBaseDTO `json:"created_user"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
type LocationDetailDTO struct {
|
||||
LocationListDTO
|
||||
}
|
||||
|
||||
// === Mapper Functions ===
|
||||
|
||||
func ToLocationBaseDTO(e entity.Location) LocationBaseDTO {
|
||||
var area *areaDTO.AreaBaseDTO
|
||||
if e.Area.Id != 0 {
|
||||
mapped := areaDTO.ToAreaBaseDTO(e.Area)
|
||||
area = &mapped
|
||||
}
|
||||
|
||||
return LocationBaseDTO{
|
||||
Id: e.Id,
|
||||
Name: e.Name,
|
||||
Address: e.Address,
|
||||
Area: area,
|
||||
}
|
||||
}
|
||||
|
||||
func ToLocationListDTO(e entity.Location) LocationListDTO {
|
||||
var createdUser *userDTO.UserBaseDTO
|
||||
if e.CreatedUser.Id != 0 {
|
||||
mapped := userDTO.ToUserBaseDTO(e.CreatedUser)
|
||||
createdUser = &mapped
|
||||
}
|
||||
|
||||
return LocationListDTO{
|
||||
LocationBaseDTO: ToLocationBaseDTO(e),
|
||||
CreatedUser: createdUser,
|
||||
CreatedAt: e.CreatedAt,
|
||||
UpdatedAt: e.UpdatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
func ToLocationListDTOs(e []entity.Location) []LocationListDTO {
|
||||
result := make([]LocationListDTO, len(e))
|
||||
for i, r := range e {
|
||||
result[i] = ToLocationListDTO(r)
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package locations
|
||||
|
||||
import (
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"gorm.io/gorm"
|
||||
|
||||
rLocation "gitlab.com/mbugroup/lti-api.git/internal/modules/master/locations/repositories"
|
||||
sLocation "gitlab.com/mbugroup/lti-api.git/internal/modules/master/locations/services"
|
||||
|
||||
rUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/repositories"
|
||||
sUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
||||
)
|
||||
|
||||
type LocationModule struct{}
|
||||
|
||||
func (LocationModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate *validator.Validate) {
|
||||
locationRepo := rLocation.NewLocationRepository(db)
|
||||
userRepo := rUser.NewUserRepository(db)
|
||||
|
||||
locationService := sLocation.NewLocationService(locationRepo, validate)
|
||||
userService := sUser.NewUserService(userRepo, validate)
|
||||
|
||||
LocationRoutes(router, userService, locationService)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type LocationRepository interface {
|
||||
repository.BaseRepository[entity.Location]
|
||||
AreaExists(ctx context.Context, areaId uint) (bool, error)
|
||||
NameExists(ctx context.Context, name string, excludeID *uint) (bool, error)
|
||||
}
|
||||
|
||||
type LocationRepositoryImpl struct {
|
||||
*repository.BaseRepositoryImpl[entity.Location]
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewLocationRepository(db *gorm.DB) LocationRepository {
|
||||
return &LocationRepositoryImpl{
|
||||
BaseRepositoryImpl: repository.NewBaseRepository[entity.Location](db),
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *LocationRepositoryImpl) AreaExists(ctx context.Context, areaId uint) (bool, error) {
|
||||
return repository.Exists[entity.Area](ctx, r.db, areaId)
|
||||
}
|
||||
|
||||
func (r *LocationRepositoryImpl) NameExists(ctx context.Context, name string, excludeID *uint) (bool, error) {
|
||||
return repository.ExistsByName[entity.Location](ctx, r.db, name, excludeID)
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package locations
|
||||
|
||||
import (
|
||||
// m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
||||
controller "gitlab.com/mbugroup/lti-api.git/internal/modules/master/locations/controllers"
|
||||
location "gitlab.com/mbugroup/lti-api.git/internal/modules/master/locations/services"
|
||||
user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func LocationRoutes(v1 fiber.Router, u user.UserService, s location.LocationService) {
|
||||
ctrl := controller.NewLocationController(s)
|
||||
|
||||
route := v1.Group("/locations")
|
||||
|
||||
// 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)
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
common "gitlab.com/mbugroup/lti-api.git/internal/common/service"
|
||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||
repository "gitlab.com/mbugroup/lti-api.git/internal/modules/master/locations/repositories"
|
||||
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/master/locations/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 LocationService interface {
|
||||
GetAll(ctx *fiber.Ctx, params *validation.Query) ([]entity.Location, int64, error)
|
||||
GetOne(ctx *fiber.Ctx, id uint) (*entity.Location, error)
|
||||
CreateOne(ctx *fiber.Ctx, req *validation.Create) (*entity.Location, error)
|
||||
UpdateOne(ctx *fiber.Ctx, req *validation.Update, id uint) (*entity.Location, error)
|
||||
DeleteOne(ctx *fiber.Ctx, id uint) error
|
||||
}
|
||||
|
||||
type locationService struct {
|
||||
Log *logrus.Logger
|
||||
Validate *validator.Validate
|
||||
Repository repository.LocationRepository
|
||||
}
|
||||
|
||||
func NewLocationService(repo repository.LocationRepository, validate *validator.Validate) LocationService {
|
||||
return &locationService{
|
||||
Log: utils.Log,
|
||||
Validate: validate,
|
||||
Repository: repo,
|
||||
}
|
||||
}
|
||||
|
||||
func (s locationService) withRelations(db *gorm.DB) *gorm.DB {
|
||||
return db.Preload("Area").Preload("CreatedUser")
|
||||
}
|
||||
|
||||
func (s locationService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entity.Location, int64, error) {
|
||||
if err := s.Validate.Struct(params); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
offset := (params.Page - 1) * params.Limit
|
||||
|
||||
locations, total, err := s.Repository.GetAll(c.Context(), offset, params.Limit, func(db *gorm.DB) *gorm.DB {
|
||||
db = s.withRelations(db)
|
||||
if params.Search != "" {
|
||||
db = 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 locations: %+v", err)
|
||||
return nil, 0, err
|
||||
}
|
||||
return locations, total, nil
|
||||
}
|
||||
|
||||
func (s locationService) GetOne(c *fiber.Ctx, id uint) (*entity.Location, error) {
|
||||
location, err := s.Repository.GetByID(c.Context(), id, s.withRelations)
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, fiber.NewError(fiber.StatusNotFound, "Location not found")
|
||||
}
|
||||
if err != nil {
|
||||
s.Log.Errorf("Failed get location by id: %+v", err)
|
||||
return nil, err
|
||||
}
|
||||
return location, nil
|
||||
}
|
||||
|
||||
func (s *locationService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entity.Location, error) {
|
||||
if err := s.Validate.Struct(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if exists, err := s.Repository.NameExists(c.Context(), req.Name, nil); err != nil {
|
||||
s.Log.Errorf("Failed to check location name: %+v", err)
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to check location name")
|
||||
} else if exists {
|
||||
return nil, fiber.NewError(fiber.StatusConflict, fmt.Sprintf("Location with name %s already exists", req.Name))
|
||||
}
|
||||
|
||||
if err := common.EnsureRelations(c.Context(),
|
||||
common.RelationCheck{Name: "Area", ID: &req.AreaId, Exists: s.Repository.AreaExists},
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//TODO: created by dummy
|
||||
createBody := &entity.Location{
|
||||
Name: req.Name,
|
||||
Address: req.Address,
|
||||
AreaId: req.AreaId,
|
||||
CreatedBy: 1,
|
||||
}
|
||||
|
||||
if err := s.Repository.CreateOne(c.Context(), createBody, nil); err != nil {
|
||||
s.Log.Errorf("Failed to create location: %+v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
created, err := s.Repository.GetByID(c.Context(), createBody.Id, s.withRelations)
|
||||
if err != nil {
|
||||
s.Log.Errorf("Failed to reload created location: %+v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return created, nil
|
||||
}
|
||||
|
||||
func (s locationService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint) (*entity.Location, error) {
|
||||
if err := s.Validate.Struct(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
updateBody := make(map[string]any)
|
||||
|
||||
if req.Name != nil {
|
||||
if exists, err := s.Repository.NameExists(c.Context(), *req.Name, &id); err != nil {
|
||||
s.Log.Errorf("Failed to check location name: %+v", err)
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to check location name")
|
||||
} else if exists {
|
||||
return nil, fiber.NewError(fiber.StatusConflict, fmt.Sprintf("Location with name %s already exists", *req.Name))
|
||||
}
|
||||
updateBody["name"] = *req.Name
|
||||
}
|
||||
|
||||
if req.Address != nil {
|
||||
updateBody["address"] = *req.Address
|
||||
}
|
||||
|
||||
if req.AreaId != nil {
|
||||
if err := common.EnsureRelations(c.Context(), common.RelationCheck{Name: "Area", ID: req.AreaId, Exists: s.Repository.AreaExists}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
updateBody["area_id"] = *req.AreaId
|
||||
}
|
||||
|
||||
if len(updateBody) == 0 {
|
||||
return s.GetOne(c, id)
|
||||
}
|
||||
|
||||
if err := s.Repository.PatchOne(c.Context(), id, updateBody, nil); err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, fiber.NewError(fiber.StatusNotFound, "Location not found")
|
||||
}
|
||||
s.Log.Errorf("Failed to update location: %+v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s.GetOne(c, id)
|
||||
}
|
||||
|
||||
func (s locationService) 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, "Location not found")
|
||||
}
|
||||
s.Log.Errorf("Failed to delete location: %+v", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package validation
|
||||
|
||||
type Create struct {
|
||||
Name string `json:"name" validate:"required_strict,min=3"`
|
||||
Address string `json:"address" validate:"required_strict"`
|
||||
AreaId uint `json:"area_id" validate:"required_strict,number,gt=0"`
|
||||
}
|
||||
|
||||
type Update struct {
|
||||
Name *string `json:"name,omitempty" validate:"omitempty,max=50"`
|
||||
Address *string `json:"address,omitempty" validate:"omitempty"`
|
||||
AreaId *uint `json:"area_id,omitempty" validate:"omitempty,number,gt=0"`
|
||||
}
|
||||
|
||||
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"`
|
||||
}
|
||||
@@ -7,15 +7,25 @@ import (
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"gorm.io/gorm"
|
||||
|
||||
areas "gitlab.com/mbugroup/lti-api.git/internal/modules/master/areas"
|
||||
customers "gitlab.com/mbugroup/lti-api.git/internal/modules/master/customers"
|
||||
kandangs "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs"
|
||||
locations "gitlab.com/mbugroup/lti-api.git/internal/modules/master/locations"
|
||||
uoms "gitlab.com/mbugroup/lti-api.git/internal/modules/master/uoms"
|
||||
warehouses "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses"
|
||||
// MODULE IMPORTS
|
||||
)
|
||||
|
||||
func RegisterRoutes(router fiber.Router, db *gorm.DB, validate *validator.Validate) {
|
||||
group := router.Group("/master")
|
||||
group := router.Group("/master-data")
|
||||
|
||||
allModules := []modules.Module{
|
||||
uoms.UomModule{},
|
||||
areas.AreaModule{},
|
||||
locations.LocationModule{},
|
||||
kandangs.KandangModule{},
|
||||
warehouses.WarehouseModule{},
|
||||
customers.CustomerModule{},
|
||||
// MODULE REGISTRY
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,8 @@ package dto
|
||||
import (
|
||||
"time"
|
||||
|
||||
model "gitlab.com/mbugroup/lti-api.git/internal/modules/master/uoms/models"
|
||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||
userDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/users/dto"
|
||||
)
|
||||
|
||||
// === DTO Structs ===
|
||||
@@ -15,6 +16,7 @@ type UomBaseDTO struct {
|
||||
|
||||
type UomListDTO struct {
|
||||
UomBaseDTO
|
||||
CreatedUser *userDTO.UserBaseDTO `json:"created_user"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
@@ -25,24 +27,31 @@ type UomDetailDTO struct {
|
||||
|
||||
// === Mapper Functions ===
|
||||
|
||||
func ToUomBaseDTO(m model.Uom) UomBaseDTO {
|
||||
func ToUomBaseDTO(e entity.Uom) UomBaseDTO {
|
||||
return UomBaseDTO{
|
||||
Id: m.Id,
|
||||
Name: m.Name,
|
||||
Id: e.Id,
|
||||
Name: e.Name,
|
||||
}
|
||||
}
|
||||
|
||||
func ToUomListDTO(m model.Uom) UomListDTO {
|
||||
func ToUomListDTO(e entity.Uom) UomListDTO {
|
||||
var createdUser *userDTO.UserBaseDTO
|
||||
if e.CreatedUser.Id != 0 {
|
||||
mapped := userDTO.ToUserBaseDTO(e.CreatedUser)
|
||||
createdUser = &mapped
|
||||
}
|
||||
|
||||
return UomListDTO{
|
||||
UomBaseDTO: ToUomBaseDTO(m),
|
||||
CreatedAt: m.CreatedAt,
|
||||
UpdatedAt: m.UpdatedAt,
|
||||
UomBaseDTO: ToUomBaseDTO(e),
|
||||
CreatedAt: e.CreatedAt,
|
||||
UpdatedAt: e.UpdatedAt,
|
||||
CreatedUser: createdUser,
|
||||
}
|
||||
}
|
||||
|
||||
func ToUomListDTOs(m []model.Uom) []UomListDTO {
|
||||
result := make([]UomListDTO, len(m))
|
||||
for i, r := range m {
|
||||
func ToUomListDTOs(e []entity.Uom) []UomListDTO {
|
||||
result := make([]UomListDTO, len(e))
|
||||
for i, r := range e {
|
||||
result[i] = ToUomListDTO(r)
|
||||
}
|
||||
return result
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
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"`
|
||||
}
|
||||
@@ -1,21 +1,30 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
model "gitlab.com/mbugroup/lti-api.git/internal/modules/master/uoms/models"
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/repository"
|
||||
"context"
|
||||
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type UomRepository interface {
|
||||
repository.BaseRepository[model.Uom]
|
||||
repository.BaseRepository[entity.Uom]
|
||||
NameExists(ctx context.Context, name string, excludeID *uint) (bool, error)
|
||||
}
|
||||
|
||||
type UomRepositoryImpl struct {
|
||||
*repository.BaseRepositoryImpl[model.Uom]
|
||||
*repository.BaseRepositoryImpl[entity.Uom]
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewUomRepository(db *gorm.DB) UomRepository {
|
||||
return &UomRepositoryImpl{
|
||||
BaseRepositoryImpl: repository.NewBaseRepository[model.Uom](db),
|
||||
BaseRepositoryImpl: repository.NewBaseRepository[entity.Uom](db),
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *UomRepositoryImpl) NameExists(ctx context.Context, name string, excludeID *uint) (bool, error) {
|
||||
return repository.ExistsByName[entity.Uom](ctx, r.db, name, excludeID)
|
||||
}
|
||||
|
||||
@@ -2,8 +2,9 @@ package service
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
model "gitlab.com/mbugroup/lti-api.git/internal/modules/master/uoms/models"
|
||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||
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"
|
||||
@@ -15,10 +16,10 @@ import (
|
||||
)
|
||||
|
||||
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)
|
||||
GetAll(ctx *fiber.Ctx, params *validation.Query) ([]entity.Uom, int64, error)
|
||||
GetOne(ctx *fiber.Ctx, id uint) (*entity.Uom, error)
|
||||
CreateOne(ctx *fiber.Ctx, req *validation.Create) (*entity.Uom, error)
|
||||
UpdateOne(ctx *fiber.Ctx, req *validation.Update, id uint) (*entity.Uom, error)
|
||||
DeleteOne(ctx *fiber.Ctx, id uint) error
|
||||
}
|
||||
|
||||
@@ -35,7 +36,12 @@ func NewUomService(repo repository.UomRepository, validate *validator.Validate)
|
||||
Repository: repo,
|
||||
}
|
||||
}
|
||||
func (s uomService) GetAll(c *fiber.Ctx, params *validation.Query) ([]model.Uom, int64, error) {
|
||||
|
||||
func (s uomService) withRelations(db *gorm.DB) *gorm.DB {
|
||||
return db.Preload("CreatedUser")
|
||||
}
|
||||
|
||||
func (s uomService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entity.Uom, int64, error) {
|
||||
if err := s.Validate.Struct(params); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
@@ -43,6 +49,7 @@ func (s uomService) GetAll(c *fiber.Ctx, params *validation.Query) ([]model.Uom,
|
||||
offset := (params.Page - 1) * params.Limit
|
||||
|
||||
uoms, 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+"%")
|
||||
}
|
||||
@@ -56,8 +63,8 @@ func (s uomService) GetAll(c *fiber.Ctx, params *validation.Query) ([]model.Uom,
|
||||
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)
|
||||
func (s uomService) GetOne(c *fiber.Ctx, id uint) (*entity.Uom, error) {
|
||||
uom, err := s.Repository.GetByID(c.Context(), id, s.withRelations)
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, fiber.NewError(fiber.StatusNotFound, "Uom not found")
|
||||
}
|
||||
@@ -68,12 +75,20 @@ func (s uomService) GetOne(c *fiber.Ctx, id uint) (*model.Uom, error) {
|
||||
return uom, nil
|
||||
}
|
||||
|
||||
func (s *uomService) CreateOne(c *fiber.Ctx, req *validation.Create) (*model.Uom, error) {
|
||||
func (s *uomService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entity.Uom, error) {
|
||||
if err := s.Validate.Struct(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
createBody := &model.Uom{
|
||||
if exists, err := s.Repository.NameExists(c.Context(), req.Name, nil); err != nil {
|
||||
s.Log.Errorf("Failed to check uom name: %+v", err)
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to check uom name")
|
||||
} else if exists {
|
||||
return nil, fiber.NewError(fiber.StatusConflict, fmt.Sprintf("Uom with name %s already exists", req.Name))
|
||||
}
|
||||
|
||||
//TODO: created by dummy
|
||||
createBody := &entity.Uom{
|
||||
Name: req.Name,
|
||||
CreatedBy: 1,
|
||||
}
|
||||
@@ -83,10 +98,10 @@ func (s *uomService) CreateOne(c *fiber.Ctx, req *validation.Create) (*model.Uom
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return createBody, nil
|
||||
return s.Repository.GetByID(c.Context(), createBody.Id, s.withRelations)
|
||||
}
|
||||
|
||||
func (s uomService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint) (*model.Uom, error) {
|
||||
func (s uomService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint) (*entity.Uom, error) {
|
||||
if err := s.Validate.Struct(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -94,9 +109,19 @@ func (s uomService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint) (*m
|
||||
updateBody := make(map[string]any)
|
||||
|
||||
if req.Name != nil {
|
||||
if exists, err := s.Repository.NameExists(c.Context(), *req.Name, &id); err != nil {
|
||||
s.Log.Errorf("Failed to check uom name: %+v", err)
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to check uom name")
|
||||
} else if exists {
|
||||
return nil, fiber.NewError(fiber.StatusConflict, fmt.Sprintf("Uom with name %s already exists", *req.Name))
|
||||
}
|
||||
updateBody["name"] = *req.Name
|
||||
}
|
||||
|
||||
if len(updateBody) == 0 {
|
||||
return s.GetOne(c, id)
|
||||
}
|
||||
|
||||
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")
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"math"
|
||||
"strconv"
|
||||
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/dto"
|
||||
service "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/services"
|
||||
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/validations"
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/response"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
type WarehouseController struct {
|
||||
WarehouseService service.WarehouseService
|
||||
}
|
||||
|
||||
func NewWarehouseController(warehouseService service.WarehouseService) *WarehouseController {
|
||||
return &WarehouseController{
|
||||
WarehouseService: warehouseService,
|
||||
}
|
||||
}
|
||||
|
||||
func (u *WarehouseController) 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.WarehouseService.GetAll(c, query)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusOK).
|
||||
JSON(response.SuccessWithPaginate[dto.WarehouseListDTO]{
|
||||
Code: fiber.StatusOK,
|
||||
Status: "success",
|
||||
Message: "Get all warehouses successfully",
|
||||
Meta: response.Meta{
|
||||
Page: query.Page,
|
||||
Limit: query.Limit,
|
||||
TotalPages: int64(math.Ceil(float64(totalResults) / float64(query.Limit))),
|
||||
TotalResults: totalResults,
|
||||
},
|
||||
Data: dto.ToWarehouseListDTOs(result),
|
||||
})
|
||||
}
|
||||
|
||||
func (u *WarehouseController) 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.WarehouseService.GetOne(c, uint(id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusOK).
|
||||
JSON(response.Success{
|
||||
Code: fiber.StatusOK,
|
||||
Status: "success",
|
||||
Message: "Get warehouse successfully",
|
||||
Data: dto.ToWarehouseListDTO(*result),
|
||||
})
|
||||
}
|
||||
|
||||
func (u *WarehouseController) 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.WarehouseService.CreateOne(c, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusCreated).
|
||||
JSON(response.Success{
|
||||
Code: fiber.StatusCreated,
|
||||
Status: "success",
|
||||
Message: "Create warehouse successfully",
|
||||
Data: dto.ToWarehouseListDTO(*result),
|
||||
})
|
||||
}
|
||||
|
||||
func (u *WarehouseController) 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.WarehouseService.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 warehouse successfully",
|
||||
Data: dto.ToWarehouseListDTO(*result),
|
||||
})
|
||||
}
|
||||
|
||||
func (u *WarehouseController) 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.WarehouseService.DeleteOne(c, uint(id)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusOK).
|
||||
JSON(response.Common{
|
||||
Code: fiber.StatusOK,
|
||||
Status: "success",
|
||||
Message: "Delete warehouse successfully",
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package dto
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||
areaDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/areas/dto"
|
||||
kandangDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/dto"
|
||||
locationDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/locations/dto"
|
||||
userDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/users/dto"
|
||||
)
|
||||
|
||||
// === DTO Structs ===
|
||||
|
||||
type WarehouseBaseDTO struct {
|
||||
Id uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Area *areaDTO.AreaBaseDTO `json:"area"`
|
||||
Location *locationDTO.LocationBaseDTO `json:"location"`
|
||||
Kandang *kandangDTO.KandangBaseDTO `json:"kandang"`
|
||||
}
|
||||
|
||||
type WarehouseListDTO struct {
|
||||
WarehouseBaseDTO
|
||||
CreatedUser *userDTO.UserBaseDTO `json:"created_user"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
type WarehouseDetailDTO struct {
|
||||
WarehouseListDTO
|
||||
}
|
||||
|
||||
// === Mapper Functions ===
|
||||
|
||||
func ToWarehouseBaseDTO(e entity.Warehouse) WarehouseBaseDTO {
|
||||
var area *areaDTO.AreaBaseDTO
|
||||
if e.Area.Id != 0 {
|
||||
mapped := areaDTO.ToAreaBaseDTO(e.Area)
|
||||
area = &mapped
|
||||
}
|
||||
|
||||
var location *locationDTO.LocationBaseDTO
|
||||
if e.Location != nil && e.Location.Id != 0 {
|
||||
mapped := locationDTO.ToLocationBaseDTO(*e.Location)
|
||||
location = &mapped
|
||||
}
|
||||
|
||||
var kandang *kandangDTO.KandangBaseDTO
|
||||
if e.Kandang != nil && e.Kandang.Id != 0 {
|
||||
mapped := kandangDTO.ToKandangBaseDTO(*e.Kandang)
|
||||
kandang = &mapped
|
||||
}
|
||||
|
||||
return WarehouseBaseDTO{
|
||||
Id: e.Id,
|
||||
Name: e.Name,
|
||||
Type: e.Type,
|
||||
Area: area,
|
||||
Location: location,
|
||||
Kandang: kandang,
|
||||
}
|
||||
}
|
||||
|
||||
func ToWarehouseListDTO(e entity.Warehouse) WarehouseListDTO {
|
||||
var createdUser *userDTO.UserBaseDTO
|
||||
if e.CreatedUser.Id != 0 {
|
||||
mapped := userDTO.ToUserBaseDTO(e.CreatedUser)
|
||||
createdUser = &mapped
|
||||
}
|
||||
|
||||
return WarehouseListDTO{
|
||||
WarehouseBaseDTO: ToWarehouseBaseDTO(e),
|
||||
CreatedAt: e.CreatedAt,
|
||||
UpdatedAt: e.UpdatedAt,
|
||||
CreatedUser: createdUser,
|
||||
}
|
||||
}
|
||||
|
||||
func ToWarehouseListDTOs(e []entity.Warehouse) []WarehouseListDTO {
|
||||
result := make([]WarehouseListDTO, len(e))
|
||||
for i, r := range e {
|
||||
result[i] = ToWarehouseListDTO(r)
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package warehouses
|
||||
|
||||
import (
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"gorm.io/gorm"
|
||||
|
||||
rWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/repositories"
|
||||
sWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/services"
|
||||
|
||||
rUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/repositories"
|
||||
sUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
||||
)
|
||||
|
||||
type WarehouseModule struct{}
|
||||
|
||||
func (WarehouseModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate *validator.Validate) {
|
||||
warehouseRepo := rWarehouse.NewWarehouseRepository(db)
|
||||
userRepo := rUser.NewUserRepository(db)
|
||||
|
||||
warehouseService := sWarehouse.NewWarehouseService(warehouseRepo, validate)
|
||||
userService := sUser.NewUserService(userRepo, validate)
|
||||
|
||||
WarehouseRoutes(router, userService, warehouseService)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type WarehouseRepository interface {
|
||||
repository.BaseRepository[entity.Warehouse]
|
||||
AreaExists(ctx context.Context, areaId uint) (bool, error)
|
||||
LocationExists(ctx context.Context, locationId uint) (bool, error)
|
||||
KandangExists(ctx context.Context, kandangId uint) (bool, error)
|
||||
NameExists(ctx context.Context, name string, excludeID *uint) (bool, error)
|
||||
}
|
||||
|
||||
type WarehouseRepositoryImpl struct {
|
||||
*repository.BaseRepositoryImpl[entity.Warehouse]
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewWarehouseRepository(db *gorm.DB) WarehouseRepository {
|
||||
return &WarehouseRepositoryImpl{
|
||||
BaseRepositoryImpl: repository.NewBaseRepository[entity.Warehouse](db),
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *WarehouseRepositoryImpl) AreaExists(ctx context.Context, areaId uint) (bool, error) {
|
||||
return repository.Exists[entity.Area](ctx, r.db, areaId)
|
||||
}
|
||||
|
||||
func (r *WarehouseRepositoryImpl) LocationExists(ctx context.Context, locationId uint) (bool, error) {
|
||||
return repository.Exists[entity.Location](ctx, r.db, locationId)
|
||||
}
|
||||
|
||||
func (r *WarehouseRepositoryImpl) KandangExists(ctx context.Context, kandangId uint) (bool, error) {
|
||||
return repository.Exists[entity.Kandang](ctx, r.db, kandangId)
|
||||
}
|
||||
|
||||
func (r *WarehouseRepositoryImpl) NameExists(ctx context.Context, name string, excludeID *uint) (bool, error) {
|
||||
return repository.ExistsByName[entity.Warehouse](ctx, r.db, name, excludeID)
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package warehouses
|
||||
|
||||
import (
|
||||
// m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
||||
controller "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/controllers"
|
||||
warehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/services"
|
||||
user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func WarehouseRoutes(v1 fiber.Router, u user.UserService, s warehouse.WarehouseService) {
|
||||
ctrl := controller.NewWarehouseController(s)
|
||||
|
||||
route := v1.Group("/warehouses")
|
||||
|
||||
// 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)
|
||||
}
|
||||
@@ -0,0 +1,268 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
common "gitlab.com/mbugroup/lti-api.git/internal/common/service"
|
||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||
repository "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/repositories"
|
||||
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/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 WarehouseService interface {
|
||||
GetAll(ctx *fiber.Ctx, params *validation.Query) ([]entity.Warehouse, int64, error)
|
||||
GetOne(ctx *fiber.Ctx, id uint) (*entity.Warehouse, error)
|
||||
CreateOne(ctx *fiber.Ctx, req *validation.Create) (*entity.Warehouse, error)
|
||||
UpdateOne(ctx *fiber.Ctx, req *validation.Update, id uint) (*entity.Warehouse, error)
|
||||
DeleteOne(ctx *fiber.Ctx, id uint) error
|
||||
}
|
||||
|
||||
type warehouseService struct {
|
||||
Log *logrus.Logger
|
||||
Validate *validator.Validate
|
||||
Repository repository.WarehouseRepository
|
||||
}
|
||||
|
||||
func NewWarehouseService(repo repository.WarehouseRepository, validate *validator.Validate) WarehouseService {
|
||||
return &warehouseService{
|
||||
Log: utils.Log,
|
||||
Validate: validate,
|
||||
Repository: repo,
|
||||
}
|
||||
}
|
||||
|
||||
func (s warehouseService) withRelations(db *gorm.DB) *gorm.DB {
|
||||
return db.Preload("CreatedUser").Preload("Area").Preload("Location").Preload("Kandang")
|
||||
}
|
||||
|
||||
func (s warehouseService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entity.Warehouse, int64, error) {
|
||||
if err := s.Validate.Struct(params); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
offset := (params.Page - 1) * params.Limit
|
||||
|
||||
warehouses, 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 warehouses: %+v", err)
|
||||
return nil, 0, err
|
||||
}
|
||||
return warehouses, total, nil
|
||||
}
|
||||
|
||||
func (s warehouseService) GetOne(c *fiber.Ctx, id uint) (*entity.Warehouse, error) {
|
||||
warehouse, err := s.Repository.GetByID(c.Context(), id, s.withRelations)
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, fiber.NewError(fiber.StatusNotFound, "Warehouse not found")
|
||||
}
|
||||
if err != nil {
|
||||
s.Log.Errorf("Failed get warehouse by id: %+v", err)
|
||||
return nil, err
|
||||
}
|
||||
return warehouse, nil
|
||||
}
|
||||
|
||||
func (s *warehouseService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entity.Warehouse, error) {
|
||||
if err := s.Validate.Struct(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if exists, err := s.Repository.NameExists(c.Context(), req.Name, nil); err != nil {
|
||||
s.Log.Errorf("Failed to check warehouse name: %+v", err)
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to check warehouse name")
|
||||
} else if exists {
|
||||
return nil, fiber.NewError(fiber.StatusConflict, fmt.Sprintf("Warehouse with name %s already exists", req.Name))
|
||||
}
|
||||
|
||||
typ := strings.ToUpper(req.Type)
|
||||
if err := validateWarehouseTypeRequirements(typ, &req.AreaId, req.LocationId, req.KandangId); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//? Check relation area, location, and kandang
|
||||
if err := common.EnsureRelations(c.Context(),
|
||||
common.RelationCheck{Name: "Area", ID: &req.AreaId, Exists: s.Repository.AreaExists},
|
||||
common.RelationCheck{Name: "Location", ID: req.LocationId, Exists: s.Repository.LocationExists},
|
||||
common.RelationCheck{Name: "Kandang", ID: req.KandangId, Exists: s.Repository.KandangExists},
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//TODO: created by dummy
|
||||
createBody := &entity.Warehouse{
|
||||
Name: req.Name,
|
||||
Type: typ,
|
||||
AreaId: req.AreaId,
|
||||
CreatedBy: 1,
|
||||
}
|
||||
if req.LocationId != nil {
|
||||
createBody.LocationId = req.LocationId
|
||||
}
|
||||
if req.KandangId != nil {
|
||||
createBody.KandangId = req.KandangId
|
||||
}
|
||||
|
||||
if err := s.Repository.CreateOne(c.Context(), createBody, nil); err != nil {
|
||||
s.Log.Errorf("Failed to create warehouse: %+v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s.Repository.GetByID(c.Context(), createBody.Id, s.withRelations)
|
||||
}
|
||||
|
||||
func (s warehouseService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint) (*entity.Warehouse, error) {
|
||||
if err := s.Validate.Struct(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
existing, err := s.GetOne(c, id)
|
||||
if err != nil {
|
||||
s.Log.Errorf("Failed to get warehouse for update: %+v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
updateBody := make(map[string]any)
|
||||
|
||||
if req.Name != nil {
|
||||
if exists, err := s.Repository.NameExists(c.Context(), *req.Name, &id); err != nil {
|
||||
s.Log.Errorf("Failed to check warehouse name: %+v", err)
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to check warehouse name")
|
||||
} else if exists {
|
||||
return nil, fiber.NewError(fiber.StatusConflict, fmt.Sprintf("Warehouse with name %s already exists", *req.Name))
|
||||
}
|
||||
updateBody["name"] = *req.Name
|
||||
}
|
||||
if req.Type != nil {
|
||||
normalizedType := strings.ToUpper(*req.Type)
|
||||
updateBody["type"] = normalizedType
|
||||
req.Type = &normalizedType
|
||||
}
|
||||
if req.AreaId != nil {
|
||||
if err := common.EnsureRelations(c.Context(), common.RelationCheck{Name: "Area", ID: req.AreaId, Exists: s.Repository.AreaExists}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
updateBody["area_id"] = *req.AreaId
|
||||
}
|
||||
if req.LocationId != nil {
|
||||
if err := common.EnsureRelations(c.Context(), common.RelationCheck{Name: "Location", ID: req.LocationId, Exists: s.Repository.LocationExists}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
updateBody["location_id"] = req.LocationId
|
||||
}
|
||||
if req.KandangId != nil {
|
||||
if err := common.EnsureRelations(c.Context(), common.RelationCheck{Name: "Kandang", ID: req.KandangId, Exists: s.Repository.KandangExists}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
updateBody["kandang_id"] = req.KandangId
|
||||
}
|
||||
|
||||
finalType := strings.ToUpper(existing.Type)
|
||||
if req.Type != nil {
|
||||
finalType = *req.Type
|
||||
}
|
||||
finalAreaId := existing.AreaId
|
||||
if req.AreaId != nil {
|
||||
finalAreaId = *req.AreaId
|
||||
}
|
||||
finalLocationId := existing.LocationId
|
||||
if req.LocationId != nil {
|
||||
finalLocationId = req.LocationId
|
||||
}
|
||||
finalKandangId := existing.KandangId
|
||||
if req.KandangId != nil {
|
||||
finalKandangId = req.KandangId
|
||||
}
|
||||
|
||||
if err := validateWarehouseTypeRequirements(finalType, &finalAreaId, finalLocationId, finalKandangId); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(updateBody) == 0 {
|
||||
return s.GetOne(c, id)
|
||||
}
|
||||
|
||||
if err := s.Repository.PatchOne(c.Context(), id, updateBody, nil); err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, fiber.NewError(fiber.StatusNotFound, "Warehouse not found")
|
||||
}
|
||||
s.Log.Errorf("Failed to update warehouse: %+v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s.GetOne(c, id)
|
||||
}
|
||||
|
||||
func (s warehouseService) 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, "Warehouse not found")
|
||||
}
|
||||
s.Log.Errorf("Failed to delete warehouse: %+v", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateWarehouseTypeRequirements(typ string, areaID *uint, locationID *uint, kandangID *uint) error {
|
||||
switch utils.WarehouseType(typ) {
|
||||
case utils.WarehouseTypeArea:
|
||||
if areaID == nil || *areaID == 0 {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "area_id is required when type is AREA")
|
||||
}
|
||||
if locationID != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "location_id must not be provided when type is AREA")
|
||||
}
|
||||
if kandangID != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "kandang_id must not be provided when type is AREA")
|
||||
}
|
||||
return nil
|
||||
case utils.WarehouseTypeLokasi:
|
||||
if areaID == nil || *areaID == 0 {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "area_id is required when type is LOCATION")
|
||||
}
|
||||
if locationID == nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "location_id is required when type is LOCATION")
|
||||
}
|
||||
if *locationID == 0 {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "location_id must be greater than 0 when type is LOCATION")
|
||||
}
|
||||
if kandangID != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "kandang_id must not be provided when type is LOCATION")
|
||||
}
|
||||
return nil
|
||||
case utils.WarehouseTypeKandang:
|
||||
if areaID == nil || *areaID == 0 {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "area_id is required when type is KANDANG")
|
||||
}
|
||||
if locationID == nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "location_id is required when type is KANDANG")
|
||||
}
|
||||
if *locationID == 0 {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "location_id must be greater than 0 when type is KANDANG")
|
||||
}
|
||||
if kandangID == nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "kandang_id is required when type is KANDANG")
|
||||
}
|
||||
if *kandangID == 0 {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "kandang_id must be greater than 0 when type is KANDANG")
|
||||
}
|
||||
return nil
|
||||
default:
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid warehouse type")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package validation
|
||||
|
||||
type Create struct {
|
||||
Name string `json:"name" validate:"required_strict,min=3"`
|
||||
Type string `json:"type" validate:"required_strict"`
|
||||
AreaId uint `json:"area_id" validate:"required_strict,number,gt=0"`
|
||||
LocationId *uint `json:"location_id,omitempty" validate:"omitempty,number,gt=0"`
|
||||
KandangId *uint `json:"kandang_id,omitempty" validate:"omitempty,number,gt=0"`
|
||||
}
|
||||
|
||||
type Update struct {
|
||||
Name *string `json:"name,omitempty" validate:"omitempty,max=50"`
|
||||
Type *string `json:"type,omitempty" validate:"omitempty"`
|
||||
AreaId *uint `json:"area_id,omitempty" validate:"omitempty,number,gt=0"`
|
||||
LocationId *uint `json:"location_id,omitempty" validate:"omitempty,number,gt=0"`
|
||||
KandangId *uint `json:"kandang_id,omitempty" validate:"omitempty,number,gt=0"`
|
||||
}
|
||||
|
||||
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"`
|
||||
}
|
||||
@@ -3,7 +3,7 @@ package dto
|
||||
import (
|
||||
"time"
|
||||
|
||||
model "gitlab.com/mbugroup/lti-api.git/internal/modules/users/models"
|
||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||
)
|
||||
|
||||
// === DTO Structs ===
|
||||
@@ -27,14 +27,16 @@ type UserDetailDTO struct {
|
||||
|
||||
// === Mapper Functions ===
|
||||
|
||||
func ToUserBaseDTO(m model.User) UserBaseDTO {
|
||||
func ToUserBaseDTO(m entity.User) UserBaseDTO {
|
||||
return UserBaseDTO{
|
||||
Id: m.Id,
|
||||
IdUser: m.IdUser,
|
||||
Email: m.Email,
|
||||
Name: m.Name,
|
||||
}
|
||||
}
|
||||
|
||||
func ToUserListDTO(m model.User) UserListDTO {
|
||||
func ToUserListDTO(m entity.User) UserListDTO {
|
||||
return UserListDTO{
|
||||
UserBaseDTO: ToUserBaseDTO(m),
|
||||
CreatedAt: m.CreatedAt,
|
||||
@@ -42,7 +44,7 @@ func ToUserListDTO(m model.User) UserListDTO {
|
||||
}
|
||||
}
|
||||
|
||||
func ToUserListDTOs(m []model.User) []UserListDTO {
|
||||
func ToUserListDTOs(m []entity.User) []UserListDTO {
|
||||
result := make([]UserListDTO, len(m))
|
||||
for i, r := range m {
|
||||
result[i] = ToUserListDTO(r)
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
Id uint `gorm:"primaryKey"`
|
||||
IdUser int64 `gorm:"uniqueIndex"`
|
||||
Email string `gorm:"uniqueIndex"`
|
||||
Name string `gorm:"not null"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||
}
|
||||
@@ -1,21 +1,21 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
model "gitlab.com/mbugroup/lti-api.git/internal/modules/users/models"
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/repository"
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type UserRepository interface {
|
||||
repository.BaseRepository[model.User]
|
||||
repository.BaseRepository[entity.User]
|
||||
}
|
||||
|
||||
type UserRepositoryImpl struct {
|
||||
*repository.BaseRepositoryImpl[model.User]
|
||||
*repository.BaseRepositoryImpl[entity.User]
|
||||
}
|
||||
|
||||
func NewUserRepository(db *gorm.DB) UserRepository {
|
||||
return &UserRepositoryImpl{
|
||||
BaseRepositoryImpl: repository.NewBaseRepository[model.User](db),
|
||||
BaseRepositoryImpl: repository.NewBaseRepository[entity.User](db),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ package service
|
||||
import (
|
||||
"errors"
|
||||
|
||||
model "gitlab.com/mbugroup/lti-api.git/internal/modules/users/models"
|
||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||
repository "gitlab.com/mbugroup/lti-api.git/internal/modules/users/repositories"
|
||||
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/users/validations"
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
||||
@@ -15,10 +15,10 @@ import (
|
||||
)
|
||||
|
||||
type UserService interface {
|
||||
GetAll(ctx *fiber.Ctx, params *validation.Query) ([]model.User, int64, error)
|
||||
GetOne(ctx *fiber.Ctx, id uint) (*model.User, error)
|
||||
CreateOne(ctx *fiber.Ctx, req *validation.Create) (*model.User, error)
|
||||
UpdateOne(ctx *fiber.Ctx, req *validation.Update, id uint) (*model.User, error)
|
||||
GetAll(ctx *fiber.Ctx, params *validation.Query) ([]entity.User, int64, error)
|
||||
GetOne(ctx *fiber.Ctx, id uint) (*entity.User, error)
|
||||
CreateOne(ctx *fiber.Ctx, req *validation.Create) (*entity.User, error)
|
||||
UpdateOne(ctx *fiber.Ctx, req *validation.Update, id uint) (*entity.User, error)
|
||||
DeleteOne(ctx *fiber.Ctx, id uint) error
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ func NewUserService(repo repository.UserRepository, validate *validator.Validate
|
||||
Repository: repo,
|
||||
}
|
||||
}
|
||||
func (s userService) GetAll(c *fiber.Ctx, params *validation.Query) ([]model.User, int64, error) {
|
||||
func (s userService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entity.User, int64, error) {
|
||||
if err := s.Validate.Struct(params); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
@@ -56,7 +56,7 @@ func (s userService) GetAll(c *fiber.Ctx, params *validation.Query) ([]model.Use
|
||||
return users, total, nil
|
||||
}
|
||||
|
||||
func (s userService) GetOne(c *fiber.Ctx, id uint) (*model.User, error) {
|
||||
func (s userService) GetOne(c *fiber.Ctx, id uint) (*entity.User, error) {
|
||||
user, err := s.Repository.GetByID(c.Context(), id, nil)
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, fiber.NewError(fiber.StatusNotFound, "User not found")
|
||||
@@ -68,12 +68,12 @@ func (s userService) GetOne(c *fiber.Ctx, id uint) (*model.User, error) {
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (s *userService) CreateOne(c *fiber.Ctx, req *validation.Create) (*model.User, error) {
|
||||
func (s *userService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entity.User, error) {
|
||||
if err := s.Validate.Struct(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
createBody := &model.User{
|
||||
createBody := &entity.User{
|
||||
Name: req.Name,
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ func (s *userService) CreateOne(c *fiber.Ctx, req *validation.Create) (*model.Us
|
||||
return createBody, nil
|
||||
}
|
||||
|
||||
func (s userService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint) (*model.User, error) {
|
||||
func (s userService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint) (*entity.User, error) {
|
||||
if err := s.Validate.Struct(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -96,6 +96,10 @@ func (s userService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint) (*
|
||||
updateBody["name"] = *req.Name
|
||||
}
|
||||
|
||||
if len(updateBody) == 0 {
|
||||
return s.GetOne(c, id)
|
||||
}
|
||||
|
||||
if err := s.Repository.PatchOne(c.Context(), id, updateBody, nil); err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, fiber.NewError(fiber.StatusNotFound, "User not found")
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
package route
|
||||
|
||||
import (
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/common/validation"
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/modules"
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/validation"
|
||||
trim "gitlab.com/mbugroup/lti-api.git/internal/middleware/trim"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"gorm.io/gorm"
|
||||
@@ -15,6 +16,7 @@ import (
|
||||
func Routes(app *fiber.App, db *gorm.DB) {
|
||||
validate := validation.Validator()
|
||||
api := app.Group("/api")
|
||||
api.Use(trim.JSONBody())
|
||||
|
||||
// root modules di sini
|
||||
allModules := []modules.Module{
|
||||
|
||||
@@ -9,6 +9,29 @@ const (
|
||||
FlagIsActive FlagType = "IS_ACTIVE"
|
||||
)
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// WarehouseType
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
type WarehouseType string
|
||||
|
||||
const (
|
||||
WarehouseTypeArea WarehouseType = "AREA"
|
||||
WarehouseTypeLokasi WarehouseType = "LOKASI"
|
||||
WarehouseTypeKandang WarehouseType = "KANDANG"
|
||||
)
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// WarehouseType
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
type CustomerSupplierType string
|
||||
|
||||
const (
|
||||
CustomerSupplierTypeBisnis CustomerSupplierType = "BISNIS"
|
||||
CustomerSupplierTypeIndividual CustomerSupplierType = "INDIVIDUAL"
|
||||
)
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// Validators
|
||||
// -------------------------------------------------------------------
|
||||
@@ -21,6 +44,22 @@ func IsValidFlagType(v string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func IsValidWarehouseType(v string) bool {
|
||||
switch WarehouseType(v) {
|
||||
case WarehouseTypeArea, WarehouseTypeLokasi, WarehouseTypeKandang:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func IsValidCustomerSupplierType(v string) bool {
|
||||
switch CustomerSupplierType(v) {
|
||||
case CustomerSupplierTypeBisnis, CustomerSupplierTypeIndividual:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// example use
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,8 +3,8 @@ package utils
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/common/validation"
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/response"
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/validation"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package utils
|
||||
|
||||
import "strings"
|
||||
|
||||
// NormalizeTrim returns the input string without leading/trailing whitespace.
|
||||
func NormalizeTrim(value string) string {
|
||||
return strings.TrimSpace(value)
|
||||
}
|
||||
|
||||
// NormalizeUpper returns the trimmed, upper-case version of value.
|
||||
func NormalizeUpper(value string) string {
|
||||
return strings.ToUpper(NormalizeTrim(value))
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func TestAreaIntegration(t *testing.T) {
|
||||
app, db := setupIntegrationApp(t)
|
||||
|
||||
t.Run("create area trims name", func(t *testing.T) {
|
||||
areaID := createArea(t, app, " Area Trim ")
|
||||
if got := fetchAreaName(t, db, areaID); got != "Area Trim" {
|
||||
t.Fatalf("expected trimmed name, got %q", got)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("duplicate area returns conflict", func(t *testing.T) {
|
||||
createArea(t, app, "Duplicate Area")
|
||||
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/areas", map[string]any{"name": "Duplicate Area"})
|
||||
if resp.StatusCode != fiber.StatusConflict {
|
||||
t.Fatalf("expected 409 conflict, got %d: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
||||
)
|
||||
|
||||
func TestCustomerIntegration(t *testing.T) {
|
||||
app, db := setupIntegrationApp(t)
|
||||
|
||||
t.Run("creating customer without existing pic fails", func(t *testing.T) {
|
||||
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/customers", map[string]any{
|
||||
"name": "Invalid Customer",
|
||||
"pic_id": 9999,
|
||||
"type": utils.CustomerSupplierTypeBisnis,
|
||||
"address": "Somewhere",
|
||||
"phone": "0800000000",
|
||||
"email": "invalid@example.com",
|
||||
"account_number": "ACC-INVALID",
|
||||
})
|
||||
if resp.StatusCode != fiber.StatusNotFound {
|
||||
t.Fatalf("expected 404 when pic is missing, got %d: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("creating customer with invalid type fails", func(t *testing.T) {
|
||||
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/customers", map[string]any{
|
||||
"name": "Invalid Type",
|
||||
"pic_id": 1,
|
||||
"type": "UNKNOWN",
|
||||
"address": "Somewhere",
|
||||
"phone": "081234567891",
|
||||
"email": "invalid-type@example.com",
|
||||
"account_number": "ACC-INVALID-TYPE",
|
||||
})
|
||||
if resp.StatusCode != fiber.StatusBadRequest {
|
||||
t.Fatalf("expected 400 when type is invalid, got %d: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
})
|
||||
|
||||
const customerName = "Customer Alpha"
|
||||
var customerID uint
|
||||
|
||||
t.Run("creating customer succeeds", func(t *testing.T) {
|
||||
customerID = createCustomer(t, app, customerName, 1)
|
||||
customer := fetchCustomer(t, db, customerID)
|
||||
if customer.Name != customerName {
|
||||
t.Fatalf("expected name %q, got %q", customerName, customer.Name)
|
||||
}
|
||||
if customer.PicId != 1 {
|
||||
t.Fatalf("expected pic id 1, got %d", customer.PicId)
|
||||
}
|
||||
if customer.Type != string(utils.CustomerSupplierTypeBisnis) {
|
||||
t.Fatalf("expected type %q, got %q", utils.CustomerSupplierTypeBisnis, customer.Type)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("creating customer with duplicate name fails", func(t *testing.T) {
|
||||
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/customers", map[string]any{
|
||||
"name": customerName,
|
||||
"pic_id": 1,
|
||||
"type": utils.CustomerSupplierTypeBisnis,
|
||||
"address": "Duplicate address",
|
||||
"phone": "0811111111",
|
||||
"email": "duplicate@example.com",
|
||||
"account_number": "ACC-DUP",
|
||||
})
|
||||
if resp.StatusCode != fiber.StatusConflict {
|
||||
t.Fatalf("expected 409 when creating duplicate customer, got %d: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("getting existing customer succeeds", func(t *testing.T) {
|
||||
resp, body := doJSONRequest(t, app, http.MethodGet, fmt.Sprintf("/api/master-data/customers/%d", customerID), nil)
|
||||
if resp.StatusCode != fiber.StatusOK {
|
||||
t.Fatalf("expected 200 when fetching customer, got %d: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
var payload struct {
|
||||
Data struct {
|
||||
Id uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
} `json:"data"`
|
||||
}
|
||||
if err := json.Unmarshal(body, &payload); err != nil {
|
||||
t.Fatalf("failed to parse customer response: %v", err)
|
||||
}
|
||||
if payload.Data.Id != customerID {
|
||||
t.Fatalf("expected id %d, got %d", customerID, payload.Data.Id)
|
||||
}
|
||||
if payload.Data.Name != customerName {
|
||||
t.Fatalf("expected name %q, got %q", customerName, payload.Data.Name)
|
||||
}
|
||||
})
|
||||
|
||||
const updatedName = "Customer Gamma"
|
||||
|
||||
t.Run("updating customer name succeeds", func(t *testing.T) {
|
||||
resp, body := doJSONRequest(t, app, http.MethodPatch, fmt.Sprintf("/api/master-data/customers/%d", customerID), map[string]any{
|
||||
"name": updatedName,
|
||||
})
|
||||
if resp.StatusCode != fiber.StatusOK {
|
||||
t.Fatalf("expected 200 when updating customer, got %d: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
var payload struct {
|
||||
Data struct {
|
||||
Name string `json:"name"`
|
||||
} `json:"data"`
|
||||
}
|
||||
if err := json.Unmarshal(body, &payload); err != nil {
|
||||
t.Fatalf("failed to parse update response: %v", err)
|
||||
}
|
||||
if payload.Data.Name != updatedName {
|
||||
t.Fatalf("expected updated name %q, got %q", updatedName, payload.Data.Name)
|
||||
}
|
||||
customer := fetchCustomer(t, db, customerID)
|
||||
if customer.Name != updatedName {
|
||||
t.Fatalf("expected persisted name %q, got %q", updatedName, customer.Name)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("updating customer type succeeds", func(t *testing.T) {
|
||||
resp, body := doJSONRequest(t, app, http.MethodPatch, fmt.Sprintf("/api/master-data/customers/%d", customerID), map[string]any{
|
||||
"type": utils.CustomerSupplierTypeIndividual,
|
||||
})
|
||||
if resp.StatusCode != fiber.StatusOK {
|
||||
t.Fatalf("expected 200 when updating customer type, got %d: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
customer := fetchCustomer(t, db, customerID)
|
||||
if customer.Type != string(utils.CustomerSupplierTypeIndividual) {
|
||||
t.Fatalf("expected persisted type %q, got %q", utils.CustomerSupplierTypeIndividual, customer.Type)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("updating customer with invalid type fails", func(t *testing.T) {
|
||||
resp, body := doJSONRequest(t, app, http.MethodPatch, fmt.Sprintf("/api/master-data/customers/%d", customerID), map[string]any{
|
||||
"type": "random-type",
|
||||
})
|
||||
if resp.StatusCode != fiber.StatusBadRequest {
|
||||
t.Fatalf("expected 400 when updating with invalid type, got %d: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
customer := fetchCustomer(t, db, customerID)
|
||||
if customer.Type != string(utils.CustomerSupplierTypeIndividual) {
|
||||
t.Fatalf("expected type to remain %q after invalid update, got %q", utils.CustomerSupplierTypeIndividual, customer.Type)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("updating non existent customer returns 404", func(t *testing.T) {
|
||||
resp, body := doJSONRequest(t, app, http.MethodPatch, "/api/master-data/customers/9999", map[string]any{
|
||||
"name": "Does Not Matter",
|
||||
})
|
||||
if resp.StatusCode != fiber.StatusNotFound {
|
||||
t.Fatalf("expected 404 when updating missing customer, got %d: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("deleting customer succeeds", func(t *testing.T) {
|
||||
resp, body := doJSONRequest(t, app, http.MethodDelete, fmt.Sprintf("/api/master-data/customers/%d", customerID), nil)
|
||||
if resp.StatusCode != fiber.StatusOK {
|
||||
t.Fatalf("expected 200 when deleting customer, got %d: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
var customer entities.Customer
|
||||
if err := db.First(&customer, customerID).Error; !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
t.Fatalf("expected customer to be deleted, got error %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("deleting non existent customer returns 404", func(t *testing.T) {
|
||||
resp, body := doJSONRequest(t, app, http.MethodDelete, fmt.Sprintf("/api/master-data/customers/%d", customerID), nil)
|
||||
if resp.StatusCode != fiber.StatusNotFound {
|
||||
t.Fatalf("expected 404 when deleting missing customer, got %d: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("getting deleted customer returns 404", func(t *testing.T) {
|
||||
resp, body := doJSONRequest(t, app, http.MethodGet, fmt.Sprintf("/api/master-data/customers/%d", customerID), nil)
|
||||
if resp.StatusCode != fiber.StatusNotFound {
|
||||
t.Fatalf("expected 404 when fetching deleted customer, got %d: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func TestKandangIntegration(t *testing.T) {
|
||||
app, _ := setupIntegrationApp(t)
|
||||
areaID := createArea(t, app, "Area Kandang")
|
||||
locationID := createLocation(t, app, "Location For Kandang", "Address", areaID)
|
||||
|
||||
t.Run("create kandang success", func(t *testing.T) {
|
||||
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/kandangs", map[string]any{
|
||||
"name": "Kandang OK",
|
||||
"location_id": locationID,
|
||||
"pic_id": 1,
|
||||
})
|
||||
if resp.StatusCode != fiber.StatusCreated {
|
||||
t.Fatalf("expected 201, got %d: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("create kandang with unknown location fails", func(t *testing.T) {
|
||||
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/kandangs", map[string]any{
|
||||
"name": "Kandang Fail",
|
||||
"location_id": 999,
|
||||
"pic_id": 1,
|
||||
})
|
||||
if resp.StatusCode != fiber.StatusNotFound {
|
||||
t.Fatalf("expected 404, got %d: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func TestLocationIntegration(t *testing.T) {
|
||||
app, _ := setupIntegrationApp(t)
|
||||
|
||||
t.Run("creating location without existing area fails", func(t *testing.T) {
|
||||
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/locations", map[string]any{
|
||||
"name": "Loc A",
|
||||
"address": "Address",
|
||||
"area_id": 999, // non-existent
|
||||
})
|
||||
if resp.StatusCode != fiber.StatusNotFound {
|
||||
t.Fatalf("expected 404, got %d: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("creating location succeeds", func(t *testing.T) {
|
||||
areaID := createArea(t, app, "Area For Location")
|
||||
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/locations", map[string]any{
|
||||
"name": "Location OK",
|
||||
"address": "Addr",
|
||||
"area_id": areaID,
|
||||
})
|
||||
if resp.StatusCode != fiber.StatusCreated {
|
||||
t.Fatalf("expected 201, got %d: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/glebarez/sqlite"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/logger"
|
||||
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/route"
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
||||
)
|
||||
|
||||
func setupIntegrationApp(t *testing.T) (*fiber.App, *gorm.DB) {
|
||||
t.Helper()
|
||||
dir := t.TempDir()
|
||||
dsn := filepath.Join(dir, "integration.db")
|
||||
|
||||
db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{Logger: logger.Default.LogMode(logger.Silent)})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to open sqlite database: %v", err)
|
||||
}
|
||||
|
||||
if err := db.Exec("PRAGMA foreign_keys = ON").Error; err != nil {
|
||||
t.Fatalf("failed to enable foreign keys: %v", err)
|
||||
}
|
||||
|
||||
if err := db.AutoMigrate(
|
||||
&entities.User{},
|
||||
&entities.Area{},
|
||||
&entities.Location{},
|
||||
&entities.Kandang{},
|
||||
&entities.Warehouse{},
|
||||
&entities.Uom{},
|
||||
&entities.Customer{},
|
||||
); err != nil {
|
||||
t.Fatalf("auto migrate failed: %v", err)
|
||||
}
|
||||
|
||||
seedUser := entities.User{
|
||||
Id: 1,
|
||||
IdUser: 1001,
|
||||
Email: "tester@example.com",
|
||||
Name: "Tester",
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
if err := db.Create(&seedUser).Error; err != nil {
|
||||
t.Fatalf("failed to seed user: %v", err)
|
||||
}
|
||||
|
||||
app := fiber.New()
|
||||
route.Routes(app, db)
|
||||
return app, db
|
||||
}
|
||||
|
||||
func doJSONRequest(t *testing.T, app *fiber.App, method, path string, payload any) (*http.Response, []byte) {
|
||||
t.Helper()
|
||||
|
||||
var body io.Reader
|
||||
if payload != nil {
|
||||
buf := &bytes.Buffer{}
|
||||
if err := json.NewEncoder(buf).Encode(payload); err != nil {
|
||||
t.Fatalf("failed to encode payload: %v", err)
|
||||
}
|
||||
body = buf
|
||||
}
|
||||
|
||||
req := httptest.NewRequest(method, path, body)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Accept", "application/json")
|
||||
|
||||
resp, err := app.Test(req, -1)
|
||||
if err != nil {
|
||||
t.Fatalf("request failed: %v", err)
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read response body: %v", err)
|
||||
}
|
||||
return resp, data
|
||||
}
|
||||
|
||||
func parseID(t *testing.T, body []byte) uint {
|
||||
t.Helper()
|
||||
var resp struct {
|
||||
Data struct {
|
||||
Id uint `json:"id"`
|
||||
} `json:"data"`
|
||||
}
|
||||
if err := json.Unmarshal(body, &resp); err != nil {
|
||||
t.Fatalf("failed to parse response: %v", err)
|
||||
}
|
||||
return resp.Data.Id
|
||||
}
|
||||
|
||||
func createArea(t *testing.T, app *fiber.App, name string) uint {
|
||||
t.Helper()
|
||||
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/areas", map[string]any{"name": name})
|
||||
if resp.StatusCode != fiber.StatusCreated {
|
||||
t.Fatalf("expected 201 when creating area, got %d: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
return parseID(t, body)
|
||||
}
|
||||
|
||||
func createLocation(t *testing.T, app *fiber.App, name, address string, areaID uint) uint {
|
||||
t.Helper()
|
||||
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/locations", map[string]any{
|
||||
"name": name,
|
||||
"address": address,
|
||||
"area_id": areaID,
|
||||
})
|
||||
if resp.StatusCode != fiber.StatusCreated {
|
||||
t.Fatalf("expected 201 when creating location, got %d: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
return parseID(t, body)
|
||||
}
|
||||
|
||||
func createKandang(t *testing.T, app *fiber.App, name string, locationID, picID uint) uint {
|
||||
t.Helper()
|
||||
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/kandangs", map[string]any{
|
||||
"name": name,
|
||||
"location_id": locationID,
|
||||
"pic_id": picID,
|
||||
})
|
||||
if resp.StatusCode != fiber.StatusCreated {
|
||||
t.Fatalf("expected 201 when creating kandang, got %d: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
return parseID(t, body)
|
||||
}
|
||||
|
||||
func createCustomer(t *testing.T, app *fiber.App, name string, picID uint) uint {
|
||||
t.Helper()
|
||||
identifier := strings.ToLower(strings.ReplaceAll(name, " ", "_"))
|
||||
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/customers", map[string]any{
|
||||
"name": name,
|
||||
"pic_id": picID,
|
||||
"type": utils.CustomerSupplierTypeBisnis,
|
||||
"address": "Customer address",
|
||||
"phone": "081234567890",
|
||||
"email": fmt.Sprintf("%s@example.com", identifier),
|
||||
"account_number": fmt.Sprintf("ACC-%s", identifier),
|
||||
})
|
||||
if resp.StatusCode != fiber.StatusCreated {
|
||||
t.Fatalf("expected 201 when creating customer, got %d: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
return parseID(t, body)
|
||||
}
|
||||
|
||||
func fetchCustomer(t *testing.T, db *gorm.DB, id uint) entities.Customer {
|
||||
t.Helper()
|
||||
var customer entities.Customer
|
||||
if err := db.Preload("Pic").Preload("CreatedUser").First(&customer, id).Error; err != nil {
|
||||
t.Fatalf("failed to fetch customer: %v", err)
|
||||
}
|
||||
return customer
|
||||
}
|
||||
|
||||
func fetchAreaName(t *testing.T, db *gorm.DB, id uint) string {
|
||||
t.Helper()
|
||||
var area entities.Area
|
||||
if err := db.First(&area, id).Error; err != nil {
|
||||
t.Fatalf("failed to fetch area: %v", err)
|
||||
}
|
||||
return area.Name
|
||||
}
|
||||
|
||||
func fetchWarehouse(t *testing.T, db *gorm.DB, id uint) entities.Warehouse {
|
||||
t.Helper()
|
||||
var wh entities.Warehouse
|
||||
if err := db.First(&wh, id).Error; err != nil {
|
||||
t.Fatalf("failed to fetch warehouse: %v", err)
|
||||
}
|
||||
return wh
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func TestWarehouseIntegration(t *testing.T) {
|
||||
app, db := setupIntegrationApp(t)
|
||||
areaID := createArea(t, app, "Warehouse Area")
|
||||
locationID := createLocation(t, app, "Location WH", "Addr", areaID)
|
||||
kandangID := createKandang(t, app, "Kandang WH", locationID, 1)
|
||||
|
||||
t.Run("type AREA only needs area_id", func(t *testing.T) {
|
||||
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/warehouses", map[string]any{
|
||||
"name": "WH Area",
|
||||
"type": "AREA",
|
||||
"area_id": areaID,
|
||||
})
|
||||
if resp.StatusCode != fiber.StatusCreated {
|
||||
t.Fatalf("expected 201, got %d: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("type AREA rejects location_id", func(t *testing.T) {
|
||||
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/warehouses", map[string]any{
|
||||
"name": "WH Area Invalid",
|
||||
"type": "AREA",
|
||||
"area_id": areaID,
|
||||
"location_id": locationID,
|
||||
})
|
||||
if resp.StatusCode != fiber.StatusBadRequest {
|
||||
t.Fatalf("expected 400, got %d: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("type LOKASI requires location_id", func(t *testing.T) {
|
||||
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/warehouses", map[string]any{
|
||||
"name": "WH Lokasi Fail",
|
||||
"type": "LOKASI",
|
||||
"area_id": areaID,
|
||||
})
|
||||
if resp.StatusCode != fiber.StatusBadRequest {
|
||||
t.Fatalf("expected 400, got %d: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("type LOKASI success", func(t *testing.T) {
|
||||
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/warehouses", map[string]any{
|
||||
"name": "WH Lokasi",
|
||||
"type": "LOKASI",
|
||||
"area_id": areaID,
|
||||
"location_id": locationID,
|
||||
})
|
||||
if resp.StatusCode != fiber.StatusCreated {
|
||||
t.Fatalf("expected 201, got %d: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
whID := parseID(t, body)
|
||||
wh := fetchWarehouse(t, db, whID)
|
||||
if wh.LocationId == nil || *wh.LocationId != locationID {
|
||||
t.Fatalf("expected location_id %d, got %v", locationID, wh.LocationId)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("type KANDANG requires all ids", func(t *testing.T) {
|
||||
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/warehouses", map[string]any{
|
||||
"name": "WH Kandang Fail",
|
||||
"type": "KANDANG",
|
||||
"area_id": areaID,
|
||||
"location_id": locationID,
|
||||
})
|
||||
if resp.StatusCode != fiber.StatusBadRequest {
|
||||
t.Fatalf("expected 400, got %d: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("type KANDANG success", func(t *testing.T) {
|
||||
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/warehouses", map[string]any{
|
||||
"name": "WH Kandang",
|
||||
"type": "KANDANG",
|
||||
"area_id": areaID,
|
||||
"location_id": locationID,
|
||||
"kandang_id": kandangID,
|
||||
})
|
||||
if resp.StatusCode != fiber.StatusCreated {
|
||||
t.Fatalf("expected 201, got %d: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
whID := parseID(t, body)
|
||||
wh := fetchWarehouse(t, db, whID)
|
||||
if wh.KandangId == nil || *wh.KandangId != kandangID {
|
||||
t.Fatalf("expected kandang_id %d, got %v", kandangID, wh.KandangId)
|
||||
}
|
||||
})
|
||||
}
|
||||
+5
-4
@@ -44,10 +44,11 @@ func main() {
|
||||
TplName string
|
||||
}{
|
||||
{
|
||||
TplPath: "tools/templates/model.tmpl",
|
||||
OutDir: filepath.Join("internal", "modules", toKebabPath(d.Parts[:len(d.Parts)-1]), toKebab(d.Entity)+"s", "models"),
|
||||
OutSuffix: ".model.go",
|
||||
TplName: "model",
|
||||
TplPath: "tools/templates/entity.tmpl",
|
||||
// Centralize entities at internal/entities
|
||||
OutDir: filepath.Join("internal", "entities"),
|
||||
OutSuffix: ".go",
|
||||
TplName: "entity",
|
||||
},
|
||||
{
|
||||
TplPath: "tools/templates/validation.tmpl",
|
||||
|
||||
+20
-11
@@ -3,7 +3,8 @@
|
||||
import (
|
||||
"time"
|
||||
|
||||
model "gitlab.com/mbugroup/lti-api.git/internal/modules/{{Kebab .FeatName}}s/models"
|
||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||
userDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/users/dto"
|
||||
)
|
||||
|
||||
// === DTO Structs ===
|
||||
@@ -15,6 +16,7 @@ type {{Pascal .Entity}}BaseDTO struct {
|
||||
|
||||
type {{Pascal .Entity}}ListDTO struct {
|
||||
{{Pascal .Entity}}BaseDTO
|
||||
CreatedUser *userDTO.UserBaseDTO `json:"created_user"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
@@ -25,24 +27,31 @@ type {{Pascal .Entity}}DetailDTO struct {
|
||||
|
||||
// === Mapper Functions ===
|
||||
|
||||
func To{{Pascal .Entity}}BaseDTO(m model.{{Pascal .Entity}}) {{Pascal .Entity}}BaseDTO {
|
||||
func To{{Pascal .Entity}}BaseDTO(e entity.{{Pascal .Entity}}) {{Pascal .Entity}}BaseDTO {
|
||||
return {{Pascal .Entity}}BaseDTO{
|
||||
Id: m.Id,
|
||||
Name: m.Name,
|
||||
Id: e.Id,
|
||||
Name: e.Name,
|
||||
}
|
||||
}
|
||||
|
||||
func To{{Pascal .Entity}}ListDTO(m model.{{Pascal .Entity}}) {{Pascal .Entity}}ListDTO {
|
||||
func To{{Pascal .Entity}}ListDTO(e entity.{{Pascal .Entity}}) {{Pascal .Entity}}ListDTO {
|
||||
var createdUser *userDTO.UserBaseDTO
|
||||
if e.CreatedUser.Id != 0 {
|
||||
mapped := userDTO.ToUserBaseDTO(e.CreatedUser)
|
||||
createdUser = &mapped
|
||||
}
|
||||
|
||||
return {{Pascal .Entity}}ListDTO{
|
||||
{{Pascal .Entity}}BaseDTO: To{{Pascal .Entity}}BaseDTO(m),
|
||||
CreatedAt: m.CreatedAt,
|
||||
UpdatedAt: m.UpdatedAt,
|
||||
{{Pascal .Entity}}BaseDTO: To{{Pascal .Entity}}BaseDTO(e),
|
||||
CreatedAt: e.CreatedAt,
|
||||
UpdatedAt: e.UpdatedAt,
|
||||
CreatedUser: createdUser,
|
||||
}
|
||||
}
|
||||
|
||||
func To{{Pascal .Entity}}ListDTOs(m []model.{{Pascal .Entity}}) []{{Pascal .Entity}}ListDTO {
|
||||
result := make([]{{Pascal .Entity}}ListDTO, len(m))
|
||||
for i, r := range m {
|
||||
func To{{Pascal .Entity}}ListDTOs(e []entity.{{Pascal .Entity}}) []{{Pascal .Entity}}ListDTO {
|
||||
result := make([]{{Pascal .Entity}}ListDTO, len(e))
|
||||
for i, r := range e {
|
||||
result[i] = To{{Pascal .Entity}}ListDTO(r)
|
||||
}
|
||||
return result
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
{{define "entity"}}package entities
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type {{Pascal .Entity}} struct {
|
||||
Id uint `gorm:"primaryKey"`
|
||||
Name string `gorm:"not null"`
|
||||
CreatedBy uint `gorm:"not null"`
|
||||
CreatedAt time.Time `gorm:"autoCreateTime"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||
|
||||
CreatedUser User `gorm:"foreignKey:CreatedBy;references:Id"`
|
||||
}
|
||||
{{end}}
|
||||
@@ -1,17 +0,0 @@
|
||||
{{define "model"}}package model
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type {{Pascal .Entity}} 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"`
|
||||
}
|
||||
{{end}}
|
||||
@@ -1,22 +1,22 @@
|
||||
{{define "repository"}}package repository
|
||||
|
||||
import (
|
||||
model "gitlab.com/mbugroup/lti-api.git/internal/modules/{{Kebab .FeatName}}s/models"
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/repository"
|
||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type {{Pascal .Entity}}Repository interface {
|
||||
repository.BaseRepository[model.{{Pascal .Entity}}]
|
||||
repository.BaseRepository[entity.{{Pascal .Entity}}]
|
||||
}
|
||||
|
||||
type {{Pascal .Entity}}RepositoryImpl struct {
|
||||
*repository.BaseRepositoryImpl[model.{{Pascal .Entity}}]
|
||||
*repository.BaseRepositoryImpl[entity.{{Pascal .Entity}}]
|
||||
}
|
||||
|
||||
func New{{Pascal .Entity}}Repository(db *gorm.DB) {{Pascal .Entity}}Repository {
|
||||
return &{{Pascal .Entity}}RepositoryImpl{
|
||||
BaseRepositoryImpl: repository.NewBaseRepository[model.{{Pascal .Entity}}](db),
|
||||
BaseRepositoryImpl: repository.NewBaseRepository[entity.{{Pascal .Entity}}](db),
|
||||
}
|
||||
}
|
||||
{{end}}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import (
|
||||
"errors"
|
||||
|
||||
model "gitlab.com/mbugroup/lti-api.git/internal/modules/{{Kebab .FeatName}}s/models"
|
||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||
repository "gitlab.com/mbugroup/lti-api.git/internal/modules/{{Kebab .FeatName}}s/repositories"
|
||||
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/{{Kebab .FeatName}}s/validations"
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
||||
@@ -15,10 +15,10 @@ import (
|
||||
)
|
||||
|
||||
type {{Pascal .Entity}}Service interface {
|
||||
GetAll(ctx *fiber.Ctx, params *validation.Query) ([]model.{{Pascal .Entity}}, int64, error)
|
||||
GetOne(ctx *fiber.Ctx, id uint) (*model.{{Pascal .Entity}}, error)
|
||||
CreateOne(ctx *fiber.Ctx, req *validation.Create) (*model.{{Pascal .Entity}}, error)
|
||||
UpdateOne(ctx *fiber.Ctx, req *validation.Update, id uint) (*model.{{Pascal .Entity}}, error)
|
||||
GetAll(ctx *fiber.Ctx, params *validation.Query) ([]entity.{{Pascal .Entity}}, int64, error)
|
||||
GetOne(ctx *fiber.Ctx, id uint) (*entity.{{Pascal .Entity}}, error)
|
||||
CreateOne(ctx *fiber.Ctx, req *validation.Create) (*entity.{{Pascal .Entity}}, error)
|
||||
UpdateOne(ctx *fiber.Ctx, req *validation.Update, id uint) (*entity.{{Pascal .Entity}}, error)
|
||||
DeleteOne(ctx *fiber.Ctx, id uint) error
|
||||
}
|
||||
|
||||
@@ -35,7 +35,12 @@ func New{{Pascal .Entity}}Service(repo repository.{{Pascal .Entity}}Repository,
|
||||
Repository: repo,
|
||||
}
|
||||
}
|
||||
func (s {{Camel .Entity}}Service) GetAll(c *fiber.Ctx, params *validation.Query) ([]model.{{Pascal .Entity}}, int64, error) {
|
||||
|
||||
func (s {{Camel .Entity}}Service) withRelations(db *gorm.DB) *gorm.DB {
|
||||
return db.Preload("CreatedUser")
|
||||
}
|
||||
|
||||
func (s {{Camel .Entity}}Service) GetAll(c *fiber.Ctx, params *validation.Query) ([]entity.{{Pascal .Entity}}, int64, error) {
|
||||
if err := s.Validate.Struct(params); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
@@ -43,6 +48,7 @@ func (s {{Camel .Entity}}Service) GetAll(c *fiber.Ctx, params *validation.Query)
|
||||
offset := (params.Page - 1) * params.Limit
|
||||
|
||||
{{Camel .Entity}}s, 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+"%")
|
||||
}
|
||||
@@ -56,8 +62,8 @@ func (s {{Camel .Entity}}Service) GetAll(c *fiber.Ctx, params *validation.Query)
|
||||
return {{Camel .Entity}}s, total, nil
|
||||
}
|
||||
|
||||
func (s {{Camel .Entity}}Service) GetOne(c *fiber.Ctx, id uint) (*model.{{Pascal .Entity}}, error) {
|
||||
{{Camel .Entity}}, err := s.Repository.GetByID(c.Context(), id, nil)
|
||||
func (s {{Camel .Entity}}Service) GetOne(c *fiber.Ctx, id uint) (*entity.{{Pascal .Entity}}, error) {
|
||||
{{Camel .Entity}}, err := s.Repository.GetByID(c.Context(), id, s.withRelations)
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, fiber.NewError(fiber.StatusNotFound, "{{Pascal .Entity}} not found")
|
||||
}
|
||||
@@ -68,12 +74,12 @@ func (s {{Camel .Entity}}Service) GetOne(c *fiber.Ctx, id uint) (*model.{{Pascal
|
||||
return {{Camel .Entity}}, nil
|
||||
}
|
||||
|
||||
func (s *{{Camel .Entity}}Service) CreateOne(c *fiber.Ctx, req *validation.Create) (*model.{{Pascal .Entity}}, error) {
|
||||
func (s *{{Camel .Entity}}Service) CreateOne(c *fiber.Ctx, req *validation.Create) (*entity.{{Pascal .Entity}}, error) {
|
||||
if err := s.Validate.Struct(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
createBody := &model.{{Pascal .Entity}}{
|
||||
createBody := &entity.{{Pascal .Entity}}{
|
||||
Name: req.Name,
|
||||
}
|
||||
|
||||
@@ -85,7 +91,7 @@ func (s *{{Camel .Entity}}Service) CreateOne(c *fiber.Ctx, req *validation.Creat
|
||||
return createBody, nil
|
||||
}
|
||||
|
||||
func (s {{Camel .Entity}}Service) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint) (*model.{{Pascal .Entity}}, error) {
|
||||
func (s {{Camel .Entity}}Service) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint) (*entity.{{Pascal .Entity}}, error) {
|
||||
if err := s.Validate.Struct(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -96,6 +102,10 @@ func (s {{Camel .Entity}}Service) UpdateOne(c *fiber.Ctx, req *validation.Update
|
||||
updateBody["name"] = *req.Name
|
||||
}
|
||||
|
||||
if len(updateBody) == 0 {
|
||||
return s.GetOne(c, id)
|
||||
}
|
||||
|
||||
if err := s.Repository.PatchOne(c.Context(), id, updateBody, nil); err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, fiber.NewError(fiber.StatusNotFound, "{{Pascal .Entity}} not found")
|
||||
|
||||
Reference in New Issue
Block a user