Base Project
This commit is contained in:
50
.dockerignore
Normal file
50
.dockerignore
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
# Ignore node_modules, build directories, and other dependencies
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
build
|
||||||
|
|
||||||
|
# Ignore log files and temporary files
|
||||||
|
*.log
|
||||||
|
*.tmp
|
||||||
|
|
||||||
|
# Ignore OS generated files
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Ignore version control directories and files
|
||||||
|
.git
|
||||||
|
.github
|
||||||
|
.vscode
|
||||||
|
tests
|
||||||
|
.gitignore
|
||||||
|
|
||||||
|
# Ignore environment and configuration files
|
||||||
|
.env.local
|
||||||
|
.env.example
|
||||||
|
.env.development
|
||||||
|
.env.production
|
||||||
|
.env.test
|
||||||
|
|
||||||
|
# Ignore IDE/editor config files
|
||||||
|
.vscode
|
||||||
|
.idea
|
||||||
|
*.swp
|
||||||
|
*~
|
||||||
|
|
||||||
|
# Ignore Docker related files (optional)
|
||||||
|
.dockerignore
|
||||||
|
Dockerfile
|
||||||
|
docker-compose*.yml
|
||||||
|
|
||||||
|
# Ignore tests and documentation (optional)
|
||||||
|
*.md
|
||||||
|
*.yaml
|
||||||
|
makefile
|
||||||
|
docs/assets
|
||||||
|
docs/postman
|
||||||
|
docs/GO.md
|
||||||
|
docs/CODE.md
|
||||||
|
|
||||||
|
.kilo
|
||||||
|
|
||||||
|
|
||||||
21
.env.example
Normal file
21
.env.example
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# APPLICATION ENVIRONMENT (dev | prod)
|
||||||
|
ENV=
|
||||||
|
|
||||||
|
#SYS_ADMIN SEED
|
||||||
|
ADMIN_USERNAME=
|
||||||
|
ADMIN_EMAIL=
|
||||||
|
ADMIN_PASSWORD=
|
||||||
|
ADMIN_FULL_NAME=
|
||||||
|
|
||||||
|
# POSTGRESQL CONFIGURATION
|
||||||
|
POSTGRES_DB=
|
||||||
|
POSTGRES_USER=
|
||||||
|
POSTGRES_PASSWORD
|
||||||
|
POSTGRES_PORT=
|
||||||
|
POSTGRES_PORT_MAPPING=
|
||||||
|
|
||||||
|
# Redis CONFIGURATION`
|
||||||
|
REDIS_PORT=
|
||||||
|
REDIS_PORT_MAPPING=
|
||||||
|
REDIS_USER=
|
||||||
|
REDIS_PASSWORD=
|
||||||
36
.gitignore
vendored
Normal file
36
.gitignore
vendored
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
# If you prefer the allow list template instead of the deny list, see community template:
|
||||||
|
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
|
||||||
|
#
|
||||||
|
# Binaries for programs and plugins
|
||||||
|
*.exe
|
||||||
|
*.exe~
|
||||||
|
*.dll
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
|
||||||
|
# Test binary, built with `go test -c`
|
||||||
|
*.test
|
||||||
|
|
||||||
|
# Code coverage profiles and other test artifacts
|
||||||
|
*.out
|
||||||
|
coverage.*
|
||||||
|
*.coverprofile
|
||||||
|
profile.cov
|
||||||
|
|
||||||
|
# Dependency directories (remove the comment below to include it)
|
||||||
|
# vendor/
|
||||||
|
|
||||||
|
# Go workspace file
|
||||||
|
go.work
|
||||||
|
go.work.sum
|
||||||
|
|
||||||
|
# env file
|
||||||
|
.env
|
||||||
|
configs/yaml/config.dev.yaml
|
||||||
|
configs/yaml/config.prod.yaml
|
||||||
|
docs/postman
|
||||||
|
.kilo
|
||||||
|
tmp
|
||||||
|
# Editor/IDE
|
||||||
|
# .idea/
|
||||||
|
# .vscode/
|
||||||
62
Makefile
Normal file
62
Makefile
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
#* GET FILE ENV
|
||||||
|
include .env
|
||||||
|
export $(shell sed 's/=.*//' .env)
|
||||||
|
|
||||||
|
DB_URL=postgres://$(POSTGRES_USER):$(POSTGRES_PASSWORD)@localhost:$(POSTGRES_PORT)/$(POSTGRES_DB)?sslmode=disable
|
||||||
|
# * FOLDER
|
||||||
|
SWAGGER_DIR=./docs/swagger
|
||||||
|
# * FILE RUN GO
|
||||||
|
GO_SERVER_PRO := ./cmd/server/main.go
|
||||||
|
GO_SERVER_DEV:= ./fsnotify.go
|
||||||
|
|
||||||
|
# * DOCKER COMPOSE
|
||||||
|
DOCKER_COMPOSE_DEV := docker-compose.dev.yml
|
||||||
|
DOCKER_COMPOSE_PRO := docker-compose.pro.yml
|
||||||
|
|
||||||
|
tidy:
|
||||||
|
go mod tidy
|
||||||
|
|
||||||
|
dev:
|
||||||
|
go run $(GO_SERVER_DEV)
|
||||||
|
################# SEED #################
|
||||||
|
seed:
|
||||||
|
go run ./cmd/seed/main.go
|
||||||
|
################# DOCKER #################
|
||||||
|
build-pro:
|
||||||
|
docker-compose -f $(DOCKER_COMPOSE_PRO) up -d --build
|
||||||
|
|
||||||
|
down-pro:
|
||||||
|
docker-compose -f $(DOCKER_COMPOSE_PRO) down
|
||||||
|
|
||||||
|
build-dev:
|
||||||
|
docker-compose -f $(DOCKER_COMPOSE_DEV) up -d --build
|
||||||
|
|
||||||
|
down-dev:
|
||||||
|
docker-compose -f $(DOCKER_COMPOSE_DEV) down
|
||||||
|
|
||||||
|
################# MIGRATE #################
|
||||||
|
new_migration:
|
||||||
|
migrate create -ext sql -dir db/migrations -seq $(name)
|
||||||
|
|
||||||
|
migrate_version:
|
||||||
|
migrate -path db/migrations -database "$(DB_URL)" version
|
||||||
|
|
||||||
|
migrate_up_all:
|
||||||
|
migrate -path db/migrations -database "$(DB_URL)" up
|
||||||
|
|
||||||
|
migrate_down_all:
|
||||||
|
migrate -path db/migrations -database "$(DB_URL)" down
|
||||||
|
|
||||||
|
migrate_up:
|
||||||
|
migrate -path db/migrations -database "$(DB_URL)" up $(version)
|
||||||
|
|
||||||
|
migrate_down:
|
||||||
|
migrate -path db/migrations -database "$(DB_URL)" down $(version)
|
||||||
|
|
||||||
|
################# SWAGGER #################
|
||||||
|
swag:
|
||||||
|
swag init -g cmd/server/main.go -o ./docs/swagger --parseDependency --parseInternal
|
||||||
|
|
||||||
|
################# SQLC #################
|
||||||
|
sqlc:
|
||||||
|
sqlc generate
|
||||||
104
cmd/seed/main.go
Normal file
104
cmd/seed/main.go
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"wm-backend/configs"
|
||||||
|
"wm-backend/internal/initialization"
|
||||||
|
"wm-backend/internal/models"
|
||||||
|
"wm-backend/pkg/helper"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/v5/pgxpool"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
cfg, err := configs.LoadConfig("configs")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("Error loading config")
|
||||||
|
}
|
||||||
|
|
||||||
|
pool, err := initialization.ConnectPostgreSQL(&cfg)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("Error connecting to database")
|
||||||
|
}
|
||||||
|
defer pool.Close()
|
||||||
|
|
||||||
|
err = seedAdmin(pool, &cfg)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("Error seeding admin")
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info().Msg("Seed completed successfully")
|
||||||
|
}
|
||||||
|
|
||||||
|
func seedAdmin(pool *pgxpool.Pool, cfg *models.Config) error {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
// Check if admin user already exists
|
||||||
|
var exists bool
|
||||||
|
err := pool.QueryRow(ctx,
|
||||||
|
"SELECT EXISTS(SELECT 1 FROM users WHERE username = $1)",
|
||||||
|
cfg.Admin.Username,
|
||||||
|
).Scan(&exists)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("checking existing admin: %w", err)
|
||||||
|
}
|
||||||
|
if exists {
|
||||||
|
log.Info().Str("username", cfg.Admin.Username).Msg("Admin user already exists, skipping")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash password
|
||||||
|
hashedPassword, err := helper.HashPassword(cfg.Admin.Password)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("hashing password: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get SYS_ADMIN role ID
|
||||||
|
var roleID string
|
||||||
|
err = pool.QueryRow(ctx,
|
||||||
|
"SELECT id FROM roles WHERE name = 'SYS_ADMIN'",
|
||||||
|
).Scan(&roleID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("SYS_ADMIN role not found (did init.sql run?): %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create admin user and assign role in a transaction
|
||||||
|
tx, err := pool.Begin(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("begin transaction: %w", err)
|
||||||
|
}
|
||||||
|
defer tx.Rollback(ctx)
|
||||||
|
|
||||||
|
var userID string
|
||||||
|
err = tx.QueryRow(ctx,
|
||||||
|
`INSERT INTO users (username, email, password_hash, full_name, is_active, created_by)
|
||||||
|
VALUES ($1, $2, $3, $4, TRUE, 'system')
|
||||||
|
RETURNING id`,
|
||||||
|
cfg.Admin.Username, cfg.Admin.Email, hashedPassword, cfg.Admin.FullName,
|
||||||
|
).Scan(&userID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("creating admin user: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = tx.Exec(ctx,
|
||||||
|
`INSERT INTO user_roles (user_id, role_id) VALUES ($1, $2)`,
|
||||||
|
userID, roleID,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("assigning SYS_ADMIN role: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tx.Commit(ctx); err != nil {
|
||||||
|
return fmt.Errorf("commit transaction: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info().
|
||||||
|
Str("username", cfg.Admin.Username).
|
||||||
|
Str("email", cfg.Admin.Email).
|
||||||
|
Str("role", "SYS_ADMIN").
|
||||||
|
Msg("Admin user created successfully")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
18
cmd/server/main.go
Normal file
18
cmd/server/main.go
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"wm-backend/global"
|
||||||
|
"wm-backend/internal/routers"
|
||||||
|
_ "wm-backend/internal/services"
|
||||||
|
)
|
||||||
|
|
||||||
|
// @title Warehouse Management API
|
||||||
|
// @version 1.0
|
||||||
|
// @description This is the Warehouse Management API server.
|
||||||
|
// @host localhost:3000
|
||||||
|
// @BasePath /api/v1
|
||||||
|
func main() {
|
||||||
|
r := routers.NewRouter()
|
||||||
|
_ = global.Cfg // ensure config is loaded via init()
|
||||||
|
r.Run(":" + global.Cfg.Server.Port)
|
||||||
|
}
|
||||||
42
configs/config.go
Normal file
42
configs/config.go
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
package configs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"wm-backend/configs/constants"
|
||||||
|
"wm-backend/internal/models"
|
||||||
|
|
||||||
|
"github.com/joho/godotenv"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
)
|
||||||
|
|
||||||
|
func LoadConfig(path string) (config models.Config, err error) {
|
||||||
|
// Load environment variables from .env file
|
||||||
|
err = godotenv.Load()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Error loading .env file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
viper.AddConfigPath(path)
|
||||||
|
env := os.Getenv("ENV")
|
||||||
|
if env == constants.ProdEnvironment {
|
||||||
|
viper.SetConfigName("yaml/config.prod")
|
||||||
|
} else {
|
||||||
|
viper.SetConfigName("yaml/config.dev")
|
||||||
|
}
|
||||||
|
viper.SetConfigType("yaml")
|
||||||
|
|
||||||
|
viper.AutomaticEnv()
|
||||||
|
|
||||||
|
// Read the configuration file
|
||||||
|
if err := viper.ReadInConfig(); err != nil {
|
||||||
|
return config, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unmarshal the configuration into the config struct
|
||||||
|
if err := viper.Unmarshal(&config); err != nil {
|
||||||
|
return config, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return config, nil
|
||||||
|
}
|
||||||
22
configs/constants/constants.go
Normal file
22
configs/constants/constants.go
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
package constants
|
||||||
|
|
||||||
|
const (
|
||||||
|
DevEnvironment = "dev"
|
||||||
|
ProdEnvironment = "prod"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
API_VERSION_1 = "/api/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
API_GROUP_AUTH = "/auth"
|
||||||
|
API_GROUP_WAREHOUSE = "/warehouses"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
API_PATH_PING = "/ping"
|
||||||
|
API_PATH_DOCS = "/swagger/*any"
|
||||||
|
API_PATH_AUTH_REGISTER = "/register"
|
||||||
|
API_PATH_AUTH_LOGIN = "/login"
|
||||||
|
)
|
||||||
71
configs/constants/permissions.go
Normal file
71
configs/constants/permissions.go
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
package constants
|
||||||
|
|
||||||
|
// Permission định nghĩa toàn bộ quyền trong hệ thống
|
||||||
|
// Format: {module}:{action}
|
||||||
|
// Giá trị string phải KHỚP với seed data trong db/init/init.sql
|
||||||
|
const (
|
||||||
|
// ── Warehouse (Kho) ──
|
||||||
|
PermWarehouseCreate = "warehouse:create"
|
||||||
|
PermWarehouseRead = "warehouse:read"
|
||||||
|
PermWarehouseUpdate = "warehouse:update"
|
||||||
|
PermWarehouseDelete = "warehouse:delete"
|
||||||
|
|
||||||
|
// ── Room (Phòng) ──
|
||||||
|
PermRoomCreate = "room:create"
|
||||||
|
PermRoomRead = "room:read"
|
||||||
|
PermRoomUpdate = "room:update"
|
||||||
|
PermRoomDelete = "room:delete"
|
||||||
|
|
||||||
|
// ── Cabinet (Tủ) ──
|
||||||
|
PermCabinetCreate = "cabinet:create"
|
||||||
|
PermCabinetRead = "cabinet:read"
|
||||||
|
PermCabinetUpdate = "cabinet:update"
|
||||||
|
PermCabinetDelete = "cabinet:delete"
|
||||||
|
|
||||||
|
// ── Shelf (Kệ) ──
|
||||||
|
PermShelfCreate = "shelf:create"
|
||||||
|
PermShelfRead = "shelf:read"
|
||||||
|
PermShelfUpdate = "shelf:update"
|
||||||
|
PermShelfDelete = "shelf:delete"
|
||||||
|
|
||||||
|
// ── Container (Vật chứa) ──
|
||||||
|
PermContainerCreate = "container:create"
|
||||||
|
PermContainerRead = "container:read"
|
||||||
|
PermContainerUpdate = "container:update"
|
||||||
|
PermContainerDelete = "container:delete"
|
||||||
|
|
||||||
|
// ── Component Type (Loại linh kiện) ──
|
||||||
|
PermComponentTypeCreate = "component_type:create"
|
||||||
|
PermComponentTypeRead = "component_type:read"
|
||||||
|
PermComponentTypeUpdate = "component_type:update"
|
||||||
|
PermComponentTypeDelete = "component_type:delete"
|
||||||
|
|
||||||
|
// ── Component (Linh kiện) ──
|
||||||
|
PermComponentCreate = "component:create"
|
||||||
|
PermComponentRead = "component:read"
|
||||||
|
PermComponentUpdate = "component:update"
|
||||||
|
PermComponentDelete = "component:delete"
|
||||||
|
|
||||||
|
// ── Invoice (Hóa đơn) ──
|
||||||
|
PermInvoiceCreate = "invoice:create"
|
||||||
|
PermInvoiceRead = "invoice:read"
|
||||||
|
PermInvoiceUpdate = "invoice:update"
|
||||||
|
PermInvoiceDelete = "invoice:delete"
|
||||||
|
PermInvoiceApprove = "invoice:approve"
|
||||||
|
|
||||||
|
// ── Stock (Kho) ──
|
||||||
|
PermStockImport = "stock:import"
|
||||||
|
PermStockExport = "stock:export"
|
||||||
|
PermStockAdjust = "stock:adjust"
|
||||||
|
PermStockTransfer = "stock:transfer"
|
||||||
|
PermStockRead = "stock:read"
|
||||||
|
|
||||||
|
// ── User (Người dùng) ──
|
||||||
|
PermUserCreate = "user:create"
|
||||||
|
PermUserRead = "user:read"
|
||||||
|
PermUserUpdate = "user:update"
|
||||||
|
PermUserDelete = "user:delete"
|
||||||
|
|
||||||
|
// ── Role (Vai trò & quyền) ──
|
||||||
|
PermRoleManage = "role:manage"
|
||||||
|
)
|
||||||
28
configs/yaml/config.example.yaml
Normal file
28
configs/yaml/config.example.yaml
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
server:
|
||||||
|
host: "localhost"
|
||||||
|
port: 3000
|
||||||
|
portfrontend: "http://localhost:8000"
|
||||||
|
keypassword: ""
|
||||||
|
|
||||||
|
admin:
|
||||||
|
username:
|
||||||
|
email:
|
||||||
|
password:
|
||||||
|
fullname:
|
||||||
|
|
||||||
|
jwt:
|
||||||
|
secretkey:
|
||||||
|
expirehours:
|
||||||
|
|
||||||
|
database:
|
||||||
|
username:
|
||||||
|
password:
|
||||||
|
name:
|
||||||
|
host:
|
||||||
|
port:
|
||||||
|
|
||||||
|
cache:
|
||||||
|
username:
|
||||||
|
password:
|
||||||
|
host:
|
||||||
|
port:
|
||||||
485
db/init/init.sql
Normal file
485
db/init/init.sql
Normal file
@@ -0,0 +1,485 @@
|
|||||||
|
-- ============================================================
|
||||||
|
-- Warehouse Management Database - Init Script (PostgreSQL)
|
||||||
|
-- Based on WareHouseDB.md specification
|
||||||
|
-- ============================================================
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- ENUM Types
|
||||||
|
-- ============================================================
|
||||||
|
CREATE TYPE container_type_enum AS ENUM ('empty_box', 'tray', 'paper_box', 'plastic_box', 'bag', 'other');
|
||||||
|
CREATE TYPE component_item_status_enum AS ENUM ('normal', 'damaged', 'long_unused', 'expired', 'pending_inspection');
|
||||||
|
CREATE TYPE invoice_type_enum AS ENUM ('import', 'export');
|
||||||
|
CREATE TYPE invoice_status_enum AS ENUM ('draft', 'pending', 'approved', 'completed', 'cancelled');
|
||||||
|
CREATE TYPE transaction_type_enum AS ENUM ('import', 'export', 'adjustment', 'transfer');
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- Trigger function: auto-update updated_at
|
||||||
|
-- ============================================================
|
||||||
|
CREATE OR REPLACE FUNCTION update_updated_at_column()
|
||||||
|
RETURNS TRIGGER AS $$
|
||||||
|
BEGIN
|
||||||
|
NEW.updated_at = CURRENT_TIMESTAMP;
|
||||||
|
RETURN NEW;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 1. warehouses (Kho)
|
||||||
|
-- ============================================================
|
||||||
|
CREATE TABLE warehouses (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
name VARCHAR(255) NOT NULL,
|
||||||
|
description TEXT,
|
||||||
|
address VARCHAR(500),
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
CREATE TRIGGER trg_warehouses_updated_at
|
||||||
|
BEFORE UPDATE ON warehouses
|
||||||
|
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 2. rooms (Phòng)
|
||||||
|
-- ============================================================
|
||||||
|
CREATE TABLE rooms (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
warehouse_id BIGINT NOT NULL REFERENCES warehouses(id),
|
||||||
|
name VARCHAR(255) NOT NULL,
|
||||||
|
description TEXT,
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
CREATE TRIGGER trg_rooms_updated_at
|
||||||
|
BEFORE UPDATE ON rooms
|
||||||
|
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 3. cabinets (Tủ)
|
||||||
|
-- ============================================================
|
||||||
|
CREATE TABLE cabinets (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
room_id BIGINT NOT NULL REFERENCES rooms(id),
|
||||||
|
name VARCHAR(255) NOT NULL,
|
||||||
|
description TEXT,
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
CREATE TRIGGER trg_cabinets_updated_at
|
||||||
|
BEFORE UPDATE ON cabinets
|
||||||
|
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 4. shelves (Tầng / Kệ)
|
||||||
|
-- ============================================================
|
||||||
|
CREATE TABLE shelves (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
cabinet_id BIGINT NOT NULL REFERENCES cabinets(id),
|
||||||
|
name VARCHAR(255) NOT NULL,
|
||||||
|
level_index INT NOT NULL,
|
||||||
|
description TEXT,
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
CREATE TRIGGER trg_shelves_updated_at
|
||||||
|
BEFORE UPDATE ON shelves
|
||||||
|
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 5. containers (Vật chứa)
|
||||||
|
-- ============================================================
|
||||||
|
CREATE TABLE containers (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
shelf_id BIGINT NOT NULL REFERENCES shelves(id),
|
||||||
|
name VARCHAR(255) NOT NULL,
|
||||||
|
container_type container_type_enum NOT NULL,
|
||||||
|
description TEXT,
|
||||||
|
max_capacity INT,
|
||||||
|
metadata JSONB,
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
CREATE TRIGGER trg_containers_updated_at
|
||||||
|
BEFORE UPDATE ON containers
|
||||||
|
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 6. component_types (Loại linh kiện)
|
||||||
|
-- ============================================================
|
||||||
|
CREATE TABLE component_types (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
name VARCHAR(255) NOT NULL,
|
||||||
|
description TEXT,
|
||||||
|
metadata JSONB,
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
CONSTRAINT uk_component_types_name UNIQUE (name)
|
||||||
|
);
|
||||||
|
CREATE TRIGGER trg_component_types_updated_at
|
||||||
|
BEFORE UPDATE ON component_types
|
||||||
|
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 7. components (Linh kiện)
|
||||||
|
-- ============================================================
|
||||||
|
CREATE TABLE components (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
component_type_id BIGINT NOT NULL REFERENCES component_types(id),
|
||||||
|
name VARCHAR(255) NOT NULL,
|
||||||
|
description TEXT,
|
||||||
|
unit VARCHAR(50) NOT NULL DEFAULT 'cái',
|
||||||
|
total_quantity INT NOT NULL DEFAULT 0,
|
||||||
|
min_quantity INT NOT NULL DEFAULT 0,
|
||||||
|
metadata JSONB,
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
CREATE TRIGGER trg_components_updated_at
|
||||||
|
BEFORE UPDATE ON components
|
||||||
|
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 8. component_codes (Mã linh kiện)
|
||||||
|
-- ============================================================
|
||||||
|
CREATE TABLE component_codes (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
component_id BIGINT NOT NULL REFERENCES components(id),
|
||||||
|
code VARCHAR(255) NOT NULL,
|
||||||
|
code_type VARCHAR(100),
|
||||||
|
is_primary BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
|
metadata JSONB,
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
CONSTRAINT uk_component_codes_code_type UNIQUE (code, code_type)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 9. component_items (Linh kiện tại từng vị trí)
|
||||||
|
-- ============================================================
|
||||||
|
CREATE TABLE component_items (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
component_id BIGINT NOT NULL REFERENCES components(id),
|
||||||
|
container_id BIGINT NOT NULL REFERENCES containers(id),
|
||||||
|
quantity INT NOT NULL DEFAULT 0,
|
||||||
|
status component_item_status_enum NOT NULL DEFAULT 'normal',
|
||||||
|
metadata JSONB,
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
CONSTRAINT uk_component_items_comp_cont_status UNIQUE (component_id, container_id, status)
|
||||||
|
);
|
||||||
|
CREATE TRIGGER trg_component_items_updated_at
|
||||||
|
BEFORE UPDATE ON component_items
|
||||||
|
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 10. component_status_history (Lịch sử thay đổi tình trạng)
|
||||||
|
-- ============================================================
|
||||||
|
CREATE TABLE component_status_history (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
component_item_id BIGINT NOT NULL REFERENCES component_items(id),
|
||||||
|
old_status component_item_status_enum,
|
||||||
|
new_status component_item_status_enum NOT NULL,
|
||||||
|
changed_quantity INT,
|
||||||
|
note TEXT,
|
||||||
|
changed_by VARCHAR(255),
|
||||||
|
changed_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 11. invoice_configs (Cấu hình hóa đơn mẫu)
|
||||||
|
-- ============================================================
|
||||||
|
CREATE TABLE invoice_configs (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
name VARCHAR(255) NOT NULL,
|
||||||
|
type invoice_type_enum NOT NULL,
|
||||||
|
description TEXT,
|
||||||
|
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
||||||
|
metadata JSONB,
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
CREATE TRIGGER trg_invoice_configs_updated_at
|
||||||
|
BEFORE UPDATE ON invoice_configs
|
||||||
|
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 12. invoice_config_items (Chi tiết cấu hình hóa đơn)
|
||||||
|
-- ============================================================
|
||||||
|
CREATE TABLE invoice_config_items (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
invoice_config_id BIGINT NOT NULL REFERENCES invoice_configs(id),
|
||||||
|
component_id BIGINT NOT NULL REFERENCES components(id),
|
||||||
|
required_quantity INT NOT NULL,
|
||||||
|
allow_alternative BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
|
priority_order INT NOT NULL DEFAULT 0,
|
||||||
|
note TEXT,
|
||||||
|
metadata JSONB,
|
||||||
|
CONSTRAINT uk_invoice_config_items_config_comp UNIQUE (invoice_config_id, component_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 13. alternative_components (Linh kiện thay thế)
|
||||||
|
-- ============================================================
|
||||||
|
CREATE TABLE alternative_components (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
invoice_config_item_id BIGINT NOT NULL REFERENCES invoice_config_items(id),
|
||||||
|
alternative_component_id BIGINT NOT NULL REFERENCES components(id),
|
||||||
|
conversion_ratio DECIMAL(10,2) NOT NULL DEFAULT 1.00,
|
||||||
|
priority INT NOT NULL DEFAULT 0,
|
||||||
|
note TEXT,
|
||||||
|
metadata JSONB,
|
||||||
|
CONSTRAINT uk_alternative_components_item_alt UNIQUE (invoice_config_item_id, alternative_component_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 14. invoices (Hóa đơn nhập/xuất)
|
||||||
|
-- ============================================================
|
||||||
|
CREATE TABLE invoices (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
invoice_code VARCHAR(100) NOT NULL,
|
||||||
|
type invoice_type_enum NOT NULL,
|
||||||
|
status invoice_status_enum NOT NULL DEFAULT 'draft',
|
||||||
|
invoice_config_id BIGINT REFERENCES invoice_configs(id),
|
||||||
|
total_items INT NOT NULL DEFAULT 0,
|
||||||
|
note TEXT,
|
||||||
|
created_by VARCHAR(255),
|
||||||
|
approved_by VARCHAR(255),
|
||||||
|
completed_at TIMESTAMPTZ,
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
metadata JSONB,
|
||||||
|
CONSTRAINT uk_invoices_invoice_code UNIQUE (invoice_code)
|
||||||
|
);
|
||||||
|
CREATE TRIGGER trg_invoices_updated_at
|
||||||
|
BEFORE UPDATE ON invoices
|
||||||
|
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 15. invoice_items (Chi tiết hóa đơn)
|
||||||
|
-- ============================================================
|
||||||
|
CREATE TABLE invoice_items (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
invoice_id BIGINT NOT NULL REFERENCES invoices(id),
|
||||||
|
component_id BIGINT NOT NULL REFERENCES components(id),
|
||||||
|
original_component_id BIGINT REFERENCES components(id),
|
||||||
|
required_quantity INT NOT NULL,
|
||||||
|
actual_quantity INT NOT NULL DEFAULT 0,
|
||||||
|
is_substituted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
|
is_short BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
|
shortage_quantity INT NOT NULL DEFAULT 0,
|
||||||
|
note TEXT,
|
||||||
|
metadata JSONB,
|
||||||
|
CONSTRAINT uk_invoice_items_invoice_comp UNIQUE (invoice_id, component_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 16. invoice_item_locations (Vị trí xuất/nhập cho từng item)
|
||||||
|
-- ============================================================
|
||||||
|
CREATE TABLE invoice_item_locations (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
invoice_item_id BIGINT NOT NULL REFERENCES invoice_items(id),
|
||||||
|
container_id BIGINT NOT NULL REFERENCES containers(id),
|
||||||
|
quantity INT NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 17. invoice_status_history (Lịch sử trạng thái hóa đơn)
|
||||||
|
-- ============================================================
|
||||||
|
CREATE TABLE invoice_status_history (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
invoice_id BIGINT NOT NULL REFERENCES invoices(id),
|
||||||
|
old_status VARCHAR(50),
|
||||||
|
new_status VARCHAR(50) NOT NULL,
|
||||||
|
changed_by VARCHAR(255),
|
||||||
|
note TEXT,
|
||||||
|
changed_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 18. stock_transactions (Lịch sử nhập xuất kho)
|
||||||
|
-- ============================================================
|
||||||
|
CREATE TABLE stock_transactions (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
invoice_id BIGINT NOT NULL REFERENCES invoices(id),
|
||||||
|
component_id BIGINT NOT NULL REFERENCES components(id),
|
||||||
|
container_id BIGINT NOT NULL REFERENCES containers(id),
|
||||||
|
transaction_type transaction_type_enum NOT NULL,
|
||||||
|
quantity INT NOT NULL,
|
||||||
|
balance_after INT,
|
||||||
|
note TEXT,
|
||||||
|
created_by VARCHAR(255),
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE users (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
username VARCHAR(50) UNIQUE NOT NULL,
|
||||||
|
email VARCHAR(255) UNIQUE NOT NULL,
|
||||||
|
password_hash VARCHAR(255) NOT NULL,
|
||||||
|
full_name VARCHAR(100),
|
||||||
|
is_active BOOLEAN DEFAULT TRUE,
|
||||||
|
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
created_by VARCHAR(50)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Bảng Roles: Lưu các vai trò (admin, editor, viewer...)
|
||||||
|
CREATE TABLE roles (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
name VARCHAR(50) UNIQUE NOT NULL,
|
||||||
|
description VARCHAR(255),
|
||||||
|
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
created_by VARCHAR(50)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Bảng Permissions: Lưu các quyền hạn (read, write, delete...)
|
||||||
|
CREATE TABLE permissions (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
name VARCHAR(100) UNIQUE NOT NULL,
|
||||||
|
description VARCHAR(255),
|
||||||
|
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
created_by VARCHAR(50)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Bảng user_roles: Liên kết user với role (N-N)
|
||||||
|
CREATE TABLE user_roles (
|
||||||
|
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||||
|
role_id UUID NOT NULL REFERENCES roles(id) ON DELETE CASCADE,
|
||||||
|
assigned_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
PRIMARY KEY (user_id, role_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Bảng role_permissions: Liên kết role với permission (N-N)
|
||||||
|
CREATE TABLE role_permissions (
|
||||||
|
role_id UUID NOT NULL REFERENCES roles(id) ON DELETE CASCADE,
|
||||||
|
permission_id UUID NOT NULL REFERENCES permissions(id) ON DELETE CASCADE,
|
||||||
|
assigned_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
PRIMARY KEY (role_id, permission_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- Indexes
|
||||||
|
-- ============================================================
|
||||||
|
|
||||||
|
-- Comment: Tạo index cho việc tìm kiếm nhanh (tùy chọn)
|
||||||
|
CREATE INDEX idx_users_username ON users(username);
|
||||||
|
CREATE INDEX idx_users_email ON users(email);
|
||||||
|
CREATE INDEX idx_roles_name ON roles(name);
|
||||||
|
-- Index cho truy vấn nhanh
|
||||||
|
CREATE INDEX idx_user_roles_user_id ON user_roles(user_id);
|
||||||
|
CREATE INDEX idx_user_roles_role_id ON user_roles(role_id);
|
||||||
|
CREATE INDEX idx_role_permissions_role_id ON role_permissions(role_id);
|
||||||
|
CREATE INDEX idx_role_permissions_permission_id ON role_permissions(permission_id);
|
||||||
|
|
||||||
|
-- Tìm linh kiện theo vị trí
|
||||||
|
CREATE INDEX idx_component_items_component ON component_items(component_id);
|
||||||
|
CREATE INDEX idx_component_items_container ON component_items(container_id);
|
||||||
|
CREATE INDEX idx_component_items_status ON component_items(status);
|
||||||
|
|
||||||
|
-- Tìm mã linh kiện
|
||||||
|
CREATE INDEX idx_component_codes_code ON component_codes(code);
|
||||||
|
CREATE INDEX idx_component_codes_component ON component_codes(component_id);
|
||||||
|
|
||||||
|
-- Thống kê hóa đơn theo thời gian
|
||||||
|
CREATE INDEX idx_invoices_type_status ON invoices(type, status);
|
||||||
|
CREATE INDEX idx_invoices_created_at ON invoices(created_at);
|
||||||
|
CREATE INDEX idx_invoices_type_created ON invoices(type, created_at);
|
||||||
|
|
||||||
|
-- Thống kê giao dịch kho
|
||||||
|
CREATE INDEX idx_stock_transactions_component_date ON stock_transactions(component_id, created_at);
|
||||||
|
CREATE INDEX idx_stock_transactions_type_date ON stock_transactions(transaction_type, created_at);
|
||||||
|
CREATE INDEX idx_stock_transactions_invoice ON stock_transactions(invoice_id);
|
||||||
|
|
||||||
|
-- Tìm vị trí container (theo cấu trúc phân cấp)
|
||||||
|
CREATE INDEX idx_containers_shelf ON containers(shelf_id);
|
||||||
|
CREATE INDEX idx_shelves_cabinet ON shelves(cabinet_id);
|
||||||
|
CREATE INDEX idx_cabinets_room ON cabinets(room_id);
|
||||||
|
CREATE INDEX idx_rooms_warehouse ON rooms(warehouse_id);
|
||||||
|
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- Seed: Default permissions (theo module)
|
||||||
|
-- ============================================================
|
||||||
|
|
||||||
|
-- Warehouse module
|
||||||
|
INSERT INTO permissions (name, description) VALUES
|
||||||
|
('warehouse:create', 'Tạo kho mới'),
|
||||||
|
('warehouse:read', 'Xem thông tin kho'),
|
||||||
|
('warehouse:update', 'Cập nhật kho'),
|
||||||
|
('warehouse:delete', 'Xóa kho');
|
||||||
|
|
||||||
|
-- Room module
|
||||||
|
INSERT INTO permissions (name, description) VALUES
|
||||||
|
('room:create', 'Tạo phòng mới'),
|
||||||
|
('room:read', 'Xem thông tin phòng'),
|
||||||
|
('room:update', 'Cập nhật phòng'),
|
||||||
|
('room:delete', 'Xóa phòng');
|
||||||
|
|
||||||
|
-- Cabinet module
|
||||||
|
INSERT INTO permissions (name, description) VALUES
|
||||||
|
('cabinet:create', 'Tạo tủ mới'),
|
||||||
|
('cabinet:read', 'Xem thông tin tủ'),
|
||||||
|
('cabinet:update', 'Cập nhật tủ'),
|
||||||
|
('cabinet:delete', 'Xóa tủ');
|
||||||
|
|
||||||
|
-- Shelf module
|
||||||
|
INSERT INTO permissions (name, description) VALUES
|
||||||
|
('shelf:create', 'Tạo kệ mới'),
|
||||||
|
('shelf:read', 'Xem thông tin kệ'),
|
||||||
|
('shelf:update', 'Cập nhật kệ'),
|
||||||
|
('shelf:delete', 'Xóa kệ');
|
||||||
|
|
||||||
|
-- Container module
|
||||||
|
INSERT INTO permissions (name, description) VALUES
|
||||||
|
('container:create', 'Tạo vật chứa mới'),
|
||||||
|
('container:read', 'Xem thông tin vật chứa'),
|
||||||
|
('container:update', 'Cập nhật vật chứa'),
|
||||||
|
('container:delete', 'Xóa vật chứa');
|
||||||
|
|
||||||
|
-- Component Type module
|
||||||
|
INSERT INTO permissions (name, description) VALUES
|
||||||
|
('component_type:create', 'Tạo loại linh kiện mới'),
|
||||||
|
('component_type:read', 'Xem loại linh kiện'),
|
||||||
|
('component_type:update', 'Cập nhật loại linh kiện'),
|
||||||
|
('component_type:delete', 'Xóa loại linh kiện');
|
||||||
|
|
||||||
|
-- Component module
|
||||||
|
INSERT INTO permissions (name, description) VALUES
|
||||||
|
('component:create', 'Tạo linh kiện mới'),
|
||||||
|
('component:read', 'Xem thông tin linh kiện'),
|
||||||
|
('component:update', 'Cập nhật linh kiện'),
|
||||||
|
('component:delete', 'Xóa linh kiện');
|
||||||
|
|
||||||
|
-- Invoice module
|
||||||
|
INSERT INTO permissions (name, description) VALUES
|
||||||
|
('invoice:create', 'Tạo hóa đơn mới'),
|
||||||
|
('invoice:read', 'Xem thông tin hóa đơn'),
|
||||||
|
('invoice:update', 'Cập nhật hóa đơn'),
|
||||||
|
('invoice:delete', 'Xóa hóa đơn'),
|
||||||
|
('invoice:approve', 'Duyệt hóa đơn');
|
||||||
|
|
||||||
|
-- Stock module
|
||||||
|
INSERT INTO permissions (name, description) VALUES
|
||||||
|
('stock:import', 'Nhập kho'),
|
||||||
|
('stock:export', 'Xuất kho'),
|
||||||
|
('stock:adjust', 'Điều chỉnh tồn kho'),
|
||||||
|
('stock:transfer', 'Chuyển kho'),
|
||||||
|
('stock:read', 'Xem báo cáo tồn kho');
|
||||||
|
|
||||||
|
-- User & RBAC module
|
||||||
|
INSERT INTO permissions (name, description) VALUES
|
||||||
|
('user:create', 'Tạo người dùng mới'),
|
||||||
|
('user:read', 'Xem thông tin người dùng'),
|
||||||
|
('user:update', 'Cập nhật người dùng'),
|
||||||
|
('user:delete', 'Xóa người dùng'),
|
||||||
|
('role:manage', 'Quản lý vai trò và quyền hạn');
|
||||||
|
|
||||||
|
-- Seed: SYS_ADMIN role
|
||||||
|
INSERT INTO roles (name, description, created_by) VALUES
|
||||||
|
('SYS_ADMIN', 'Quản trị viên hệ thống - toàn quyền', 'system');
|
||||||
|
|
||||||
|
-- Gán TẤT CẢ permissions cho SYS_ADMIN
|
||||||
|
INSERT INTO role_permissions (role_id, permission_id)
|
||||||
|
SELECT r.id, p.id
|
||||||
|
FROM roles r
|
||||||
|
CROSS JOIN permissions p
|
||||||
|
WHERE r.name = 'SYS_ADMIN';
|
||||||
29
db/queries/cabinet.sql
Normal file
29
db/queries/cabinet.sql
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
-- name: GetCabinetByID :one
|
||||||
|
SELECT * FROM cabinets
|
||||||
|
WHERE id = sqlc.arg(id);
|
||||||
|
|
||||||
|
-- name: ListCabinets :many
|
||||||
|
SELECT * FROM cabinets
|
||||||
|
ORDER BY created_at DESC;
|
||||||
|
|
||||||
|
-- name: CreateCabinet :one
|
||||||
|
INSERT INTO cabinets (room_id,name, description, created_at)
|
||||||
|
VALUES (
|
||||||
|
sqlc.arg(room_id),
|
||||||
|
sqlc.arg(name),
|
||||||
|
sqlc.arg(description),
|
||||||
|
sqlc.arg(created_at)
|
||||||
|
)
|
||||||
|
RETURNING *;
|
||||||
|
|
||||||
|
-- name: UpdateCabinet :one
|
||||||
|
UPDATE cabinets
|
||||||
|
SET name = coalesce(sqlc.arg(name), name),
|
||||||
|
description = coalesce(sqlc.arg(description), description),
|
||||||
|
updated_at = sqlc.arg(updated_at)
|
||||||
|
WHERE id = sqlc.arg(id)
|
||||||
|
RETURNING *;
|
||||||
|
|
||||||
|
-- name: DeleteCabinet :exec
|
||||||
|
DELETE FROM cabinets
|
||||||
|
WHERE id = sqlc.arg(id);
|
||||||
0
db/queries/permission.sql
Normal file
0
db/queries/permission.sql
Normal file
26
db/queries/roles.sql
Normal file
26
db/queries/roles.sql
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
-- name: GetRoleByID :one
|
||||||
|
SELECT * FROM roles
|
||||||
|
WHERE id = sqlc.arg(id);
|
||||||
|
|
||||||
|
-- name: ListRoles :many
|
||||||
|
SELECT * FROM roles
|
||||||
|
ORDER BY created_at DESC;
|
||||||
|
|
||||||
|
-- name: CreateRole :one
|
||||||
|
INSERT INTO roles (name, description, created_by)
|
||||||
|
VALUES (
|
||||||
|
sqlc.arg(name),
|
||||||
|
sqlc.arg(description),
|
||||||
|
sqlc.arg(created_by))
|
||||||
|
RETURNING *;
|
||||||
|
|
||||||
|
-- name: UpdateRole :one
|
||||||
|
UPDATE roles
|
||||||
|
SET name = sqlc.arg(name),
|
||||||
|
description = sqlc.arg(description)
|
||||||
|
WHERE id = sqlc.arg(id)
|
||||||
|
RETURNING *;
|
||||||
|
|
||||||
|
-- name: DeleteRole :exec
|
||||||
|
DELETE FROM roles
|
||||||
|
WHERE id = sqlc.arg(id);
|
||||||
29
db/queries/room.sql
Normal file
29
db/queries/room.sql
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
-- name: GetRoomByID :one
|
||||||
|
SELECT * FROM rooms
|
||||||
|
WHERE id = sqlc.arg(id);
|
||||||
|
|
||||||
|
-- name: ListRooms :many
|
||||||
|
SELECT * FROM rooms
|
||||||
|
ORDER BY created_at DESC;
|
||||||
|
|
||||||
|
-- name: CreateRoom :one
|
||||||
|
INSERT INTO rooms (warehouse_id,name, description, created_at)
|
||||||
|
VALUES (
|
||||||
|
sqlc.arg(warehouse_id),
|
||||||
|
sqlc.arg(name),
|
||||||
|
sqlc.arg(description),
|
||||||
|
sqlc.arg(created_at)
|
||||||
|
)
|
||||||
|
RETURNING *;
|
||||||
|
|
||||||
|
-- name: UpdateRoom :one
|
||||||
|
UPDATE rooms
|
||||||
|
SET name = coalesce(sqlc.arg(name), name),
|
||||||
|
description = coalesce(sqlc.arg(description), description),
|
||||||
|
updated_at = sqlc.arg(updated_at)
|
||||||
|
WHERE id = sqlc.arg(id)
|
||||||
|
RETURNING *;
|
||||||
|
|
||||||
|
-- name: DeleteRoom :exec
|
||||||
|
DELETE FROM rooms
|
||||||
|
WHERE id = sqlc.arg(id);
|
||||||
36
db/queries/user_roles.sql
Normal file
36
db/queries/user_roles.sql
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
-- name: GetUserRolesByUserID :many
|
||||||
|
SELECT ur.*, r.name AS role_name, r.description AS role_description
|
||||||
|
FROM user_roles ur
|
||||||
|
JOIN roles r ON r.id = ur.role_id
|
||||||
|
WHERE ur.user_id = sqlc.arg(user_id)
|
||||||
|
ORDER BY ur.assigned_at DESC;
|
||||||
|
|
||||||
|
-- name: GetUserRolesByRoleID :many
|
||||||
|
SELECT ur.*, u.username, u.email, u.full_name
|
||||||
|
FROM user_roles ur
|
||||||
|
JOIN users u ON u.id = ur.user_id
|
||||||
|
WHERE ur.role_id = sqlc.arg(role_id)
|
||||||
|
ORDER BY ur.assigned_at DESC;
|
||||||
|
|
||||||
|
-- name: GetUserRole :one
|
||||||
|
SELECT * FROM user_roles
|
||||||
|
WHERE user_id = sqlc.arg(user_id) AND role_id = sqlc.arg(role_id);
|
||||||
|
|
||||||
|
-- name: AssignRoleToUser :one
|
||||||
|
INSERT INTO user_roles (user_id, role_id)
|
||||||
|
VALUES (
|
||||||
|
sqlc.arg(user_id),
|
||||||
|
sqlc.arg(role_id))
|
||||||
|
RETURNING *;
|
||||||
|
|
||||||
|
-- name: RemoveRoleFromUser :exec
|
||||||
|
DELETE FROM user_roles
|
||||||
|
WHERE user_id = sqlc.arg(user_id) AND role_id = sqlc.arg(role_id);
|
||||||
|
|
||||||
|
-- name: RemoveAllRolesFromUser :exec
|
||||||
|
DELETE FROM user_roles
|
||||||
|
WHERE user_id = sqlc.arg(user_id);
|
||||||
|
|
||||||
|
-- name: CountUsersByRoleID :one
|
||||||
|
SELECT COUNT(*) FROM user_roles
|
||||||
|
WHERE role_id = sqlc.arg(role_id);
|
||||||
23
db/queries/users.sql
Normal file
23
db/queries/users.sql
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
-- name: GetUserByID :one
|
||||||
|
SELECT * FROM users
|
||||||
|
WHERE id = sqlc.arg(id);
|
||||||
|
|
||||||
|
-- name: GetUserByEmail :one
|
||||||
|
SELECT * FROM users
|
||||||
|
WHERE email = sqlc.arg(email)
|
||||||
|
LIMIT 1;
|
||||||
|
|
||||||
|
-- name: GetUserByUsername :one
|
||||||
|
SELECT * FROM users
|
||||||
|
WHERE username = sqlc.arg(username)
|
||||||
|
LIMIT 1;
|
||||||
|
|
||||||
|
-- name: CreateUser :one
|
||||||
|
INSERT INTO users (username, email, password_hash, full_name, created_by)
|
||||||
|
VALUES (
|
||||||
|
sqlc.arg(username),
|
||||||
|
sqlc.arg(email),
|
||||||
|
sqlc.arg(password_hash),
|
||||||
|
sqlc.arg(full_name),
|
||||||
|
sqlc.arg(created_by))
|
||||||
|
RETURNING id;
|
||||||
30
db/queries/warehouse.sql
Normal file
30
db/queries/warehouse.sql
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
-- name: GetWarehouseByID :one
|
||||||
|
SELECT * FROM warehouses
|
||||||
|
WHERE id = sqlc.arg(id);
|
||||||
|
|
||||||
|
-- name: ListWarehouses :many
|
||||||
|
SELECT * FROM warehouses
|
||||||
|
ORDER BY created_at DESC;
|
||||||
|
|
||||||
|
-- name: CreateWarehouse :one
|
||||||
|
INSERT INTO warehouses (name, description, address, created_at)
|
||||||
|
VALUES (
|
||||||
|
sqlc.arg(name),
|
||||||
|
sqlc.arg(description),
|
||||||
|
sqlc.arg(address),
|
||||||
|
sqlc.arg(created_at)
|
||||||
|
)
|
||||||
|
RETURNING *;
|
||||||
|
|
||||||
|
-- name: UpdateWarehouse :one
|
||||||
|
UPDATE warehouses
|
||||||
|
SET name = CASE WHEN sqlc.arg(name) = '' THEN name ELSE sqlc.arg(name) END,
|
||||||
|
description = coalesce(sqlc.arg(description), description),
|
||||||
|
address = coalesce(sqlc.arg(address), address),
|
||||||
|
updated_at = sqlc.arg(updated_at)
|
||||||
|
WHERE id = sqlc.arg(id)
|
||||||
|
RETURNING *;
|
||||||
|
|
||||||
|
-- name: DeleteWarehouse :exec
|
||||||
|
DELETE FROM warehouses
|
||||||
|
WHERE id = sqlc.arg(id);
|
||||||
47
docker-compose.dev.yml
Normal file
47
docker-compose.dev.yml
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
services:
|
||||||
|
postgres:
|
||||||
|
container_name: wm-postgres
|
||||||
|
image: postgres:18-alpine
|
||||||
|
ports:
|
||||||
|
- "${POSTGRES_PORT_MAPPING}:${POSTGRES_PORT}"
|
||||||
|
environment:
|
||||||
|
- POSTGRES_DB=${POSTGRES_DB}
|
||||||
|
- POSTGRES_USER=${POSTGRES_USER}
|
||||||
|
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
volumes:
|
||||||
|
- postgres_data:/var/lib/postgresql
|
||||||
|
- ./db/init/init.sql:/docker-entrypoint-initdb.d/01_init.sql
|
||||||
|
restart: unless-stopped
|
||||||
|
healthcheck:
|
||||||
|
test:
|
||||||
|
[
|
||||||
|
"CMD-SHELL",
|
||||||
|
"sh -c 'pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}'",
|
||||||
|
]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 3s
|
||||||
|
retries: 5
|
||||||
|
start_period: 30s
|
||||||
|
|
||||||
|
redis:
|
||||||
|
container_name: wm-redis
|
||||||
|
image: redis:8-alpine
|
||||||
|
command: redis-server --requirepass ${REDIS_PASSWORD}
|
||||||
|
ports:
|
||||||
|
- "${REDIS_PORT_MAPPING}:${REDIS_PORT}"
|
||||||
|
restart: unless-stopped
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
volumes:
|
||||||
|
- redis_data:/data
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "redis-cli", "--pass", "${REDIS_PASSWORD}", "ping"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 3s
|
||||||
|
retries: 5
|
||||||
|
start_period: 30s
|
||||||
|
volumes:
|
||||||
|
postgres_data:
|
||||||
|
redis_data:
|
||||||
427
docs/db/WareHouseDB.md
Normal file
427
docs/db/WareHouseDB.md
Normal file
@@ -0,0 +1,427 @@
|
|||||||
|
# Warehouse Management Database Schema
|
||||||
|
|
||||||
|
## ER Diagram Overview
|
||||||
|
|
||||||
|
```
|
||||||
|
Warehouse 1──N Room 1──N Cabinet 1──N Shelf 1──N Container
|
||||||
|
│
|
||||||
|
M──N Component (qua ComponentItem)
|
||||||
|
│
|
||||||
|
ComponentType 1──N Component 1──N ComponentCode
|
||||||
|
│
|
||||||
|
├── ComponentItem (instance tại từng Container)
|
||||||
|
│
|
||||||
|
└── ComponentStatusHistory (lịch sử thay đổi tình trạng)
|
||||||
|
|
||||||
|
InvoiceConfig 1──N InvoiceConfigItem 1──N AlternativeComponent
|
||||||
|
│
|
||||||
|
Invoice 1──N InvoiceItem
|
||||||
|
│
|
||||||
|
└── InvoiceStatusHistory (lịch sử trạng thái hóa đơn)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Tables
|
||||||
|
|
||||||
|
### 1. warehouses (Kho)
|
||||||
|
|
||||||
|
| Column | Type | Constraints | Description |
|
||||||
|
| ------------ | ------------ | -------------------- | ------------------ |
|
||||||
|
| id | BIGINT | PK, AUTO_INCREMENT | |
|
||||||
|
| name | VARCHAR(255) | NOT NULL | Tên kho |
|
||||||
|
| description | TEXT | | Mô tả |
|
||||||
|
| address | VARCHAR(500) | | Địa chỉ |
|
||||||
|
| created_at | DATETIME | DEFAULT NOW() | |
|
||||||
|
| updated_at | DATETIME | DEFAULT NOW() | |
|
||||||
|
|
||||||
|
### 2. rooms (Phòng)
|
||||||
|
|
||||||
|
| Column | Type | Constraints | Description |
|
||||||
|
| ------------ | ------------ | -------------------- | ------------------ |
|
||||||
|
| id | BIGINT | PK, AUTO_INCREMENT | |
|
||||||
|
| warehouse_id | BIGINT | FK → warehouses(id) | Thuộc kho nào |
|
||||||
|
| name | VARCHAR(255) | NOT NULL | Tên phòng |
|
||||||
|
| description | TEXT | | Mô tả |
|
||||||
|
| created_at | DATETIME | DEFAULT NOW() | |
|
||||||
|
| updated_at | DATETIME | DEFAULT NOW() | |
|
||||||
|
|
||||||
|
### 3. cabinets (Tủ)
|
||||||
|
|
||||||
|
| Column | Type | Constraints | Description |
|
||||||
|
| ------------ | ------------ | -------------------- | ------------------ |
|
||||||
|
| id | BIGINT | PK, AUTO_INCREMENT | |
|
||||||
|
| room_id | BIGINT | FK → rooms(id) | Thuộc phòng nào |
|
||||||
|
| name | VARCHAR(255) | NOT NULL | Tên tủ |
|
||||||
|
| description | TEXT | | Mô tả |
|
||||||
|
| created_at | DATETIME | DEFAULT NOW() | |
|
||||||
|
| updated_at | DATETIME | DEFAULT NOW() | |
|
||||||
|
|
||||||
|
### 4. shelves (Tầng / Kệ)
|
||||||
|
|
||||||
|
| Column | Type | Constraints | Description |
|
||||||
|
| ------------ | ------------ | -------------------- | -------------------- |
|
||||||
|
| id | BIGINT | PK, AUTO_INCREMENT | |
|
||||||
|
| cabinet_id | BIGINT | FK → cabinets(id) | Thuộc tủ nào |
|
||||||
|
| name | VARCHAR(255) | NOT NULL | Tên tầng (VD: Tầng 1)|
|
||||||
|
| level_index | INT | NOT NULL | Thứ tự tầng |
|
||||||
|
| description | TEXT | | Mô tả |
|
||||||
|
| created_at | DATETIME | DEFAULT NOW() | |
|
||||||
|
| updated_at | DATETIME | DEFAULT NOW() | |
|
||||||
|
|
||||||
|
### 5. containers (Vật chứa: thùng rỗng, khay, thùng giấy, ...)
|
||||||
|
|
||||||
|
| Column | Type | Constraints | Description |
|
||||||
|
| ------------ | ------------ | -------------------- | ----------------------------- |
|
||||||
|
| id | BIGINT | PK, AUTO_INCREMENT | |
|
||||||
|
| shelf_id | BIGINT | FK → shelves(id) | Nằm trên tầng nào |
|
||||||
|
| name | VARCHAR(255) | NOT NULL | Tên vật chứa |
|
||||||
|
| container_type | ENUM('empty_box', 'tray', 'paper_box', 'plastic_box', 'bag', 'other') | NOT NULL | Loại vật chứa |
|
||||||
|
| description | TEXT | | Mô tả |
|
||||||
|
| max_capacity | INT | | Sức chứa tối đa (số linh kiện)|
|
||||||
|
| created_at | DATETIME | DEFAULT NOW() | |
|
||||||
|
| updated_at | DATETIME | DEFAULT NOW() | |
|
||||||
|
|
||||||
|
### 6. component_types (Loại linh kiện)
|
||||||
|
|
||||||
|
| Column | Type | Constraints | Description |
|
||||||
|
| ------------ | ------------ | -------------------- | ---------------------------- |
|
||||||
|
| id | BIGINT | PK, AUTO_INCREMENT | |
|
||||||
|
| name | VARCHAR(255) | NOT NULL, UNIQUE | Tên loại (VD: Resistor, IC) |
|
||||||
|
| description | TEXT | | Mô tả |
|
||||||
|
| created_at | DATETIME | DEFAULT NOW() | |
|
||||||
|
| updated_at | DATETIME | DEFAULT NOW() | |
|
||||||
|
|
||||||
|
### 7. components (Linh kiện)
|
||||||
|
|
||||||
|
| Column | Type | Constraints | Description |
|
||||||
|
| ----------------- | ------------ | ------------------------- | ----------------------------- |
|
||||||
|
| id | BIGINT | PK, AUTO_INCREMENT | |
|
||||||
|
| component_type_id | BIGINT | FK → component_types(id) | Thuộc loại nào |
|
||||||
|
| name | VARCHAR(255) | NOT NULL | Tên linh kiện |
|
||||||
|
| description | TEXT | | Mô tả chi tiết |
|
||||||
|
| unit | VARCHAR(50) | DEFAULT 'cái' | Đơn vị tính (cái, m, kg, ...) |
|
||||||
|
| total_quantity | INT | DEFAULT 0 | Tổng số lượng (tính tự động) |
|
||||||
|
| min_quantity | INT | DEFAULT 0 | Số lượng tối thiểu (cảnh báo) |
|
||||||
|
| created_at | DATETIME | DEFAULT NOW() | |
|
||||||
|
| updated_at | DATETIME | DEFAULT NOW() | |
|
||||||
|
|
||||||
|
> **Note**: `total_quantity` được tính tổng từ `component_items.quantity` và nên được cache/update qua trigger hoặc application logic.
|
||||||
|
|
||||||
|
### 8. component_codes (Mã linh kiện - 1 linh kiện có thể có nhiều mã)
|
||||||
|
|
||||||
|
| Column | Type | Constraints | Description |
|
||||||
|
| ------------ | ------------ | ----------------------- | -------------------------------- |
|
||||||
|
| id | BIGINT | PK, AUTO_INCREMENT | |
|
||||||
|
| component_id | BIGINT | FK → components(id) | Thuộc linh kiện nào |
|
||||||
|
| code | VARCHAR(255) | NOT NULL | Mã (VD: ESP32-WROOM-32D) |
|
||||||
|
| code_type | VARCHAR(100) | | Loại mã (VD: SKU, Part Number) |
|
||||||
|
| is_primary | BOOLEAN | DEFAULT FALSE | Mã chính |
|
||||||
|
| created_at | DATETIME | DEFAULT NOW() | |
|
||||||
|
|
||||||
|
```
|
||||||
|
UNIQUE (code, code_type)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 9. component_items (Linh kiện tại từng vị trí - nằm trong container)
|
||||||
|
|
||||||
|
| Column | Type | Constraints | Description |
|
||||||
|
| ------------ | ------- | ------------------------------------ | ------------------------------ |
|
||||||
|
| id | BIGINT | PK, AUTO_INCREMENT | |
|
||||||
|
| component_id | BIGINT | FK → components(id) | Linh kiện nào |
|
||||||
|
| container_id | BIGINT | FK → containers(id) | Nằm trong vật chứa nào |
|
||||||
|
| quantity | INT | NOT NULL, DEFAULT 0 | Số lượng tại vị trí này |
|
||||||
|
| status | ENUM('normal', 'damaged', 'long_unused', 'expired', 'pending_inspection') | NOT NULL DEFAULT 'normal' | Tình trạng |
|
||||||
|
| created_at | DATETIME| DEFAULT NOW() | |
|
||||||
|
| updated_at | DATETIME| DEFAULT NOW() | |
|
||||||
|
|
||||||
|
```
|
||||||
|
UNIQUE (component_id, container_id, status)
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Note**: Cùng 1 linh kiện ở cùng 1 container nhưng khác status sẽ là các record khác nhau. Điều này cho phép phân biệt linh kiện hỏng và bình thường trong cùng vị trí.
|
||||||
|
|
||||||
|
### 10. component_status_history (Lịch sử thay đổi tình trạng linh kiện)
|
||||||
|
|
||||||
|
| Column | Type | Constraints | Description |
|
||||||
|
| ---------------- | ---------- | --------------------------------- | ----------------------------------- |
|
||||||
|
| id | BIGINT | PK, AUTO_INCREMENT | |
|
||||||
|
| component_item_id| BIGINT | FK → component_items(id) | Linh kiện item nào |
|
||||||
|
| old_status | ENUM (như component_items.status) | | Tình trạng cũ |
|
||||||
|
| new_status | ENUM (như component_items.status) | | Tình trạng mới |
|
||||||
|
| changed_quantity | INT | | Số lượng bị thay đổi tình trạng |
|
||||||
|
| note | TEXT | | Ghi chú lý do |
|
||||||
|
| changed_by | VARCHAR(255)| | Người thay đổi |
|
||||||
|
| changed_at | DATETIME | DEFAULT NOW() | |
|
||||||
|
|
||||||
|
### 11. invoice_configs (Cấu hình hóa đơn mẫu)
|
||||||
|
|
||||||
|
| Column | Type | Constraints | Description |
|
||||||
|
| ------------ | ------------ | -------------------- | -------------------------------- |
|
||||||
|
| id | BIGINT | PK, AUTO_INCREMENT | |
|
||||||
|
| name | VARCHAR(255) | NOT NULL | Tên cấu hình (VD: Kit xuất kho A)|
|
||||||
|
| type | ENUM('import', 'export') | NOT NULL | Loại hóa đơn |
|
||||||
|
| description | TEXT | | Mô tả |
|
||||||
|
| is_active | BOOLEAN | DEFAULT TRUE | Đang sử dụng |
|
||||||
|
| created_at | DATETIME | DEFAULT NOW() | |
|
||||||
|
| updated_at | DATETIME | DEFAULT NOW() | |
|
||||||
|
|
||||||
|
### 12. invoice_config_items (Chi tiết cấu hình hóa đơn - linh kiện cần xuất/nhập)
|
||||||
|
|
||||||
|
| Column | Type | Constraints | Description |
|
||||||
|
| ----------------- | ------------ | ---------------------------------- | ----------------------- |
|
||||||
|
| id | BIGINT | PK, AUTO_INCREMENT | |
|
||||||
|
| invoice_config_id | BIGINT | FK → invoice_configs(id) | Thuộc cấu hình nào |
|
||||||
|
| component_id | BIGINT | FK → components(id) | Linh kiện cần |
|
||||||
|
| required_quantity | INT | NOT NULL | Số lượng yêu cầu |
|
||||||
|
| allow_alternative | BOOLEAN | DEFAULT FALSE | Cho phép dùng linh kiện thay thế |
|
||||||
|
| priority_order | INT | DEFAULT 0 | Thứ tự ưu tiên |
|
||||||
|
| note | TEXT | | Ghi chú |
|
||||||
|
|
||||||
|
```
|
||||||
|
UNIQUE (invoice_config_id, component_id)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 13. alternative_components (Linh kiện thay thế - dùng khi thiếu)
|
||||||
|
|
||||||
|
| Column | Type | Constraints | Description |
|
||||||
|
| ---------------------- | ------- | ---------------------------------------- | ---------------------------------- |
|
||||||
|
| id | BIGINT | PK, AUTO_INCREMENT | |
|
||||||
|
| invoice_config_item_id | BIGINT | FK → invoice_config_items(id) | Item cấu hình nào |
|
||||||
|
| alternative_component_id | BIGINT | FK → components(id) | Linh kiện thay thế |
|
||||||
|
| conversion_ratio | DECIMAL(10,2) | DEFAULT 1.0 | Tỷ lệ quy đổi (VD: 2 cái nhỏ = 1 cái lớn) |
|
||||||
|
| priority | INT | DEFAULT 0 | Thứ tự ưu tiên khi thay thế |
|
||||||
|
| note | TEXT | | Ghi chú |
|
||||||
|
|
||||||
|
```
|
||||||
|
UNIQUE (invoice_config_item_id, alternative_component_id)
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Ví dụ**: Nếu cấu hình yêu cầu "IC WiFi ESP32" nhưng hết, có thể dùng "IC WiFi ESP32-C3" thay thế với `conversion_ratio = 1.0`.
|
||||||
|
|
||||||
|
### 14. invoices (Hóa đơn nhập / xuất)
|
||||||
|
|
||||||
|
| Column | Type | Constraints | Description |
|
||||||
|
| ----------------- | ------------ | ---------------------------- | ---------------------------------------- |
|
||||||
|
| id | BIGINT | PK, AUTO_INCREMENT | |
|
||||||
|
| invoice_code | VARCHAR(100) | NOT NULL, UNIQUE | Mã hóa đơn (tự generate) |
|
||||||
|
| type | ENUM('import', 'export') | NOT NULL | Loại hóa đơn |
|
||||||
|
| status | ENUM('draft', 'pending', 'approved', 'completed', 'cancelled') | NOT NULL DEFAULT 'draft' | Trạng thái |
|
||||||
|
| invoice_config_id | BIGINT | FK → invoice_configs(id), NULL | Cấu hình áp dụng (có thể null) |
|
||||||
|
| total_items | INT | DEFAULT 0 | Tổng số loại linh kiện |
|
||||||
|
| note | TEXT | | Ghi chú |
|
||||||
|
| created_by | VARCHAR(255) | | Người tạo |
|
||||||
|
| approved_by | VARCHAR(255) | | Người duyệt |
|
||||||
|
| completed_at | DATETIME | | Thời gian hoàn thành |
|
||||||
|
| created_at | DATETIME | DEFAULT NOW() | |
|
||||||
|
| updated_at | DATETIME | DEFAULT NOW() | |
|
||||||
|
|
||||||
|
### 15. invoice_items (Chi tiết hóa đơn - từng linh kiện)
|
||||||
|
|
||||||
|
| Column | Type | Constraints | Description |
|
||||||
|
| ----------------- | ------------ | ---------------------------------- | -------------------------------- |
|
||||||
|
| id | BIGINT | PK, AUTO_INCREMENT | |
|
||||||
|
| invoice_id | BIGINT | FK → invoices(id) | Thuộc hóa đơn nào |
|
||||||
|
| component_id | BIGINT | FK → components(id) | Linh kiện |
|
||||||
|
| original_component_id | BIGINT | FK → components(id), NULL | Linh kiện gốc (nếu dùng thay thế)|
|
||||||
|
| required_quantity | INT | NOT NULL | Số lượng yêu cầu |
|
||||||
|
| actual_quantity | INT | DEFAULT 0 | Số lượng thực tế |
|
||||||
|
| is_substituted | BOOLEAN | DEFAULT FALSE | Đã dùng linh kiện thay thế |
|
||||||
|
| is_short | BOOLEAN | DEFAULT FALSE | Có thiếu hay không |
|
||||||
|
| shortage_quantity | INT | DEFAULT 0 | Số lượng thiếu |
|
||||||
|
| note | TEXT | | Ghi chú |
|
||||||
|
|
||||||
|
```
|
||||||
|
UNIQUE (invoice_id, component_id)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 16. invoice_item_locations (Chi tiết vị trí xuất/nhập cho từng item)
|
||||||
|
|
||||||
|
| Column | Type | Constraints | Description |
|
||||||
|
| --------------- | ------- | --------------------------------- | ------------------------------------ |
|
||||||
|
| id | BIGINT | PK, AUTO_INCREMENT | |
|
||||||
|
| invoice_item_id | BIGINT | FK → invoice_items(id) | Item hóa đơn nào |
|
||||||
|
| container_id | BIGINT | FK → containers(id) | Xuất/nhập từ container nào |
|
||||||
|
| quantity | INT | NOT NULL | Số lượng xuất/nhập tại vị trí này |
|
||||||
|
|
||||||
|
> **Note**: Bảng này cho phép xuất 1 linh kiện từ nhiều vị trí khác nhau, và ghi lại chính xác vị trí.
|
||||||
|
|
||||||
|
### 17. invoice_status_history (Lịch sử trạng thái hóa đơn)
|
||||||
|
|
||||||
|
| Column | Type | Constraints | Description |
|
||||||
|
| ---------- | ------------ | ---------------------------- | -------------------- |
|
||||||
|
| id | BIGINT | PK, AUTO_INCREMENT | |
|
||||||
|
| invoice_id | BIGINT | FK → invoices(id) | Hóa đơn nào |
|
||||||
|
| old_status | VARCHAR(50) | | Trạng thái cũ |
|
||||||
|
| new_status | VARCHAR(50) | NOT NULL | Trạng thái mới |
|
||||||
|
| changed_by | VARCHAR(255) | | Người thay đổi |
|
||||||
|
| note | TEXT | | Ghi chú |
|
||||||
|
| changed_at | DATETIME | DEFAULT NOW() | |
|
||||||
|
|
||||||
|
### 18. stock_transactions (Lịch sử nhập xuất kho - phục vụ thống kê)
|
||||||
|
|
||||||
|
| Column | Type | Constraints | Description |
|
||||||
|
| ----------------- | ------------ | ---------------------------------- | ----------------------------- |
|
||||||
|
| id | BIGINT | PK, AUTO_INCREMENT | |
|
||||||
|
| invoice_id | BIGINT | FK → invoices(id) | Hóa đơn liên quan |
|
||||||
|
| component_id | BIGINT | FK → components(id) | Linh kiện |
|
||||||
|
| container_id | BIGINT | FK → containers(id) | Vị trí |
|
||||||
|
| transaction_type | ENUM('import', 'export', 'adjustment', 'transfer') | NOT NULL | Loại giao dịch |
|
||||||
|
| quantity | INT | NOT NULL | Số lượng (âm = xuất, dương = nhập) |
|
||||||
|
| balance_after | INT | | Số dư sau giao dịch |
|
||||||
|
| note | TEXT | | Ghi chú |
|
||||||
|
| created_by | VARCHAR(255) | | Người thực hiện |
|
||||||
|
| created_at | DATETIME | DEFAULT NOW() | |
|
||||||
|
|
||||||
|
> **Note**: Bảng này là bảng tổng hợp để phục vụ thống kê nhanh, tránh phải tính toán từ invoice_items mỗi lần.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Indexes (Quan trọng)
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- Tìm linh kiện theo vị trí
|
||||||
|
CREATE INDEX idx_component_items_component ON component_items(component_id);
|
||||||
|
CREATE INDEX idx_component_items_container ON component_items(container_id);
|
||||||
|
CREATE INDEX idx_component_items_status ON component_items(status);
|
||||||
|
|
||||||
|
-- Tìm mã linh kiện
|
||||||
|
CREATE INDEX idx_component_codes_code ON component_codes(code);
|
||||||
|
CREATE INDEX idx_component_codes_component ON component_codes(component_id);
|
||||||
|
|
||||||
|
-- Thống kê hóa đơn theo thời gian
|
||||||
|
CREATE INDEX idx_invoices_type_status ON invoices(type, status);
|
||||||
|
CREATE INDEX idx_invoices_created_at ON invoices(created_at);
|
||||||
|
CREATE INDEX idx_invoices_type_created ON invoices(type, created_at);
|
||||||
|
|
||||||
|
-- Thống kê giao dịch kho
|
||||||
|
CREATE INDEX idx_stock_transactions_component_date ON stock_transactions(component_id, created_at);
|
||||||
|
CREATE INDEX idx_stock_transactions_type_date ON stock_transactions(transaction_type, created_at);
|
||||||
|
CREATE INDEX idx_stock_transactions_invoice ON stock_transactions(invoice_id);
|
||||||
|
|
||||||
|
-- Tìm vị trí container
|
||||||
|
CREATE INDEX idx_containers_shelf ON containers(shelf_id);
|
||||||
|
CREATE INDEX idx_shelves_cabinet ON shelves(cabinet_id);
|
||||||
|
CREATE INDEX idx_cabinets_room ON cabinets(room_id);
|
||||||
|
CREATE INDEX idx_rooms_warehouse ON rooms(warehouse_id);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Ví dụ truy vấn phổ biến
|
||||||
|
|
||||||
|
### 1. Tìm vị trí của 1 linh kiện
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT
|
||||||
|
c.name AS component_name,
|
||||||
|
ct.name AS type_name,
|
||||||
|
ci.quantity,
|
||||||
|
ci.status,
|
||||||
|
cn.name AS container_name,
|
||||||
|
cn.container_type,
|
||||||
|
s.name AS shelf_name,
|
||||||
|
cb.name AS cabinet_name,
|
||||||
|
r.name AS room_name,
|
||||||
|
w.name AS warehouse_name
|
||||||
|
FROM component_items ci
|
||||||
|
JOIN components c ON ci.component_id = c.id
|
||||||
|
JOIN component_types ct ON c.component_type_id = ct.id
|
||||||
|
JOIN containers cn ON ci.container_id = cn.id
|
||||||
|
JOIN shelves s ON cn.shelf_id = s.id
|
||||||
|
JOIN cabinets cb ON s.cabinet_id = cb.id
|
||||||
|
JOIN rooms r ON cb.room_id = r.id
|
||||||
|
JOIN warehouses w ON r.warehouse_id = w.id
|
||||||
|
WHERE ci.component_id = :componentId AND ci.quantity > 0;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Kiểm tra thiếu hụt khi xuất hóa đơn
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT
|
||||||
|
ici.component_id,
|
||||||
|
c.name AS component_name,
|
||||||
|
ici.required_quantity,
|
||||||
|
COALESCE(SUM(ci.quantity), 0) AS available_quantity,
|
||||||
|
CASE
|
||||||
|
WHEN COALESCE(SUM(ci.quantity), 0) < ici.required_quantity THEN TRUE
|
||||||
|
ELSE FALSE
|
||||||
|
END AS is_short,
|
||||||
|
ici.allow_alternative
|
||||||
|
FROM invoice_config_items ici
|
||||||
|
JOIN components c ON ici.component_id = c.id
|
||||||
|
LEFT JOIN component_items ci ON ci.component_id = ici.component_id
|
||||||
|
AND ci.status = 'normal'
|
||||||
|
WHERE ici.invoice_config_id = :configId
|
||||||
|
GROUP BY ici.component_id, c.name, ici.required_quantity, ici.allow_alternative;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Thống kê nhập xuất theo thời gian
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT
|
||||||
|
st.transaction_type,
|
||||||
|
c.name AS component_name,
|
||||||
|
SUM(ABS(st.quantity)) AS total_quantity,
|
||||||
|
DATE(st.created_at) AS transaction_date
|
||||||
|
FROM stock_transactions st
|
||||||
|
JOIN components c ON st.component_id = c.id
|
||||||
|
WHERE st.created_at BETWEEN :startDate AND :endDate
|
||||||
|
GROUP BY st.transaction_type, c.id, DATE(st.created_at)
|
||||||
|
ORDER BY transaction_date DESC;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Linh kiện sắp hết (dưới mức tối thiểu)
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT
|
||||||
|
c.id,
|
||||||
|
c.name,
|
||||||
|
c.total_quantity,
|
||||||
|
c.min_quantity,
|
||||||
|
ct.name AS type_name
|
||||||
|
FROM components c
|
||||||
|
JOIN component_types ct ON c.component_type_id = ct.id
|
||||||
|
WHERE c.total_quantity <= c.min_quantity AND c.min_quantity > 0;
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Flow xử lý hóa đơn xuất
|
||||||
|
|
||||||
|
```
|
||||||
|
1. Tạo Invoice (status: draft)
|
||||||
|
↓
|
||||||
|
2. Chọn InvoiceConfig hoặc thêm items thủ công
|
||||||
|
↓
|
||||||
|
3. Kiểm tra tồn kho:
|
||||||
|
- Query component_items WHERE status = 'normal'
|
||||||
|
- So sánh available vs required
|
||||||
|
- Nếu thiếu:
|
||||||
|
a. Cảnh báo item thiếu
|
||||||
|
b. Kiểm tra allow_alternative
|
||||||
|
c. Query alternative_components
|
||||||
|
d. Đề xuất linh kiện thay thế
|
||||||
|
↓
|
||||||
|
4. Chấp nhận / Điều chỉnh (status: approved)
|
||||||
|
↓
|
||||||
|
5. Thực hiện xuất kho:
|
||||||
|
- Trừ quantity trong component_items
|
||||||
|
- Tạo stock_transactions
|
||||||
|
- Cập nhật total_quantity trong components
|
||||||
|
- Tạo invoice_item_locations
|
||||||
|
↓
|
||||||
|
6. Hoàn thành (status: completed)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Enum Values Reference
|
||||||
|
|
||||||
|
| Enum Field | Values |
|
||||||
|
| ---------------- | --------------------------------------------------- |
|
||||||
|
| container_type | `empty_box`, `tray`, `paper_box`, `plastic_box`, `bag`, `other` |
|
||||||
|
| status (item) | `normal`, `damaged`, `long_unused`, `expired`, `pending_inspection` |
|
||||||
|
| invoice type | `import`, `export` |
|
||||||
|
| invoice status | `draft`, `pending`, `approved`, `completed`, `cancelled` |
|
||||||
|
| transaction_type | `import`, `export`, `adjustment`, `transfer` |
|
||||||
170
docs/sqlc/config.md
Normal file
170
docs/sqlc/config.md
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
# sqlc.yaml — Configuration Reference (Version 2)
|
||||||
|
|
||||||
|
sqlc sử dụng **Configuration Version 2** khi khai báo `version: "2"` ở đầu file. Dưới đây là bộ quy tắc và danh sách đầy đủ các key.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Cấu trúc tổng thể
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
version: "2"
|
||||||
|
|
||||||
|
sql:
|
||||||
|
- engine: "<engine>"
|
||||||
|
queries: "<path>"
|
||||||
|
schema: "<path>"
|
||||||
|
gen:
|
||||||
|
go:
|
||||||
|
package: "<name>"
|
||||||
|
out: "<path>"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Các key cấp gốc (root-level)
|
||||||
|
|
||||||
|
| Key | Kiểu | Bắt buộc | Mô tả |
|
||||||
|
| ----------- | ----- | -------- | ------------------------------------------------------------------------ |
|
||||||
|
| `version` | `"2"` | ✅ | Khai báo phiên bản cấu hình. Giá trị `"2"` cho Version 2. |
|
||||||
|
| `sql` | list | ✅ | Danh sách các block cấu hình, mỗi block sinh code cho một ngôn ngữ đích. |
|
||||||
|
| `overrides` | map | ❌ | (Deprecated v1) — Không dùng trong v2, thay bằng `gen.<lang>.overrides`. |
|
||||||
|
| `rename` | map | ❌ | (Deprecated v1) — Không dùng trong v2, thay bằng `gen.<lang>.rename`. |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Các key bên trong mỗi phần tử của `sql[]`
|
||||||
|
|
||||||
|
| Key | Kiểu | Bắt buộc | Mô tả |
|
||||||
|
| ------------------------ | ----------------- | -------------- | --------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
|
| `engine` | string | ✅ | CSDL mục tiêu. Giá trị: `"postgresql"`, `"mysql"`, `"sqlite"`. |
|
||||||
|
| `queries` | string / string[] | ✅ | Đường dẫn tới thư mục/file chứa các query `.sql`. Có thể là mảng nhiều path. |
|
||||||
|
| `schema` | string / string[] | ✅ | Đường dẫn tới thư mục/file chứa schema DDL (`.sql`). |
|
||||||
|
| `strict_function_checks` | bool | ❌ | Nếu `true`, sqlc sẽ báo lỗi khi gọi function không tồn tại trong schema. Mặc định `false`. |
|
||||||
|
| `strict_order_by` | bool | ❌ | Nếu `true`, yêu cầu tất cả column trong `ORDER BY` phải tồn tại. Mặc định `false` (chỉ cho PostgreSQL). |
|
||||||
|
| `query_parameter_limit` | int | ❌ | Giới hạn số lượng parameter trong một query. Mặc định `1` (nếu > 1 thì sqlc ưu tiên sinh `sql.NamedArg`). Đặt `0` để bỏ giới hạn. |
|
||||||
|
| `codegen` | list | ❌ | Danh sách cấu hình cho **plugin codegen** bên ngoài. Mỗi item có `out`, `plugin`, `options`. |
|
||||||
|
| `gen` | map | ✅ (ít nhất 1) | Map các ngôn ngữ sinh code. Các key con: `go`, `kotlin`, `python`, `json`, `typescript`, `java`, `swift`, `rust`, `csharp`. |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Các key bên trong `gen.go`
|
||||||
|
|
||||||
|
| Key | Kiểu | Bắt buộc | Mô tả |
|
||||||
|
| -------------------------------- | ------ | -------- | ----------------------------------------------------------------------------------------------------------------- |
|
||||||
|
| `out` | string | ✅ | Thư mục output cho Go code sinh ra. |
|
||||||
|
| `package` | string | ✅ | Tên Go package. |
|
||||||
|
| `sql_package` | string | ❌ | Package SQL driver. Giá trị: `"database/sql"`, `"pgx/v4"`, `"pgx/v5"`, `"lib/pq"`. Mặc định `"database/sql"`. |
|
||||||
|
| `sql_driver` | string | ❌ | Tên driver cụ thể, dùng để sinh import đúng. VD: `"github.com/jackc/pgx/v5/stdlib"`. |
|
||||||
|
| `emit_json_tags` | bool | ❌ | Nếu `true`, sinh `json:"column_name"` tag cho struct field. Mặc định `false`. |
|
||||||
|
| `emit_db_tags` | bool | ❌ | Nếu `true`, sinh `db:"column_name"` tag. Mặc định `false`. |
|
||||||
|
| `emit_prepared_queries` | bool | ❌ | Nếu `true`, sinh method `Prepare` cho mỗi query. Mặc định `false`. |
|
||||||
|
| `emit_interface` | bool | ❌ | Nếu `true`, sinh interface ` Querier` thay vì chỉ struct. Mặc định `false`. |
|
||||||
|
| `emit_empty_slices` | bool | ❌ | Nếu `true`, trả về `[]T` rỗng thay vì `nil` khi không có row. Mặc định `false`. |
|
||||||
|
| `emit_result_struct_pointers` | bool | ❌ | Sinh con trỏ `*T` cho result struct. Mặc định `false`. |
|
||||||
|
| `emit_params_struct_pointers` | bool | ❌ | Sinh con trỏ `*T` cho params struct. Mặc định `false`. |
|
||||||
|
| `emit_method_with_db_argument` | bool | ❌ | Nếu `true`, mỗi method nhận thêm `DB` argument, cho phép dùng transaction dễ hơn. Mặc định `false`. |
|
||||||
|
| `emit_pointers_for_null_types` | bool | ❌ | Nếu `true`, dùng con trỏ cho null type thay vì `sql.Null*`. Mặc định `false`. |
|
||||||
|
| `emit_enum_valid_method` | bool | ❌ | Sinh method `Valid()` cho enum type. Mặc định `false`. |
|
||||||
|
| `emit_all_enum_values` | bool | ❌ | Sinh constant cho tất cả giá trị enum. Mặc định `false`. |
|
||||||
|
| `emit_build_tags` | string | ❌ | Thêm Go build tag vào file sinh ra. VD: `"//go:build linux"`. |
|
||||||
|
| `json_tags_case_style` | string | ❌ | Style cho JSON tag. Giá trị: `"camel"`, `"pascal"`, `"snake"`, `"none"`. Mặc định phụ thuộc vào `emit_json_tags`. |
|
||||||
|
| `output_db_file_name` | string | ❌ | Tên file chứa `DB` struct. Mặc định `"db.go"`. |
|
||||||
|
| `output_models_file_name` | string | ❌ | Tên file chứa model struct. Mặc định `"models.go"`. |
|
||||||
|
| `output_querier_file_name` | string | ❌ | Tên file chứa interface. Mặc định `"querier.go"`. |
|
||||||
|
| `output_files_suffix` | string | ❌ | Hậu tố cho file query. Mặc định `""`. VD: `"_sql"` → `user_sql.go`. |
|
||||||
|
| `inflection_exclude_table_names` | list | ❌ | Danh sách tên table không áp dụng quy tắc số nhiều. VD: `["user"]`. |
|
||||||
|
| `overrides` | list | ❌ | Ghi đè kiểu dữ liệu cho column cụ thể hoặc cho kiểu Go toàn cục (xem chi tiết bên dưới). |
|
||||||
|
| `rename` | map | ❌ | Map đổi tên. Key = tên cần đổi, Value = tên mới. Dùng để rename struct field. |
|
||||||
|
| `import` | string | ❌ | Import path của Go module dùng trong generated code. |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Cấu trúc của `overrides[]` (bên trong `gen.go`)
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
overrides:
|
||||||
|
- db_type: "uuid"
|
||||||
|
go_type: "github.com/google/uuid.UUID"
|
||||||
|
- db_type: "timestamptz"
|
||||||
|
go_type: "time.Time"
|
||||||
|
- column: "users.status"
|
||||||
|
go_type: "UserStatus"
|
||||||
|
go_struct_tag:
|
||||||
|
tags:
|
||||||
|
json: "status,omitempty"
|
||||||
|
- db_type: "text"
|
||||||
|
go_type:
|
||||||
|
import: "github.com/lib/pq"
|
||||||
|
type: "StringArray"
|
||||||
|
nullable: true
|
||||||
|
```
|
||||||
|
|
||||||
|
| Key | Mô tả |
|
||||||
|
| --------------- | ------------------------------------------------------------------------- |
|
||||||
|
| `db_type` | Kiểu dữ liệu SQL cần ghi đè. Dùng cùng với `go_type`. |
|
||||||
|
| `column` | Đường dẫn `"table.column"` cụ thể. Ưu tiên cao hơn `db_type`. |
|
||||||
|
| `go_type` | Kiểu Go thay thế. Có thể là string hoặc object `{import, type, pointer}`. |
|
||||||
|
| `nullable` | bool — Nếu `true`, áp dụng cho phiên bản nullable của kiểu. |
|
||||||
|
| `go_struct_tag` | Custom struct tag cho field. |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## `gen.json`
|
||||||
|
|
||||||
|
| Key | Kiểu | Bắt buộc | Mô tả |
|
||||||
|
| ---------- | ------ | -------- | --------------------------------------------------- |
|
||||||
|
| `out` | string | ✅ | Thư mục output file JSON. |
|
||||||
|
| `indent` | string | ❌ | Ký tự indent. Mặc định `" "`. |
|
||||||
|
| `filename` | string | ❌ | Tên file output. Mặc định `"codegen_request.json"`. |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## `gen.typescript`
|
||||||
|
|
||||||
|
| Key | Kiểu | Bắt buộc | Mô tả |
|
||||||
|
| ------------------- | ------ | -------- | ------------------------------------------------------ |
|
||||||
|
| `out` | string | ✅ | Thư mục output. |
|
||||||
|
| `plugin` | string | ❌ | Tên plugin (nếu dùng plugin ngoài). |
|
||||||
|
| `runtime` | string | ❌ | Runtime cho generated code: `"node"` hoặc `"browser"`. |
|
||||||
|
| `driver` | string | ❌ | Driver: `"pg"` hoặc `"pg-query-stream"`. |
|
||||||
|
| `emit_json_tags` | bool | ❌ | Sinh JSON tag cho property. |
|
||||||
|
| `emit_result_types` | bool | ❌ | Sinh interface cho result. |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Ví dụ hoàn chỉnh
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
version: "2"
|
||||||
|
|
||||||
|
sql:
|
||||||
|
- engine: "postgresql"
|
||||||
|
queries: "query/"
|
||||||
|
schema: "schema/"
|
||||||
|
gen:
|
||||||
|
go:
|
||||||
|
package: "db"
|
||||||
|
out: "db"
|
||||||
|
sql_package: "pgx/v5"
|
||||||
|
emit_json_tags: true
|
||||||
|
emit_interface: true
|
||||||
|
emit_empty_slices: true
|
||||||
|
emit_prepared_queries: false
|
||||||
|
json_tags_case_style: "camel"
|
||||||
|
overrides:
|
||||||
|
- db_type: "uuid"
|
||||||
|
go_type: "github.com/google/uuid.UUID"
|
||||||
|
- db_type: "timestamptz"
|
||||||
|
go_type: "time.Time"
|
||||||
|
- column: "orders.status"
|
||||||
|
go_type:
|
||||||
|
import: "warehouse-management/types"
|
||||||
|
type: "OrderStatus"
|
||||||
|
pointer: true
|
||||||
|
inflection_exclude_table_names:
|
||||||
|
- "status"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Tóm lại**: Key bắt buộc tối thiểu cho một config sqlc v2 hoạt động là `version`, `sql[].engine`, `sql[].queries`, `sql[].schema`, và ít nhất một block `gen.<lang>` với `out` + `package` (hoặc tương đương cho ngôn ngữ khác).
|
||||||
400
docs/sqlc/query.md
Normal file
400
docs/sqlc/query.md
Normal file
@@ -0,0 +1,400 @@
|
|||||||
|
# Bộ quy tắc viết file query trong sqlc
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Cấu trúc cơ bản
|
||||||
|
|
||||||
|
Mỗi file query là một file `.sql` thông thường, nhưng chứa **sqlc annotation** ở dạng SQL comment để sqlc phân tích metadata.
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- name: <TênMethod> <:command>
|
||||||
|
-- <comment mô tả (tuỳ chọn)>
|
||||||
|
SELECT * FROM users WHERE id = $1;
|
||||||
|
```
|
||||||
|
|
||||||
|
**Cú pháp annotation:**
|
||||||
|
|
||||||
|
```
|
||||||
|
-- name: <MethodName> <:command>
|
||||||
|
```
|
||||||
|
|
||||||
|
- `MethodName` → Tên method sẽ được sinh trong Go code (phải là identifier hợp lệ, khuyến nghị dùng **camelCase** hoặc **PascalCase**).
|
||||||
|
- `:command` → Loại thao tác, quyết định kiểu trả về.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Các loại `:command`
|
||||||
|
|
||||||
|
| Command | Kiểu trả về (Go) | Dùng khi |
|
||||||
|
| ------------- | --------------------- | ------------------------------------------------------------------------------------- |
|
||||||
|
| `:one` | `(T, error)` | Trả về **đúng 1 row**. Nếu không tìm thấy → `sql.ErrNoRows`. |
|
||||||
|
| `:many` | `([]T, error)` | Trả về **danh sách rows**. Có thể rỗng. |
|
||||||
|
| `:exec` | `(sql.Result, error)` | Thực thi **không trả về row** (INSERT/UPDATE/DELETE không cần data trả về). |
|
||||||
|
| `:execrows` | `(int64, error)` | Giống `:exec` nhưng trả về **số rows affected**. |
|
||||||
|
| `:execresult` | `(sql.Result, error)` | Giống `:exec`, trả về `sql.Result` đầy đủ (có `.LastInsertId()` + `.RowsAffected()`). |
|
||||||
|
| `:copyfrom` | `(int64, error)` | Dùng cho PostgreSQL `COPY FROM`, truyền slice struct. |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Quy tắc viết annotation
|
||||||
|
|
||||||
|
### 3.1. Annotation phải nằm **ngay trên** câu query
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- ✅ ĐÚNG: annotation ngay trên query
|
||||||
|
-- name: GetUser :one
|
||||||
|
SELECT * FROM users WHERE id = $1;
|
||||||
|
|
||||||
|
-- ❌ SAI: có dòng trống giữa annotation và query
|
||||||
|
-- name: GetUser :one
|
||||||
|
|
||||||
|
SELECT * FROM users WHERE id = $1;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2. Mỗi query phải kết thúc bằng dấu `;`
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- ✅ ĐÚNG
|
||||||
|
-- name: GetUser :one
|
||||||
|
SELECT * FROM users WHERE id = $1;
|
||||||
|
|
||||||
|
-- ❌ SAI: thiếu dấu ;
|
||||||
|
-- name: GetUser :one
|
||||||
|
SELECT * FROM users WHERE id = $1
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.3. Tên method **không được trùng** trong cùng một file (hoặc cùng package queries)
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- ❌ SAI: trùng tên
|
||||||
|
-- name: GetUser :one
|
||||||
|
SELECT * FROM users WHERE id = $1;
|
||||||
|
|
||||||
|
-- name: GetUser :many
|
||||||
|
SELECT * FROM users WHERE role = $1;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.4. Có thể viết nhiều query trong 1 file
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- name: GetUser :one
|
||||||
|
SELECT * FROM users WHERE id = $1;
|
||||||
|
|
||||||
|
-- name: ListUsers :many
|
||||||
|
SELECT * FROM users ORDER BY created_at DESC;
|
||||||
|
|
||||||
|
-- name: CreateUser :one
|
||||||
|
INSERT INTO users (id, username, email)
|
||||||
|
VALUES ($1, $2, $3)
|
||||||
|
RETURNING *;
|
||||||
|
|
||||||
|
-- name: DeleteUser :exec
|
||||||
|
DELETE FROM users WHERE id = $1;
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Tham số (Parameters)
|
||||||
|
|
||||||
|
### 4.1. PostgreSQL — dùng positional params `$1, $2, ...`
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- name: CreateUser :one
|
||||||
|
INSERT INTO users (username, email, password_hash)
|
||||||
|
VALUES ($1, $2, $3)
|
||||||
|
RETURNING *;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.2. Sử dụng `sqlc.arg()` — **khuyến nghị dùng** vì rõ ràng hơn
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- name: CreateUser :one
|
||||||
|
INSERT INTO users (username, email, password_hash)
|
||||||
|
VALUES (sqlc.arg(username), sqlc.arg(email), sqlc.arg(password_hash))
|
||||||
|
RETURNING *;
|
||||||
|
```
|
||||||
|
|
||||||
|
> Khi dùng `sqlc.arg()`, sqlc sinh **tên param có ý nghĩa** trong Go struct thay vì `ID`/`Column2` không rõ ràng.
|
||||||
|
|
||||||
|
### 4.3. `sqlc.narg()` — nullable parameter
|
||||||
|
|
||||||
|
Dùng khi tham số có thể là `NULL`:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- name: SearchUsers :many
|
||||||
|
SELECT * FROM users
|
||||||
|
WHERE (
|
||||||
|
sqlc.narg(username) IS NULL OR username = sqlc.narg(username)
|
||||||
|
)
|
||||||
|
AND (
|
||||||
|
sqlc.narg(email) IS NULL OR email = sqlc.narg(email)
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
sqlc sẽ sinh kiểu `NullString` hoặc con trỏ `*string` (tuỳ config) cho các param này.
|
||||||
|
|
||||||
|
### 4.4. Truyền struct/array — `sqlc.arg()` với nhiều field
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- name: UpdateUser :one
|
||||||
|
UPDATE users
|
||||||
|
SET
|
||||||
|
username = COALESCE(sqlc.arg(username), username),
|
||||||
|
email = COALESCE(sqlc.arg(email), email)
|
||||||
|
WHERE id = sqlc.arg(id)
|
||||||
|
RETURNING *;
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. `RETURNING *` — Khuyến nghị dùng với `:one` / `:many`
|
||||||
|
|
||||||
|
Khi `INSERT`, `UPDATE`, `DELETE` mà bạn muốn nhận lại dữ liệu, hãy dùng `RETURNING *`:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- name: CreateUser :one
|
||||||
|
INSERT INTO users (username, email)
|
||||||
|
VALUES ($1, $2)
|
||||||
|
RETURNING *;
|
||||||
|
|
||||||
|
-- name: UpdateUser :one
|
||||||
|
UPDATE users SET email = $2 WHERE id = $1
|
||||||
|
RETURNING *;
|
||||||
|
|
||||||
|
-- name: DeleteUser :one
|
||||||
|
DELETE FROM users WHERE id = $1
|
||||||
|
RETURNING *;
|
||||||
|
```
|
||||||
|
|
||||||
|
Nếu không có `RETURNING *`, phải dùng `:exec` hoặc `:execrows`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Quy tắc cho `SELECT *`
|
||||||
|
|
||||||
|
sqlc sẽ phân tích schema và thay `*` bằng danh sách cột cụ thể trong code sinh ra. Tuy nhiên:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- ✅ Khuyến nghị: chỉ định cột rõ ràng
|
||||||
|
-- name: GetUser :one
|
||||||
|
SELECT id, username, email, created_at FROM users WHERE id = $1;
|
||||||
|
|
||||||
|
-- ✅ Cũng hợp lệ: dùng *
|
||||||
|
-- name: GetUser :one
|
||||||
|
SELECT * FROM users WHERE id = $1;
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Khi JOIN nhiều bảng**, KHÔNG nên dùng `SELECT *` vì có thể trùng tên cột. Nên alias rõ ràng.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. JOIN và Alias
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- name: GetUserWithRole :one
|
||||||
|
SELECT
|
||||||
|
u.id,
|
||||||
|
u.username,
|
||||||
|
u.email,
|
||||||
|
r.name AS role_name
|
||||||
|
FROM users u
|
||||||
|
JOIN user_roles ur ON u.id = ur.user_id
|
||||||
|
JOIN roles r ON ur.role_id = r.id
|
||||||
|
WHERE u.id = $1;
|
||||||
|
```
|
||||||
|
|
||||||
|
sqlc sẽ sinh struct với các field `ID`, `Username`, `Email`, `RoleName`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Sử dụng `sqlc.embed()`
|
||||||
|
|
||||||
|
Dùng khi muốn **nhúng toàn bộ một struct** vào kết quả (từ sqlc v1.18+):
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- name: GetUserWithRole :one
|
||||||
|
SELECT
|
||||||
|
sqlc.embed(u),
|
||||||
|
r.name AS role_name
|
||||||
|
FROM users u
|
||||||
|
JOIN user_roles ur ON u.id = ur.user_id
|
||||||
|
JOIN roles r ON ur.role_id = r.id
|
||||||
|
WHERE u.id = $1;
|
||||||
|
```
|
||||||
|
|
||||||
|
→ Go struct sinh ra sẽ có dạng:
|
||||||
|
|
||||||
|
```go
|
||||||
|
type GetUserWithRoleRow struct {
|
||||||
|
User // embedded struct từ bảng users
|
||||||
|
RoleName string
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Sử dụng `sqlc.slice()` — IN clause
|
||||||
|
|
||||||
|
Dùng cho `WHERE id IN ($1, $2, ...)` với số lượng phần tử động:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- name: GetUsersByIDs :many
|
||||||
|
SELECT * FROM users
|
||||||
|
WHERE id = ANY(sqlc.slice(ids));
|
||||||
|
```
|
||||||
|
|
||||||
|
hoặc với PostgreSQL:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- name: GetUsersByIDs :many
|
||||||
|
SELECT * FROM users
|
||||||
|
WHERE id = ANY($1::uuid[]);
|
||||||
|
```
|
||||||
|
|
||||||
|
Nhưng `sqlc.slice()` được khuyến nghị hơn vì sqlc sẽ tự xử lý kiểu.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Sub-query và CTE
|
||||||
|
|
||||||
|
sqlc hỗ trợ đầy đủ:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- name: GetUserStats :one
|
||||||
|
WITH user_orders AS (
|
||||||
|
SELECT user_id, COUNT(*) AS order_count
|
||||||
|
FROM orders
|
||||||
|
GROUP BY user_id
|
||||||
|
)
|
||||||
|
SELECT u.*, COALESCE(uo.order_count, 0) AS order_count
|
||||||
|
FROM users u
|
||||||
|
LEFT JOIN user_orders uo ON u.id = uo.user_id
|
||||||
|
WHERE u.id = $1;
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. CASE expression
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- name: ListUsersWithStatus :many
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
username,
|
||||||
|
CASE
|
||||||
|
WHEN deleted_at IS NOT NULL THEN 'deleted'
|
||||||
|
WHEN last_login_at > NOW() - INTERVAL '30 days' THEN 'active'
|
||||||
|
ELSE 'inactive'
|
||||||
|
END AS status
|
||||||
|
FROM users;
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12. Transaction — không viết trong file query
|
||||||
|
|
||||||
|
sqlc **không quản lý transaction** trong file `.sql`. Transaction được xử lý ở tầng ứng dụng Go:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Trong Go code (không phải file .sql)
|
||||||
|
tx, _ := db.BeginTx(ctx, nil)
|
||||||
|
q := New(tx) // tạo Queries instance với tx
|
||||||
|
q.CreateUser(ctx, ...)
|
||||||
|
q.CreateUserRole(ctx, ...)
|
||||||
|
tx.Commit()
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 13. Comment thường vs sqlc annotation
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- Đây là comment thường, sqlc bỏ qua
|
||||||
|
-- name: GetUser :one ← Đây là sqlc annotation
|
||||||
|
SELECT * FROM users WHERE id = $1;
|
||||||
|
|
||||||
|
/*
|
||||||
|
Multi-line comment cũng được
|
||||||
|
sqlc bỏ qua
|
||||||
|
*/
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 14. Quy tắc đặt tên file
|
||||||
|
|
||||||
|
| Quy ước | Ví dụ |
|
||||||
|
| ------------------------------------------------------------------ | ----------------------------------------- |
|
||||||
|
| 1 file = 1 bảng chính | `users.sql`, `orders.sql`, `products.sql` |
|
||||||
|
| Đặt tên theo **feature/domain** | `auth.sql`, `inventory.sql` |
|
||||||
|
| File nằm trong thư mục được chỉ định ở `queries` trong `sqlc.yaml` | `./db/queries/` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 15. Toàn bộ mẫu tham khảo
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- name: GetUser :one
|
||||||
|
SELECT * FROM users WHERE id = sqlc.arg(id);
|
||||||
|
|
||||||
|
-- name: GetUserByEmail :one
|
||||||
|
SELECT * FROM users WHERE email = sqlc.arg(email);
|
||||||
|
|
||||||
|
-- name: ListUsers :many
|
||||||
|
SELECT * FROM users
|
||||||
|
ORDER BY created_at DESC
|
||||||
|
LIMIT sqlc.arg(limit) OFFSET sqlc.arg(offset);
|
||||||
|
|
||||||
|
-- name: SearchUsers :many
|
||||||
|
SELECT * FROM users
|
||||||
|
WHERE (
|
||||||
|
sqlc.narg(username) IS NULL OR username ILIKE '%' || sqlc.narg(username) || '%'
|
||||||
|
)
|
||||||
|
AND (
|
||||||
|
sqlc.narg(email) IS NULL OR email ILIKE '%' || sqlc.narg(email) || '%'
|
||||||
|
);
|
||||||
|
|
||||||
|
-- name: CreateUser :one
|
||||||
|
INSERT INTO users (id, username, email, password_hash)
|
||||||
|
VALUES (
|
||||||
|
sqlc.arg(id),
|
||||||
|
sqlc.arg(username),
|
||||||
|
sqlc.arg(email),
|
||||||
|
sqlc.arg(password_hash)
|
||||||
|
)
|
||||||
|
RETURNING *;
|
||||||
|
|
||||||
|
-- name: UpdateUser :one
|
||||||
|
UPDATE users
|
||||||
|
SET
|
||||||
|
username = COALESCE(sqlc.arg(username), username),
|
||||||
|
email = COALESCE(sqlc.arg(email), email)
|
||||||
|
WHERE id = sqlc.arg(id)
|
||||||
|
RETURNING *;
|
||||||
|
|
||||||
|
-- name: DeleteUser :execrows
|
||||||
|
DELETE FROM users WHERE id = sqlc.arg(id);
|
||||||
|
|
||||||
|
-- name: GetUsersByIDs :many
|
||||||
|
SELECT * FROM users WHERE id = ANY(sqlc.slice(ids));
|
||||||
|
|
||||||
|
-- name: CountUsers :one
|
||||||
|
SELECT COUNT(*) AS count FROM users;
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Tóm tắt nhanh
|
||||||
|
|
||||||
|
| Quy tắc | Mô tả |
|
||||||
|
| -------------------------- | ----------------------------------------------------- |
|
||||||
|
| Annotation format | `-- name: MethodName :command` |
|
||||||
|
| Phải có `;` kết thúc | Mỗi query kết thúc bằng dấu chấm phẩy |
|
||||||
|
| Annotation ngay trên query | Không có dòng trống ở giữa |
|
||||||
|
| Tên method không trùng | Trong cùng package queries |
|
||||||
|
| Dùng `sqlc.arg()` | Khuyến nghị thay vì `$1` để sinh tên param có ý nghĩa |
|
||||||
|
| Dùng `sqlc.narg()` | Cho nullable parameter |
|
||||||
|
| Dùng `sqlc.slice()` | Cho `IN (...)` dynamic |
|
||||||
|
| Dùng `sqlc.embed()` | Để nhúng struct khi JOIN |
|
||||||
|
| `RETURNING *` | Khi cần dữ liệu trả về với INSERT/UPDATE/DELETE |
|
||||||
|
| Transaction | Không viết trong `.sql`, xử lý ở Go code |
|
||||||
526
docs/swagger/docs.go
Normal file
526
docs/swagger/docs.go
Normal file
@@ -0,0 +1,526 @@
|
|||||||
|
// Package swagger Code generated by swaggo/swag. DO NOT EDIT
|
||||||
|
package swagger
|
||||||
|
|
||||||
|
import "github.com/swaggo/swag"
|
||||||
|
|
||||||
|
const docTemplate = `{
|
||||||
|
"schemes": {{ marshal .Schemes }},
|
||||||
|
"swagger": "2.0",
|
||||||
|
"info": {
|
||||||
|
"description": "{{escape .Description}}",
|
||||||
|
"title": "{{.Title}}",
|
||||||
|
"contact": {},
|
||||||
|
"version": "{{.Version}}"
|
||||||
|
},
|
||||||
|
"host": "{{.Host}}",
|
||||||
|
"basePath": "{{.BasePath}}",
|
||||||
|
"paths": {
|
||||||
|
"/auth/register": {
|
||||||
|
"post": {
|
||||||
|
"description": "Register with email, username and password",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"auth"
|
||||||
|
],
|
||||||
|
"summary": "Register a new user",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "Register request",
|
||||||
|
"name": "body",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/requests.BodyRegisterRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"201": {
|
||||||
|
"description": "Created",
|
||||||
|
"schema": {
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/response.SuccessResponse"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"data": {
|
||||||
|
"$ref": "#/definitions/responses.BodyRegisterResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.ErrorResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"409": {
|
||||||
|
"description": "Conflict",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.ErrorResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.ErrorResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/ping": {
|
||||||
|
"get": {
|
||||||
|
"description": "Check server is running",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"health"
|
||||||
|
],
|
||||||
|
"summary": "Health check",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/v1/warehouses": {
|
||||||
|
"get": {
|
||||||
|
"description": "Retrieve a list of all warehouses ordered by creation date",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"warehouse"
|
||||||
|
],
|
||||||
|
"summary": "List all warehouses",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/response.SuccessResponse"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"data": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/models.Warehouse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.ErrorResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"description": "Create a new warehouse with the provided details",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"warehouse"
|
||||||
|
],
|
||||||
|
"summary": "Create a new warehouse",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "Warehouse request body",
|
||||||
|
"name": "body",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/requests.CUWarehouseRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"201": {
|
||||||
|
"description": "Created",
|
||||||
|
"schema": {
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/response.SuccessResponse"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"data": {
|
||||||
|
"$ref": "#/definitions/responses.CreateWarehouseResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.ErrorResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.ErrorResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/v1/warehouses/{id}": {
|
||||||
|
"get": {
|
||||||
|
"description": "Retrieve a single warehouse using its unique identifier",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"warehouse"
|
||||||
|
],
|
||||||
|
"summary": "Get warehouse by ID",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Warehouse ID",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/response.SuccessResponse"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"data": {
|
||||||
|
"$ref": "#/definitions/models.Warehouse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.ErrorResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.ErrorResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.ErrorResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"put": {
|
||||||
|
"description": "Update an existing warehouse by its ID with the provided details",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"warehouse"
|
||||||
|
],
|
||||||
|
"summary": "Update warehouse",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Warehouse ID",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Warehouse request body",
|
||||||
|
"name": "body",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/requests.CUWarehouseRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/response.SuccessResponse"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"data": {
|
||||||
|
"$ref": "#/definitions/responses.UpdateWarehouseResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.ErrorResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.ErrorResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"description": "Delete a warehouse by its unique identifier",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"warehouse"
|
||||||
|
],
|
||||||
|
"summary": "Delete warehouse",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Warehouse ID",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.SuccessResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.ErrorResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.ErrorResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"definitions": {
|
||||||
|
"models.Warehouse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"address": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"createdAt": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"updatedAt": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"requests.BodyRegisterRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"email",
|
||||||
|
"password",
|
||||||
|
"username"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"email": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"fullName": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"password": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 8
|
||||||
|
},
|
||||||
|
"username": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"requests.CUWarehouseRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"address",
|
||||||
|
"name"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"address": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"response.ErrorResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"code": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"message": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"now": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"response.SuccessResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"data": {},
|
||||||
|
"message": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"option": {},
|
||||||
|
"reason_status_code": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"responses.BodyRegisterResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"responses.CreateWarehouseResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"responses.UpdateWarehouseResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"address": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
|
||||||
|
// SwaggerInfo holds exported Swagger Info so clients can modify it
|
||||||
|
var SwaggerInfo = &swag.Spec{
|
||||||
|
Version: "1.0",
|
||||||
|
Host: "localhost:3000",
|
||||||
|
BasePath: "/api/v1",
|
||||||
|
Schemes: []string{},
|
||||||
|
Title: "Warehouse Management API",
|
||||||
|
Description: "This is the Warehouse Management API server.",
|
||||||
|
InfoInstanceName: "swagger",
|
||||||
|
SwaggerTemplate: docTemplate,
|
||||||
|
LeftDelim: "{{",
|
||||||
|
RightDelim: "}}",
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo)
|
||||||
|
}
|
||||||
502
docs/swagger/swagger.json
Normal file
502
docs/swagger/swagger.json
Normal file
@@ -0,0 +1,502 @@
|
|||||||
|
{
|
||||||
|
"swagger": "2.0",
|
||||||
|
"info": {
|
||||||
|
"description": "This is the Warehouse Management API server.",
|
||||||
|
"title": "Warehouse Management API",
|
||||||
|
"contact": {},
|
||||||
|
"version": "1.0"
|
||||||
|
},
|
||||||
|
"host": "localhost:3000",
|
||||||
|
"basePath": "/api/v1",
|
||||||
|
"paths": {
|
||||||
|
"/auth/register": {
|
||||||
|
"post": {
|
||||||
|
"description": "Register with email, username and password",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"auth"
|
||||||
|
],
|
||||||
|
"summary": "Register a new user",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "Register request",
|
||||||
|
"name": "body",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/requests.BodyRegisterRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"201": {
|
||||||
|
"description": "Created",
|
||||||
|
"schema": {
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/response.SuccessResponse"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"data": {
|
||||||
|
"$ref": "#/definitions/responses.BodyRegisterResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.ErrorResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"409": {
|
||||||
|
"description": "Conflict",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.ErrorResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.ErrorResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/ping": {
|
||||||
|
"get": {
|
||||||
|
"description": "Check server is running",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"health"
|
||||||
|
],
|
||||||
|
"summary": "Health check",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/v1/warehouses": {
|
||||||
|
"get": {
|
||||||
|
"description": "Retrieve a list of all warehouses ordered by creation date",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"warehouse"
|
||||||
|
],
|
||||||
|
"summary": "List all warehouses",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/response.SuccessResponse"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"data": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/models.Warehouse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.ErrorResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"description": "Create a new warehouse with the provided details",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"warehouse"
|
||||||
|
],
|
||||||
|
"summary": "Create a new warehouse",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "Warehouse request body",
|
||||||
|
"name": "body",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/requests.CUWarehouseRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"201": {
|
||||||
|
"description": "Created",
|
||||||
|
"schema": {
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/response.SuccessResponse"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"data": {
|
||||||
|
"$ref": "#/definitions/responses.CreateWarehouseResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.ErrorResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.ErrorResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/v1/warehouses/{id}": {
|
||||||
|
"get": {
|
||||||
|
"description": "Retrieve a single warehouse using its unique identifier",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"warehouse"
|
||||||
|
],
|
||||||
|
"summary": "Get warehouse by ID",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Warehouse ID",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/response.SuccessResponse"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"data": {
|
||||||
|
"$ref": "#/definitions/models.Warehouse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.ErrorResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.ErrorResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.ErrorResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"put": {
|
||||||
|
"description": "Update an existing warehouse by its ID with the provided details",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"warehouse"
|
||||||
|
],
|
||||||
|
"summary": "Update warehouse",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Warehouse ID",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Warehouse request body",
|
||||||
|
"name": "body",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/requests.CUWarehouseRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/response.SuccessResponse"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"data": {
|
||||||
|
"$ref": "#/definitions/responses.UpdateWarehouseResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.ErrorResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.ErrorResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"description": "Delete a warehouse by its unique identifier",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"warehouse"
|
||||||
|
],
|
||||||
|
"summary": "Delete warehouse",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Warehouse ID",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.SuccessResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.ErrorResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.ErrorResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"definitions": {
|
||||||
|
"models.Warehouse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"address": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"createdAt": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"updatedAt": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"requests.BodyRegisterRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"email",
|
||||||
|
"password",
|
||||||
|
"username"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"email": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"fullName": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"password": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 8
|
||||||
|
},
|
||||||
|
"username": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"requests.CUWarehouseRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"address",
|
||||||
|
"name"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"address": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"response.ErrorResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"code": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"message": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"now": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"response.SuccessResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"data": {},
|
||||||
|
"message": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"option": {},
|
||||||
|
"reason_status_code": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"responses.BodyRegisterResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"responses.CreateWarehouseResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"responses.UpdateWarehouseResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"address": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
317
docs/swagger/swagger.yaml
Normal file
317
docs/swagger/swagger.yaml
Normal file
@@ -0,0 +1,317 @@
|
|||||||
|
basePath: /api/v1
|
||||||
|
definitions:
|
||||||
|
models.Warehouse:
|
||||||
|
properties:
|
||||||
|
address:
|
||||||
|
type: string
|
||||||
|
createdAt:
|
||||||
|
type: string
|
||||||
|
description:
|
||||||
|
type: string
|
||||||
|
id:
|
||||||
|
type: integer
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
updatedAt:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
requests.BodyRegisterRequest:
|
||||||
|
properties:
|
||||||
|
email:
|
||||||
|
type: string
|
||||||
|
fullName:
|
||||||
|
type: string
|
||||||
|
password:
|
||||||
|
minLength: 8
|
||||||
|
type: string
|
||||||
|
username:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- email
|
||||||
|
- password
|
||||||
|
- username
|
||||||
|
type: object
|
||||||
|
requests.CUWarehouseRequest:
|
||||||
|
properties:
|
||||||
|
address:
|
||||||
|
type: string
|
||||||
|
description:
|
||||||
|
type: string
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- address
|
||||||
|
- name
|
||||||
|
type: object
|
||||||
|
response.ErrorResponse:
|
||||||
|
properties:
|
||||||
|
code:
|
||||||
|
type: integer
|
||||||
|
message:
|
||||||
|
type: string
|
||||||
|
now:
|
||||||
|
type: integer
|
||||||
|
status:
|
||||||
|
type: integer
|
||||||
|
type: object
|
||||||
|
response.SuccessResponse:
|
||||||
|
properties:
|
||||||
|
data: {}
|
||||||
|
message:
|
||||||
|
type: string
|
||||||
|
option: {}
|
||||||
|
reason_status_code:
|
||||||
|
type: string
|
||||||
|
status:
|
||||||
|
type: integer
|
||||||
|
type: object
|
||||||
|
responses.BodyRegisterResponse:
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
responses.CreateWarehouseResponse:
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: integer
|
||||||
|
type: object
|
||||||
|
responses.UpdateWarehouseResponse:
|
||||||
|
properties:
|
||||||
|
address:
|
||||||
|
type: string
|
||||||
|
description:
|
||||||
|
type: string
|
||||||
|
id:
|
||||||
|
type: integer
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
host: localhost:3000
|
||||||
|
info:
|
||||||
|
contact: {}
|
||||||
|
description: This is the Warehouse Management API server.
|
||||||
|
title: Warehouse Management API
|
||||||
|
version: "1.0"
|
||||||
|
paths:
|
||||||
|
/auth/register:
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Register with email, username and password
|
||||||
|
parameters:
|
||||||
|
- description: Register request
|
||||||
|
in: body
|
||||||
|
name: body
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/requests.BodyRegisterRequest'
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"201":
|
||||||
|
description: Created
|
||||||
|
schema:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/definitions/response.SuccessResponse'
|
||||||
|
- properties:
|
||||||
|
data:
|
||||||
|
$ref: '#/definitions/responses.BodyRegisterResponse'
|
||||||
|
type: object
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.ErrorResponse'
|
||||||
|
"409":
|
||||||
|
description: Conflict
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.ErrorResponse'
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.ErrorResponse'
|
||||||
|
summary: Register a new user
|
||||||
|
tags:
|
||||||
|
- auth
|
||||||
|
/ping:
|
||||||
|
get:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Check server is running
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
summary: Health check
|
||||||
|
tags:
|
||||||
|
- health
|
||||||
|
/v1/warehouses:
|
||||||
|
get:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Retrieve a list of all warehouses ordered by creation date
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/definitions/response.SuccessResponse'
|
||||||
|
- properties:
|
||||||
|
data:
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/models.Warehouse'
|
||||||
|
type: array
|
||||||
|
type: object
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.ErrorResponse'
|
||||||
|
summary: List all warehouses
|
||||||
|
tags:
|
||||||
|
- warehouse
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Create a new warehouse with the provided details
|
||||||
|
parameters:
|
||||||
|
- description: Warehouse request body
|
||||||
|
in: body
|
||||||
|
name: body
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/requests.CUWarehouseRequest'
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"201":
|
||||||
|
description: Created
|
||||||
|
schema:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/definitions/response.SuccessResponse'
|
||||||
|
- properties:
|
||||||
|
data:
|
||||||
|
$ref: '#/definitions/responses.CreateWarehouseResponse'
|
||||||
|
type: object
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.ErrorResponse'
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.ErrorResponse'
|
||||||
|
summary: Create a new warehouse
|
||||||
|
tags:
|
||||||
|
- warehouse
|
||||||
|
/v1/warehouses/{id}:
|
||||||
|
delete:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Delete a warehouse by its unique identifier
|
||||||
|
parameters:
|
||||||
|
- description: Warehouse ID
|
||||||
|
in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
type: integer
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.SuccessResponse'
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.ErrorResponse'
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.ErrorResponse'
|
||||||
|
summary: Delete warehouse
|
||||||
|
tags:
|
||||||
|
- warehouse
|
||||||
|
get:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Retrieve a single warehouse using its unique identifier
|
||||||
|
parameters:
|
||||||
|
- description: Warehouse ID
|
||||||
|
in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
type: integer
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/definitions/response.SuccessResponse'
|
||||||
|
- properties:
|
||||||
|
data:
|
||||||
|
$ref: '#/definitions/models.Warehouse'
|
||||||
|
type: object
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.ErrorResponse'
|
||||||
|
"404":
|
||||||
|
description: Not Found
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.ErrorResponse'
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.ErrorResponse'
|
||||||
|
summary: Get warehouse by ID
|
||||||
|
tags:
|
||||||
|
- warehouse
|
||||||
|
put:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Update an existing warehouse by its ID with the provided details
|
||||||
|
parameters:
|
||||||
|
- description: Warehouse ID
|
||||||
|
in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
type: integer
|
||||||
|
- description: Warehouse request body
|
||||||
|
in: body
|
||||||
|
name: body
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/requests.CUWarehouseRequest'
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/definitions/response.SuccessResponse'
|
||||||
|
- properties:
|
||||||
|
data:
|
||||||
|
$ref: '#/definitions/responses.UpdateWarehouseResponse'
|
||||||
|
type: object
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.ErrorResponse'
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.ErrorResponse'
|
||||||
|
summary: Update warehouse
|
||||||
|
tags:
|
||||||
|
- warehouse
|
||||||
|
swagger: "2.0"
|
||||||
107
fsnotify.go
Normal file
107
fsnotify.go
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"sync"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/fsnotify/fsnotify"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getFreePort(port int) {
|
||||||
|
// macOS-compatible: use lsof to find and kill process on port
|
||||||
|
cmd := exec.Command("sh", "-c", fmt.Sprintf("lsof -ti :%d | xargs kill -9 2>/dev/null", port))
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
log.Printf("Failed to free port %d: %v", port, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
watcher, err := fsnotify.NewWatcher()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer watcher.Close()
|
||||||
|
|
||||||
|
done := make(chan bool)
|
||||||
|
var cmd *exec.Cmd
|
||||||
|
var mu sync.Mutex
|
||||||
|
var debounceTimer *time.Timer
|
||||||
|
buildAndRestart := func() {
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
|
||||||
|
// Build the application
|
||||||
|
buildCmd := exec.Command("go", "build", "-o", "./tmp/main", "./cmd/server/main.go")
|
||||||
|
buildCmd.Stdout = os.Stdout
|
||||||
|
buildCmd.Stderr = os.Stderr
|
||||||
|
if err := buildCmd.Run(); err != nil {
|
||||||
|
log.Println("Build failed:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Free the port
|
||||||
|
getFreePort(3000)
|
||||||
|
|
||||||
|
// Restart the application
|
||||||
|
if cmd != nil && cmd.Process != nil {
|
||||||
|
if err := cmd.Process.Signal(syscall.SIGTERM); err != nil {
|
||||||
|
log.Printf("Failed to terminate process: %v", err)
|
||||||
|
}
|
||||||
|
_ = cmd.Wait()
|
||||||
|
}
|
||||||
|
cmd = exec.Command("./tmp/main")
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
if err := cmd.Start(); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
log.Println("Application restarted")
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
buildAndRestart()
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case event, ok := <-watcher.Events:
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (event.Op&fsnotify.Write == fsnotify.Write || event.Op&fsnotify.Create == fsnotify.Create) && filepath.Ext(event.Name) == ".go" {
|
||||||
|
log.Println("Modified file:", event.Name)
|
||||||
|
if debounceTimer != nil {
|
||||||
|
debounceTimer.Stop()
|
||||||
|
}
|
||||||
|
debounceTimer = time.AfterFunc(500*time.Millisecond, buildAndRestart)
|
||||||
|
}
|
||||||
|
case err := <-watcher.Errors:
|
||||||
|
log.Println("error:", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
err = filepath.Walk(".", func(path string, info os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if info.IsDir() {
|
||||||
|
switch info.Name() {
|
||||||
|
case "vendor", "tmp", "node_modules", ".git", ".vscode", "docs", "tests", "scripts", "github":
|
||||||
|
return filepath.SkipDir
|
||||||
|
default:
|
||||||
|
return watcher.Add(path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
<-done
|
||||||
|
}
|
||||||
45
global/global.go
Normal file
45
global/global.go
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
package global
|
||||||
|
|
||||||
|
import (
|
||||||
|
"wm-backend/configs"
|
||||||
|
"wm-backend/internal/initialization"
|
||||||
|
"wm-backend/internal/models"
|
||||||
|
db "wm-backend/sqlc_gen"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/v5/pgxpool"
|
||||||
|
"github.com/redis/go-redis/v9"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
Cfg models.Config
|
||||||
|
DB *pgxpool.Pool
|
||||||
|
Queries *db.Queries
|
||||||
|
Cache *redis.Client
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// Load Configurations
|
||||||
|
Cfg, err = configs.LoadConfig("configs")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("Error loading config")
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DATABASE
|
||||||
|
DB, err = initialization.ConnectPostgreSQL(&Cfg)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("Error connecting to database")
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
Queries = db.New(DB)
|
||||||
|
|
||||||
|
// CACHE
|
||||||
|
Cache, err = initialization.ConnectRedis(Cfg)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("Error connecting to Redis")
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
80
go.mod
Normal file
80
go.mod
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
module wm-backend
|
||||||
|
|
||||||
|
go 1.25.1
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/fsnotify/fsnotify v1.10.1
|
||||||
|
github.com/gin-gonic/gin v1.12.0
|
||||||
|
github.com/golang-jwt/jwt/v5 v5.3.1
|
||||||
|
github.com/google/uuid v1.6.0
|
||||||
|
github.com/jackc/pgx/v5 v5.9.2
|
||||||
|
github.com/joho/godotenv v1.5.1
|
||||||
|
github.com/redis/go-redis/v9 v9.19.0
|
||||||
|
github.com/rs/zerolog v1.35.1
|
||||||
|
github.com/spf13/viper v1.21.0
|
||||||
|
github.com/swaggo/files v1.0.1
|
||||||
|
github.com/swaggo/gin-swagger v1.6.1
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/KyleBanks/depth v1.2.1 // indirect
|
||||||
|
github.com/PuerkitoBio/purell v1.1.1 // indirect
|
||||||
|
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
|
||||||
|
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
||||||
|
github.com/go-openapi/jsonreference v0.19.6 // indirect
|
||||||
|
github.com/go-openapi/spec v0.20.4 // indirect
|
||||||
|
github.com/go-openapi/swag v0.19.15 // indirect
|
||||||
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||||
|
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||||
|
github.com/josharian/intern v1.0.0 // indirect
|
||||||
|
github.com/mailru/easyjson v0.7.6 // indirect
|
||||||
|
golang.org/x/mod v0.32.0 // indirect
|
||||||
|
golang.org/x/sync v0.19.0 // indirect
|
||||||
|
golang.org/x/tools v0.41.0 // indirect
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/bytedance/gopkg v0.1.3 // indirect
|
||||||
|
github.com/bytedance/sonic v1.15.0 // indirect
|
||||||
|
github.com/bytedance/sonic/loader v0.5.0 // indirect
|
||||||
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
|
github.com/cloudwego/base64x v0.1.6 // indirect
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.12 // indirect
|
||||||
|
github.com/gin-contrib/sse v1.1.0 // indirect
|
||||||
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
|
github.com/go-playground/validator/v10 v10.30.1
|
||||||
|
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
||||||
|
github.com/goccy/go-json v0.10.5 // indirect
|
||||||
|
github.com/goccy/go-yaml v1.19.2 // indirect
|
||||||
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
|
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||||
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
|
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||||
|
github.com/quic-go/qpack v0.6.0 // indirect
|
||||||
|
github.com/quic-go/quic-go v0.59.0 // indirect
|
||||||
|
github.com/sagikazarmark/locafero v0.11.0 // indirect
|
||||||
|
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
|
||||||
|
github.com/spf13/afero v1.15.0 // indirect
|
||||||
|
github.com/spf13/cast v1.10.0 // indirect
|
||||||
|
github.com/spf13/pflag v1.0.10 // indirect
|
||||||
|
github.com/subosito/gotenv v1.6.0 // indirect
|
||||||
|
github.com/swaggo/swag v1.16.6
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
|
github.com/ugorji/go/codec v1.3.1 // indirect
|
||||||
|
go.mongodb.org/mongo-driver/v2 v2.5.0 // indirect
|
||||||
|
go.uber.org/atomic v1.11.0 // indirect
|
||||||
|
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||||
|
golang.org/x/arch v0.22.0 // indirect
|
||||||
|
golang.org/x/crypto v0.48.0
|
||||||
|
golang.org/x/net v0.51.0 // indirect
|
||||||
|
golang.org/x/sys v0.41.0 // indirect
|
||||||
|
golang.org/x/text v0.34.0 // indirect
|
||||||
|
google.golang.org/protobuf v1.36.10 // indirect
|
||||||
|
)
|
||||||
228
go.sum
Normal file
228
go.sum
Normal file
@@ -0,0 +1,228 @@
|
|||||||
|
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
|
||||||
|
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
|
||||||
|
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
|
||||||
|
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||||
|
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
|
||||||
|
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||||
|
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||||
|
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
|
||||||
|
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
||||||
|
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
|
||||||
|
github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=
|
||||||
|
github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=
|
||||||
|
github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE=
|
||||||
|
github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k=
|
||||||
|
github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE=
|
||||||
|
github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
|
||||||
|
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||||
|
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
|
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
|
||||||
|
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
|
||||||
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
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.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho=
|
||||||
|
github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo=
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw=
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
|
||||||
|
github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4=
|
||||||
|
github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk=
|
||||||
|
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
|
||||||
|
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
|
||||||
|
github.com/gin-gonic/gin v1.12.0 h1:b3YAbrZtnf8N//yjKeU2+MQsh2mY5htkZidOM7O0wG8=
|
||||||
|
github.com/gin-gonic/gin v1.12.0/go.mod h1:VxccKfsSllpKshkBWgVgRniFFAzFb9csfngsqANjnLc=
|
||||||
|
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||||
|
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
|
||||||
|
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||||
|
github.com/go-openapi/jsonreference v0.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs=
|
||||||
|
github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns=
|
||||||
|
github.com/go-openapi/spec v0.20.4 h1:O8hJrt0UMnhHcluhIdUgCLRWyM2x7QkBXRvOs7m+O1M=
|
||||||
|
github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I=
|
||||||
|
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||||
|
github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM=
|
||||||
|
github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
|
||||||
|
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=
|
||||||
|
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||||
|
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||||
|
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||||
|
github.com/go-playground/validator/v10 v10.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w=
|
||||||
|
github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM=
|
||||||
|
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
|
||||||
|
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||||
|
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||||
|
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||||
|
github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
|
||||||
|
github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||||
|
github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
|
||||||
|
github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
|
||||||
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||||
|
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||||
|
github.com/jackc/pgx/v5 v5.9.2 h1:3ZhOzMWnR4yJ+RW1XImIPsD1aNSz4T4fyP7zlQb56hw=
|
||||||
|
github.com/jackc/pgx/v5 v5.9.2/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4=
|
||||||
|
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
|
||||||
|
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||||
|
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||||
|
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||||
|
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||||
|
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||||
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||||
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
|
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||||
|
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||||
|
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||||
|
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||||
|
github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA=
|
||||||
|
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||||
|
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||||
|
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||||
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||||
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
|
||||||
|
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
|
||||||
|
github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw=
|
||||||
|
github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU=
|
||||||
|
github.com/redis/go-redis/v9 v9.19.0 h1:XPVaaPSnG6RhYf7p+rmSa9zZfeVAnWsH5h3lxthOm/k=
|
||||||
|
github.com/redis/go-redis/v9 v9.19.0/go.mod h1:v/M13XI1PVCDcm01VtPFOADfZtHf8YW3baQf57KlIkA=
|
||||||
|
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||||
|
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||||
|
github.com/rs/zerolog v1.35.1 h1:m7xQeoiLIiV0BCEY4Hs+j2NG4Gp2o2KPKmhnnLiazKI=
|
||||||
|
github.com/rs/zerolog v1.35.1/go.mod h1:EjML9kdfa/RMA7h/6z6pYmq1ykOuA8/mjWaEvGI+jcw=
|
||||||
|
github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc=
|
||||||
|
github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik=
|
||||||
|
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw=
|
||||||
|
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U=
|
||||||
|
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
|
||||||
|
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
|
||||||
|
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
|
||||||
|
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
|
||||||
|
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
||||||
|
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
|
github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=
|
||||||
|
github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
|
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
|
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||||
|
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||||
|
github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE=
|
||||||
|
github.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg=
|
||||||
|
github.com/swaggo/gin-swagger v1.6.1 h1:Ri06G4gc9N4t4k8hekMigJ9zKTFSlqj/9paAQCQs7cY=
|
||||||
|
github.com/swaggo/gin-swagger v1.6.1/go.mod h1:LQ+hJStHakCWRiK/YNYtJOu4mR2FP+pxLnILT/qNiTw=
|
||||||
|
github.com/swaggo/swag v1.16.6 h1:qBNcx53ZaX+M5dxVyTrgQ0PJ/ACK+NzhwcbieTt+9yI=
|
||||||
|
github.com/swaggo/swag v1.16.6/go.mod h1:ngP2etMK5a0P3QBizic5MEwpRmluJZPHjXcMoj4Xesg=
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||||
|
github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY=
|
||||||
|
github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
|
||||||
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
|
github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs=
|
||||||
|
github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s=
|
||||||
|
go.mongodb.org/mongo-driver/v2 v2.5.0 h1:yXUhImUjjAInNcpTcAlPHiT7bIXhshCTL3jVBkF3xaE=
|
||||||
|
go.mongodb.org/mongo-driver/v2 v2.5.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0=
|
||||||
|
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||||
|
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||||
|
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
|
||||||
|
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
|
||||||
|
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||||
|
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||||
|
golang.org/x/arch v0.22.0 h1:c/Zle32i5ttqRXjdLyyHZESLD/bB90DCU1g9l/0YBDI=
|
||||||
|
golang.org/x/arch v0.22.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
|
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
|
||||||
|
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
|
||||||
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
|
golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c=
|
||||||
|
golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU=
|
||||||
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
|
golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
|
||||||
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
|
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
|
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
|
||||||
|
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||||
|
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/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-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
||||||
|
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
|
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
||||||
|
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
|
golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=
|
||||||
|
golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=
|
||||||
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
|
||||||
|
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
47
internal/initialization/postgresql.go
Normal file
47
internal/initialization/postgresql.go
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
package initialization
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
"wm-backend/internal/models"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/v5/pgxpool"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ConnectPostgreSQL(config *models.Config) (*pgxpool.Pool, error) {
|
||||||
|
connStr := fmt.Sprintf("postgres://%s:%s@%s:%s/%s?sslmode=disable",
|
||||||
|
config.Database.Username, config.Database.Password, config.Database.Host, config.Database.Port, config.Database.Name)
|
||||||
|
|
||||||
|
var pool *pgxpool.Pool
|
||||||
|
var err error
|
||||||
|
|
||||||
|
maxRetries := 5
|
||||||
|
for i := range maxRetries {
|
||||||
|
poolConfig, parseErr := pgxpool.ParseConfig(connStr)
|
||||||
|
if parseErr != nil {
|
||||||
|
log.Error().Err(parseErr).Msgf("Failed to parse DB config (attempt %d/%d)", i+1, maxRetries)
|
||||||
|
time.Sleep(5 * time.Second)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
pool, err = pgxpool.NewWithConfig(context.Background(), poolConfig)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msgf("Failed to connect to PostgreSQL (attempt %d/%d)", i+1, maxRetries)
|
||||||
|
time.Sleep(5 * time.Second)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
err = pool.Ping(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msgf("Failed to ping PostgreSQL (attempt %d/%d)", i+1, maxRetries)
|
||||||
|
time.Sleep(5 * time.Second)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info().Msg("Successfully connected to PostgreSQL")
|
||||||
|
return pool, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("failed to connect to PostgreSQL after %d attempts: %v", maxRetries, err)
|
||||||
|
}
|
||||||
39
internal/initialization/redis.go
Normal file
39
internal/initialization/redis.go
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
package initialization
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
"wm-backend/internal/models"
|
||||||
|
|
||||||
|
"github.com/redis/go-redis/v9"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ConnectRedis(cfg models.Config) (*redis.Client, error) {
|
||||||
|
var rdb *redis.Client
|
||||||
|
var pong string
|
||||||
|
var err error
|
||||||
|
|
||||||
|
maxRetries := 10
|
||||||
|
for range maxRetries {
|
||||||
|
rdb = redis.NewClient(&redis.Options{
|
||||||
|
Addr: fmt.Sprintf("%s:%s", cfg.Cache.Host, cfg.Cache.Port),
|
||||||
|
Password: cfg.Cache.Password,
|
||||||
|
DB: 0,
|
||||||
|
})
|
||||||
|
|
||||||
|
pong, err = rdb.Ping(context.Background()).Result()
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("Error connecting to Redis")
|
||||||
|
log.Error().Msg("Retrying in 5 seconds...")
|
||||||
|
time.Sleep(5 * time.Second)
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
log.Info().Msg("CONNECTED TO REDIS:" + pong + "🥩")
|
||||||
|
return rdb, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("failed to connect to Redis after %d attempts: %v", maxRetries, err)
|
||||||
|
}
|
||||||
28
internal/mapper/role_mapper.go
Normal file
28
internal/mapper/role_mapper.go
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
package mapper
|
||||||
|
|
||||||
|
import (
|
||||||
|
"wm-backend/internal/models"
|
||||||
|
db "wm-backend/sqlc_gen"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ToDomainRole maps a SQLC-generated Role to the domain Role model.
|
||||||
|
func ToDomainRole(r db.Role) *models.Role {
|
||||||
|
return &models.Role{
|
||||||
|
ID: r.ID.String(),
|
||||||
|
Name: r.Name,
|
||||||
|
Description: r.Description.String,
|
||||||
|
CreatedAt: r.CreatedAt.Time,
|
||||||
|
CreatedBy: r.CreatedBy.String,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToModelRole maps a domain Role model to the parameters needed for creating a Role in the database.
|
||||||
|
func ToModelRole(r *models.Role) *db.CreateRoleParams {
|
||||||
|
return &db.CreateRoleParams{
|
||||||
|
Name: r.Name,
|
||||||
|
Description: pgtype.Text{String: r.Description, Valid: r.Description != ""},
|
||||||
|
CreatedBy: pgtype.Text{String: r.CreatedBy, Valid: r.CreatedBy != ""},
|
||||||
|
}
|
||||||
|
}
|
||||||
21
internal/mapper/user_mapper.go
Normal file
21
internal/mapper/user_mapper.go
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
package mapper
|
||||||
|
|
||||||
|
import (
|
||||||
|
"wm-backend/internal/models"
|
||||||
|
db "wm-backend/sqlc_gen"
|
||||||
|
)
|
||||||
|
|
||||||
|
// toDomainUser maps a SQLC-generated User to the domain User model.
|
||||||
|
func ToDomainUser(u db.User) *models.User {
|
||||||
|
return &models.User{
|
||||||
|
ID: u.ID.String(),
|
||||||
|
Username: u.Username,
|
||||||
|
Email: u.Email,
|
||||||
|
FullName: u.FullName.String,
|
||||||
|
PasswordHash: u.PasswordHash,
|
||||||
|
IsActive: u.IsActive.Bool,
|
||||||
|
CreatedAt: u.CreatedAt.Time,
|
||||||
|
UpdatedAt: u.UpdatedAt.Time,
|
||||||
|
CreatedBy: u.CreatedBy.String,
|
||||||
|
}
|
||||||
|
}
|
||||||
51
internal/mapper/warehouse_mapper.go
Normal file
51
internal/mapper/warehouse_mapper.go
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
package mapper
|
||||||
|
|
||||||
|
import (
|
||||||
|
"wm-backend/internal/models"
|
||||||
|
db "wm-backend/sqlc_gen"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ToDomainWarehouse maps a SQLC-generated Warehouse to the domain Warehouse model.
|
||||||
|
func ToDomainWarehouse(r db.Warehouse) *models.Warehouse {
|
||||||
|
return &models.Warehouse{
|
||||||
|
ID: r.ID,
|
||||||
|
Name: r.Name,
|
||||||
|
Description: r.Description.String,
|
||||||
|
Address: r.Address.String,
|
||||||
|
CreatedAt: r.CreatedAt,
|
||||||
|
UpdatedAt: r.UpdatedAt,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToModelWarehouse(r *models.Warehouse) *db.CreateWarehouseParams {
|
||||||
|
return &db.CreateWarehouseParams{
|
||||||
|
Name: r.Name,
|
||||||
|
Description: pgtype.Text{
|
||||||
|
String: r.Description,
|
||||||
|
Valid: r.Description != "",
|
||||||
|
},
|
||||||
|
Address: pgtype.Text{
|
||||||
|
String: r.Address,
|
||||||
|
Valid: r.Address != "",
|
||||||
|
},
|
||||||
|
CreatedAt: r.CreatedAt,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToUpdateModelWarehouse(r *models.Warehouse) *db.UpdateWarehouseParams {
|
||||||
|
return &db.UpdateWarehouseParams{
|
||||||
|
Name: r.Name,
|
||||||
|
Description: pgtype.Text{
|
||||||
|
String: r.Description,
|
||||||
|
Valid: r.Description != "",
|
||||||
|
},
|
||||||
|
Address: pgtype.Text{
|
||||||
|
String: r.Address,
|
||||||
|
Valid: r.Address != "",
|
||||||
|
},
|
||||||
|
UpdatedAt: r.UpdatedAt,
|
||||||
|
ID: r.ID,
|
||||||
|
}
|
||||||
|
}
|
||||||
10
internal/middlewares/auth_middleware.go
Normal file
10
internal/middlewares/auth_middleware.go
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
package middlewares
|
||||||
|
|
||||||
|
import "github.com/gin-gonic/gin"
|
||||||
|
|
||||||
|
func AuthMiddleware() gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
// 1. Get the Authorization header
|
||||||
|
c.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
34
internal/middlewares/logging_middleware.go
Normal file
34
internal/middlewares/logging_middleware.go
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
package middlewares
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
func LoggingMiddleware() gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
start := time.Now()
|
||||||
|
|
||||||
|
log.Info().
|
||||||
|
Str("type", "request").
|
||||||
|
Str("method", c.Request.Method).
|
||||||
|
Str("path", c.Request.URL.Path).
|
||||||
|
Str("ip", c.ClientIP()).
|
||||||
|
Str("query", c.Request.URL.RawQuery).
|
||||||
|
Time("time", start).
|
||||||
|
Msg("incoming request")
|
||||||
|
|
||||||
|
c.Next()
|
||||||
|
|
||||||
|
log.Info().
|
||||||
|
Str("type", "response").
|
||||||
|
Str("method", c.Request.Method).
|
||||||
|
Str("path", c.Request.URL.Path).
|
||||||
|
Int("status", c.Writer.Status()).
|
||||||
|
Dur("latency", time.Since(start)).
|
||||||
|
Time("time", time.Now()).
|
||||||
|
Msg("outgoing response")
|
||||||
|
}
|
||||||
|
}
|
||||||
43
internal/models/config_model.go
Normal file
43
internal/models/config_model.go
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
Server ServerConfig
|
||||||
|
Database DatabaseConfig
|
||||||
|
Cache CacheConfig
|
||||||
|
Admin AdminConfig
|
||||||
|
JWT JWTConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
type AdminConfig struct {
|
||||||
|
Username string
|
||||||
|
Email string
|
||||||
|
Password string
|
||||||
|
FullName string
|
||||||
|
}
|
||||||
|
|
||||||
|
type JWTConfig struct {
|
||||||
|
SecretKey string
|
||||||
|
ExpirationHours int
|
||||||
|
}
|
||||||
|
|
||||||
|
type DatabaseConfig struct {
|
||||||
|
Host string
|
||||||
|
Port string
|
||||||
|
Username string
|
||||||
|
Password string
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
type ServerConfig struct {
|
||||||
|
Host string
|
||||||
|
Port string
|
||||||
|
PortFrontend string
|
||||||
|
KeyPassword string
|
||||||
|
}
|
||||||
|
|
||||||
|
type CacheConfig struct {
|
||||||
|
Username string
|
||||||
|
Password string
|
||||||
|
Host string
|
||||||
|
Port string
|
||||||
|
}
|
||||||
1
internal/models/jwt_model.go
Normal file
1
internal/models/jwt_model.go
Normal file
@@ -0,0 +1 @@
|
|||||||
|
package models
|
||||||
13
internal/models/requests/auth_request.go
Normal file
13
internal/models/requests/auth_request.go
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package requests
|
||||||
|
|
||||||
|
type BodyRegisterRequest struct {
|
||||||
|
Username string `json:"username" binding:"required"`
|
||||||
|
Email string `json:"email" binding:"required,email"`
|
||||||
|
FullName string `json:"fullName"`
|
||||||
|
Password string `json:"password" binding:"required,min=8"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type BodyLoginRequest struct {
|
||||||
|
Username string `json:"username" binding:"required"`
|
||||||
|
Password string `json:"password" binding:"required"`
|
||||||
|
}
|
||||||
6
internal/models/requests/role_request.go
Normal file
6
internal/models/requests/role_request.go
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
package requests
|
||||||
|
|
||||||
|
type CreateRoleRequest struct {
|
||||||
|
Name string `json:"name" binding:"required"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
}
|
||||||
13
internal/models/requests/warehouse_request.go
Normal file
13
internal/models/requests/warehouse_request.go
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package requests
|
||||||
|
|
||||||
|
type CreateWarehouseRequest struct {
|
||||||
|
Name string `json:"name" binding:"required"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
Address string `json:"address" binding:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateWarehouseRequest struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
Address string `json:"address"`
|
||||||
|
}
|
||||||
8
internal/models/responses/auth_response.go
Normal file
8
internal/models/responses/auth_response.go
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
package responses
|
||||||
|
|
||||||
|
type BodyRegisterResponse struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
}
|
||||||
|
type BodyLoginResponse struct {
|
||||||
|
Token string `json:"token"`
|
||||||
|
}
|
||||||
5
internal/models/responses/role_response.go
Normal file
5
internal/models/responses/role_response.go
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
package responses
|
||||||
|
|
||||||
|
type BodyRoleResponse struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
}
|
||||||
12
internal/models/responses/warehouse_response.go
Normal file
12
internal/models/responses/warehouse_response.go
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
package responses
|
||||||
|
|
||||||
|
type CreateWarehouseResponse struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateWarehouseResponse struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
Address string `json:"address"`
|
||||||
|
}
|
||||||
11
internal/models/role_model.go
Normal file
11
internal/models/role_model.go
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type Role struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
CreatedAt time.Time `json:"createdAt"`
|
||||||
|
CreatedBy string `json:"createdBy"`
|
||||||
|
}
|
||||||
17
internal/models/user_model.go
Normal file
17
internal/models/user_model.go
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type User struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
FullName string `json:"fullName"`
|
||||||
|
PasswordHash string `json:"-"`
|
||||||
|
IsActive bool `json:"isActive"`
|
||||||
|
CreatedAt time.Time `json:"createdAt"`
|
||||||
|
UpdatedAt time.Time `json:"updatedAt"`
|
||||||
|
CreatedBy string `json:"createdBy"`
|
||||||
|
}
|
||||||
12
internal/models/warehouse_model.go
Normal file
12
internal/models/warehouse_model.go
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type Warehouse struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
Address string `json:"address"`
|
||||||
|
CreatedAt time.Time `json:"createdAt"`
|
||||||
|
UpdatedAt time.Time `json:"updatedAt"`
|
||||||
|
}
|
||||||
45
internal/repositories/auth_repository.go
Normal file
45
internal/repositories/auth_repository.go
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
package repositories
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"wm-backend/internal/mapper"
|
||||||
|
"wm-backend/internal/models"
|
||||||
|
db "wm-backend/sqlc_gen"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetUserByEmail retrieves a user by their email address using SQLC-generated queries.
|
||||||
|
// Returns nil, nil if no user is found.
|
||||||
|
func GetUserByEmail(ctx context.Context, queries *db.Queries, email string) (*models.User, error) {
|
||||||
|
user, err := queries.GetUserByEmail(ctx, email)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, pgx.ErrNoRows) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return mapper.ToDomainUser(user), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetUserByUsername(ctx context.Context, queries *db.Queries, username string) (*models.User, error) {
|
||||||
|
user, err := queries.GetUserByUsername(ctx, username)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, pgx.ErrNoRows) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return mapper.ToDomainUser(user), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateUser inserts a new user using SQLC-generated queries.
|
||||||
|
// Returns the created user's ID as a string.
|
||||||
|
func CreateUser(ctx context.Context, queries *db.Queries, params db.CreateUserParams) (string, error) {
|
||||||
|
id, err := queries.CreateUser(ctx, params)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return id.String(), nil
|
||||||
|
}
|
||||||
1
internal/repositories/redis/permission_redis.go
Normal file
1
internal/repositories/redis/permission_redis.go
Normal file
@@ -0,0 +1 @@
|
|||||||
|
package redis
|
||||||
16
internal/repositories/role_repository.go
Normal file
16
internal/repositories/role_repository.go
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
package repositories
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"wm-backend/internal/mapper"
|
||||||
|
"wm-backend/internal/models"
|
||||||
|
db "wm-backend/sqlc_gen"
|
||||||
|
)
|
||||||
|
|
||||||
|
func CreateRole(ctx context.Context, queries *db.Queries, body models.Role) (models.Role, error) {
|
||||||
|
role, err := queries.CreateRole(ctx, *mapper.ToModelRole(&body))
|
||||||
|
if err != nil {
|
||||||
|
return models.Role{}, err
|
||||||
|
}
|
||||||
|
return *mapper.ToDomainRole(role), nil
|
||||||
|
}
|
||||||
48
internal/repositories/warehouse_repository.go
Normal file
48
internal/repositories/warehouse_repository.go
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
package repositories
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"wm-backend/internal/mapper"
|
||||||
|
"wm-backend/internal/models"
|
||||||
|
db "wm-backend/sqlc_gen"
|
||||||
|
)
|
||||||
|
|
||||||
|
func CreateWareHouse(ctx context.Context, queries *db.Queries, body models.Warehouse) (models.Warehouse, error) {
|
||||||
|
warehouse, err := queries.CreateWarehouse(ctx, *mapper.ToModelWarehouse(&body))
|
||||||
|
if err != nil {
|
||||||
|
return models.Warehouse{}, err
|
||||||
|
}
|
||||||
|
return *mapper.ToDomainWarehouse(warehouse), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetWarehouseByID(ctx context.Context, queries *db.Queries, id int64) (models.Warehouse, error) {
|
||||||
|
result, err := queries.GetWarehouseByID(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return models.Warehouse{}, err
|
||||||
|
}
|
||||||
|
return *mapper.ToDomainWarehouse(result), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ListWarehouses(ctx context.Context, queries *db.Queries) ([]models.Warehouse, error) {
|
||||||
|
results, err := queries.ListWarehouses(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var items []models.Warehouse
|
||||||
|
for _, r := range results {
|
||||||
|
items = append(items, *mapper.ToDomainWarehouse(r))
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateWarehouse(ctx context.Context, queries *db.Queries, body models.Warehouse) (models.Warehouse, error) {
|
||||||
|
result, err := queries.UpdateWarehouse(ctx, *mapper.ToUpdateModelWarehouse(&body))
|
||||||
|
if err != nil {
|
||||||
|
return models.Warehouse{}, err
|
||||||
|
}
|
||||||
|
return *mapper.ToDomainWarehouse(result), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteWarehouse(ctx context.Context, queries *db.Queries, id int64) error {
|
||||||
|
return queries.DeleteWarehouse(ctx, id)
|
||||||
|
}
|
||||||
45
internal/routers/router.go
Normal file
45
internal/routers/router.go
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
package routers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"wm-backend/configs/constants"
|
||||||
|
_ "wm-backend/docs/swagger"
|
||||||
|
"wm-backend/internal/middlewares"
|
||||||
|
"wm-backend/internal/services"
|
||||||
|
"wm-backend/pkg/utils"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
swaggerFiles "github.com/swaggo/files"
|
||||||
|
ginSwagger "github.com/swaggo/gin-swagger"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewRouter() *gin.Engine {
|
||||||
|
nodeEnv := os.Getenv("ENV")
|
||||||
|
|
||||||
|
if nodeEnv != constants.DevEnvironment {
|
||||||
|
gin.SetMode(gin.ReleaseMode)
|
||||||
|
}
|
||||||
|
r := gin.Default()
|
||||||
|
r.Use(middlewares.LoggingMiddleware())
|
||||||
|
v1 := r.Group(constants.API_VERSION_1)
|
||||||
|
{
|
||||||
|
auth := v1.Group(constants.API_GROUP_AUTH)
|
||||||
|
{
|
||||||
|
auth.POST(constants.API_PATH_AUTH_REGISTER, utils.AsyncHandler(services.Register))
|
||||||
|
auth.POST(constants.API_PATH_AUTH_LOGIN, utils.AsyncHandler(services.Login))
|
||||||
|
}
|
||||||
|
|
||||||
|
warehouse := v1.Group(constants.API_GROUP_WAREHOUSE)
|
||||||
|
{
|
||||||
|
warehouse.GET("", utils.AsyncHandler(services.WareHouseList))
|
||||||
|
warehouse.GET("/:id", utils.AsyncHandler(services.WareHouseGetByID))
|
||||||
|
warehouse.POST("", utils.AsyncHandler(services.WareHouseCreate))
|
||||||
|
warehouse.PUT("/:id", utils.AsyncHandler(services.WareHouseUpdate))
|
||||||
|
warehouse.DELETE("/:id", utils.AsyncHandler(services.WareHouseDelete))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
r.GET(constants.API_PATH_PING, services.PingHandler)
|
||||||
|
r.GET(constants.API_PATH_DOCS, ginSwagger.WrapHandler(swaggerFiles.Handler))
|
||||||
|
return r
|
||||||
|
}
|
||||||
128
internal/services/auth_service.go
Normal file
128
internal/services/auth_service.go
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"wm-backend/global"
|
||||||
|
"wm-backend/internal/models"
|
||||||
|
"wm-backend/internal/models/requests"
|
||||||
|
"wm-backend/internal/models/responses"
|
||||||
|
"wm-backend/internal/repositories"
|
||||||
|
"wm-backend/pkg/helper"
|
||||||
|
"wm-backend/response"
|
||||||
|
db "wm-backend/sqlc_gen"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Register handles user registration.
|
||||||
|
// It validates the request body, checks for duplicate email,
|
||||||
|
// hashes the password, and creates the user in the database.
|
||||||
|
//
|
||||||
|
// @Summary Register a new user
|
||||||
|
// @Description Register with email, username and password
|
||||||
|
// @Tags auth
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param body body requests.BodyRegisterRequest true "Register request"
|
||||||
|
// @Success 201 {object} response.SuccessResponse{data=responses.BodyRegisterResponse}
|
||||||
|
// @Failure 400 {object} response.ErrorResponse
|
||||||
|
// @Failure 409 {object} response.ErrorResponse
|
||||||
|
// @Failure 500 {object} response.ErrorResponse
|
||||||
|
// @Router /auth/register [post]
|
||||||
|
func Register(c *gin.Context) error {
|
||||||
|
// 1. Bind & validate request body
|
||||||
|
requestBody := requests.BodyRegisterRequest{}
|
||||||
|
if helper.IsShouldBindJSON(c, &requestBody) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Check if user already exists by email
|
||||||
|
existingUser, err := repositories.GetUserByEmail(c.Request.Context(), global.Queries, requestBody.Email)
|
||||||
|
if err != nil {
|
||||||
|
response.InternalServerError(c, http.StatusInternalServerError)
|
||||||
|
log.Error().Err(err).Msg("Error checking existing user")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if existingUser != nil {
|
||||||
|
response.ConflictError(c, http.StatusConflict, "Email already registered")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Hash the password
|
||||||
|
hashedPassword, err := helper.HashPassword(requestBody.Password)
|
||||||
|
if err != nil {
|
||||||
|
response.InternalServerError(c, http.StatusInternalServerError)
|
||||||
|
log.Error().Err(err).Msg("Password hashing error")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Create user in database
|
||||||
|
userID, err := repositories.CreateUser(c.Request.Context(), global.Queries, db.CreateUserParams{
|
||||||
|
Username: requestBody.Username,
|
||||||
|
Email: requestBody.Email,
|
||||||
|
PasswordHash: hashedPassword,
|
||||||
|
FullName: pgtype.Text{String: requestBody.FullName, Valid: requestBody.FullName != ""},
|
||||||
|
CreatedBy: pgtype.Text{String: requestBody.Username, Valid: true},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
response.InternalServerError(c, http.StatusInternalServerError)
|
||||||
|
log.Error().Err(err).Msg("Error creating user")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Return success response
|
||||||
|
response.Created(c, "User registered successfully", responses.BodyRegisterResponse{
|
||||||
|
ID: userID,
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Login(c *gin.Context) error {
|
||||||
|
loginRequestBody := requests.BodyLoginRequest{}
|
||||||
|
if helper.IsShouldBindJSON(c, &loginRequestBody) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var user *models.User
|
||||||
|
var err error
|
||||||
|
if helper.IsEmail(loginRequestBody.Username) {
|
||||||
|
user, err = repositories.GetUserByEmail(c.Request.Context(), global.Queries, loginRequestBody.Username)
|
||||||
|
} else {
|
||||||
|
user, err = repositories.GetUserByUsername(c.Request.Context(), global.Queries, loginRequestBody.Username)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
response.InternalServerError(c, http.StatusInternalServerError)
|
||||||
|
log.Error().Err(err).Msg("Error finding user")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if user == nil {
|
||||||
|
response.UnauthorizedError(c, http.StatusUnauthorized, "Invalid credentials")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// 2. Check if user is active
|
||||||
|
if !user.IsActive {
|
||||||
|
response.UnauthorizedError(c, http.StatusUnauthorized, "Account is disabled")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Compare password
|
||||||
|
if err := helper.ComparePassword(loginRequestBody.Password, user.PasswordHash); err != nil {
|
||||||
|
response.UnauthorizedError(c, http.StatusUnauthorized, "Invalid credentials")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Generate JWT token
|
||||||
|
token, err := helper.GenerateToken(user.ID)
|
||||||
|
if err != nil {
|
||||||
|
response.InternalServerError(c, http.StatusInternalServerError)
|
||||||
|
log.Error().Err(err).Msg("Error generating token")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Return token
|
||||||
|
response.Ok(c, "Login successful", responses.BodyLoginResponse{
|
||||||
|
Token: token,
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
18
internal/services/check_service.go
Normal file
18
internal/services/check_service.go
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"wm-backend/response"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
// @Summary Health check
|
||||||
|
// @Description Check server is running
|
||||||
|
// @Tags health
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {object} map[string]string
|
||||||
|
// @Router /ping [get]
|
||||||
|
func PingHandler(c *gin.Context) {
|
||||||
|
response.Ok(c, "Ponggg", nil)
|
||||||
|
}
|
||||||
58
internal/services/role_service.go
Normal file
58
internal/services/role_service.go
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"wm-backend/global"
|
||||||
|
"wm-backend/internal/models"
|
||||||
|
"wm-backend/internal/models/requests"
|
||||||
|
"wm-backend/internal/models/responses"
|
||||||
|
"wm-backend/internal/repositories"
|
||||||
|
"wm-backend/pkg/helper"
|
||||||
|
"wm-backend/response"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func RoleCreate(c *gin.Context) error {
|
||||||
|
requestBody := requests.CreateRoleRequest{}
|
||||||
|
if helper.IsShouldBindJSON(c, &requestBody) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
roleModel := &models.Role{
|
||||||
|
Name: requestBody.Name,
|
||||||
|
Description: requestBody.Description,
|
||||||
|
CreatedBy: "",
|
||||||
|
}
|
||||||
|
role, err := repositories.CreateRole(c.Request.Context(), global.Queries, *roleModel)
|
||||||
|
if err != nil {
|
||||||
|
|
||||||
|
response.InternalServerError(c, http.StatusInternalServerError, "Failed to create role")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// if isUniqueViolation(err) {
|
||||||
|
// response.BadRequest(c, "Role name already exists")
|
||||||
|
// } else {
|
||||||
|
// response.InternalServerError(c, "Failed to create role")
|
||||||
|
// }
|
||||||
|
// Return success response with the created role
|
||||||
|
response.Created(c, "Role created successfully", &responses.BodyRoleResponse{
|
||||||
|
ID: role.ID,
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func RoleList(c *gin.Context) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func RoleGetByID(c *gin.Context) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func RoleUpdate(c *gin.Context) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func RoleDelete(c *gin.Context) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
171
internal/services/warehouse_service.go
Normal file
171
internal/services/warehouse_service.go
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
"wm-backend/global"
|
||||||
|
"wm-backend/internal/models"
|
||||||
|
"wm-backend/internal/models/requests"
|
||||||
|
"wm-backend/internal/models/responses"
|
||||||
|
"wm-backend/internal/repositories"
|
||||||
|
"wm-backend/pkg/helper"
|
||||||
|
"wm-backend/response"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
// WareHouseCreate creates a new warehouse.
|
||||||
|
// It validates the request body and creates the warehouse in the database.
|
||||||
|
//
|
||||||
|
// @Summary Create a new warehouse
|
||||||
|
// @Description Create a new warehouse with the provided details
|
||||||
|
// @Tags warehouse
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param body body requests.CreateWarehouseRequest true "Warehouse request body"
|
||||||
|
// @Success 201 {object} response.SuccessResponse{data=responses.CreateWarehouseResponse}
|
||||||
|
// @Failure 400 {object} response.ErrorResponse
|
||||||
|
// @Failure 500 {object} response.ErrorResponse
|
||||||
|
// @Router /v1/warehouses [post]
|
||||||
|
func WareHouseCreate(c *gin.Context) error {
|
||||||
|
requestBody := requests.CreateWarehouseRequest{}
|
||||||
|
if helper.IsShouldBindJSON(c, &requestBody) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
warehouseModel := &models.Warehouse{
|
||||||
|
Name: requestBody.Name,
|
||||||
|
Description: requestBody.Description,
|
||||||
|
Address: requestBody.Address,
|
||||||
|
CreatedAt: time.Now(),
|
||||||
|
}
|
||||||
|
warehouse, err := repositories.CreateWareHouse(c.Request.Context(), global.Queries, *warehouseModel)
|
||||||
|
if err != nil {
|
||||||
|
response.InternalServerError(c, http.StatusInternalServerError, "Failed to create warehouse")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
response.Created(c, "Warehouse created successfully", &responses.CreateWarehouseResponse{
|
||||||
|
ID: warehouse.ID,
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WareHouseGetByID retrieves a single warehouse by its ID.
|
||||||
|
//
|
||||||
|
// @Summary Get warehouse by ID
|
||||||
|
// @Description Retrieve a single warehouse using its unique identifier
|
||||||
|
// @Tags warehouse
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param id path int true "Warehouse ID"
|
||||||
|
// @Success 200 {object} response.SuccessResponse{data=models.Warehouse}
|
||||||
|
// @Failure 400 {object} response.ErrorResponse
|
||||||
|
// @Failure 404 {object} response.ErrorResponse
|
||||||
|
// @Failure 500 {object} response.ErrorResponse
|
||||||
|
// @Router /v1/warehouses/{id} [get]
|
||||||
|
func WareHouseGetByID(c *gin.Context) error {
|
||||||
|
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
response.BadRequestError(c, http.StatusBadRequest, "Invalid ID")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
warehouse, err := repositories.GetWarehouseByID(c.Request.Context(), global.Queries, id)
|
||||||
|
if err != nil {
|
||||||
|
response.NotFoundError(c, http.StatusNotFound, "Warehouse not found")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
response.Ok(c, "Success", warehouse)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WareHouseList retrieves all warehouses.
|
||||||
|
//
|
||||||
|
// @Summary List all warehouses
|
||||||
|
// @Description Retrieve a list of all warehouses ordered by creation date
|
||||||
|
// @Tags warehouse
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {object} response.SuccessResponse{data=[]models.Warehouse}
|
||||||
|
// @Failure 500 {object} response.ErrorResponse
|
||||||
|
// @Router /v1/warehouses [get]
|
||||||
|
func WareHouseList(c *gin.Context) error {
|
||||||
|
warehouses, err := repositories.ListWarehouses(c.Request.Context(), global.Queries)
|
||||||
|
if err != nil {
|
||||||
|
response.InternalServerError(c, http.StatusInternalServerError, "Failed to list warehouses")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
response.Ok(c, "Success", warehouses)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WareHouseUpdate updates an existing warehouse by its ID.
|
||||||
|
// It validates the request body and updates the warehouse in the database.
|
||||||
|
//
|
||||||
|
// @Summary Update warehouse
|
||||||
|
// @Description Update an existing warehouse by its ID with the provided details
|
||||||
|
// @Tags warehouse
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param id path int true "Warehouse ID"
|
||||||
|
// @Param body body requests.UpdateWarehouseRequest true "Warehouse request body"
|
||||||
|
// @Success 200 {object} response.SuccessResponse{data=responses.UpdateWarehouseResponse}
|
||||||
|
// @Failure 400 {object} response.ErrorResponse
|
||||||
|
// @Failure 500 {object} response.ErrorResponse
|
||||||
|
// @Router /v1/warehouses/{id} [put]
|
||||||
|
func WareHouseUpdate(c *gin.Context) error {
|
||||||
|
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
response.BadRequestError(c, http.StatusBadRequest, "Invalid ID")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
requestBody := requests.UpdateWarehouseRequest{}
|
||||||
|
if helper.IsShouldBindJSON(c, &requestBody) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
warehouseModel := &models.Warehouse{
|
||||||
|
ID: id,
|
||||||
|
Name: requestBody.Name,
|
||||||
|
Description: requestBody.Description,
|
||||||
|
Address: requestBody.Address,
|
||||||
|
UpdatedAt: time.Now(),
|
||||||
|
}
|
||||||
|
warehouse, err := repositories.UpdateWarehouse(c.Request.Context(), global.Queries, *warehouseModel)
|
||||||
|
if err != nil {
|
||||||
|
response.InternalServerError(c, http.StatusInternalServerError, "Failed to update warehouse")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
response.Ok(c, "Warehouse updated successfully", &responses.UpdateWarehouseResponse{
|
||||||
|
ID: warehouse.ID,
|
||||||
|
Name: warehouse.Name,
|
||||||
|
Description: warehouse.Description,
|
||||||
|
Address: warehouse.Address,
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WareHouseDelete deletes a warehouse by its ID.
|
||||||
|
//
|
||||||
|
// @Summary Delete warehouse
|
||||||
|
// @Description Delete a warehouse by its unique identifier
|
||||||
|
// @Tags warehouse
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param id path int true "Warehouse ID"
|
||||||
|
// @Success 200 {object} response.SuccessResponse
|
||||||
|
// @Failure 400 {object} response.ErrorResponse
|
||||||
|
// @Failure 500 {object} response.ErrorResponse
|
||||||
|
// @Router /v1/warehouses/{id} [delete]
|
||||||
|
func WareHouseDelete(c *gin.Context) error {
|
||||||
|
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
response.BadRequestError(c, http.StatusBadRequest, "Invalid ID")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
err = repositories.DeleteWarehouse(c.Request.Context(), global.Queries, id)
|
||||||
|
if err != nil {
|
||||||
|
response.InternalServerError(c, http.StatusInternalServerError, "Failed to delete warehouse")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
response.Ok(c, "Đã xóa thành công", nil)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
39
pkg/helper/jwt.go
Normal file
39
pkg/helper/jwt.go
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
package helper
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
"wm-backend/global"
|
||||||
|
|
||||||
|
"github.com/golang-jwt/jwt/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GenerateToken tạo JWT token cho user
|
||||||
|
func GenerateToken(userID string) (string, error) {
|
||||||
|
claims := jwt.MapClaims{
|
||||||
|
"user_id": userID,
|
||||||
|
"iat": time.Now().Unix(), // issued at
|
||||||
|
"exp": time.Now().Add(time.Duration(global.Cfg.JWT.ExpirationHours) * time.Hour * 7).Unix(), // expiry
|
||||||
|
}
|
||||||
|
|
||||||
|
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||||
|
return token.SignedString([]byte(global.Cfg.JWT.SecretKey)) // <-- lấy từ config
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseToken(tokenString string) (jwt.MapClaims, error) {
|
||||||
|
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (any, error) {
|
||||||
|
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||||
|
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
|
||||||
|
}
|
||||||
|
return []byte(global.Cfg.JWT.SecretKey), nil // <-- lấy từ config
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
|
||||||
|
return claims, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("invalid token")
|
||||||
|
}
|
||||||
22
pkg/helper/password.go
Normal file
22
pkg/helper/password.go
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
package helper
|
||||||
|
|
||||||
|
import "golang.org/x/crypto/bcrypt"
|
||||||
|
|
||||||
|
// HashPassword generates a salted and hashed password using bcrypt.
|
||||||
|
// It takes the plain-text password and the number of salt rounds as input.
|
||||||
|
// It returns the generated salt, the hashed password, and any error encountered.
|
||||||
|
func HashPassword(password string) (string, error) {
|
||||||
|
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(hashedPassword), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ComparePassword compares a plain-text password with a hashed password and returns true if they match.
|
||||||
|
// It uses bcrypt.CompareHashAndPassword to perform the comparison.
|
||||||
|
func ComparePassword(password string, hashedPassword string) error {
|
||||||
|
err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password))
|
||||||
|
return err
|
||||||
|
}
|
||||||
24
pkg/helper/validator.go
Normal file
24
pkg/helper/validator.go
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
package helper
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"wm-backend/response"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
|
)
|
||||||
|
|
||||||
|
func IsShouldBindJSON(c *gin.Context, obj any) bool {
|
||||||
|
if err := c.ShouldBindJSON(obj); err != nil {
|
||||||
|
response.BadRequestError(c, http.StatusBadRequest, err.Error())
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
var validate = validator.New()
|
||||||
|
|
||||||
|
func IsEmail(input string) bool {
|
||||||
|
err := validate.Var(input, "email")
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
74
pkg/log/logger.go
Normal file
74
pkg/log/logger.go
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
package log
|
||||||
|
|
||||||
|
// import (
|
||||||
|
// "fmt"
|
||||||
|
|
||||||
|
// "github.com/rs/zerolog"
|
||||||
|
// "github.com/rs/zerolog/log"
|
||||||
|
// )
|
||||||
|
|
||||||
|
// type Logger struct{}
|
||||||
|
|
||||||
|
// func NewLogger() *Logger {
|
||||||
|
// return &Logger{}
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (logger *Logger) Print(level zerolog.Level, args any) {
|
||||||
|
// log.WithLevel(level).Msg(fmt.Sprint(args))
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Printf logs a formatted message at the given level.
|
||||||
|
// func (logger *Logger) Printf(level zerolog.Level, format string, args ...any) {
|
||||||
|
// log.WithLevel(level).Msg(fmt.Sprintf(format, args...))
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Debug logs a message at Debug level.
|
||||||
|
// func (logger *Logger) Debug(args ...any) {
|
||||||
|
// logger.Print(zerolog.DebugLevel, args)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Debugf logs a formatted message at Debug level.
|
||||||
|
// func (logger *Logger) Debugf(format string, args ...any) {
|
||||||
|
// logger.Printf(zerolog.DebugLevel, format, args...)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Info logs a message at Info level.
|
||||||
|
// func (logger *Logger) Info(args ...any) {
|
||||||
|
// logger.Print(zerolog.InfoLevel, args)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Infof logs a formatted message at Info level.
|
||||||
|
// func (logger *Logger) Infof(format string, args ...any) {
|
||||||
|
// logger.Printf(zerolog.InfoLevel, format, args...)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Warn logs a message at Warning level.
|
||||||
|
// func (logger *Logger) Warn(args ...any) {
|
||||||
|
// logger.Print(zerolog.WarnLevel, args)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Warnf logs a formatted message at Warning level.
|
||||||
|
// func (logger *Logger) Warnf(format string, args ...any) {
|
||||||
|
// logger.Printf(zerolog.WarnLevel, format, args...)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Error logs a message at Error level.
|
||||||
|
// func (logger *Logger) Error(args ...any) {
|
||||||
|
// logger.Print(zerolog.ErrorLevel, args)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Errorf logs a formatted message at Error level.
|
||||||
|
// func (logger *Logger) Errorf(format string, args ...any) {
|
||||||
|
// logger.Printf(zerolog.ErrorLevel, format, args...)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Fatal logs a message at Fatal level
|
||||||
|
// // and process will exit with status set to 1.
|
||||||
|
// func (logger *Logger) Fatal(args ...any) {
|
||||||
|
// logger.Print(zerolog.FatalLevel, args)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Fatalf logs a formatted message at Fatal level.
|
||||||
|
// func (logger *Logger) Fatalf(format string, args ...any) {
|
||||||
|
// logger.Printf(zerolog.FatalLevel, format, args...)
|
||||||
|
// }
|
||||||
12
pkg/utils/async_handler.go
Normal file
12
pkg/utils/async_handler.go
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import "github.com/gin-gonic/gin"
|
||||||
|
|
||||||
|
func AsyncHandler(fn func(c *gin.Context) error) gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
if err := fn(c); err != nil {
|
||||||
|
c.Error(err)
|
||||||
|
c.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
175
response/error_response.go
Normal file
175
response/error_response.go
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
package response
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrorResponse represents a structured error response
|
||||||
|
type ErrorResponse struct {
|
||||||
|
Code int `json:"code"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
Status int `json:"status"`
|
||||||
|
Now int64 `json:"now"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewErrorResponse creates a new ErrorResponse
|
||||||
|
func NewErrorResponse(message string, status int, code int) *ErrorResponse {
|
||||||
|
return &ErrorResponse{
|
||||||
|
Code: code,
|
||||||
|
Message: message,
|
||||||
|
Status: status,
|
||||||
|
Now: time.Now().Unix(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send sends the error response to the client.
|
||||||
|
// It aborts the request and responds with the error response as JSON.
|
||||||
|
func (sr *ErrorResponse) Send(c *gin.Context) {
|
||||||
|
c.AbortWithStatusJSON(sr.Status, sr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BadRequestError represents a 400 Bad Request error
|
||||||
|
func BadRequestError(c *gin.Context, code int, messages ...string) {
|
||||||
|
message := ""
|
||||||
|
if len(messages) > 0 {
|
||||||
|
message = messages[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
if message == "" {
|
||||||
|
message = GetReasonPhrase(http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
response := NewErrorResponse(message, http.StatusBadRequest, code)
|
||||||
|
response.Send(c)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotFoundError represents a 404 Not Found error
|
||||||
|
func NotFoundError(c *gin.Context, code int, messages ...string) {
|
||||||
|
message := ""
|
||||||
|
if len(messages) > 0 {
|
||||||
|
message = messages[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
if message == "" {
|
||||||
|
message = GetReasonPhrase(http.StatusNotFound)
|
||||||
|
}
|
||||||
|
response := NewErrorResponse(message, http.StatusNotFound, code)
|
||||||
|
response.Send(c)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// TooManyRequestsError represents a 429 Too Many Requests error
|
||||||
|
func TooManyRequestsError(c *gin.Context, code int, messages ...string) {
|
||||||
|
message := ""
|
||||||
|
if len(messages) > 0 {
|
||||||
|
message = messages[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
if message == "" {
|
||||||
|
message = GetReasonPhrase(http.StatusTooManyRequests)
|
||||||
|
}
|
||||||
|
response := NewErrorResponse(message, http.StatusTooManyRequests, code)
|
||||||
|
response.Send(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnauthorizedError represents a 401 Unauthorized error
|
||||||
|
func UnauthorizedError(c *gin.Context, code int, messages ...string) {
|
||||||
|
message := ""
|
||||||
|
if len(messages) > 0 {
|
||||||
|
message = messages[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
if message == "" {
|
||||||
|
message = GetReasonPhrase(http.StatusUnauthorized)
|
||||||
|
}
|
||||||
|
response := NewErrorResponse(message, http.StatusUnauthorized, code)
|
||||||
|
response.Send(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ForbiddenError handles the generation and sending of a Forbidden error response.
|
||||||
|
func ForbiddenError(c *gin.Context, code int, messages ...string) {
|
||||||
|
message := ""
|
||||||
|
if len(messages) > 0 {
|
||||||
|
message = messages[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
if message == "" {
|
||||||
|
message = GetReasonPhrase(http.StatusForbidden)
|
||||||
|
}
|
||||||
|
response := NewErrorResponse(message, http.StatusForbidden, code)
|
||||||
|
response.Send(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConflictError represents a 409 Conflict error
|
||||||
|
func ConflictError(c *gin.Context, code int, messages ...string) {
|
||||||
|
message := ""
|
||||||
|
if len(messages) > 0 {
|
||||||
|
message = messages[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
if message == "" {
|
||||||
|
message = GetReasonPhrase(http.StatusConflict)
|
||||||
|
}
|
||||||
|
response := NewErrorResponse(message, http.StatusConflict, code)
|
||||||
|
response.Send(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EntityTooLargeError handles the HTTP 413 Request Entity Too Large error.
|
||||||
|
func EntityTooLargeError(c *gin.Context, code int, messages ...string) {
|
||||||
|
message := ""
|
||||||
|
if len(messages) > 0 {
|
||||||
|
message = messages[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
if message == "" {
|
||||||
|
message = GetReasonPhrase(http.StatusRequestEntityTooLarge)
|
||||||
|
}
|
||||||
|
response := NewErrorResponse(message, http.StatusRequestEntityTooLarge, code)
|
||||||
|
response.Send(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnSupportMediaTypeError handles the unsupported media type error by sending an error response to the client.
|
||||||
|
func UnSupportMediaTypeError(c *gin.Context, code int, messages ...string) {
|
||||||
|
message := ""
|
||||||
|
if len(messages) > 0 {
|
||||||
|
message = messages[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
if message == "" {
|
||||||
|
message = GetReasonPhrase(http.StatusUnsupportedMediaType)
|
||||||
|
}
|
||||||
|
response := NewErrorResponse(message, http.StatusUnsupportedMediaType, code)
|
||||||
|
response.Send(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// InternalServerError represents a 500 Internal Server Error
|
||||||
|
func InternalServerError(c *gin.Context, code int, messages ...string) {
|
||||||
|
message := ""
|
||||||
|
if len(messages) > 0 {
|
||||||
|
message = messages[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
if message == "" {
|
||||||
|
message = GetReasonPhrase(http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
response := NewErrorResponse(message, http.StatusInternalServerError, code)
|
||||||
|
response.Send(c)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServiceUnavailable represents a 503 Service Unavailable
|
||||||
|
func ServiceUnavailable(c *gin.Context, code int, messages ...string) {
|
||||||
|
message := ""
|
||||||
|
if len(messages) > 0 {
|
||||||
|
message = messages[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
if message == "" {
|
||||||
|
message = GetReasonPhrase(http.StatusServiceUnavailable)
|
||||||
|
}
|
||||||
|
response := NewErrorResponse(message, http.StatusServiceUnavailable, code)
|
||||||
|
response.Send(c)
|
||||||
|
}
|
||||||
51
response/http_reason_phrases.go
Normal file
51
response/http_reason_phrases.go
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
package response
|
||||||
|
|
||||||
|
// ReasonPhrases is a map of HTTP status codes to their reason phrases
|
||||||
|
var ReasonPhrases = map[int]string{
|
||||||
|
100: "Continue",
|
||||||
|
101: "Switching Protocols",
|
||||||
|
200: "OK",
|
||||||
|
201: "Created",
|
||||||
|
202: "Accepted",
|
||||||
|
203: "Non-Authoritative Information",
|
||||||
|
204: "No Content",
|
||||||
|
205: "Reset Content",
|
||||||
|
206: "Partial Content",
|
||||||
|
300: "Multiple Choices",
|
||||||
|
301: "Moved Permanently",
|
||||||
|
302: "Found",
|
||||||
|
303: "See Other",
|
||||||
|
304: "Not Modified",
|
||||||
|
305: "Use Proxy",
|
||||||
|
307: "Temporary Redirect",
|
||||||
|
400: "Bad Request",
|
||||||
|
401: "Unauthorized",
|
||||||
|
402: "Payment Required",
|
||||||
|
403: "Forbidden",
|
||||||
|
404: "Not Found",
|
||||||
|
405: "Method Not Allowed",
|
||||||
|
406: "Not Acceptable",
|
||||||
|
407: "Proxy Authentication Required",
|
||||||
|
408: "Request Timeout",
|
||||||
|
409: "Conflict",
|
||||||
|
410: "Gone",
|
||||||
|
411: "Length Required",
|
||||||
|
412: "Precondition Failed",
|
||||||
|
413: "Payload Too Large",
|
||||||
|
414: "URI Too Long",
|
||||||
|
415: "Unsupported Media Type",
|
||||||
|
416: "Range Not Satisfiable",
|
||||||
|
417: "Expectation Failed",
|
||||||
|
429: "Too Many Requests",
|
||||||
|
500: "Internal Server Error",
|
||||||
|
501: "Not Implemented",
|
||||||
|
502: "Bad Gateway",
|
||||||
|
503: "Service Unavailable",
|
||||||
|
504: "Gateway Timeout",
|
||||||
|
505: "HTTP Version Not Supported",
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetReasonPhrase returns the reason phrase for a given status code
|
||||||
|
func GetReasonPhrase(statusCode int) string {
|
||||||
|
return ReasonPhrases[statusCode]
|
||||||
|
}
|
||||||
50
response/success_response.go
Normal file
50
response/success_response.go
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
package response
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SuccessResponse represents a structured success response
|
||||||
|
type SuccessResponse struct {
|
||||||
|
Message string `json:"message"`
|
||||||
|
Status int `json:"status"`
|
||||||
|
ReasonStatusCode string `json:"reason_status_code"`
|
||||||
|
Option any `json:"option,omitempty"`
|
||||||
|
Data any `json:"data,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSuccessResponse creates a new SuccessResponse
|
||||||
|
func NewSuccessResponse(message string, statusCode int, reasonStatusCode string, option any, data any) *SuccessResponse {
|
||||||
|
return &SuccessResponse{
|
||||||
|
Message: message,
|
||||||
|
Status: statusCode,
|
||||||
|
ReasonStatusCode: reasonStatusCode,
|
||||||
|
Option: option,
|
||||||
|
Data: data,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send sends the success response to the client
|
||||||
|
func (sr *SuccessResponse) Send(c *gin.Context) {
|
||||||
|
c.JSON(sr.Status, sr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ok represents a 200 OK success response
|
||||||
|
func Ok(c *gin.Context, message string, metadata any) {
|
||||||
|
if message == "" {
|
||||||
|
message = GetReasonPhrase(http.StatusOK)
|
||||||
|
}
|
||||||
|
response := NewSuccessResponse(message, http.StatusOK, GetReasonPhrase(http.StatusOK), nil, metadata)
|
||||||
|
response.Send(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Created represents a 201 Created success response
|
||||||
|
func Created(c *gin.Context, message string, metadata any) {
|
||||||
|
if message == "" {
|
||||||
|
message = GetReasonPhrase(http.StatusCreated)
|
||||||
|
}
|
||||||
|
response := NewSuccessResponse(message, http.StatusCreated, GetReasonPhrase(http.StatusCreated), nil, metadata)
|
||||||
|
response.Send(c)
|
||||||
|
}
|
||||||
36
sqlc.yaml
Normal file
36
sqlc.yaml
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
version: "2"
|
||||||
|
sql:
|
||||||
|
- engine: "postgresql"
|
||||||
|
queries: "./db/queries/"
|
||||||
|
schema:
|
||||||
|
- "./db/init/"
|
||||||
|
- "./db/migrations/"
|
||||||
|
gen:
|
||||||
|
go:
|
||||||
|
emit_interface: true
|
||||||
|
emit_json_tags: true
|
||||||
|
emit_db_tags: true
|
||||||
|
emit_prepared_queries: false
|
||||||
|
emit_empty_slices: false
|
||||||
|
emit_result_struct_pointers: false
|
||||||
|
emit_params_struct_pointers: false
|
||||||
|
emit_methods_with_db_argument: false
|
||||||
|
json_tags_case_style: "camel"
|
||||||
|
output_db_file_name: db.go
|
||||||
|
output_models_file_name: models.go
|
||||||
|
output_querier_file_name: querier.go
|
||||||
|
package: "db"
|
||||||
|
out: "sqlc_gen"
|
||||||
|
sql_package: "pgx/v5"
|
||||||
|
overrides:
|
||||||
|
- db_type: "uuid"
|
||||||
|
go_type: "github.com/google/uuid.UUID"
|
||||||
|
- db_type: "timestamptz"
|
||||||
|
go_type: "time.Time"
|
||||||
|
# - column: "orders.status"
|
||||||
|
# go_type:
|
||||||
|
# import: "warehouse-management/types"
|
||||||
|
# type: "OrderStatus"
|
||||||
|
# pointer: true
|
||||||
|
# inflection_exclude_table_names:
|
||||||
|
# - "status"
|
||||||
146
sqlc_gen/cabinet.sql.go
Normal file
146
sqlc_gen/cabinet.sql.go
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// sqlc v1.30.0
|
||||||
|
// source: cabinet.sql
|
||||||
|
|
||||||
|
package db
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
|
)
|
||||||
|
|
||||||
|
const createCabinet = `-- name: CreateCabinet :one
|
||||||
|
INSERT INTO cabinets (room_id,name, description, created_at)
|
||||||
|
VALUES (
|
||||||
|
$1,
|
||||||
|
$2,
|
||||||
|
$3,
|
||||||
|
$4
|
||||||
|
)
|
||||||
|
RETURNING id, room_id, name, description, created_at, updated_at
|
||||||
|
`
|
||||||
|
|
||||||
|
type CreateCabinetParams struct {
|
||||||
|
RoomID int64 `db:"room_id" json:"roomId"`
|
||||||
|
Name string `db:"name" json:"name"`
|
||||||
|
Description pgtype.Text `db:"description" json:"description"`
|
||||||
|
CreatedAt time.Time `db:"created_at" json:"createdAt"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) CreateCabinet(ctx context.Context, arg CreateCabinetParams) (Cabinet, error) {
|
||||||
|
row := q.db.QueryRow(ctx, createCabinet,
|
||||||
|
arg.RoomID,
|
||||||
|
arg.Name,
|
||||||
|
arg.Description,
|
||||||
|
arg.CreatedAt,
|
||||||
|
)
|
||||||
|
var i Cabinet
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.RoomID,
|
||||||
|
&i.Name,
|
||||||
|
&i.Description,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteCabinet = `-- name: DeleteCabinet :exec
|
||||||
|
DELETE FROM cabinets
|
||||||
|
WHERE id = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) DeleteCabinet(ctx context.Context, id int64) error {
|
||||||
|
_, err := q.db.Exec(ctx, deleteCabinet, id)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const getCabinetByID = `-- name: GetCabinetByID :one
|
||||||
|
SELECT id, room_id, name, description, created_at, updated_at FROM cabinets
|
||||||
|
WHERE id = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) GetCabinetByID(ctx context.Context, id int64) (Cabinet, error) {
|
||||||
|
row := q.db.QueryRow(ctx, getCabinetByID, id)
|
||||||
|
var i Cabinet
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.RoomID,
|
||||||
|
&i.Name,
|
||||||
|
&i.Description,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const listCabinets = `-- name: ListCabinets :many
|
||||||
|
SELECT id, room_id, name, description, created_at, updated_at FROM cabinets
|
||||||
|
ORDER BY created_at DESC
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) ListCabinets(ctx context.Context) ([]Cabinet, error) {
|
||||||
|
rows, err := q.db.Query(ctx, listCabinets)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var items []Cabinet
|
||||||
|
for rows.Next() {
|
||||||
|
var i Cabinet
|
||||||
|
if err := rows.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.RoomID,
|
||||||
|
&i.Name,
|
||||||
|
&i.Description,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateCabinet = `-- name: UpdateCabinet :one
|
||||||
|
UPDATE cabinets
|
||||||
|
SET name = coalesce($1, name),
|
||||||
|
description = coalesce($2, description),
|
||||||
|
updated_at = $3
|
||||||
|
WHERE id = $4
|
||||||
|
RETURNING id, room_id, name, description, created_at, updated_at
|
||||||
|
`
|
||||||
|
|
||||||
|
type UpdateCabinetParams struct {
|
||||||
|
Name string `db:"name" json:"name"`
|
||||||
|
Description pgtype.Text `db:"description" json:"description"`
|
||||||
|
UpdatedAt time.Time `db:"updated_at" json:"updatedAt"`
|
||||||
|
ID int64 `db:"id" json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) UpdateCabinet(ctx context.Context, arg UpdateCabinetParams) (Cabinet, error) {
|
||||||
|
row := q.db.QueryRow(ctx, updateCabinet,
|
||||||
|
arg.Name,
|
||||||
|
arg.Description,
|
||||||
|
arg.UpdatedAt,
|
||||||
|
arg.ID,
|
||||||
|
)
|
||||||
|
var i Cabinet
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.RoomID,
|
||||||
|
&i.Name,
|
||||||
|
&i.Description,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
32
sqlc_gen/db.go
Normal file
32
sqlc_gen/db.go
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// sqlc v1.30.0
|
||||||
|
|
||||||
|
package db
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/v5"
|
||||||
|
"github.com/jackc/pgx/v5/pgconn"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DBTX interface {
|
||||||
|
Exec(context.Context, string, ...interface{}) (pgconn.CommandTag, error)
|
||||||
|
Query(context.Context, string, ...interface{}) (pgx.Rows, error)
|
||||||
|
QueryRow(context.Context, string, ...interface{}) pgx.Row
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(db DBTX) *Queries {
|
||||||
|
return &Queries{db: db}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Queries struct {
|
||||||
|
db DBTX
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) WithTx(tx pgx.Tx) *Queries {
|
||||||
|
return &Queries{
|
||||||
|
db: tx,
|
||||||
|
}
|
||||||
|
}
|
||||||
471
sqlc_gen/models.go
Normal file
471
sqlc_gen/models.go
Normal file
@@ -0,0 +1,471 @@
|
|||||||
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// sqlc v1.30.0
|
||||||
|
|
||||||
|
package db
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql/driver"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ComponentItemStatusEnum string
|
||||||
|
|
||||||
|
const (
|
||||||
|
ComponentItemStatusEnumNormal ComponentItemStatusEnum = "normal"
|
||||||
|
ComponentItemStatusEnumDamaged ComponentItemStatusEnum = "damaged"
|
||||||
|
ComponentItemStatusEnumLongUnused ComponentItemStatusEnum = "long_unused"
|
||||||
|
ComponentItemStatusEnumExpired ComponentItemStatusEnum = "expired"
|
||||||
|
ComponentItemStatusEnumPendingInspection ComponentItemStatusEnum = "pending_inspection"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (e *ComponentItemStatusEnum) Scan(src interface{}) error {
|
||||||
|
switch s := src.(type) {
|
||||||
|
case []byte:
|
||||||
|
*e = ComponentItemStatusEnum(s)
|
||||||
|
case string:
|
||||||
|
*e = ComponentItemStatusEnum(s)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unsupported scan type for ComponentItemStatusEnum: %T", src)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type NullComponentItemStatusEnum struct {
|
||||||
|
ComponentItemStatusEnum ComponentItemStatusEnum `json:"componentItemStatusEnum"`
|
||||||
|
Valid bool `json:"valid"` // Valid is true if ComponentItemStatusEnum is not NULL
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan implements the Scanner interface.
|
||||||
|
func (ns *NullComponentItemStatusEnum) Scan(value interface{}) error {
|
||||||
|
if value == nil {
|
||||||
|
ns.ComponentItemStatusEnum, ns.Valid = "", false
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
ns.Valid = true
|
||||||
|
return ns.ComponentItemStatusEnum.Scan(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value implements the driver Valuer interface.
|
||||||
|
func (ns NullComponentItemStatusEnum) Value() (driver.Value, error) {
|
||||||
|
if !ns.Valid {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return string(ns.ComponentItemStatusEnum), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type ContainerTypeEnum string
|
||||||
|
|
||||||
|
const (
|
||||||
|
ContainerTypeEnumEmptyBox ContainerTypeEnum = "empty_box"
|
||||||
|
ContainerTypeEnumTray ContainerTypeEnum = "tray"
|
||||||
|
ContainerTypeEnumPaperBox ContainerTypeEnum = "paper_box"
|
||||||
|
ContainerTypeEnumPlasticBox ContainerTypeEnum = "plastic_box"
|
||||||
|
ContainerTypeEnumBag ContainerTypeEnum = "bag"
|
||||||
|
ContainerTypeEnumOther ContainerTypeEnum = "other"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (e *ContainerTypeEnum) Scan(src interface{}) error {
|
||||||
|
switch s := src.(type) {
|
||||||
|
case []byte:
|
||||||
|
*e = ContainerTypeEnum(s)
|
||||||
|
case string:
|
||||||
|
*e = ContainerTypeEnum(s)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unsupported scan type for ContainerTypeEnum: %T", src)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type NullContainerTypeEnum struct {
|
||||||
|
ContainerTypeEnum ContainerTypeEnum `json:"containerTypeEnum"`
|
||||||
|
Valid bool `json:"valid"` // Valid is true if ContainerTypeEnum is not NULL
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan implements the Scanner interface.
|
||||||
|
func (ns *NullContainerTypeEnum) Scan(value interface{}) error {
|
||||||
|
if value == nil {
|
||||||
|
ns.ContainerTypeEnum, ns.Valid = "", false
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
ns.Valid = true
|
||||||
|
return ns.ContainerTypeEnum.Scan(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value implements the driver Valuer interface.
|
||||||
|
func (ns NullContainerTypeEnum) Value() (driver.Value, error) {
|
||||||
|
if !ns.Valid {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return string(ns.ContainerTypeEnum), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type InvoiceStatusEnum string
|
||||||
|
|
||||||
|
const (
|
||||||
|
InvoiceStatusEnumDraft InvoiceStatusEnum = "draft"
|
||||||
|
InvoiceStatusEnumPending InvoiceStatusEnum = "pending"
|
||||||
|
InvoiceStatusEnumApproved InvoiceStatusEnum = "approved"
|
||||||
|
InvoiceStatusEnumCompleted InvoiceStatusEnum = "completed"
|
||||||
|
InvoiceStatusEnumCancelled InvoiceStatusEnum = "cancelled"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (e *InvoiceStatusEnum) Scan(src interface{}) error {
|
||||||
|
switch s := src.(type) {
|
||||||
|
case []byte:
|
||||||
|
*e = InvoiceStatusEnum(s)
|
||||||
|
case string:
|
||||||
|
*e = InvoiceStatusEnum(s)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unsupported scan type for InvoiceStatusEnum: %T", src)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type NullInvoiceStatusEnum struct {
|
||||||
|
InvoiceStatusEnum InvoiceStatusEnum `json:"invoiceStatusEnum"`
|
||||||
|
Valid bool `json:"valid"` // Valid is true if InvoiceStatusEnum is not NULL
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan implements the Scanner interface.
|
||||||
|
func (ns *NullInvoiceStatusEnum) Scan(value interface{}) error {
|
||||||
|
if value == nil {
|
||||||
|
ns.InvoiceStatusEnum, ns.Valid = "", false
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
ns.Valid = true
|
||||||
|
return ns.InvoiceStatusEnum.Scan(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value implements the driver Valuer interface.
|
||||||
|
func (ns NullInvoiceStatusEnum) Value() (driver.Value, error) {
|
||||||
|
if !ns.Valid {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return string(ns.InvoiceStatusEnum), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type InvoiceTypeEnum string
|
||||||
|
|
||||||
|
const (
|
||||||
|
InvoiceTypeEnumImport InvoiceTypeEnum = "import"
|
||||||
|
InvoiceTypeEnumExport InvoiceTypeEnum = "export"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (e *InvoiceTypeEnum) Scan(src interface{}) error {
|
||||||
|
switch s := src.(type) {
|
||||||
|
case []byte:
|
||||||
|
*e = InvoiceTypeEnum(s)
|
||||||
|
case string:
|
||||||
|
*e = InvoiceTypeEnum(s)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unsupported scan type for InvoiceTypeEnum: %T", src)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type NullInvoiceTypeEnum struct {
|
||||||
|
InvoiceTypeEnum InvoiceTypeEnum `json:"invoiceTypeEnum"`
|
||||||
|
Valid bool `json:"valid"` // Valid is true if InvoiceTypeEnum is not NULL
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan implements the Scanner interface.
|
||||||
|
func (ns *NullInvoiceTypeEnum) Scan(value interface{}) error {
|
||||||
|
if value == nil {
|
||||||
|
ns.InvoiceTypeEnum, ns.Valid = "", false
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
ns.Valid = true
|
||||||
|
return ns.InvoiceTypeEnum.Scan(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value implements the driver Valuer interface.
|
||||||
|
func (ns NullInvoiceTypeEnum) Value() (driver.Value, error) {
|
||||||
|
if !ns.Valid {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return string(ns.InvoiceTypeEnum), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type TransactionTypeEnum string
|
||||||
|
|
||||||
|
const (
|
||||||
|
TransactionTypeEnumImport TransactionTypeEnum = "import"
|
||||||
|
TransactionTypeEnumExport TransactionTypeEnum = "export"
|
||||||
|
TransactionTypeEnumAdjustment TransactionTypeEnum = "adjustment"
|
||||||
|
TransactionTypeEnumTransfer TransactionTypeEnum = "transfer"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (e *TransactionTypeEnum) Scan(src interface{}) error {
|
||||||
|
switch s := src.(type) {
|
||||||
|
case []byte:
|
||||||
|
*e = TransactionTypeEnum(s)
|
||||||
|
case string:
|
||||||
|
*e = TransactionTypeEnum(s)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unsupported scan type for TransactionTypeEnum: %T", src)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type NullTransactionTypeEnum struct {
|
||||||
|
TransactionTypeEnum TransactionTypeEnum `json:"transactionTypeEnum"`
|
||||||
|
Valid bool `json:"valid"` // Valid is true if TransactionTypeEnum is not NULL
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan implements the Scanner interface.
|
||||||
|
func (ns *NullTransactionTypeEnum) Scan(value interface{}) error {
|
||||||
|
if value == nil {
|
||||||
|
ns.TransactionTypeEnum, ns.Valid = "", false
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
ns.Valid = true
|
||||||
|
return ns.TransactionTypeEnum.Scan(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value implements the driver Valuer interface.
|
||||||
|
func (ns NullTransactionTypeEnum) Value() (driver.Value, error) {
|
||||||
|
if !ns.Valid {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return string(ns.TransactionTypeEnum), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type AlternativeComponent struct {
|
||||||
|
ID int64 `db:"id" json:"id"`
|
||||||
|
InvoiceConfigItemID int64 `db:"invoice_config_item_id" json:"invoiceConfigItemId"`
|
||||||
|
AlternativeComponentID int64 `db:"alternative_component_id" json:"alternativeComponentId"`
|
||||||
|
ConversionRatio pgtype.Numeric `db:"conversion_ratio" json:"conversionRatio"`
|
||||||
|
Priority int32 `db:"priority" json:"priority"`
|
||||||
|
Note pgtype.Text `db:"note" json:"note"`
|
||||||
|
Metadata []byte `db:"metadata" json:"metadata"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Cabinet struct {
|
||||||
|
ID int64 `db:"id" json:"id"`
|
||||||
|
RoomID int64 `db:"room_id" json:"roomId"`
|
||||||
|
Name string `db:"name" json:"name"`
|
||||||
|
Description pgtype.Text `db:"description" json:"description"`
|
||||||
|
CreatedAt time.Time `db:"created_at" json:"createdAt"`
|
||||||
|
UpdatedAt time.Time `db:"updated_at" json:"updatedAt"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Component struct {
|
||||||
|
ID int64 `db:"id" json:"id"`
|
||||||
|
ComponentTypeID int64 `db:"component_type_id" json:"componentTypeId"`
|
||||||
|
Name string `db:"name" json:"name"`
|
||||||
|
Description pgtype.Text `db:"description" json:"description"`
|
||||||
|
Unit string `db:"unit" json:"unit"`
|
||||||
|
TotalQuantity int32 `db:"total_quantity" json:"totalQuantity"`
|
||||||
|
MinQuantity int32 `db:"min_quantity" json:"minQuantity"`
|
||||||
|
Metadata []byte `db:"metadata" json:"metadata"`
|
||||||
|
CreatedAt time.Time `db:"created_at" json:"createdAt"`
|
||||||
|
UpdatedAt time.Time `db:"updated_at" json:"updatedAt"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ComponentCode struct {
|
||||||
|
ID int64 `db:"id" json:"id"`
|
||||||
|
ComponentID int64 `db:"component_id" json:"componentId"`
|
||||||
|
Code string `db:"code" json:"code"`
|
||||||
|
CodeType pgtype.Text `db:"code_type" json:"codeType"`
|
||||||
|
IsPrimary bool `db:"is_primary" json:"isPrimary"`
|
||||||
|
Metadata []byte `db:"metadata" json:"metadata"`
|
||||||
|
CreatedAt time.Time `db:"created_at" json:"createdAt"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ComponentItem struct {
|
||||||
|
ID int64 `db:"id" json:"id"`
|
||||||
|
ComponentID int64 `db:"component_id" json:"componentId"`
|
||||||
|
ContainerID int64 `db:"container_id" json:"containerId"`
|
||||||
|
Quantity int32 `db:"quantity" json:"quantity"`
|
||||||
|
Status ComponentItemStatusEnum `db:"status" json:"status"`
|
||||||
|
Metadata []byte `db:"metadata" json:"metadata"`
|
||||||
|
CreatedAt time.Time `db:"created_at" json:"createdAt"`
|
||||||
|
UpdatedAt time.Time `db:"updated_at" json:"updatedAt"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ComponentStatusHistory struct {
|
||||||
|
ID int64 `db:"id" json:"id"`
|
||||||
|
ComponentItemID int64 `db:"component_item_id" json:"componentItemId"`
|
||||||
|
OldStatus NullComponentItemStatusEnum `db:"old_status" json:"oldStatus"`
|
||||||
|
NewStatus ComponentItemStatusEnum `db:"new_status" json:"newStatus"`
|
||||||
|
ChangedQuantity pgtype.Int4 `db:"changed_quantity" json:"changedQuantity"`
|
||||||
|
Note pgtype.Text `db:"note" json:"note"`
|
||||||
|
ChangedBy pgtype.Text `db:"changed_by" json:"changedBy"`
|
||||||
|
ChangedAt time.Time `db:"changed_at" json:"changedAt"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ComponentType struct {
|
||||||
|
ID int64 `db:"id" json:"id"`
|
||||||
|
Name string `db:"name" json:"name"`
|
||||||
|
Description pgtype.Text `db:"description" json:"description"`
|
||||||
|
Metadata []byte `db:"metadata" json:"metadata"`
|
||||||
|
CreatedAt time.Time `db:"created_at" json:"createdAt"`
|
||||||
|
UpdatedAt time.Time `db:"updated_at" json:"updatedAt"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Container struct {
|
||||||
|
ID int64 `db:"id" json:"id"`
|
||||||
|
ShelfID int64 `db:"shelf_id" json:"shelfId"`
|
||||||
|
Name string `db:"name" json:"name"`
|
||||||
|
ContainerType ContainerTypeEnum `db:"container_type" json:"containerType"`
|
||||||
|
Description pgtype.Text `db:"description" json:"description"`
|
||||||
|
MaxCapacity pgtype.Int4 `db:"max_capacity" json:"maxCapacity"`
|
||||||
|
Metadata []byte `db:"metadata" json:"metadata"`
|
||||||
|
CreatedAt time.Time `db:"created_at" json:"createdAt"`
|
||||||
|
UpdatedAt time.Time `db:"updated_at" json:"updatedAt"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Invoice struct {
|
||||||
|
ID int64 `db:"id" json:"id"`
|
||||||
|
InvoiceCode string `db:"invoice_code" json:"invoiceCode"`
|
||||||
|
Type InvoiceTypeEnum `db:"type" json:"type"`
|
||||||
|
Status InvoiceStatusEnum `db:"status" json:"status"`
|
||||||
|
InvoiceConfigID pgtype.Int8 `db:"invoice_config_id" json:"invoiceConfigId"`
|
||||||
|
TotalItems int32 `db:"total_items" json:"totalItems"`
|
||||||
|
Note pgtype.Text `db:"note" json:"note"`
|
||||||
|
CreatedBy pgtype.Text `db:"created_by" json:"createdBy"`
|
||||||
|
ApprovedBy pgtype.Text `db:"approved_by" json:"approvedBy"`
|
||||||
|
CompletedAt pgtype.Timestamptz `db:"completed_at" json:"completedAt"`
|
||||||
|
CreatedAt time.Time `db:"created_at" json:"createdAt"`
|
||||||
|
UpdatedAt time.Time `db:"updated_at" json:"updatedAt"`
|
||||||
|
Metadata []byte `db:"metadata" json:"metadata"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type InvoiceConfig struct {
|
||||||
|
ID int64 `db:"id" json:"id"`
|
||||||
|
Name string `db:"name" json:"name"`
|
||||||
|
Type InvoiceTypeEnum `db:"type" json:"type"`
|
||||||
|
Description pgtype.Text `db:"description" json:"description"`
|
||||||
|
IsActive bool `db:"is_active" json:"isActive"`
|
||||||
|
Metadata []byte `db:"metadata" json:"metadata"`
|
||||||
|
CreatedAt time.Time `db:"created_at" json:"createdAt"`
|
||||||
|
UpdatedAt time.Time `db:"updated_at" json:"updatedAt"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type InvoiceConfigItem struct {
|
||||||
|
ID int64 `db:"id" json:"id"`
|
||||||
|
InvoiceConfigID int64 `db:"invoice_config_id" json:"invoiceConfigId"`
|
||||||
|
ComponentID int64 `db:"component_id" json:"componentId"`
|
||||||
|
RequiredQuantity int32 `db:"required_quantity" json:"requiredQuantity"`
|
||||||
|
AllowAlternative bool `db:"allow_alternative" json:"allowAlternative"`
|
||||||
|
PriorityOrder int32 `db:"priority_order" json:"priorityOrder"`
|
||||||
|
Note pgtype.Text `db:"note" json:"note"`
|
||||||
|
Metadata []byte `db:"metadata" json:"metadata"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type InvoiceItem struct {
|
||||||
|
ID int64 `db:"id" json:"id"`
|
||||||
|
InvoiceID int64 `db:"invoice_id" json:"invoiceId"`
|
||||||
|
ComponentID int64 `db:"component_id" json:"componentId"`
|
||||||
|
OriginalComponentID pgtype.Int8 `db:"original_component_id" json:"originalComponentId"`
|
||||||
|
RequiredQuantity int32 `db:"required_quantity" json:"requiredQuantity"`
|
||||||
|
ActualQuantity int32 `db:"actual_quantity" json:"actualQuantity"`
|
||||||
|
IsSubstituted bool `db:"is_substituted" json:"isSubstituted"`
|
||||||
|
IsShort bool `db:"is_short" json:"isShort"`
|
||||||
|
ShortageQuantity int32 `db:"shortage_quantity" json:"shortageQuantity"`
|
||||||
|
Note pgtype.Text `db:"note" json:"note"`
|
||||||
|
Metadata []byte `db:"metadata" json:"metadata"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type InvoiceItemLocation struct {
|
||||||
|
ID int64 `db:"id" json:"id"`
|
||||||
|
InvoiceItemID int64 `db:"invoice_item_id" json:"invoiceItemId"`
|
||||||
|
ContainerID int64 `db:"container_id" json:"containerId"`
|
||||||
|
Quantity int32 `db:"quantity" json:"quantity"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type InvoiceStatusHistory struct {
|
||||||
|
ID int64 `db:"id" json:"id"`
|
||||||
|
InvoiceID int64 `db:"invoice_id" json:"invoiceId"`
|
||||||
|
OldStatus pgtype.Text `db:"old_status" json:"oldStatus"`
|
||||||
|
NewStatus string `db:"new_status" json:"newStatus"`
|
||||||
|
ChangedBy pgtype.Text `db:"changed_by" json:"changedBy"`
|
||||||
|
Note pgtype.Text `db:"note" json:"note"`
|
||||||
|
ChangedAt time.Time `db:"changed_at" json:"changedAt"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Permission struct {
|
||||||
|
ID uuid.UUID `db:"id" json:"id"`
|
||||||
|
Name string `db:"name" json:"name"`
|
||||||
|
Description pgtype.Text `db:"description" json:"description"`
|
||||||
|
CreatedAt pgtype.Timestamptz `db:"created_at" json:"createdAt"`
|
||||||
|
CreatedBy pgtype.Text `db:"created_by" json:"createdBy"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Role struct {
|
||||||
|
ID uuid.UUID `db:"id" json:"id"`
|
||||||
|
Name string `db:"name" json:"name"`
|
||||||
|
Description pgtype.Text `db:"description" json:"description"`
|
||||||
|
CreatedAt pgtype.Timestamptz `db:"created_at" json:"createdAt"`
|
||||||
|
CreatedBy pgtype.Text `db:"created_by" json:"createdBy"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RolePermission struct {
|
||||||
|
RoleID uuid.UUID `db:"role_id" json:"roleId"`
|
||||||
|
PermissionID uuid.UUID `db:"permission_id" json:"permissionId"`
|
||||||
|
AssignedAt pgtype.Timestamptz `db:"assigned_at" json:"assignedAt"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Room struct {
|
||||||
|
ID int64 `db:"id" json:"id"`
|
||||||
|
WarehouseID int64 `db:"warehouse_id" json:"warehouseId"`
|
||||||
|
Name string `db:"name" json:"name"`
|
||||||
|
Description pgtype.Text `db:"description" json:"description"`
|
||||||
|
CreatedAt time.Time `db:"created_at" json:"createdAt"`
|
||||||
|
UpdatedAt time.Time `db:"updated_at" json:"updatedAt"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Shelf struct {
|
||||||
|
ID int64 `db:"id" json:"id"`
|
||||||
|
CabinetID int64 `db:"cabinet_id" json:"cabinetId"`
|
||||||
|
Name string `db:"name" json:"name"`
|
||||||
|
LevelIndex int32 `db:"level_index" json:"levelIndex"`
|
||||||
|
Description pgtype.Text `db:"description" json:"description"`
|
||||||
|
CreatedAt time.Time `db:"created_at" json:"createdAt"`
|
||||||
|
UpdatedAt time.Time `db:"updated_at" json:"updatedAt"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type StockTransaction struct {
|
||||||
|
ID int64 `db:"id" json:"id"`
|
||||||
|
InvoiceID int64 `db:"invoice_id" json:"invoiceId"`
|
||||||
|
ComponentID int64 `db:"component_id" json:"componentId"`
|
||||||
|
ContainerID int64 `db:"container_id" json:"containerId"`
|
||||||
|
TransactionType TransactionTypeEnum `db:"transaction_type" json:"transactionType"`
|
||||||
|
Quantity int32 `db:"quantity" json:"quantity"`
|
||||||
|
BalanceAfter pgtype.Int4 `db:"balance_after" json:"balanceAfter"`
|
||||||
|
Note pgtype.Text `db:"note" json:"note"`
|
||||||
|
CreatedBy pgtype.Text `db:"created_by" json:"createdBy"`
|
||||||
|
CreatedAt time.Time `db:"created_at" json:"createdAt"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type User struct {
|
||||||
|
ID uuid.UUID `db:"id" json:"id"`
|
||||||
|
Username string `db:"username" json:"username"`
|
||||||
|
Email string `db:"email" json:"email"`
|
||||||
|
PasswordHash string `db:"password_hash" json:"passwordHash"`
|
||||||
|
FullName pgtype.Text `db:"full_name" json:"fullName"`
|
||||||
|
IsActive pgtype.Bool `db:"is_active" json:"isActive"`
|
||||||
|
CreatedAt pgtype.Timestamptz `db:"created_at" json:"createdAt"`
|
||||||
|
UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updatedAt"`
|
||||||
|
CreatedBy pgtype.Text `db:"created_by" json:"createdBy"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserRole struct {
|
||||||
|
UserID uuid.UUID `db:"user_id" json:"userId"`
|
||||||
|
RoleID uuid.UUID `db:"role_id" json:"roleId"`
|
||||||
|
AssignedAt pgtype.Timestamptz `db:"assigned_at" json:"assignedAt"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Warehouse struct {
|
||||||
|
ID int64 `db:"id" json:"id"`
|
||||||
|
Name string `db:"name" json:"name"`
|
||||||
|
Description pgtype.Text `db:"description" json:"description"`
|
||||||
|
Address pgtype.Text `db:"address" json:"address"`
|
||||||
|
CreatedAt time.Time `db:"created_at" json:"createdAt"`
|
||||||
|
UpdatedAt time.Time `db:"updated_at" json:"updatedAt"`
|
||||||
|
}
|
||||||
47
sqlc_gen/querier.go
Normal file
47
sqlc_gen/querier.go
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// sqlc v1.30.0
|
||||||
|
|
||||||
|
package db
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Querier interface {
|
||||||
|
AssignRoleToUser(ctx context.Context, arg AssignRoleToUserParams) (UserRole, error)
|
||||||
|
CountUsersByRoleID(ctx context.Context, roleID uuid.UUID) (int64, error)
|
||||||
|
CreateCabinet(ctx context.Context, arg CreateCabinetParams) (Cabinet, error)
|
||||||
|
CreateRole(ctx context.Context, arg CreateRoleParams) (Role, error)
|
||||||
|
CreateRoom(ctx context.Context, arg CreateRoomParams) (Room, error)
|
||||||
|
CreateUser(ctx context.Context, arg CreateUserParams) (uuid.UUID, error)
|
||||||
|
CreateWarehouse(ctx context.Context, arg CreateWarehouseParams) (Warehouse, error)
|
||||||
|
DeleteCabinet(ctx context.Context, id int64) error
|
||||||
|
DeleteRole(ctx context.Context, id uuid.UUID) error
|
||||||
|
DeleteRoom(ctx context.Context, id int64) error
|
||||||
|
DeleteWarehouse(ctx context.Context, id int64) error
|
||||||
|
GetCabinetByID(ctx context.Context, id int64) (Cabinet, error)
|
||||||
|
GetRoleByID(ctx context.Context, id uuid.UUID) (Role, error)
|
||||||
|
GetRoomByID(ctx context.Context, id int64) (Room, error)
|
||||||
|
GetUserByEmail(ctx context.Context, email string) (User, error)
|
||||||
|
GetUserByID(ctx context.Context, id uuid.UUID) (User, error)
|
||||||
|
GetUserByUsername(ctx context.Context, username string) (User, error)
|
||||||
|
GetUserRole(ctx context.Context, arg GetUserRoleParams) (UserRole, error)
|
||||||
|
GetUserRolesByRoleID(ctx context.Context, roleID uuid.UUID) ([]GetUserRolesByRoleIDRow, error)
|
||||||
|
GetUserRolesByUserID(ctx context.Context, userID uuid.UUID) ([]GetUserRolesByUserIDRow, error)
|
||||||
|
GetWarehouseByID(ctx context.Context, id int64) (Warehouse, error)
|
||||||
|
ListCabinets(ctx context.Context) ([]Cabinet, error)
|
||||||
|
ListRoles(ctx context.Context) ([]Role, error)
|
||||||
|
ListRooms(ctx context.Context) ([]Room, error)
|
||||||
|
ListWarehouses(ctx context.Context) ([]Warehouse, error)
|
||||||
|
RemoveAllRolesFromUser(ctx context.Context, userID uuid.UUID) error
|
||||||
|
RemoveRoleFromUser(ctx context.Context, arg RemoveRoleFromUserParams) error
|
||||||
|
UpdateCabinet(ctx context.Context, arg UpdateCabinetParams) (Cabinet, error)
|
||||||
|
UpdateRole(ctx context.Context, arg UpdateRoleParams) (Role, error)
|
||||||
|
UpdateRoom(ctx context.Context, arg UpdateRoomParams) (Room, error)
|
||||||
|
UpdateWarehouse(ctx context.Context, arg UpdateWarehouseParams) (Warehouse, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Querier = (*Queries)(nil)
|
||||||
127
sqlc_gen/roles.sql.go
Normal file
127
sqlc_gen/roles.sql.go
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// sqlc v1.30.0
|
||||||
|
// source: roles.sql
|
||||||
|
|
||||||
|
package db
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
|
)
|
||||||
|
|
||||||
|
const createRole = `-- name: CreateRole :one
|
||||||
|
INSERT INTO roles (name, description, created_by)
|
||||||
|
VALUES (
|
||||||
|
$1,
|
||||||
|
$2,
|
||||||
|
$3)
|
||||||
|
RETURNING id, name, description, created_at, created_by
|
||||||
|
`
|
||||||
|
|
||||||
|
type CreateRoleParams struct {
|
||||||
|
Name string `db:"name" json:"name"`
|
||||||
|
Description pgtype.Text `db:"description" json:"description"`
|
||||||
|
CreatedBy pgtype.Text `db:"created_by" json:"createdBy"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) CreateRole(ctx context.Context, arg CreateRoleParams) (Role, error) {
|
||||||
|
row := q.db.QueryRow(ctx, createRole, arg.Name, arg.Description, arg.CreatedBy)
|
||||||
|
var i Role
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.Name,
|
||||||
|
&i.Description,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.CreatedBy,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteRole = `-- name: DeleteRole :exec
|
||||||
|
DELETE FROM roles
|
||||||
|
WHERE id = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) DeleteRole(ctx context.Context, id uuid.UUID) error {
|
||||||
|
_, err := q.db.Exec(ctx, deleteRole, id)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const getRoleByID = `-- name: GetRoleByID :one
|
||||||
|
SELECT id, name, description, created_at, created_by FROM roles
|
||||||
|
WHERE id = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) GetRoleByID(ctx context.Context, id uuid.UUID) (Role, error) {
|
||||||
|
row := q.db.QueryRow(ctx, getRoleByID, id)
|
||||||
|
var i Role
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.Name,
|
||||||
|
&i.Description,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.CreatedBy,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const listRoles = `-- name: ListRoles :many
|
||||||
|
SELECT id, name, description, created_at, created_by FROM roles
|
||||||
|
ORDER BY created_at DESC
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) ListRoles(ctx context.Context) ([]Role, error) {
|
||||||
|
rows, err := q.db.Query(ctx, listRoles)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var items []Role
|
||||||
|
for rows.Next() {
|
||||||
|
var i Role
|
||||||
|
if err := rows.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.Name,
|
||||||
|
&i.Description,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.CreatedBy,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateRole = `-- name: UpdateRole :one
|
||||||
|
UPDATE roles
|
||||||
|
SET name = $1,
|
||||||
|
description = $2
|
||||||
|
WHERE id = $3
|
||||||
|
RETURNING id, name, description, created_at, created_by
|
||||||
|
`
|
||||||
|
|
||||||
|
type UpdateRoleParams struct {
|
||||||
|
Name string `db:"name" json:"name"`
|
||||||
|
Description pgtype.Text `db:"description" json:"description"`
|
||||||
|
ID uuid.UUID `db:"id" json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) UpdateRole(ctx context.Context, arg UpdateRoleParams) (Role, error) {
|
||||||
|
row := q.db.QueryRow(ctx, updateRole, arg.Name, arg.Description, arg.ID)
|
||||||
|
var i Role
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.Name,
|
||||||
|
&i.Description,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.CreatedBy,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
146
sqlc_gen/room.sql.go
Normal file
146
sqlc_gen/room.sql.go
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// sqlc v1.30.0
|
||||||
|
// source: room.sql
|
||||||
|
|
||||||
|
package db
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
|
)
|
||||||
|
|
||||||
|
const createRoom = `-- name: CreateRoom :one
|
||||||
|
INSERT INTO rooms (warehouse_id,name, description, created_at)
|
||||||
|
VALUES (
|
||||||
|
$1,
|
||||||
|
$2,
|
||||||
|
$3,
|
||||||
|
$4
|
||||||
|
)
|
||||||
|
RETURNING id, warehouse_id, name, description, created_at, updated_at
|
||||||
|
`
|
||||||
|
|
||||||
|
type CreateRoomParams struct {
|
||||||
|
WarehouseID int64 `db:"warehouse_id" json:"warehouseId"`
|
||||||
|
Name string `db:"name" json:"name"`
|
||||||
|
Description pgtype.Text `db:"description" json:"description"`
|
||||||
|
CreatedAt time.Time `db:"created_at" json:"createdAt"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) CreateRoom(ctx context.Context, arg CreateRoomParams) (Room, error) {
|
||||||
|
row := q.db.QueryRow(ctx, createRoom,
|
||||||
|
arg.WarehouseID,
|
||||||
|
arg.Name,
|
||||||
|
arg.Description,
|
||||||
|
arg.CreatedAt,
|
||||||
|
)
|
||||||
|
var i Room
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.WarehouseID,
|
||||||
|
&i.Name,
|
||||||
|
&i.Description,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteRoom = `-- name: DeleteRoom :exec
|
||||||
|
DELETE FROM rooms
|
||||||
|
WHERE id = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) DeleteRoom(ctx context.Context, id int64) error {
|
||||||
|
_, err := q.db.Exec(ctx, deleteRoom, id)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const getRoomByID = `-- name: GetRoomByID :one
|
||||||
|
SELECT id, warehouse_id, name, description, created_at, updated_at FROM rooms
|
||||||
|
WHERE id = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) GetRoomByID(ctx context.Context, id int64) (Room, error) {
|
||||||
|
row := q.db.QueryRow(ctx, getRoomByID, id)
|
||||||
|
var i Room
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.WarehouseID,
|
||||||
|
&i.Name,
|
||||||
|
&i.Description,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const listRooms = `-- name: ListRooms :many
|
||||||
|
SELECT id, warehouse_id, name, description, created_at, updated_at FROM rooms
|
||||||
|
ORDER BY created_at DESC
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) ListRooms(ctx context.Context) ([]Room, error) {
|
||||||
|
rows, err := q.db.Query(ctx, listRooms)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var items []Room
|
||||||
|
for rows.Next() {
|
||||||
|
var i Room
|
||||||
|
if err := rows.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.WarehouseID,
|
||||||
|
&i.Name,
|
||||||
|
&i.Description,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateRoom = `-- name: UpdateRoom :one
|
||||||
|
UPDATE rooms
|
||||||
|
SET name = coalesce($1, name),
|
||||||
|
description = coalesce($2, description),
|
||||||
|
updated_at = $3
|
||||||
|
WHERE id = $4
|
||||||
|
RETURNING id, warehouse_id, name, description, created_at, updated_at
|
||||||
|
`
|
||||||
|
|
||||||
|
type UpdateRoomParams struct {
|
||||||
|
Name string `db:"name" json:"name"`
|
||||||
|
Description pgtype.Text `db:"description" json:"description"`
|
||||||
|
UpdatedAt time.Time `db:"updated_at" json:"updatedAt"`
|
||||||
|
ID int64 `db:"id" json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) UpdateRoom(ctx context.Context, arg UpdateRoomParams) (Room, error) {
|
||||||
|
row := q.db.QueryRow(ctx, updateRoom,
|
||||||
|
arg.Name,
|
||||||
|
arg.Description,
|
||||||
|
arg.UpdatedAt,
|
||||||
|
arg.ID,
|
||||||
|
)
|
||||||
|
var i Room
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.WarehouseID,
|
||||||
|
&i.Name,
|
||||||
|
&i.Description,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
173
sqlc_gen/user_roles.sql.go
Normal file
173
sqlc_gen/user_roles.sql.go
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// sqlc v1.30.0
|
||||||
|
// source: user_roles.sql
|
||||||
|
|
||||||
|
package db
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
|
)
|
||||||
|
|
||||||
|
const assignRoleToUser = `-- name: AssignRoleToUser :one
|
||||||
|
INSERT INTO user_roles (user_id, role_id)
|
||||||
|
VALUES (
|
||||||
|
$1,
|
||||||
|
$2)
|
||||||
|
RETURNING user_id, role_id, assigned_at
|
||||||
|
`
|
||||||
|
|
||||||
|
type AssignRoleToUserParams struct {
|
||||||
|
UserID uuid.UUID `db:"user_id" json:"userId"`
|
||||||
|
RoleID uuid.UUID `db:"role_id" json:"roleId"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) AssignRoleToUser(ctx context.Context, arg AssignRoleToUserParams) (UserRole, error) {
|
||||||
|
row := q.db.QueryRow(ctx, assignRoleToUser, arg.UserID, arg.RoleID)
|
||||||
|
var i UserRole
|
||||||
|
err := row.Scan(&i.UserID, &i.RoleID, &i.AssignedAt)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const countUsersByRoleID = `-- name: CountUsersByRoleID :one
|
||||||
|
SELECT COUNT(*) FROM user_roles
|
||||||
|
WHERE role_id = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) CountUsersByRoleID(ctx context.Context, roleID uuid.UUID) (int64, error) {
|
||||||
|
row := q.db.QueryRow(ctx, countUsersByRoleID, roleID)
|
||||||
|
var count int64
|
||||||
|
err := row.Scan(&count)
|
||||||
|
return count, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const getUserRole = `-- name: GetUserRole :one
|
||||||
|
SELECT user_id, role_id, assigned_at FROM user_roles
|
||||||
|
WHERE user_id = $1 AND role_id = $2
|
||||||
|
`
|
||||||
|
|
||||||
|
type GetUserRoleParams struct {
|
||||||
|
UserID uuid.UUID `db:"user_id" json:"userId"`
|
||||||
|
RoleID uuid.UUID `db:"role_id" json:"roleId"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) GetUserRole(ctx context.Context, arg GetUserRoleParams) (UserRole, error) {
|
||||||
|
row := q.db.QueryRow(ctx, getUserRole, arg.UserID, arg.RoleID)
|
||||||
|
var i UserRole
|
||||||
|
err := row.Scan(&i.UserID, &i.RoleID, &i.AssignedAt)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const getUserRolesByRoleID = `-- name: GetUserRolesByRoleID :many
|
||||||
|
SELECT ur.user_id, ur.role_id, ur.assigned_at, u.username, u.email, u.full_name
|
||||||
|
FROM user_roles ur
|
||||||
|
JOIN users u ON u.id = ur.user_id
|
||||||
|
WHERE ur.role_id = $1
|
||||||
|
ORDER BY ur.assigned_at DESC
|
||||||
|
`
|
||||||
|
|
||||||
|
type GetUserRolesByRoleIDRow struct {
|
||||||
|
UserID uuid.UUID `db:"user_id" json:"userId"`
|
||||||
|
RoleID uuid.UUID `db:"role_id" json:"roleId"`
|
||||||
|
AssignedAt pgtype.Timestamptz `db:"assigned_at" json:"assignedAt"`
|
||||||
|
Username string `db:"username" json:"username"`
|
||||||
|
Email string `db:"email" json:"email"`
|
||||||
|
FullName pgtype.Text `db:"full_name" json:"fullName"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) GetUserRolesByRoleID(ctx context.Context, roleID uuid.UUID) ([]GetUserRolesByRoleIDRow, error) {
|
||||||
|
rows, err := q.db.Query(ctx, getUserRolesByRoleID, roleID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var items []GetUserRolesByRoleIDRow
|
||||||
|
for rows.Next() {
|
||||||
|
var i GetUserRolesByRoleIDRow
|
||||||
|
if err := rows.Scan(
|
||||||
|
&i.UserID,
|
||||||
|
&i.RoleID,
|
||||||
|
&i.AssignedAt,
|
||||||
|
&i.Username,
|
||||||
|
&i.Email,
|
||||||
|
&i.FullName,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const getUserRolesByUserID = `-- name: GetUserRolesByUserID :many
|
||||||
|
SELECT ur.user_id, ur.role_id, ur.assigned_at, r.name AS role_name, r.description AS role_description
|
||||||
|
FROM user_roles ur
|
||||||
|
JOIN roles r ON r.id = ur.role_id
|
||||||
|
WHERE ur.user_id = $1
|
||||||
|
ORDER BY ur.assigned_at DESC
|
||||||
|
`
|
||||||
|
|
||||||
|
type GetUserRolesByUserIDRow struct {
|
||||||
|
UserID uuid.UUID `db:"user_id" json:"userId"`
|
||||||
|
RoleID uuid.UUID `db:"role_id" json:"roleId"`
|
||||||
|
AssignedAt pgtype.Timestamptz `db:"assigned_at" json:"assignedAt"`
|
||||||
|
RoleName string `db:"role_name" json:"roleName"`
|
||||||
|
RoleDescription pgtype.Text `db:"role_description" json:"roleDescription"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) GetUserRolesByUserID(ctx context.Context, userID uuid.UUID) ([]GetUserRolesByUserIDRow, error) {
|
||||||
|
rows, err := q.db.Query(ctx, getUserRolesByUserID, userID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var items []GetUserRolesByUserIDRow
|
||||||
|
for rows.Next() {
|
||||||
|
var i GetUserRolesByUserIDRow
|
||||||
|
if err := rows.Scan(
|
||||||
|
&i.UserID,
|
||||||
|
&i.RoleID,
|
||||||
|
&i.AssignedAt,
|
||||||
|
&i.RoleName,
|
||||||
|
&i.RoleDescription,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeAllRolesFromUser = `-- name: RemoveAllRolesFromUser :exec
|
||||||
|
DELETE FROM user_roles
|
||||||
|
WHERE user_id = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) RemoveAllRolesFromUser(ctx context.Context, userID uuid.UUID) error {
|
||||||
|
_, err := q.db.Exec(ctx, removeAllRolesFromUser, userID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeRoleFromUser = `-- name: RemoveRoleFromUser :exec
|
||||||
|
DELETE FROM user_roles
|
||||||
|
WHERE user_id = $1 AND role_id = $2
|
||||||
|
`
|
||||||
|
|
||||||
|
type RemoveRoleFromUserParams struct {
|
||||||
|
UserID uuid.UUID `db:"user_id" json:"userId"`
|
||||||
|
RoleID uuid.UUID `db:"role_id" json:"roleId"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) RemoveRoleFromUser(ctx context.Context, arg RemoveRoleFromUserParams) error {
|
||||||
|
_, err := q.db.Exec(ctx, removeRoleFromUser, arg.UserID, arg.RoleID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
113
sqlc_gen/users.sql.go
Normal file
113
sqlc_gen/users.sql.go
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// sqlc v1.30.0
|
||||||
|
// source: users.sql
|
||||||
|
|
||||||
|
package db
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
|
)
|
||||||
|
|
||||||
|
const createUser = `-- name: CreateUser :one
|
||||||
|
INSERT INTO users (username, email, password_hash, full_name, created_by)
|
||||||
|
VALUES (
|
||||||
|
$1,
|
||||||
|
$2,
|
||||||
|
$3,
|
||||||
|
$4,
|
||||||
|
$5)
|
||||||
|
RETURNING id
|
||||||
|
`
|
||||||
|
|
||||||
|
type CreateUserParams struct {
|
||||||
|
Username string `db:"username" json:"username"`
|
||||||
|
Email string `db:"email" json:"email"`
|
||||||
|
PasswordHash string `db:"password_hash" json:"passwordHash"`
|
||||||
|
FullName pgtype.Text `db:"full_name" json:"fullName"`
|
||||||
|
CreatedBy pgtype.Text `db:"created_by" json:"createdBy"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) CreateUser(ctx context.Context, arg CreateUserParams) (uuid.UUID, error) {
|
||||||
|
row := q.db.QueryRow(ctx, createUser,
|
||||||
|
arg.Username,
|
||||||
|
arg.Email,
|
||||||
|
arg.PasswordHash,
|
||||||
|
arg.FullName,
|
||||||
|
arg.CreatedBy,
|
||||||
|
)
|
||||||
|
var id uuid.UUID
|
||||||
|
err := row.Scan(&id)
|
||||||
|
return id, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const getUserByEmail = `-- name: GetUserByEmail :one
|
||||||
|
SELECT id, username, email, password_hash, full_name, is_active, created_at, updated_at, created_by FROM users
|
||||||
|
WHERE email = $1
|
||||||
|
LIMIT 1
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) GetUserByEmail(ctx context.Context, email string) (User, error) {
|
||||||
|
row := q.db.QueryRow(ctx, getUserByEmail, email)
|
||||||
|
var i User
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.Username,
|
||||||
|
&i.Email,
|
||||||
|
&i.PasswordHash,
|
||||||
|
&i.FullName,
|
||||||
|
&i.IsActive,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
&i.CreatedBy,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const getUserByID = `-- name: GetUserByID :one
|
||||||
|
SELECT id, username, email, password_hash, full_name, is_active, created_at, updated_at, created_by FROM users
|
||||||
|
WHERE id = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) GetUserByID(ctx context.Context, id uuid.UUID) (User, error) {
|
||||||
|
row := q.db.QueryRow(ctx, getUserByID, id)
|
||||||
|
var i User
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.Username,
|
||||||
|
&i.Email,
|
||||||
|
&i.PasswordHash,
|
||||||
|
&i.FullName,
|
||||||
|
&i.IsActive,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
&i.CreatedBy,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const getUserByUsername = `-- name: GetUserByUsername :one
|
||||||
|
SELECT id, username, email, password_hash, full_name, is_active, created_at, updated_at, created_by FROM users
|
||||||
|
WHERE username = $1
|
||||||
|
LIMIT 1
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) GetUserByUsername(ctx context.Context, username string) (User, error) {
|
||||||
|
row := q.db.QueryRow(ctx, getUserByUsername, username)
|
||||||
|
var i User
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.Username,
|
||||||
|
&i.Email,
|
||||||
|
&i.PasswordHash,
|
||||||
|
&i.FullName,
|
||||||
|
&i.IsActive,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
&i.CreatedBy,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
149
sqlc_gen/warehouse.sql.go
Normal file
149
sqlc_gen/warehouse.sql.go
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// sqlc v1.30.0
|
||||||
|
// source: warehouse.sql
|
||||||
|
|
||||||
|
package db
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
|
)
|
||||||
|
|
||||||
|
const createWarehouse = `-- name: CreateWarehouse :one
|
||||||
|
INSERT INTO warehouses (name, description, address, created_at)
|
||||||
|
VALUES (
|
||||||
|
$1,
|
||||||
|
$2,
|
||||||
|
$3,
|
||||||
|
$4
|
||||||
|
)
|
||||||
|
RETURNING id, name, description, address, created_at, updated_at
|
||||||
|
`
|
||||||
|
|
||||||
|
type CreateWarehouseParams struct {
|
||||||
|
Name string `db:"name" json:"name"`
|
||||||
|
Description pgtype.Text `db:"description" json:"description"`
|
||||||
|
Address pgtype.Text `db:"address" json:"address"`
|
||||||
|
CreatedAt time.Time `db:"created_at" json:"createdAt"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) CreateWarehouse(ctx context.Context, arg CreateWarehouseParams) (Warehouse, error) {
|
||||||
|
row := q.db.QueryRow(ctx, createWarehouse,
|
||||||
|
arg.Name,
|
||||||
|
arg.Description,
|
||||||
|
arg.Address,
|
||||||
|
arg.CreatedAt,
|
||||||
|
)
|
||||||
|
var i Warehouse
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.Name,
|
||||||
|
&i.Description,
|
||||||
|
&i.Address,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteWarehouse = `-- name: DeleteWarehouse :exec
|
||||||
|
DELETE FROM warehouses
|
||||||
|
WHERE id = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) DeleteWarehouse(ctx context.Context, id int64) error {
|
||||||
|
_, err := q.db.Exec(ctx, deleteWarehouse, id)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const getWarehouseByID = `-- name: GetWarehouseByID :one
|
||||||
|
SELECT id, name, description, address, created_at, updated_at FROM warehouses
|
||||||
|
WHERE id = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) GetWarehouseByID(ctx context.Context, id int64) (Warehouse, error) {
|
||||||
|
row := q.db.QueryRow(ctx, getWarehouseByID, id)
|
||||||
|
var i Warehouse
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.Name,
|
||||||
|
&i.Description,
|
||||||
|
&i.Address,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const listWarehouses = `-- name: ListWarehouses :many
|
||||||
|
SELECT id, name, description, address, created_at, updated_at FROM warehouses
|
||||||
|
ORDER BY created_at DESC
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) ListWarehouses(ctx context.Context) ([]Warehouse, error) {
|
||||||
|
rows, err := q.db.Query(ctx, listWarehouses)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var items []Warehouse
|
||||||
|
for rows.Next() {
|
||||||
|
var i Warehouse
|
||||||
|
if err := rows.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.Name,
|
||||||
|
&i.Description,
|
||||||
|
&i.Address,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateWarehouse = `-- name: UpdateWarehouse :one
|
||||||
|
UPDATE warehouses
|
||||||
|
SET name = CASE WHEN $1 = '' THEN name ELSE $1 END,
|
||||||
|
description = coalesce($2, description),
|
||||||
|
address = coalesce($3, address),
|
||||||
|
updated_at = $4
|
||||||
|
WHERE id = $5
|
||||||
|
RETURNING id, name, description, address, created_at, updated_at
|
||||||
|
`
|
||||||
|
|
||||||
|
type UpdateWarehouseParams struct {
|
||||||
|
Name interface{} `db:"name" json:"name"`
|
||||||
|
Description pgtype.Text `db:"description" json:"description"`
|
||||||
|
Address pgtype.Text `db:"address" json:"address"`
|
||||||
|
UpdatedAt time.Time `db:"updated_at" json:"updatedAt"`
|
||||||
|
ID int64 `db:"id" json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) UpdateWarehouse(ctx context.Context, arg UpdateWarehouseParams) (Warehouse, error) {
|
||||||
|
row := q.db.QueryRow(ctx, updateWarehouse,
|
||||||
|
arg.Name,
|
||||||
|
arg.Description,
|
||||||
|
arg.Address,
|
||||||
|
arg.UpdatedAt,
|
||||||
|
arg.ID,
|
||||||
|
)
|
||||||
|
var i Warehouse
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.Name,
|
||||||
|
&i.Description,
|
||||||
|
&i.Address,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user