This commit is contained in:
koh 2025-04-12 15:25:34 +07:00
commit d83f6c1145
38 changed files with 2131 additions and 0 deletions

46
.air.toml Normal file
View File

@ -0,0 +1,46 @@
root = "."
testdata_dir = "testdata"
tmp_dir = "tmp"
[build]
args_bin = []
bin = "./main"
cmd = "make build"
delay = 1000
exclude_dir = ["assets", "tmp", "vendor", "testdata", "node_modules"]
exclude_file = []
exclude_regex = ["_test.go"]
exclude_unchanged = false
follow_symlink = false
full_bin = ""
include_dir = []
include_ext = ["go", "tpl", "tmpl", "html"]
include_file = []
kill_delay = "0s"
log = "build-errors.log"
poll = false
poll_interval = 0
post_cmd = []
pre_cmd = []
rerun = false
rerun_delay = 500
send_interrupt = false
stop_on_error = false
[color]
app = ""
build = "yellow"
main = "magenta"
runner = "green"
watcher = "cyan"
[log]
main_only = false
time = false
[misc]
clean_on_exit = false
[screen]
clear_on_rebuild = false
keep_scroll = true

41
.env.prod Normal file
View File

@ -0,0 +1,41 @@
# App Configuration
PORT=8080
APP_ENV=production
# Database Configuration
BLUEPRINT_DB_HOST=mysql_bp
BLUEPRINT_DB_PORT=3306
BLUEPRINT_DB_DATABASE=blueprint
BLUEPRINT_DB_USERNAME=blueprint_user
BLUEPRINT_DB_PASSWORD=your_secure_password
BLUEPRINT_DB_ROOT_PASSWORD=your_secure_root_password
BLUEPRINT_DB_MAX_IDLE_CONNS=10
BLUEPRINT_DB_MAX_OPEN_CONNS=100
BLUEPRINT_DB_CONN_MAX_LIFETIME=1h
# Logger Configuration
LOGGER_LOG_LEVEL=info
LOGGER_FILE_LOG_NAME=logs/app.log
LOGGER_MAX_SIZE=1000
LOGGER_MAX_BACKUPS=7
LOGGER_MAX_AGE=30
LOGGER_COMPRESS=true
# Monitoring Configuration
GRAFANA_ADMIN_PASSWORD=admin
PROMETHEUS_RETENTION_TIME=15d
PROMETHEUS_SCRAPE_INTERVAL=15s
# Backup Configuration
BACKUP_RETENTION_DAYS=7
BACKUP_SCHEDULE="0 2 * * *"
# SSL Configuration
SSL_CERT_PATH=/etc/nginx/ssl/cert.pem
SSL_KEY_PATH=/etc/nginx/ssl/key.pem
# Network Configuration
NGINX_PORT=80
NGINX_SSL_PORT=443
GRAFANA_PORT=3000
PROMETHEUS_PORT=9090

35
.gitignore vendored Normal file
View File

@ -0,0 +1,35 @@
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, built with "go test -c"
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Dependency directories (remove the comment below to include it)
# vendor/
# Go workspace file
go.work
tmp/
# IDE specific files
.vscode
.idea
# .env file
.env
# Project build
main
*templ.go
# OS X generated file
.DS_Store
/logs/app.log

27
Dockerfile.dev Normal file
View File

@ -0,0 +1,27 @@
FROM golang:1.23.3-alpine
WORKDIR /app
# Install build dependencies and air
RUN apk add --no-cache git make && \
go install github.com/cosmtrek/air@v1.49.0
# Sao chép file go.mod và go.sum
COPY go.mod go.sum ./
# Tải các dependency
RUN go mod download
# Sao chép file .air.toml
COPY .air.toml ./
# Sao chép toàn bộ mã nguồn
COPY . .
# Expose port
ARG PORT=8080
ENV PORT=${PORT}
EXPOSE ${PORT}
# Chạy Air để hot-reload
CMD ["air"]

46
Dockerfile.prod Normal file
View File

@ -0,0 +1,46 @@
# Build stage
FROM golang:1.23.3-alpine AS builder
WORKDIR /app
# Install build dependencies
RUN apk add --no-cache git make
# Copy go mod and sum files
COPY go.mod go.sum ./
# Download dependencies
RUN go mod download
# Copy source code
COPY . .
# Build the application
RUN CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -o main ./cmd/api
# Production stage
FROM alpine:latest
WORKDIR /app
# Copy binary from builder
COPY --from=builder /app/main .
# Copy SSL certificates
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
# Create necessary directories
RUN mkdir -p /app/logs
# Copy environment file
COPY .env.prod .env
# Expose port
EXPOSE 8080
# Set environment variables
ENV PORT=8080
ENV TZ=Asia/Ho_Chi_Minh
# Run the application
CMD ["./main"]

64
Makefile Normal file
View File

@ -0,0 +1,64 @@
# Simple Makefile for a Go project
# Build the application
all: build test
build:
@echo "Building..."
@go build -o main cmd/api/main.go
# Run the application
run:
@go run cmd/api/main.go
# Create DB container
docker-run:
@if docker compose up --build 2>/dev/null; then \
: ; \
else \
echo "Falling back to Docker Compose V1"; \
docker-compose up --build; \
fi
# Shutdown DB container
docker-down:
@if docker compose down 2>/dev/null; then \
: ; \
else \
echo "Falling back to Docker Compose V1"; \
docker-compose down; \
fi
# Test the application
test:
@echo "Testing..."
@go test ./... -v
# Integrations Tests for the application
itest:
@echo "Running integration tests..."
@go test ./internal/database -v
# Clean the binary
clean:
@echo "Cleaning..."
@rm -f main
# Live Reload
watch:
@if command -v air > /dev/null; then \
air; \
echo "Watching...";\
else \
read -p "Go's 'air' is not installed on your machine. Do you want to install it? [Y/n] " choice; \
if [ "$$choice" != "n" ] && [ "$$choice" != "N" ]; then \
go install github.com/air-verse/air@latest; \
air; \
echo "Watching...";\
else \
echo "You chose not to install air. Exiting..."; \
exit 1; \
fi; \
fi
.PHONY: all build run test clean watch docker-run docker-down itest

138
README.md Normal file
View File

@ -0,0 +1,138 @@
## Cấu trúc dự án chi tiết
```
.
├── cmd/ # Điểm vào ứng dụng
│ └── api/ # API server
│ └── main.go # File main khởi động ứng dụng
├── global/ # Biến toàn cục và cấu trúc cấu hình
│ └── global.go # Định nghĩa các biến và cấu trúc toàn cục
├── internal/ # Mã nguồn nội bộ
│ ├── controllers/ # Xử lý request và response
│ │ └── user_controller.go # Controller xử lý các request liên quan đến user
│ ├── initialize/ # Khởi tạo các thành phần của ứng dụng
│ │ ├── loadconfig.go # Đọc cấu hình từ file .env
│ │ ├── logger.go # Khởi tạo logger
│ │ ├── mysql.go # Khởi tạo kết nối MySQL
│ │ └── run.go # Điểm khởi động chính của ứng dụng
│ ├── models/ # Định nghĩa các model dữ liệu
│ │ └── user.go # Model User
│ ├── repositories/ # Tương tác với cơ sở dữ liệu
│ │ └── user_repository.go # Repository xử lý dữ liệu user
│ ├── routers/ # Định nghĩa các router
│ │ ├── router_group.go # Nhóm các router
│ │ └── user/ # Router liên quan đến user
│ │ ├── router_group.go # Nhóm các router user
│ │ └── user_router.go # Định nghĩa các endpoint user
│ ├── services/ # Xử lý logic nghiệp vụ
│ │ └── user_service.go # Service xử lý logic liên quan đến user
│ └── wire/ # Dependency injection với Google Wire
│ ├── injector.go # Định nghĩa các injector
│ ├── wire.go # Định nghĩa các provider
│ └── wire_gen.go # File được tạo tự động bởi Wire
├── logs/ # Thư mục chứa log
├── .air.toml # Cấu hình cho Air (hot-reload)
├── .env # Biến môi trường
├── Dockerfile # Cấu hình Docker cho môi trường production
├── Dockerfile.dev # Cấu hình Docker cho môi trường phát triển
├── Makefile # Các lệnh make
└── docker-compose.yml, docker-compose.dev.yml # Cấu hình Docker Compose
```
## Quy tắc đặt tên file
Dự án tuân theo các quy tắc đặt tên file sau để đảm bảo tính nhất quán:
1. **Sử dụng dấu gạch dưới (_) để phân tách các từ** trong tên file, ví dụ: `user_controller.go`, `user_repository.go`.
2. **Tên file mô tả rõ chức năng** của file đó:
- Controllers: `<entity>_controller.go` (ví dụ: `user_controller.go`)
- Services: `<entity>_service.go` (ví dụ: `user_service.go`)
- Repositories: `<entity>_repository.go` (ví dụ: `user_repository.go`)
- Routers: `<entity>_router.go` (ví dụ: `user_router.go`)
- Models: `<entity>.go` (ví dụ: `user.go`)
3. **File khởi tạo và cấu hình** sử dụng tên mô tả chức năng:
- `loadconfig.go`: Đọc cấu hình
- `logger.go`: Khởi tạo logger
- `mysql.go`: Khởi tạo kết nối MySQL
- `run.go`: Điểm khởi động ứng dụng
4. **File Wire** tuân theo quy ước của Google Wire:
- `wire.go`: Định nghĩa các provider
- `injector.go`: Định nghĩa các injector
- `wire_gen.go`: File được tạo tự động bởi Wire
## Kiến trúc ứng dụng
Ứng dụng được tổ chức theo kiến trúc phân lớp:
1. **Controllers**: Xử lý request và response, gọi các service để thực hiện logic nghiệp vụ.
2. **Services**: Chứa logic nghiệp vụ, gọi các repository để tương tác với dữ liệu.
3. **Repositories**: Tương tác trực tiếp với cơ sở dữ liệu, thực hiện các thao tác CRUD.
4. **Models**: Định nghĩa cấu trúc dữ liệu.
5. **Routers**: Định nghĩa các endpoint API và kết nối với controllers.
6. **Wire**: Quản lý dependency injection, kết nối các thành phần lại với nhau.
## Dependency Injection với Google Wire
Dự án sử dụng Google Wire để quản lý dependency injection. Các thành phần chính:
1. **Provider**: Định nghĩa cách tạo các dependency (trong `wire.go`).
2. **Injector**: Định nghĩa cách kết nối các dependency (trong `injector.go`).
3. **Wire Gen**: File được tạo tự động bởi Wire, chứa code khởi tạo dependency (trong `wire_gen.go`).
Để cập nhật file `wire_gen.go` sau khi thay đổi các provider hoặc injector:
```bash
cd internal/wire
go run github.com/google/wire/cmd/wire
```
## Môi trường phát triển (Development)
### Phương pháp 1: Sử dụng Docker Compose cho toàn bộ stack
Phương pháp này sử dụng Docker Compose để chạy cả ứng dụng Go và MySQL, với hot-reload được hỗ trợ bởi Air.
```bash
# Khởi động môi trường phát triển
docker-compose -f docker-compose.dev.yml up -d
# Xem logs
docker-compose -f docker-compose.dev.yml logs -f
# Dừng môi trường phát triển
docker-compose -f docker-compose.dev.yml down
```
### Phương pháp 2: Chạy ứng dụng Go trực tiếp, MySQL trong Docker
```bash
# Khởi động MySQL trong Docker
make docker-run
# Chạy ứng dụng với hot-reload
make watch
# Hoặc chạy ứng dụng thông thường
make run
# Dừng MySQL
make docker-down
```
## Môi trường sản xuất (Production)
### Phương pháp 1: Sử dụng Docker Compose
```bash
# Khởi động môi trường sản xuất
docker-compose up -d
# Xem logs
docker-compose logs -f
# Dừng môi trường sản xuất
docker-compose down
```

9
cmd/api/main.go Normal file
View File

@ -0,0 +1,9 @@
package main
import (
"github.com/dungnt11/todoms_golang/internal/initialize"
)
func main() {
initialize.Run()
}

92
docker-compose.dev.yml Normal file
View File

@ -0,0 +1,92 @@
services:
app:
build:
context: .
dockerfile: Dockerfile.dev
restart: unless-stopped
ports:
- ${PORT}:${PORT}
environment:
APP_ENV: ${APP_ENV}
PORT: ${PORT}
BLUEPRINT_DB_HOST: mysql_bp
BLUEPRINT_DB_PORT: ${BLUEPRINT_DB_PORT}
BLUEPRINT_DB_DATABASE: ${BLUEPRINT_DB_DATABASE}
BLUEPRINT_DB_USERNAME: ${BLUEPRINT_DB_USERNAME}
BLUEPRINT_DB_PASSWORD: ${BLUEPRINT_DB_PASSWORD}
BLUEPRINT_DB_MAX_IDLE_CONNS: ${BLUEPRINT_DB_MAX_IDLE_CONNS}
BLUEPRINT_DB_MAX_OPEN_CONNS: ${BLUEPRINT_DB_MAX_OPEN_CONNS}
BLUEPRINT_DB_CONN_MAX_LIFETIME: ${BLUEPRINT_DB_CONN_MAX_LIFETIME}
LOGGER_LOG_LEVEL: ${LOGGER_LOG_LEVEL}
LOGGER_FILE_LOG_NAME: ${LOGGER_FILE_LOG_NAME}
LOGGER_MAX_SIZE: ${LOGGER_MAX_SIZE}
LOGGER_MAX_BACKUPS: ${LOGGER_MAX_BACKUPS}
LOGGER_MAX_AGE: ${LOGGER_MAX_AGE}
LOGGER_COMPRESS: ${LOGGER_COMPRESS}
volumes:
- .:/app # Mount current directory to /app in container
command: air # Use air for hot-reload
depends_on:
mysql_bp:
condition: service_healthy
networks:
- blueprint
mysql_bp:
image: mysql:8.0
restart: unless-stopped
environment:
MYSQL_DATABASE: ${BLUEPRINT_DB_DATABASE}
MYSQL_USER: ${BLUEPRINT_DB_USERNAME}
MYSQL_PASSWORD: ${BLUEPRINT_DB_PASSWORD}
MYSQL_ROOT_PASSWORD: ${BLUEPRINT_DB_ROOT_PASSWORD}
MYSQL_ALLOW_EMPTY_PASSWORD: "no"
ports:
- "3306:3306"
volumes:
- mysql_volume_bp:/var/lib/mysql
- ./mysql/init:/docker-entrypoint-initdb.d
command: >
--default-authentication-plugin=mysql_native_password
--character-set-server=utf8mb4
--collation-server=utf8mb4_unicode_ci
--skip-name-resolve
--explicit_defaults_for_timestamp=1
--max_connections=1000
--innodb_buffer_pool_size=256M
--innodb_log_buffer_size=16M
--innodb_log_file_size=256M
--innodb_write_io_threads=8
--innodb_read_io_threads=8
--innodb_thread_concurrency=0
--innodb_flush_log_at_trx_commit=2
--innodb_flush_method=O_DIRECT
--innodb_file_per_table=1
--bind-address=0.0.0.0
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p${BLUEPRINT_DB_ROOT_PASSWORD}", "--connect-timeout=5"]
interval: 10s
timeout: 5s
retries: 5
start_period: 180s
networks:
- blueprint
phpmyadmin:
image: phpmyadmin:latest
restart: unless-stopped
ports:
- "8081:80"
environment:
PMA_HOST: mysql_bp
PMA_PORT: 3306
MYSQL_ROOT_PASSWORD: ${BLUEPRINT_DB_ROOT_PASSWORD}
depends_on:
mysql_bp:
condition: service_healthy
networks:
- blueprint
volumes:
mysql_volume_bp:
networks:
blueprint:

155
docker-compose.prod.yml Normal file
View File

@ -0,0 +1,155 @@
services:
app:
build:
context: .
dockerfile: Dockerfile.prod
restart: unless-stopped
ports:
- "8083-8085:${PORT}"
environment:
APP_ENV: ${APP_ENV}
PORT: ${PORT}
GIN_MODE: release
BLUEPRINT_DB_HOST: mysql_bp
BLUEPRINT_DB_PORT: ${BLUEPRINT_DB_PORT}
BLUEPRINT_DB_DATABASE: ${BLUEPRINT_DB_DATABASE}
BLUEPRINT_DB_USERNAME: root
BLUEPRINT_DB_PASSWORD: ${BLUEPRINT_DB_ROOT_PASSWORD}
BLUEPRINT_DB_MAX_IDLE_CONNS: ${BLUEPRINT_DB_MAX_IDLE_CONNS}
BLUEPRINT_DB_MAX_OPEN_CONNS: ${BLUEPRINT_DB_MAX_OPEN_CONNS}
BLUEPRINT_DB_CONN_MAX_LIFETIME: ${BLUEPRINT_DB_CONN_MAX_LIFETIME}
LOGGER_LOG_LEVEL: ${LOGGER_LOG_LEVEL}
LOGGER_FILE_LOG_NAME: ${LOGGER_FILE_LOG_NAME}
LOGGER_MAX_SIZE: ${LOGGER_MAX_SIZE}
LOGGER_MAX_BACKUPS: ${LOGGER_MAX_BACKUPS}
LOGGER_MAX_AGE: ${LOGGER_MAX_AGE}
LOGGER_COMPRESS: ${LOGGER_COMPRESS}
depends_on:
mysql_bp:
condition: service_healthy
networks:
- blueprint
deploy:
replicas: 3
update_config:
parallelism: 1
delay: 10s
restart_policy:
condition: on-failure
healthcheck:
test: ["CMD", "wget", "--spider", "http://localhost:${PORT}/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
mysql_bp:
image: mysql:8.0
restart: unless-stopped
environment:
MYSQL_DATABASE: ${BLUEPRINT_DB_DATABASE}
MYSQL_ROOT_PASSWORD: ${BLUEPRINT_DB_ROOT_PASSWORD}
MYSQL_ALLOW_EMPTY_PASSWORD: "no"
ports:
- "3306:3306"
volumes:
- mysql_volume_bp:/var/lib/mysql
- mysql_backup:/backup
command: >
--default-authentication-plugin=mysql_native_password
--character-set-server=utf8mb4
--collation-server=utf8mb4_unicode_ci
--skip-name-resolve
--explicit_defaults_for_timestamp=1
--max_connections=1000
--innodb_buffer_pool_size=256M
--innodb_log_buffer_size=16M
--innodb_log_file_size=256M
--innodb_write_io_threads=8
--innodb_read_io_threads=8
--innodb_thread_concurrency=0
--innodb_flush_log_at_trx_commit=2
--innodb_flush_method=O_DIRECT
--innodb_file_per_table=1
--bind-address=0.0.0.0
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p${BLUEPRINT_DB_ROOT_PASSWORD}", "--connect-timeout=5"]
interval: 10s
timeout: 5s
retries: 5
start_period: 180s
networks:
- blueprint
nginx:
image: nginx:alpine
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d
- ./nginx/ssl:/etc/nginx/ssl
depends_on:
- app
networks:
- blueprint
prometheus:
image: prom/prometheus:latest
restart: unless-stopped
ports:
- "9090:9090"
volumes:
- ./prometheus:/etc/prometheus
- prometheus_data:/prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
- '--web.console.libraries=/usr/share/prometheus/console_libraries'
- '--web.console.templates=/usr/share/prometheus/consoles'
networks:
- blueprint
grafana:
image: grafana/grafana:latest
restart: unless-stopped
ports:
- "3000:3000"
volumes:
- grafana_data:/var/lib/grafana
environment:
- GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_ADMIN_PASSWORD}
depends_on:
- prometheus
networks:
- blueprint
mysql-backup:
image: mysql:8.0
volumes:
- mysql_backup:/backup
environment:
MYSQL_HOST: mysql_bp
MYSQL_USER: blueprint_user
MYSQL_PASSWORD: ${BLUEPRINT_DB_PASSWORD}
MYSQL_DATABASE: ${BLUEPRINT_DB_DATABASE}
entrypoint: |
/bin/sh -c '
while true; do
mysqldump -h$$MYSQL_HOST -u$$MYSQL_USER -p$$MYSQL_PASSWORD $$MYSQL_DATABASE > /backup/backup-$$(date +%Y%m%d-%H%M%S).sql;
find /backup -type f -mtime +7 -delete;
sleep 86400;
done
'
networks:
- blueprint
networks:
blueprint:
volumes:
mysql_volume_bp:
mysql_backup:
prometheus_data:
grafana_data:

44
global/global.go Normal file
View File

@ -0,0 +1,44 @@
package global
import (
"go.uber.org/zap"
"gorm.io/gorm"
)
var (
Config Configuration
Logger *zap.Logger
Mdb *gorm.DB
)
type Configuration struct {
Server ServerConfig
Database DatabaseConfig
Logger LoggerConfig
}
type ServerConfig struct {
Port string
AppEnv string
}
type DatabaseConfig struct {
Host string
Port string
Database string
Username string
Password string
RootPassword string
MaxIdleConns int
MaxOpenConns int
ConnMaxLifetime int
}
type LoggerConfig struct {
LogLevel string
FileLogName string
MaxSize int
MaxBackups int
MaxAge int
Compress bool
}

52
go.mod Normal file
View File

@ -0,0 +1,52 @@
module github.com/dungnt11/todoms_golang
go 1.23.3
require go.uber.org/zap v1.27.0
require (
filippo.io/edwards25519 v1.1.0 // indirect
github.com/bytedance/sonic v1.12.9 // indirect
github.com/bytedance/sonic/loader v0.2.3 // indirect
github.com/cloudwego/base64x v0.1.5 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect
github.com/fatih/color v1.18.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
github.com/gin-contrib/sse v1.0.0 // indirect
github.com/gin-gonic/gin v1.10.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.25.0 // indirect
github.com/go-sql-driver/mysql v1.9.0 // indirect
github.com/goccy/go-json v0.10.5 // indirect
github.com/google/subcommands v1.2.0 // indirect
github.com/google/wire v0.6.0 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/joho/godotenv v1.5.1 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-colorable v0.1.13 // 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/natefinch/lumberjack v2.0.0+incompatible // indirect
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
go.uber.org/multierr v1.10.0 // indirect
golang.org/x/arch v0.14.0 // indirect
golang.org/x/crypto v0.35.0 // indirect
golang.org/x/mod v0.17.0 // indirect
golang.org/x/net v0.35.0 // indirect
golang.org/x/sync v0.11.0 // indirect
golang.org/x/sys v0.30.0 // indirect
golang.org/x/text v0.22.0 // indirect
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
google.golang.org/protobuf v1.36.5 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
gorm.io/driver/mysql v1.5.7 // indirect
gorm.io/gorm v1.25.12 // indirect
)

168
go.sum Normal file
View File

@ -0,0 +1,168 @@
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/bytedance/sonic v1.12.9 h1:Od1BvK55NnewtGaJsTDeAOSnLVO2BTSLOe0+ooKokmQ=
github.com/bytedance/sonic v1.12.9/go.mod h1:uVvFidNmlt9+wa31S1urfwwthTWteBgG0hWuoKAXTx8=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/bytedance/sonic/loader v0.2.3 h1:yctD0Q3v2NOGfSWPLPvG2ggA2kV6TS6s4wioyEqssH0=
github.com/bytedance/sonic/loader v0.2.3/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
github.com/gin-contrib/sse v1.0.0 h1:y3bT1mUWUxDpW4JLQg/HnTqV4rozuW4tC9eFKTxYI9E=
github.com/gin-contrib/sse v1.0.0/go.mod h1:zNuFdwarAygJBht0NTKiSi3jRf6RbqeILZ9Sp6Slhe0=
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
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.25.0 h1:5Dh7cjvzR7BRZadnsVOzPhWsrwUr0nmsZJxEAnFLNO8=
github.com/go-playground/validator/v10 v10.25.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus=
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/go-sql-driver/mysql v1.9.0 h1:Y0zIbQXhQKmQgTp44Y1dp3wTXcn804QoTptLZT1vtvo=
github.com/go-sql-driver/mysql v1.9.0/go.mod h1:pDetrLJeA3oMujJuvXc8RJoasr589B6A9fwzD3QMrqw=
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/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/subcommands v1.2.0 h1:vWQspBTo2nEqTUFita5/KeEWlUL8kQObDFbub/EN9oE=
github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
github.com/google/wire v0.6.0 h1:HBkoIh4BdSxoyo9PveV8giw7ZsaBOvzWKfcg/6MrVwI=
github.com/google/wire v0.6.0/go.mod h1:F4QhpQ9EDIdJ1Mbop/NZBRB+5yrR6qg3BnctaoUk6NA=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
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.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
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/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
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/natefinch/lumberjack v2.0.0+incompatible h1:4QJd3OLAMgj7ph+yZTuX13Ld4UpgHp07nNdFX7mqFfM=
github.com/natefinch/lumberjack v2.0.0+incompatible/go.mod h1:Wi9p2TTF5DG5oU+6YfsmYQpsTIOm0B1VNzQg9Mw6nPk=
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
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/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.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.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
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/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.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/arch v0.14.0 h1:z9JUEZWr8x4rR0OU6c4/4t6E6jOZ8/QBS2bBYBm4tx4=
golang.org/x/arch v0.14.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
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.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs=
golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
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-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
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.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
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-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.0.0-20220811171246-fbc7d0a398ab/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.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
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/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
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.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.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
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.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/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=
gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo=
gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM=
gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=

View File

@ -0,0 +1,15 @@
package controllers
// Controllers chứa tất cả các controller của ứng dụng
type Controllers struct {
UserController IUserController
ProductController IProductController
}
// NewControllers tạo một instance mới của Controllers
func NewControllers(userController IUserController, productController IProductController) *Controllers {
return &Controllers{
UserController: userController,
ProductController: productController,
}
}

View File

@ -0,0 +1,22 @@
package controllers
import "github.com/gin-gonic/gin"
// IUserController định nghĩa interface cho UserController
type IUserController interface {
Register(c *gin.Context)
Login(c *gin.Context)
GetProfile(c *gin.Context)
UpdateProfile(c *gin.Context)
}
// IProductController định nghĩa interface cho ProductController
type IProductController interface {
CreateProduct(c *gin.Context)
GetProduct(c *gin.Context)
GetAllProducts(c *gin.Context)
UpdateProduct(c *gin.Context)
DeleteProduct(c *gin.Context)
}
// Các interface khác có thể được thêm vào đây khi cần thiết

View File

@ -0,0 +1,125 @@
package controllers
import (
"strconv"
"github.com/dungnt11/todoms_golang/internal/services"
"github.com/gin-gonic/gin"
)
// ProductController triển khai IProductController
type ProductController struct {
productService services.IProductService
}
// NewProductController tạo một instance mới của ProductController
func NewProductController(productService services.IProductService) *ProductController {
return &ProductController{
productService: productService,
}
}
// CreateProduct xử lý tạo sản phẩm mới
func (pc *ProductController) CreateProduct(c *gin.Context) {
// Gọi service để thực hiện logic tạo sản phẩm
result, err := pc.productService.CreateProduct(c)
if err != nil {
c.JSON(400, gin.H{
"error": err.Error(),
})
return
}
c.JSON(201, result)
}
// GetProduct xử lý lấy thông tin sản phẩm theo ID
func (pc *ProductController) GetProduct(c *gin.Context) {
// Lấy ID từ param
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
c.JSON(400, gin.H{
"error": "ID không hợp lệ",
})
return
}
// Gọi service để lấy thông tin sản phẩm
result, err := pc.productService.GetProduct(uint(id))
if err != nil {
c.JSON(404, gin.H{
"error": err.Error(),
})
return
}
c.JSON(200, result)
}
// GetAllProducts xử lý lấy tất cả sản phẩm
func (pc *ProductController) GetAllProducts(c *gin.Context) {
// Gọi service để lấy tất cả sản phẩm
result, err := pc.productService.GetAllProducts()
if err != nil {
c.JSON(400, gin.H{
"error": err.Error(),
})
return
}
c.JSON(200, result)
}
// UpdateProduct xử lý cập nhật thông tin sản phẩm
func (pc *ProductController) UpdateProduct(c *gin.Context) {
// Lấy ID từ param
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
c.JSON(400, gin.H{
"error": "ID không hợp lệ",
})
return
}
// Gọi service để cập nhật thông tin sản phẩm
result, err := pc.productService.UpdateProduct(uint(id), c)
if err != nil {
c.JSON(400, gin.H{
"error": err.Error(),
})
return
}
c.JSON(200, result)
}
// DeleteProduct xử lý xóa sản phẩm
func (pc *ProductController) DeleteProduct(c *gin.Context) {
// Lấy ID từ param
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
c.JSON(400, gin.H{
"error": "ID không hợp lệ",
})
return
}
// Gọi service để xóa sản phẩm
result, err := pc.productService.DeleteProduct(uint(id))
if err != nil {
c.JSON(400, gin.H{
"error": err.Error(),
})
return
}
c.JSON(200, result)
}

View File

@ -0,0 +1,93 @@
package controllers
import (
"github.com/dungnt11/todoms_golang/internal/services"
"github.com/gin-gonic/gin"
)
// UserController triển khai IUserController
type UserController struct {
userService services.IUserService
}
// NewUserController tạo một instance mới của UserController
func NewUserController(userService services.IUserService) *UserController {
return &UserController{
userService: userService,
}
}
// Register xử lý đăng ký người dùng
func (uc *UserController) Register(c *gin.Context) {
// Gọi service để thực hiện logic đăng ký
result, err := uc.userService.Register(c)
if err != nil {
c.JSON(400, gin.H{
"error": err.Error(),
})
return
}
c.JSON(200, result)
}
// Login xử lý đăng nhập
func (uc *UserController) Login(c *gin.Context) {
// Gọi service để thực hiện logic đăng nhập
result, err := uc.userService.Login(c)
if err != nil {
c.JSON(400, gin.H{
"error": err.Error(),
})
return
}
c.JSON(200, result)
}
// GetProfile xử lý lấy thông tin profile
func (uc *UserController) GetProfile(c *gin.Context) {
// Lấy user ID từ token hoặc param (giả định)
userID := uint(1) // Trong thực tế, bạn sẽ lấy từ JWT token hoặc param
// Gọi service để lấy thông tin profile
result, err := uc.userService.GetProfile(userID)
if err != nil {
c.JSON(400, gin.H{
"error": err.Error(),
})
return
}
c.JSON(200, result)
}
// UpdateProfile xử lý cập nhật profile
func (uc *UserController) UpdateProfile(c *gin.Context) {
// Lấy user ID từ token hoặc param (giả định)
userID := uint(1) // Trong thực tế, bạn sẽ lấy từ JWT token hoặc param
// Đọc dữ liệu từ request
var data map[string]interface{}
if err := c.ShouldBindJSON(&data); err != nil {
c.JSON(400, gin.H{
"error": err.Error(),
})
return
}
// Gọi service để cập nhật profile
result, err := uc.userService.UpdateProfile(userID, data)
if err != nil {
c.JSON(400, gin.H{
"error": err.Error(),
})
return
}
c.JSON(200, result)
}

View File

@ -0,0 +1,65 @@
package initialize
import (
"fmt"
"os"
"strings"
"github.com/dungnt11/todoms_golang/global"
"github.com/joho/godotenv"
)
func LoadConfig() error {
err := godotenv.Load()
if err != nil {
return err
}
// Cấu hình server
global.Config.Server.Port = os.Getenv("PORT")
global.Config.Server.AppEnv = os.Getenv("APP_ENV")
// Cấu hình database
global.Config.Database.Host = os.Getenv("BLUEPRINT_DB_HOST")
global.Config.Database.Port = os.Getenv("BLUEPRINT_DB_PORT")
global.Config.Database.Database = os.Getenv("BLUEPRINT_DB_DATABASE")
global.Config.Database.Username = os.Getenv("BLUEPRINT_DB_USERNAME")
global.Config.Database.Password = os.Getenv("BLUEPRINT_DB_PASSWORD")
global.Config.Database.RootPassword = os.Getenv("BLUEPRINT_DB_ROOT_PASSWORD")
// Chuyển đổi các giá trị cấu hình database từ string sang int
if maxIdleConns := os.Getenv("BLUEPRINT_DB_MAX_IDLE_CONNS"); maxIdleConns != "" {
fmt.Sscanf(maxIdleConns, "%d", &global.Config.Database.MaxIdleConns)
}
if maxOpenConns := os.Getenv("BLUEPRINT_DB_MAX_OPEN_CONNS"); maxOpenConns != "" {
fmt.Sscanf(maxOpenConns, "%d", &global.Config.Database.MaxOpenConns)
}
if connMaxLifetime := os.Getenv("BLUEPRINT_DB_CONN_MAX_LIFETIME"); connMaxLifetime != "" {
fmt.Sscanf(connMaxLifetime, "%d", &global.Config.Database.ConnMaxLifetime)
}
// Cấu hình logger
global.Config.Logger.LogLevel = os.Getenv("LOGGER_LOG_LEVEL")
global.Config.Logger.FileLogName = os.Getenv("LOGGER_FILE_LOG_NAME")
// Chuyển đổi từ string sang int và bool
if maxSize := os.Getenv("LOGGER_MAX_SIZE"); maxSize != "" {
fmt.Sscanf(maxSize, "%d", &global.Config.Logger.MaxSize)
}
if maxBackups := os.Getenv("LOGGER_MAX_BACKUPS"); maxBackups != "" {
fmt.Sscanf(maxBackups, "%d", &global.Config.Logger.MaxBackups)
}
if maxAge := os.Getenv("LOGGER_MAX_AGE"); maxAge != "" {
fmt.Sscanf(maxAge, "%d", &global.Config.Logger.MaxAge)
}
if compress := os.Getenv("LOGGER_COMPRESS"); compress != "" {
global.Config.Logger.Compress = strings.ToLower(compress) == "true"
}
return nil
}

View File

@ -0,0 +1,103 @@
package initialize
import (
"os"
"github.com/dungnt11/todoms_golang/global"
"github.com/natefinch/lumberjack"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
// InitLogger khởi tạo logger
func InitLogger() *zap.Logger {
// Đảm bảo thư mục logs tồn tại
ensureLogDir(global.Config.Logger.FileLogName)
// Khởi tạo logger
return newLogger(global.Config.Logger)
}
// ensureLogDir đảm bảo thư mục logs tồn tại
func ensureLogDir(logPath string) {
// Tìm vị trí thư mục cuối cùng trong đường dẫn
lastSlash := 0
for i := len(logPath) - 1; i >= 0; i-- {
if logPath[i] == '/' {
lastSlash = i
break
}
}
// Nếu không có thư mục, không cần tạo
if lastSlash == 0 {
return
}
// Tạo thư mục nếu chưa tồn tại
dirPath := logPath[:lastSlash]
if _, err := os.Stat(dirPath); os.IsNotExist(err) {
os.MkdirAll(dirPath, 0755)
}
}
// newLogger tạo một logger mới
func newLogger(config global.LoggerConfig) *zap.Logger {
// Xác định level log
var level zapcore.Level
switch config.LogLevel {
case "debug":
level = zapcore.DebugLevel
case "info":
level = zapcore.InfoLevel
case "warn":
level = zapcore.WarnLevel
case "error":
level = zapcore.ErrorLevel
default:
level = zapcore.InfoLevel
}
// Cấu hình encoder
encoder := getEncoder()
// Cấu hình lumberjack để xoay vòng file log
hook := lumberjack.Logger{
Filename: config.FileLogName,
MaxSize: config.MaxSize, // megabytes
MaxBackups: config.MaxBackups,
MaxAge: config.MaxAge, // days
Compress: config.Compress,
}
// Tạo core cho zap
core := zapcore.NewCore(
encoder,
zapcore.NewMultiWriteSyncer(zapcore.AddSync(os.Stdout), zapcore.AddSync(&hook)),
level,
)
// Tạo logger với các tùy chọn
return zap.New(core,
zap.AddCaller(),
zap.AddStacktrace(zapcore.ErrorLevel),
)
}
// getEncoder trả về encoder cho zap
func getEncoder() zapcore.Encoder {
encoderConfig := zap.NewProductionEncoderConfig()
// Cấu hình thời gian
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
encoderConfig.TimeKey = "time"
// Cấu hình level
encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
// Cấu hình caller
encoderConfig.EncodeCaller = zapcore.ShortCallerEncoder
// Trả về encoder JSON
return zapcore.NewJSONEncoder(encoderConfig)
}

View File

@ -0,0 +1,47 @@
package initialize
import (
"fmt"
"time"
"github.com/dungnt11/todoms_golang/global"
"go.uber.org/zap"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
// checkErrorPanic kiểm tra lỗi và gọi panic nếu có lỗi, đồng thời ghi log chi tiết.
func checkErrorPanic(err error, errString string) {
if err != nil {
global.Logger.Error(errString, zap.Error(err))
panic(err)
}
}
// InitMysql khởi tạo kết nối đến cơ sở dữ liệu MySQL và trả về đối tượng *gorm.DB.
func InitMysql() *gorm.DB {
m := global.Config.Database
dsn := "%s:%s@tcp(%s:%v)/%s?charset=utf8mb4&parseTime=True&loc=Local"
s := fmt.Sprintf(dsn, m.Username, m.Password, m.Host, m.Port, m.Database)
db, err := gorm.Open(mysql.Open(s), &gorm.Config{
SkipDefaultTransaction: false,
})
checkErrorPanic(err, "InitMysql initialization error")
global.Logger.Info("Initializing MySQL Successfully")
setPool(db)
return db
}
// setPool thiết lập các thông số cho connection pool của cơ sở dữ liệu.
func setPool(db *gorm.DB) {
m := global.Config.Database
sqlDb, err := db.DB()
if err != nil {
global.Logger.Error("Failed to get sql.DB from gorm.DB", zap.Error(err))
return
}
sqlDb.SetMaxIdleConns(m.MaxIdleConns)
sqlDb.SetMaxOpenConns(m.MaxOpenConns)
sqlDb.SetConnMaxLifetime(time.Duration(m.ConnMaxLifetime) * time.Second)
}

View File

@ -0,0 +1,93 @@
package initialize
import (
"context"
"fmt"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"github.com/dungnt11/todoms_golang/global"
"github.com/dungnt11/todoms_golang/internal/routers"
"github.com/dungnt11/todoms_golang/internal/wire"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
)
// Run khởi động ứng dụng
func Run() {
// Tải cấu hình từ file .env
if err := LoadConfig(); err != nil {
fmt.Printf("Không thể tải cấu hình: %s\n", err.Error())
return
}
// Khởi tạo logger
global.Logger = InitLogger()
if global.Logger == nil {
fmt.Println("Khởi tạo logger thất bại")
return
}
// Khởi tạo database
global.Mdb = InitMysql()
if global.Mdb == nil {
global.Logger.Error("Khởi tạo MySQL thất bại")
return
}
// Khởi tạo router
r := provideRouter()
// Khởi tạo controllers thông qua wire
controllers, err := wire.InitializeControllers()
if err != nil {
global.Logger.Error("Khởi tạo controllers thất bại", zap.Error(err))
return
}
// Khởi tạo routers
routers.InitRouters(r, controllers)
// Khởi động server
port := global.Config.Server.Port
server := &http.Server{
Addr: fmt.Sprintf(":%s", port),
Handler: r,
}
// Khởi động server trong goroutine riêng
go func() {
global.Logger.Info(fmt.Sprintf("Server đang chạy trên cổng %s", port))
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
global.Logger.Error("Server gặp lỗi", zap.Error(err))
}
}()
// Graceful shutdown
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
global.Logger.Info("Đang tắt server...")
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := server.Shutdown(ctx); err != nil {
global.Logger.Error("Server shutdown gặp lỗi", zap.Error(err))
}
global.Logger.Info("Server đã tắt thành công.")
}
func provideRouter() *gin.Engine {
if global.Config.Server.AppEnv == "local" {
gin.SetMode(gin.DebugMode)
gin.ForceConsoleColor()
return gin.Default()
} else {
gin.SetMode(gin.ReleaseMode)
return gin.New()
}
}

View File

@ -0,0 +1,16 @@
package models
import (
"time"
)
// Product đại diện cho thông tin sản phẩm
type Product struct {
ID uint `gorm:"primaryKey" json:"id"`
Name string `gorm:"size:255;not null" json:"name"`
Description string `gorm:"type:text" json:"description"`
Price float64 `gorm:"not null" json:"price"`
Quantity int `gorm:"not null" json:"quantity"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}

18
internal/models/user.go Normal file
View File

@ -0,0 +1,18 @@
package models
import (
"time"
"gorm.io/gorm"
)
// User đại diện cho người dùng trong hệ thống
type User struct {
ID uint `json:"id" gorm:"primaryKey"`
Username string `json:"username" gorm:"uniqueIndex;not null"`
Password string `json:"-" gorm:"not null"` // Không trả về password trong JSON
Name string `json:"name"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `json:"-" gorm:"index"`
}

View File

@ -0,0 +1,23 @@
package repositories
import "github.com/dungnt11/todoms_golang/internal/models"
// IUserRepository định nghĩa interface cho UserRepository
type IUserRepository interface {
Create(user *models.User) error
FindByID(id uint) (*models.User, error)
FindByUsername(username string) (*models.User, error)
Update(user *models.User) error
Delete(id uint) error
}
// IProductRepository định nghĩa interface cho ProductRepository
type IProductRepository interface {
Create(product *models.Product) error
FindByID(id uint) (*models.Product, error)
FindAll() ([]*models.Product, error)
Update(product *models.Product) error
Delete(id uint) error
}
// Các interface khác có thể được thêm vào đây khi cần thiết

View File

@ -0,0 +1,53 @@
package repositories
import (
"github.com/dungnt11/todoms_golang/internal/models"
"gorm.io/gorm"
)
// ProductRepository triển khai IProductRepository
type ProductRepository struct {
db *gorm.DB
}
// NewProductRepository tạo một instance mới của ProductRepository
func NewProductRepository(db *gorm.DB) *ProductRepository {
return &ProductRepository{
db: db,
}
}
// Create tạo một sản phẩm mới
func (r *ProductRepository) Create(product *models.Product) error {
return r.db.Create(product).Error
}
// FindByID tìm sản phẩm theo ID
func (r *ProductRepository) FindByID(id uint) (*models.Product, error) {
var product models.Product
err := r.db.First(&product, id).Error
if err != nil {
return nil, err
}
return &product, nil
}
// FindAll lấy tất cả sản phẩm
func (r *ProductRepository) FindAll() ([]*models.Product, error) {
var products []*models.Product
err := r.db.Find(&products).Error
if err != nil {
return nil, err
}
return products, nil
}
// Update cập nhật thông tin sản phẩm
func (r *ProductRepository) Update(product *models.Product) error {
return r.db.Save(product).Error
}
// Delete xóa sản phẩm theo ID
func (r *ProductRepository) Delete(id uint) error {
return r.db.Delete(&models.Product{}, id).Error
}

View File

@ -0,0 +1,53 @@
package repositories
import (
"github.com/dungnt11/todoms_golang/internal/models"
"gorm.io/gorm"
)
// UserRepository triển khai IUserRepository
type UserRepository struct {
db *gorm.DB
}
// NewUserRepository tạo một instance mới của UserRepository
func NewUserRepository(db *gorm.DB) *UserRepository {
return &UserRepository{
db: db,
}
}
// Create tạo một user mới trong database
func (ur *UserRepository) Create(user *models.User) error {
return ur.db.Create(user).Error
}
// FindByID tìm user theo ID
func (ur *UserRepository) FindByID(id uint) (*models.User, error) {
var user models.User
err := ur.db.First(&user, id).Error
if err != nil {
return nil, err
}
return &user, nil
}
// FindByUsername tìm user theo username
func (ur *UserRepository) FindByUsername(username string) (*models.User, error) {
var user models.User
err := ur.db.Where("username = ?", username).First(&user).Error
if err != nil {
return nil, err
}
return &user, nil
}
// Update cập nhật thông tin user
func (ur *UserRepository) Update(user *models.User) error {
return ur.db.Save(user).Error
}
// Delete xóa user theo ID
func (ur *UserRepository) Delete(id uint) error {
return ur.db.Delete(&models.User{}, id).Error
}

View File

@ -0,0 +1,18 @@
package product
import (
"github.com/dungnt11/todoms_golang/internal/controllers"
"github.com/gin-gonic/gin"
)
// InitProductRoutes khởi tạo các routes cho product
func InitProductRoutes(router *gin.RouterGroup, productController controllers.IProductController) {
productRoutes := router.Group("/products")
{
productRoutes.POST("", productController.CreateProduct)
productRoutes.GET("", productController.GetAllProducts)
productRoutes.GET("/:id", productController.GetProduct)
productRoutes.PUT("/:id", productController.UpdateProduct)
productRoutes.DELETE("/:id", productController.DeleteProduct)
}
}

View File

@ -0,0 +1,20 @@
package routers
import (
"github.com/dungnt11/todoms_golang/internal/controllers"
"github.com/dungnt11/todoms_golang/internal/routers/product"
"github.com/dungnt11/todoms_golang/internal/routers/user"
"github.com/gin-gonic/gin"
)
// InitRouters khởi tạo tất cả các routers
func InitRouters(r *gin.Engine, controllers *controllers.Controllers) {
// API v1
apiV1 := r.Group("/api/v1")
// Khởi tạo các routes
user.InitUserRoutes(apiV1, controllers.UserController)
product.InitProductRoutes(apiV1, controllers.ProductController)
// Các routes khác có thể được thêm vào đây
}

View File

@ -0,0 +1,17 @@
package user
import (
"github.com/dungnt11/todoms_golang/internal/controllers"
"github.com/gin-gonic/gin"
)
// InitUserRoutes khởi tạo các routes liên quan đến user
func InitUserRoutes(router *gin.RouterGroup, userController controllers.IUserController) {
userGroup := router.Group("/users")
{
userGroup.POST("/register", userController.Register)
userGroup.POST("/login", userController.Login)
userGroup.GET("/profile", userController.GetProfile)
userGroup.PUT("/profile", userController.UpdateProfile)
}
}

View File

@ -0,0 +1,22 @@
package services
import "github.com/gin-gonic/gin"
// IUserService định nghĩa interface cho UserService
type IUserService interface {
Register(c *gin.Context) (map[string]interface{}, error)
Login(c *gin.Context) (map[string]interface{}, error)
GetProfile(userID uint) (map[string]interface{}, error)
UpdateProfile(userID uint, data map[string]interface{}) (map[string]interface{}, error)
}
// IProductService định nghĩa interface cho ProductService
type IProductService interface {
CreateProduct(c *gin.Context) (map[string]interface{}, error)
GetProduct(id uint) (map[string]interface{}, error)
GetAllProducts() (map[string]interface{}, error)
UpdateProduct(id uint, c *gin.Context) (map[string]interface{}, error)
DeleteProduct(id uint) (map[string]interface{}, error)
}
// Các interface khác có thể được thêm vào đây khi cần thiết

View File

@ -0,0 +1,123 @@
package services
import (
"errors"
"github.com/dungnt11/todoms_golang/internal/models"
"github.com/dungnt11/todoms_golang/internal/repositories"
"github.com/gin-gonic/gin"
)
// ProductService triển khai IProductService
type ProductService struct {
productRepo repositories.IProductRepository
}
// NewProductService tạo một instance mới của ProductService
func NewProductService(productRepo repositories.IProductRepository) *ProductService {
return &ProductService{
productRepo: productRepo,
}
}
// CreateProduct tạo một sản phẩm mới
func (s *ProductService) CreateProduct(c *gin.Context) (map[string]interface{}, error) {
var product models.Product
if err := c.ShouldBindJSON(&product); err != nil {
return nil, err
}
if product.Name == "" {
return nil, errors.New("tên sản phẩm không được để trống")
}
if product.Price <= 0 {
return nil, errors.New("giá sản phẩm phải lớn hơn 0")
}
if err := s.productRepo.Create(&product); err != nil {
return nil, err
}
return map[string]interface{}{
"message": "tạo sản phẩm thành công",
"product": product,
}, nil
}
// GetProduct lấy thông tin sản phẩm theo ID
func (s *ProductService) GetProduct(id uint) (map[string]interface{}, error) {
product, err := s.productRepo.FindByID(id)
if err != nil {
return nil, errors.New("không tìm thấy sản phẩm")
}
return map[string]interface{}{
"product": product,
}, nil
}
// GetAllProducts lấy tất cả sản phẩm
func (s *ProductService) GetAllProducts() (map[string]interface{}, error) {
products, err := s.productRepo.FindAll()
if err != nil {
return nil, err
}
return map[string]interface{}{
"products": products,
}, nil
}
// UpdateProduct cập nhật thông tin sản phẩm
func (s *ProductService) UpdateProduct(id uint, c *gin.Context) (map[string]interface{}, error) {
product, err := s.productRepo.FindByID(id)
if err != nil {
return nil, errors.New("không tìm thấy sản phẩm")
}
var updatedProduct models.Product
if err := c.ShouldBindJSON(&updatedProduct); err != nil {
return nil, err
}
// Cập nhật thông tin
if updatedProduct.Name != "" {
product.Name = updatedProduct.Name
}
if updatedProduct.Description != "" {
product.Description = updatedProduct.Description
}
if updatedProduct.Price > 0 {
product.Price = updatedProduct.Price
}
if updatedProduct.Quantity >= 0 {
product.Quantity = updatedProduct.Quantity
}
if err := s.productRepo.Update(product); err != nil {
return nil, err
}
return map[string]interface{}{
"message": "cập nhật sản phẩm thành công",
"product": product,
}, nil
}
// DeleteProduct xóa sản phẩm theo ID
func (s *ProductService) DeleteProduct(id uint) (map[string]interface{}, error) {
// Kiểm tra sản phẩm tồn tại
_, err := s.productRepo.FindByID(id)
if err != nil {
return nil, errors.New("không tìm thấy sản phẩm")
}
if err := s.productRepo.Delete(id); err != nil {
return nil, err
}
return map[string]interface{}{
"message": "xóa sản phẩm thành công",
}, nil
}

View File

@ -0,0 +1,118 @@
package services
import (
"errors"
"github.com/dungnt11/todoms_golang/internal/models"
"github.com/dungnt11/todoms_golang/internal/repositories"
"github.com/gin-gonic/gin"
)
// UserService triển khai IUserService
type UserService struct {
userRepo repositories.IUserRepository
}
// NewUserService tạo một instance mới của UserService
func NewUserService(userRepo repositories.IUserRepository) *UserService {
return &UserService{
userRepo: userRepo,
}
}
// Register xử lý logic đăng ký người dùng
func (us *UserService) Register(c *gin.Context) (map[string]interface{}, error) {
// Đọc dữ liệu từ request
var user models.User
if err := c.ShouldBindJSON(&user); err != nil {
return nil, err
}
// Kiểm tra username đã tồn tại chưa
existingUser, _ := us.userRepo.FindByUsername(user.Username)
if existingUser != nil {
return nil, errors.New("username đã được sử dụng")
}
// Lưu vào database thông qua repository
err := us.userRepo.Create(&user)
if err != nil {
return nil, err
}
// Trả về kết quả
return map[string]interface{}{
"message": "Đăng ký thành công",
"user_id": user.ID,
}, nil
}
// Login xử lý logic đăng nhập
func (us *UserService) Login(c *gin.Context) (map[string]interface{}, error) {
// Đọc dữ liệu từ request
var loginData struct {
Username string `json:"username"`
Password string `json:"password"`
}
if err := c.ShouldBindJSON(&loginData); err != nil {
return nil, err
}
// Tìm user theo username
user, err := us.userRepo.FindByUsername(loginData.Username)
if err != nil {
return nil, errors.New("username hoặc mật khẩu không đúng")
}
// Kiểm tra mật khẩu (giả định)
if user.Password != loginData.Password {
return nil, errors.New("username hoặc mật khẩu không đúng")
}
// Trả về kết quả
return map[string]interface{}{
"message": "Đăng nhập thành công",
"user_id": user.ID,
"token": "jwt_token_here", // Trong thực tế, bạn sẽ tạo JWT token
}, nil
}
// GetProfile lấy thông tin profile của người dùng
func (us *UserService) GetProfile(userID uint) (map[string]interface{}, error) {
// Tìm user theo ID
user, err := us.userRepo.FindByID(userID)
if err != nil {
return nil, errors.New("không tìm thấy người dùng")
}
// Trả về kết quả
return map[string]interface{}{
"user": user,
}, nil
}
// UpdateProfile cập nhật thông tin profile của người dùng
func (us *UserService) UpdateProfile(userID uint, data map[string]interface{}) (map[string]interface{}, error) {
// Tìm user theo ID
user, err := us.userRepo.FindByID(userID)
if err != nil {
return nil, errors.New("không tìm thấy người dùng")
}
// Cập nhật thông tin (giả định)
if name, ok := data["name"].(string); ok {
user.Name = name
}
// Lưu vào database
err = us.userRepo.Update(user)
if err != nil {
return nil, err
}
// Trả về kết quả
return map[string]interface{}{
"message": "Cập nhật thành công",
"user": user,
}, nil
}

17
internal/wire/injector.go Normal file
View File

@ -0,0 +1,17 @@
//go:build wireinject
// +build wireinject
package wire
import (
"github.com/dungnt11/todoms_golang/internal/controllers"
"github.com/google/wire"
)
// InitializeControllers khởi tạo tất cả các controllers
func InitializeControllers() (*controllers.Controllers, error) {
wire.Build(
AppSet,
)
return nil, nil
}

49
internal/wire/wire.go Normal file
View File

@ -0,0 +1,49 @@
// Package wire chứa các định nghĩa cho dependency injection sử dụng Google Wire
package wire
import (
"github.com/dungnt11/todoms_golang/global"
"github.com/dungnt11/todoms_golang/internal/controllers"
"github.com/dungnt11/todoms_golang/internal/repositories"
"github.com/dungnt11/todoms_golang/internal/services"
"github.com/google/wire"
"gorm.io/gorm"
)
// RepositorySet là tập hợp các provider cho repositories
var RepositorySet = wire.NewSet(
repositories.NewUserRepository,
wire.Bind(new(repositories.IUserRepository), new(*repositories.UserRepository)),
repositories.NewProductRepository,
wire.Bind(new(repositories.IProductRepository), new(*repositories.ProductRepository)),
)
// ServiceSet là tập hợp các provider cho services
var ServiceSet = wire.NewSet(
services.NewUserService,
wire.Bind(new(services.IUserService), new(*services.UserService)),
services.NewProductService,
wire.Bind(new(services.IProductService), new(*services.ProductService)),
)
// ControllerSet là tập hợp các provider cho controllers
var ControllerSet = wire.NewSet(
controllers.NewUserController,
wire.Bind(new(controllers.IUserController), new(*controllers.UserController)),
controllers.NewProductController,
wire.Bind(new(controllers.IProductController), new(*controllers.ProductController)),
controllers.NewControllers,
)
// ProvideDB cung cấp instance của *gorm.DB
func ProvideDB() *gorm.DB {
return global.Mdb
}
// AppSet là tập hợp tất cả các provider
var AppSet = wire.NewSet(
RepositorySet,
ServiceSet,
ControllerSet,
ProvideDB,
)

28
internal/wire/wire_gen.go Normal file
View File

@ -0,0 +1,28 @@
// Code generated by Wire. DO NOT EDIT.
//go:generate go run -mod=mod github.com/google/wire/cmd/wire
//go:build !wireinject
// +build !wireinject
package wire
import (
"github.com/dungnt11/todoms_golang/internal/controllers"
"github.com/dungnt11/todoms_golang/internal/repositories"
"github.com/dungnt11/todoms_golang/internal/services"
)
// Injectors from injector.go:
// InitializeControllers khởi tạo tất cả các controllers
func InitializeControllers() (*controllers.Controllers, error) {
db := ProvideDB()
userRepository := repositories.NewUserRepository(db)
userService := services.NewUserService(userRepository)
userController := controllers.NewUserController(userService)
productRepository := repositories.NewProductRepository(db)
productService := services.NewProductService(productRepository)
productController := controllers.NewProductController(productService)
controllersControllers := controllers.NewControllers(userController, productController)
return controllersControllers, nil
}

30
nginx/conf.d/default.conf Normal file
View File

@ -0,0 +1,30 @@
upstream app_servers {
server app:8083;
server app:8084;
server app:8085;
}
server {
listen 80;
server_name localhost;
location / {
proxy_pass http://app_servers;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
proxy_buffering on;
proxy_buffer_size 128k;
proxy_buffers 4 256k;
proxy_busy_buffers_size 256k;
}
location /health {
access_log off;
return 200 'healthy\n';
}
}

25
prometheus/prometheus.yml Normal file
View File

@ -0,0 +1,25 @@
global:
scrape_interval: 15s
evaluation_interval: 15s
scrape_timeout: 10s
scrape_configs:
- job_name: 'app'
static_configs:
- targets: ['app:8080']
metrics_path: '/metrics'
scrape_interval: 15s
scrape_timeout: 10s
- job_name: 'mysql'
static_configs:
- targets: ['mysql_bp:3306']
metrics_path: '/metrics'
scrape_interval: 15s
scrape_timeout: 10s
- job_name: 'prometheus'
static_configs:
- targets: ['localhost:9090']
scrape_interval: 15s
scrape_timeout: 10s

21
scripts/backup.sh Normal file
View File

@ -0,0 +1,21 @@
#!/bin/bash
# Thông tin kết nối
DB_HOST="mysql_bp"
DB_USER="root"
DB_PASS="root"
DB_NAME="blueprint"
BACKUP_DIR="/backup"
DATE=$(date +%Y%m%d_%H%M%S)
# Tạo backup
echo "Starting backup at $DATE"
mysqldump -h $DB_HOST -u $DB_USER -p$DB_PASS $DB_NAME > "$BACKUP_DIR/backup_$DATE.sql"
# Nén backup
gzip "$BACKUP_DIR/backup_$DATE.sql"
# Xóa backup cũ (giữ 7 ngày)
find $BACKUP_DIR -name "backup_*.sql.gz" -mtime +7 -delete
echo "Backup completed"