init project

This commit is contained in:
koh 2025-03-02 14:59:34 +07:00
commit e2a219cacd
No known key found for this signature in database
35 changed files with 2269 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

34
.gitignore vendored Normal file
View File

@ -0,0 +1,34 @@
# 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

18
Dockerfile Normal file
View File

@ -0,0 +1,18 @@
FROM golang:1.23-alpine AS build
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN go build -o main cmd/api/main.go
FROM alpine:3.20.1 AS prod
WORKDIR /app
COPY --from=build /app/main /app/main
EXPOSE ${PORT}
CMD ["./main"]

29
Dockerfile.dev Normal file
View File

@ -0,0 +1,29 @@
FROM golang:1.23-alpine
WORKDIR /app
# Cài đặt các công cụ phát triển
RUN apk add --no-cache git make
# Cài đặt Air cho hot-reload
RUN go install github.com/air-verse/air@latest
# 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"]

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

410
README.md Normal file
View File

@ -0,0 +1,410 @@
# Senflow Server
Ứng dụng backend được viết bằng Go, sử dụng MySQL làm cơ sở dữ liệu và Google Wire cho dependency injection.
## Yêu cầu hệ thống
- Go 1.21 hoặc cao hơn
- Docker và Docker Compose
- Make
## 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
```
### Phương pháp 2: Triển khai thủ công
```bash
# Build ứng dụng
make build
# Hoặc build với Go trực tiếp
go build -o main cmd/api/main.go
# Chạy ứng dụng
./main
```
## Các lệnh Make hữu ích
```bash
# Build ứng dụng
make build
# Chạy ứng dụng
make run
# Chạy tests
make test
# Chạy integration tests
make itest
# Dọn dẹp binary
make clean
# Hot-reload trong quá trình phát triển
make watch
# Khởi động container MySQL
make docker-run
# Dừng container MySQL
make docker-down
```
## Biến môi trường
Sao chép file `.env.example` thành `.env` và điều chỉnh các giá trị theo nhu cầu của bạn:
```
# Cấu hình ứng dụng
APP_ENV=development # development hoặc production
PORT=8080
# Cấu hình database
BLUEPRINT_DB_HOST=mysql_bp
BLUEPRINT_DB_PORT=3306
BLUEPRINT_DB_DATABASE=blueprint
BLUEPRINT_DB_USERNAME=user
BLUEPRINT_DB_PASSWORD=password
BLUEPRINT_DB_ROOT_PASSWORD=root_password
BLUEPRINT_DB_MAX_IDLE_CONNS=10
BLUEPRINT_DB_MAX_OPEN_CONNS=100
BLUEPRINT_DB_CONN_MAX_LIFETIME=3600
# Cấu hình logger
LOGGER_LOG_LEVEL=debug
LOGGER_FILE_LOG_NAME=./logs/app.log
LOGGER_MAX_SIZE=10
LOGGER_MAX_BACKUPS=5
LOGGER_MAX_AGE=30
LOGGER_COMPRESS=true
```
## Graceful Shutdown
Ứng dụng hỗ trợ graceful shutdown để đảm bảo tất cả các request đang xử lý được hoàn thành trước khi ứng dụng dừng lại. Khi nhận tín hiệu SIGINT hoặc SIGTERM (ví dụ: khi nhấn Ctrl+C), ứng dụng sẽ:
1. Dừng nhận request mới
2. Đợi các request đang xử lý hoàn thành (tối đa 5 giây)
3. Đóng kết nối đến cơ sở dữ liệu
4. Hiển thị thông báo "Graceful shutdown complete."
## Thêm tính năng mới
Để thêm một tính năng mới vào ứng dụng, bạn cần:
1. Tạo model mới trong thư mục `internal/models`
2. Tạo repository mới trong thư mục `internal/repositories`
3. Tạo service mới trong thư mục `internal/services`
4. Tạo controller mới trong thư mục `internal/controllers`
5. Tạo router mới trong thư mục `internal/routers`
6. Cập nhật các provider trong `internal/wire/wire.go`
7. Cập nhật injector trong `internal/wire/injector.go`
8. Chạy lệnh wire để cập nhật `wire_gen.go`
9. Cập nhật `internal/initialize/run.go` để khởi tạo router mới
## Đóng góp
Vui lòng đảm bảo code của bạn tuân theo các quy tắc đặt tên và cấu trúc dự án đã được mô tả ở trên. Sử dụng `gofmt` để định dạng code trước khi commit.
## Chức năng của các thư mục chính
### cmd
Chứa điểm vào của ứng dụng. Thư mục `api` chứa file `main.go` - nơi khởi động ứng dụng.
### global
Chứa các biến và cấu hình toàn cục được sử dụng trong toàn bộ ứng dụng:
- `global.go`: Định nghĩa các biến toàn cục như DB, Logger
- `config.go`: Định nghĩa cấu trúc cấu hình ứng dụng
### internal
Chứa mã nguồn nội bộ của ứng dụng, được tổ chức theo kiến trúc phân lớp:
#### controllers
Xử lý HTTP request và response, gọi đến services để thực hiện logic nghiệp vụ:
- `interfaces.go`: Định nghĩa các interface cho controllers
- `controllers.go`: Struct chứa tất cả controllers để sử dụng với dependency injection
- Các file controller cụ thể: `user_controller.go`, `product_controller.go`, ...
#### services
Chứa logic nghiệp vụ của ứng dụng:
- `interfaces.go`: Định nghĩa các interface cho services
- Các file service cụ thể: `user_service.go`, `product_service.go`, ...
#### repositories
Tương tác với cơ sở dữ liệu, thực hiện các thao tác CRUD:
- `interfaces.go`: Định nghĩa các interface cho repositories
- Các file repository cụ thể: `user_repository.go`, `product_repository.go`, ...
#### models
Định nghĩa cấu trúc dữ liệu và các phương thức liên quan:
- Các file model cụ thể: `user.go`, `product.go`, ...
#### routers
Định nghĩa các routes của ứng dụng:
- `router_group.go`: Khởi tạo tất cả routes
- Các thư mục route cụ thể: `user/`, `product/`, ...
#### middleware
Chứa các middleware được sử dụng trong ứng dụng như authentication, logging, ...
#### initialize
Khởi tạo các thành phần của ứng dụng:
- `loadconfig.go`: Tải 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`: Khởi động ứng dụng
#### wire
Quản lý dependency injection với Google Wire:
- `wire.go`: Định nghĩa các providers
- `injector.go`: Định nghĩa các injectors
- `wire_gen.go`: File được tạo tự động bởi Wire
### pkg
Chứa các thư viện và tiện ích có thể được sử dụng bởi các ứng dụng khác:
- `utils/`: Các hàm tiện ích
### configs
Chứa các file cấu hình của ứng dụng.
## Kiến trúc ứng dụng
Ứng dụng được thiết kế theo kiến trúc phân lớp:
1. **Controller Layer**: Xử lý HTTP request và response
2. **Service Layer**: Xử lý logic nghiệp vụ
3. **Repository Layer**: Tương tác với cơ sở dữ liệu
4. **Model Layer**: Định nghĩa cấu trúc dữ liệu
Mỗi lớp chỉ giao tiếp với lớp liền kề, giúp giảm sự phụ thuộc và dễ dàng thay đổi implementation mà không ảnh hưởng đến các lớp khác.
## Dependency Injection với Google Wire
Ứng dụng sử dụng Google Wire để quản lý dependency injection. Các thành phần được định nghĩa trong `wire.go` và được kết nối tự động bởi Wire.
Cấu trúc Wire:
- `RepositorySet`: Providers cho repositories
- `ServiceSet`: Providers cho services
- `ControllerSet`: Providers cho controllers
- `AppSet`: Tập hợp tất cả providers
Khi thêm một thành phần mới (ví dụ: Product, Gift), bạn cần:
1. Tạo model, repository, service, controller tương ứng
2. Thêm interface vào các file `interfaces.go`
3. Cập nhật struct `Controllers` trong `controllers.go`
4. Thêm providers vào `wire.go`
5. Chạy lệnh wire để cập nhật `wire_gen.go`
## Môi trường phát triển
### Yêu cầu
- Go 1.16+
- MySQL 8.0+
- Docker và Docker Compose (tùy chọn)
### Cài đặt và chạy
1. Clone repository:
```
git clone https://github.com/dungnt11/senflow_app.git
cd senflow_app
```
2. Cài đặt dependencies:
```
go mod download
```
3. Tạo file .env từ .env.example:
```
cp .env.example .env
```
4. Chạy ứng dụng:
```
go run cmd/api/main.go
```
### Sử dụng Docker
```bash
docker-compose up -d
```
## API Endpoints
### User
- `POST /api/v1/users/register`: Đăng ký người dùng mới
- `POST /api/v1/users/login`: Đăng nhập
- `GET /api/v1/users/profile`: Lấy thông tin profile
- `PUT /api/v1/users/profile`: Cập nhật profile
### Product
- `POST /api/v1/products`: Tạo sản phẩm mới
- `GET /api/v1/products`: Lấy tất cả sản phẩm
- `GET /api/v1/products/:id`: Lấy thông tin sản phẩm theo ID
- `PUT /api/v1/products/:id`: Cập nhật sản phẩm
- `DELETE /api/v1/products/:id`: Xóa sản phẩm
## Quy ước đặt tên
### Files
- Controllers: `<entity>_controller.go`
- Services: `<entity>_service.go`
- Repositories: `<entity>_repository.go`
- Models: `<entity>.go`
- Routers: `<entity>_router.go`
### Interfaces
- Controllers: `I<Entity>Controller`
- Services: `I<Entity>Service`
- Repositories: `I<Entity>Repository`
## Đóng góp
1. Fork repository
2. Tạo branch mới: `git checkout -b feature/your-feature-name`
3. Commit changes: `git commit -m 'Add some feature'`
4. Push to branch: `git push origin feature/your-feature-name`
5. Submit pull request
## License
[MIT](LICENSE)

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

@ -0,0 +1,10 @@
package main
import (
"github.com/dungnt11/senflow_app/internal/initialize"
)
func main() {
// Khởi động ứng dụng
initialize.Run()
}

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

@ -0,0 +1,49 @@
services:
app:
build:
context: .
dockerfile: Dockerfile.dev
restart: unless-stopped
ports:
- ${PORT}:${PORT}
environment:
APP_ENV: local
PORT: ${PORT}
BLUEPRINT_DB_HOST: ${BLUEPRINT_DB_HOST}
BLUEPRINT_DB_PORT: ${BLUEPRINT_DB_PORT}
BLUEPRINT_DB_DATABASE: ${BLUEPRINT_DB_DATABASE}
BLUEPRINT_DB_USERNAME: ${BLUEPRINT_DB_USERNAME}
BLUEPRINT_DB_PASSWORD: ${BLUEPRINT_DB_PASSWORD}
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:latest
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}
ports:
- "${BLUEPRINT_DB_PORT}:3306"
volumes:
- mysql_volume_bp:/var/lib/mysql
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "${BLUEPRINT_DB_HOST}", "-u", "${BLUEPRINT_DB_USERNAME}", "--password=${BLUEPRINT_DB_PASSWORD}"]
interval: 5s
timeout: 5s
retries: 3
start_period: 15s
networks:
- blueprint
volumes:
mysql_volume_bp:
networks:
blueprint:

47
docker-compose.yml Normal file
View File

@ -0,0 +1,47 @@
services:
app:
build:
context: .
dockerfile: Dockerfile
target: prod
restart: unless-stopped
ports:
- ${PORT}:${PORT}
environment:
APP_ENV: ${APP_ENV}
PORT: ${PORT}
BLUEPRINT_DB_HOST: ${BLUEPRINT_DB_HOST}
BLUEPRINT_DB_PORT: ${BLUEPRINT_DB_PORT}
BLUEPRINT_DB_DATABASE: ${BLUEPRINT_DB_DATABASE}
BLUEPRINT_DB_USERNAME: ${BLUEPRINT_DB_USERNAME}
BLUEPRINT_DB_PASSWORD: ${BLUEPRINT_DB_PASSWORD}
depends_on:
mysql_bp:
condition: service_healthy
networks:
- blueprint
mysql_bp:
image: mysql:latest
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}
ports:
- "${BLUEPRINT_DB_PORT}:3306"
volumes:
- mysql_volume_bp:/var/lib/mysql
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "${BLUEPRINT_DB_HOST}", "-u", "${BLUEPRINT_DB_USERNAME}", "--password=${BLUEPRINT_DB_PASSWORD}"]
interval: 5s
timeout: 5s
retries: 3
start_period: 15s
networks:
- blueprint
volumes:
mysql_volume_bp:
networks:
blueprint:

48
global/global.go Normal file
View File

@ -0,0 +1,48 @@
package global
import (
"go.uber.org/zap"
"gorm.io/gorm"
)
var (
Config Configuration
Logger *zap.Logger
Mdb *gorm.DB
)
// Configuration là cấu trúc chứa tất cả cấu hình ứng dụng
type Configuration struct {
Server ServerConfig
Database DatabaseConfig
Logger LoggerConfig
}
// ServerConfig chứa cấu hình liên quan đến server
type ServerConfig struct {
Port string
AppEnv string
}
// DatabaseConfig chứa cấu hình liên quan đến database
type DatabaseConfig struct {
Host string
Port string
Database string
Username string
Password string
RootPassword string
MaxIdleConns int
MaxOpenConns int
ConnMaxLifetime int
}
// LoggerConfig chứa cấu hình liên quan đến logger
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/senflow_app
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/senflow_app/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/senflow_app/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,119 @@
package initialize
import (
"fmt"
"os"
"reflect"
"strings"
"github.com/dungnt11/senflow_app/global"
"github.com/fatih/color"
"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"
}
printConfig()
return nil
}
func printConfig() {
// Tạo các đối tượng màu sắc
titleColor := color.New(color.FgHiCyan, color.Bold)
sectionColor := color.New(color.FgHiYellow, color.Bold)
keyColor := color.New(color.FgHiGreen)
valueColor := color.New(color.FgHiWhite)
fmt.Println() // Thêm dòng trống ở đầu
// In tiêu đề
titleColor.Println("✨✨✨ CẤU HÌNH ỨNG DỤNG ✨✨✨")
fmt.Println()
// Sử dụng reflection để tự động in tất cả các cấu hình
configValue := reflect.ValueOf(global.Config)
configType := configValue.Type()
// Duyệt qua tất cả các trường của cấu hình
for i := 0; i < configValue.NumField(); i++ {
sectionName := configType.Field(i).Name
sectionValue := configValue.Field(i)
sectionType := sectionValue.Type()
// In tên section
sectionColor.Printf("[%s]\n", strings.ToUpper(sectionName))
// Duyệt qua tất cả các trường của section
for j := 0; j < sectionValue.NumField(); j++ {
fieldName := sectionType.Field(j).Name
fieldValue := sectionValue.Field(j).Interface()
// Ẩn mật khẩu
displayValue := fmt.Sprintf("%v", fieldValue)
if strings.Contains(strings.ToLower(fieldName), "password") {
displayValue = "******** (ẩn)"
}
// In tên trường và giá trị
keyColor.Printf(" %-15s: ", fieldName)
valueColor.Printf("%s\n", displayValue)
}
fmt.Println() // Thêm dòng trống giữa các section
}
// In thông báo thành công
titleColor.Println("✅ Cấu hình đã được tải thành công!")
fmt.Println() // Thêm dòng trống ở cuối
}

View File

@ -0,0 +1,103 @@
package initialize
import (
"os"
"github.com/dungnt11/senflow_app/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,46 @@
package initialize
import (
"fmt"
"time"
"github.com/dungnt11/senflow_app/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)
}

82
internal/initialize/run.go Executable file
View File

@ -0,0 +1,82 @@
package initialize
import (
"context"
"fmt"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"github.com/dungnt11/senflow_app/global"
"github.com/dungnt11/senflow_app/internal/routers"
"github.com/dungnt11/senflow_app/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 := gin.Default()
// 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.")
}

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"`
Email string `json:"email" 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/senflow_app/internal/models"
// IUserRepository định nghĩa interface cho UserRepository
type IUserRepository interface {
Create(user *models.User) error
FindByID(id uint) (*models.User, error)
FindByEmail(email 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/senflow_app/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/senflow_app/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
}
// FindByEmail tìm user theo email
func (ur *UserRepository) FindByEmail(email string) (*models.User, error) {
var user models.User
err := ur.db.Where("email = ?", email).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/senflow_app/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/senflow_app/internal/controllers"
"github.com/dungnt11/senflow_app/internal/routers/product"
"github.com/dungnt11/senflow_app/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/senflow_app/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/senflow_app/internal/models"
"github.com/dungnt11/senflow_app/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/senflow_app/internal/models"
"github.com/dungnt11/senflow_app/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 email đã tồn tại chưa
existingUser, _ := us.userRepo.FindByEmail(user.Email)
if existingUser != nil {
return nil, errors.New("email đã đượ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 {
Email string `json:"email"`
Password string `json:"password"`
}
if err := c.ShouldBindJSON(&loginData); err != nil {
return nil, err
}
// Tìm user theo email
user, err := us.userRepo.FindByEmail(loginData.Email)
if err != nil {
return nil, errors.New("email 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("email 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/senflow_app/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
}

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

@ -0,0 +1,62 @@
// Package wire chứa các định nghĩa cho dependency injection sử dụng Google Wire
package wire
import (
"github.com/dungnt11/senflow_app/global"
"github.com/dungnt11/senflow_app/internal/controllers"
"github.com/dungnt11/senflow_app/internal/repositories"
"github.com/dungnt11/senflow_app/internal/services"
"github.com/gin-gonic/gin"
"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,
)
// AppSet là tập hợp tất cả các provider
var AppSet = wire.NewSet(
RepositorySet,
ServiceSet,
ControllerSet,
ProvideDB,
)
// ProvideDB cung cấp instance của *gorm.DB
func ProvideDB() *gorm.DB {
return global.Mdb
}
// ProvideRouter cung cấp instance của *gin.Engine
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()
}
}

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/senflow_app/internal/controllers"
"github.com/dungnt11/senflow_app/internal/repositories"
"github.com/dungnt11/senflow_app/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
}

101
logs/app.log Normal file
View File

@ -0,0 +1,101 @@
{"level":"INFO","time":"2025-03-02T06:19:20.738Z","caller":"initialize/mysql.go:30","msg":"Initializing MySQL Successfully"}
{"level":"INFO","time":"2025-03-02T06:52:19.221Z","caller":"initialize/mysql.go:30","msg":"Initializing MySQL Successfully"}
{"level":"INFO","time":"2025-03-02T06:52:22.395Z","caller":"initialize/mysql.go:30","msg":"Initializing MySQL Successfully"}
{"level":"INFO","time":"2025-03-02T06:52:33.883Z","caller":"initialize/mysql.go:30","msg":"Initializing MySQL Successfully"}
{"level":"INFO","time":"2025-03-02T06:52:40.280Z","caller":"initialize/mysql.go:30","msg":"Initializing MySQL Successfully"}
{"level":"INFO","time":"2025-03-02T06:52:44.041Z","caller":"initialize/mysql.go:30","msg":"Initializing MySQL Successfully"}
{"level":"INFO","time":"2025-03-02T06:53:02.797Z","caller":"initialize/mysql.go:30","msg":"Initializing MySQL Successfully"}
{"level":"INFO","time":"2025-03-02T06:53:08.577Z","caller":"initialize/mysql.go:30","msg":"Initializing MySQL Successfully"}
{"level":"INFO","time":"2025-03-02T06:53:11.772Z","caller":"initialize/mysql.go:30","msg":"Initializing MySQL Successfully"}
{"level":"INFO","time":"2025-03-02T06:53:15.672Z","caller":"initialize/mysql.go:30","msg":"Initializing MySQL Successfully"}
{"level":"INFO","time":"2025-03-02T06:53:18.848Z","caller":"initialize/mysql.go:30","msg":"Initializing MySQL Successfully"}
{"level":"INFO","time":"2025-03-02T06:53:21.615Z","caller":"initialize/mysql.go:30","msg":"Initializing MySQL Successfully"}
{"level":"INFO","time":"2025-03-02T06:53:25.524Z","caller":"initialize/mysql.go:30","msg":"Initializing MySQL Successfully"}
{"level":"INFO","time":"2025-03-02T06:53:27.107Z","caller":"initialize/mysql.go:30","msg":"Initializing MySQL Successfully"}
{"level":"INFO","time":"2025-03-02T06:53:40.908Z","caller":"initialize/mysql.go:30","msg":"Initializing MySQL Successfully"}
{"level":"INFO","time":"2025-03-02T06:53:43.319Z","caller":"initialize/mysql.go:30","msg":"Initializing MySQL Successfully"}
{"level":"INFO","time":"2025-03-02T06:55:15.415Z","caller":"initialize/mysql.go:30","msg":"Initializing MySQL Successfully"}
{"level":"INFO","time":"2025-03-02T06:55:17.387Z","caller":"initialize/mysql.go:30","msg":"Initializing MySQL Successfully"}
{"level":"INFO","time":"2025-03-02T06:55:34.012Z","caller":"initialize/mysql.go:30","msg":"Initializing MySQL Successfully"}
{"level":"INFO","time":"2025-03-02T06:55:38.379Z","caller":"initialize/mysql.go:30","msg":"Initializing MySQL Successfully"}
{"level":"INFO","time":"2025-03-02T06:55:50.025Z","caller":"initialize/mysql.go:30","msg":"Initializing MySQL Successfully"}
{"level":"INFO","time":"2025-03-02T06:55:59.580Z","caller":"initialize/mysql.go:30","msg":"Initializing MySQL Successfully"}
{"level":"INFO","time":"2025-03-02T06:56:14.324Z","caller":"initialize/mysql.go:30","msg":"Initializing MySQL Successfully"}
{"level":"INFO","time":"2025-03-02T06:56:24.134Z","caller":"initialize/mysql.go:30","msg":"Initializing MySQL Successfully"}
{"level":"INFO","time":"2025-03-02T06:56:39.190Z","caller":"initialize/mysql.go:30","msg":"Initializing MySQL Successfully"}
{"level":"INFO","time":"2025-03-02T06:56:48.338Z","caller":"initialize/mysql.go:30","msg":"Initializing MySQL Successfully"}
{"level":"INFO","time":"2025-03-02T06:56:50.797Z","caller":"initialize/mysql.go:30","msg":"Initializing MySQL Successfully"}
{"level":"INFO","time":"2025-03-02T06:56:54.593Z","caller":"initialize/mysql.go:30","msg":"Initializing MySQL Successfully"}
{"level":"INFO","time":"2025-03-02T06:56:55.809Z","caller":"initialize/mysql.go:30","msg":"Initializing MySQL Successfully"}
{"level":"INFO","time":"2025-03-02T06:58:05.175Z","caller":"initialize/mysql.go:30","msg":"Initializing MySQL Successfully"}
{"level":"INFO","time":"2025-03-02T06:58:11.139Z","caller":"initialize/mysql.go:30","msg":"Initializing MySQL Successfully"}
{"level":"INFO","time":"2025-03-02T06:58:30.314Z","caller":"initialize/mysql.go:30","msg":"Initializing MySQL Successfully"}
{"level":"INFO","time":"2025-03-02T06:58:48.890Z","caller":"initialize/mysql.go:30","msg":"Initializing MySQL Successfully"}
{"level":"INFO","time":"2025-03-02T06:58:52.017Z","caller":"initialize/mysql.go:30","msg":"Initializing MySQL Successfully"}
{"level":"INFO","time":"2025-03-02T06:59:18.065Z","caller":"initialize/mysql.go:30","msg":"Initializing MySQL Successfully"}
{"level":"INFO","time":"2025-03-02T06:59:36.447Z","caller":"initialize/mysql.go:30","msg":"Initializing MySQL Successfully"}
{"level":"INFO","time":"2025-03-02T07:00:26.169Z","caller":"initialize/mysql.go:30","msg":"Initializing MySQL Successfully"}
{"level":"INFO","time":"2025-03-02T07:01:48.197Z","caller":"initialize/mysql.go:30","msg":"Initializing MySQL Successfully"}
{"level":"INFO","time":"2025-03-02T07:02:05.929Z","caller":"initialize/mysql.go:30","msg":"Initializing MySQL Successfully"}
{"level":"INFO","time":"2025-03-02T07:02:31.997Z","caller":"initialize/mysql.go:30","msg":"Initializing MySQL Successfully"}
{"level":"INFO","time":"2025-03-02T07:03:57.684Z","caller":"initialize/mysql.go:30","msg":"Initializing MySQL Successfully"}
{"level":"INFO","time":"2025-03-02T07:06:54.013Z","caller":"initialize/mysql.go:30","msg":"Initializing MySQL Successfully"}
{"level":"INFO","time":"2025-03-02T07:06:54.014Z","caller":"initialize/run.go:63","msg":"Server đang chạy trên cổng 8080"}
{"level":"INFO","time":"2025-03-02T07:06:57.377Z","caller":"initialize/mysql.go:30","msg":"Initializing MySQL Successfully"}
{"level":"INFO","time":"2025-03-02T07:06:57.378Z","caller":"initialize/run.go:63","msg":"Server đang chạy trên cổng 8080"}
{"level":"INFO","time":"2025-03-02T07:09:01.068Z","caller":"initialize/mysql.go:30","msg":"Initializing MySQL Successfully"}
{"level":"INFO","time":"2025-03-02T07:09:01.069Z","caller":"initialize/run.go:57","msg":"Server đang chạy trên cổng 8080"}
{"level":"INFO","time":"2025-03-02T07:09:14.735Z","caller":"initialize/mysql.go:30","msg":"Initializing MySQL Successfully"}
{"level":"INFO","time":"2025-03-02T07:09:14.735Z","caller":"initialize/run.go:63","msg":"Server đang chạy trên cổng 8080"}
{"level":"INFO","time":"2025-03-02T07:09:18.382Z","caller":"initialize/mysql.go:30","msg":"Initializing MySQL Successfully"}
{"level":"INFO","time":"2025-03-02T07:09:18.383Z","caller":"initialize/run.go:63","msg":"Server đang chạy trên cổng 8080"}
{"level":"INFO","time":"2025-03-02T07:09:56.164Z","caller":"initialize/mysql.go:30","msg":"Initializing MySQL Successfully"}
{"level":"INFO","time":"2025-03-02T07:09:56.165Z","caller":"initialize/run.go:63","msg":"Server đang chạy trên cổng 8080"}
{"level":"INFO","time":"2025-03-02T07:13:58.737Z","caller":"initialize/mysql.go:30","msg":"Initializing MySQL Successfully"}
{"level":"INFO","time":"2025-03-02T07:13:58.738Z","caller":"initialize/run.go:63","msg":"Server đang chạy trên cổng 8080"}
{"level":"INFO","time":"2025-03-02T07:16:31.477Z","caller":"initialize/mysql.go:30","msg":"Initializing MySQL Successfully"}
{"level":"INFO","time":"2025-03-02T07:16:31.477Z","caller":"initialize/run.go:63","msg":"Server đang chạy trên cổng 8080"}
{"level":"INFO","time":"2025-03-02T07:16:35.112Z","caller":"initialize/mysql.go:30","msg":"Initializing MySQL Successfully"}
{"level":"INFO","time":"2025-03-02T07:16:35.113Z","caller":"initialize/run.go:63","msg":"Server đang chạy trên cổng 8080"}
{"level":"INFO","time":"2025-03-02T07:16:44.045Z","caller":"initialize/mysql.go:30","msg":"Initializing MySQL Successfully"}
{"level":"INFO","time":"2025-03-02T07:16:44.046Z","caller":"initialize/run.go:63","msg":"Server đang chạy trên cổng 8080"}
{"level":"INFO","time":"2025-03-02T07:16:52.346Z","caller":"initialize/mysql.go:30","msg":"Initializing MySQL Successfully"}
{"level":"INFO","time":"2025-03-02T07:16:52.347Z","caller":"initialize/run.go:63","msg":"Server đang chạy trên cổng 8080"}
{"level":"INFO","time":"2025-03-02T07:16:55.854Z","caller":"initialize/mysql.go:30","msg":"Initializing MySQL Successfully"}
{"level":"INFO","time":"2025-03-02T07:16:55.855Z","caller":"initialize/run.go:63","msg":"Server đang chạy trên cổng 8080"}
{"level":"INFO","time":"2025-03-02T07:17:06.269Z","caller":"initialize/mysql.go:30","msg":"Initializing MySQL Successfully"}
{"level":"INFO","time":"2025-03-02T07:17:06.270Z","caller":"initialize/run.go:63","msg":"Server đang chạy trên cổng 8080"}
{"level":"INFO","time":"2025-03-02T07:17:21.892Z","caller":"initialize/mysql.go:30","msg":"Initializing MySQL Successfully"}
{"level":"INFO","time":"2025-03-02T07:17:21.893Z","caller":"initialize/run.go:63","msg":"Server đang chạy trên cổng 8080"}
{"level":"INFO","time":"2025-03-02T07:17:24.557Z","caller":"initialize/mysql.go:30","msg":"Initializing MySQL Successfully"}
{"level":"INFO","time":"2025-03-02T07:17:24.558Z","caller":"initialize/run.go:63","msg":"Server đang chạy trên cổng 8080"}
{"level":"INFO","time":"2025-03-02T07:17:35.462Z","caller":"initialize/mysql.go:30","msg":"Initializing MySQL Successfully"}
{"level":"INFO","time":"2025-03-02T07:17:35.463Z","caller":"initialize/run.go:63","msg":"Server đang chạy trên cổng 8080"}
{"level":"INFO","time":"2025-03-02T07:17:50.325Z","caller":"initialize/mysql.go:30","msg":"Initializing MySQL Successfully"}
{"level":"INFO","time":"2025-03-02T07:17:50.326Z","caller":"initialize/run.go:63","msg":"Server đang chạy trên cổng 8080"}
{"level":"INFO","time":"2025-03-02T07:17:53.693Z","caller":"initialize/mysql.go:30","msg":"Initializing MySQL Successfully"}
{"level":"INFO","time":"2025-03-02T07:17:53.694Z","caller":"initialize/run.go:63","msg":"Server đang chạy trên cổng 8080"}
{"level":"INFO","time":"2025-03-02T07:17:59.903Z","caller":"initialize/mysql.go:30","msg":"Initializing MySQL Successfully"}
{"level":"INFO","time":"2025-03-02T07:17:59.905Z","caller":"initialize/run.go:63","msg":"Server đang chạy trên cổng 8080"}
{"level":"INFO","time":"2025-03-02T07:18:03.203Z","caller":"initialize/mysql.go:30","msg":"Initializing MySQL Successfully"}
{"level":"INFO","time":"2025-03-02T07:18:03.204Z","caller":"initialize/run.go:63","msg":"Server đang chạy trên cổng 8080"}
{"level":"INFO","time":"2025-03-02T07:18:17.654Z","caller":"initialize/mysql.go:30","msg":"Initializing MySQL Successfully"}
{"level":"INFO","time":"2025-03-02T07:18:17.655Z","caller":"initialize/run.go:63","msg":"Server đang chạy trên cổng 8080"}
{"level":"INFO","time":"2025-03-02T07:18:24.242Z","caller":"initialize/mysql.go:30","msg":"Initializing MySQL Successfully"}
{"level":"INFO","time":"2025-03-02T07:18:24.243Z","caller":"initialize/run.go:63","msg":"Server đang chạy trên cổng 8080"}
{"level":"INFO","time":"2025-03-02T07:18:28.431Z","caller":"initialize/mysql.go:30","msg":"Initializing MySQL Successfully"}
{"level":"INFO","time":"2025-03-02T07:18:28.432Z","caller":"initialize/run.go:63","msg":"Server đang chạy trên cổng 8080"}
{"level":"INFO","time":"2025-03-02T07:18:39.133Z","caller":"initialize/mysql.go:30","msg":"Initializing MySQL Successfully"}
{"level":"INFO","time":"2025-03-02T07:18:39.134Z","caller":"initialize/run.go:63","msg":"Server đang chạy trên cổng 8080"}
{"level":"INFO","time":"2025-03-02T07:20:51.975Z","caller":"initialize/mysql.go:30","msg":"Initializing MySQL Successfully"}
{"level":"INFO","time":"2025-03-02T07:20:51.976Z","caller":"initialize/run.go:63","msg":"Server đang chạy trên cổng 8080"}
{"level":"INFO","time":"2025-03-02T07:21:04.844Z","caller":"initialize/mysql.go:30","msg":"Initializing MySQL Successfully"}
{"level":"INFO","time":"2025-03-02T07:21:04.845Z","caller":"initialize/run.go:63","msg":"Server đang chạy trên cổng 8080"}
{"level":"INFO","time":"2025-03-02T07:21:19.730Z","caller":"initialize/mysql.go:30","msg":"Initializing MySQL Successfully"}
{"level":"INFO","time":"2025-03-02T07:21:19.731Z","caller":"initialize/run.go:63","msg":"Server đang chạy trên cổng 8080"}
{"level":"INFO","time":"2025-03-02T07:21:29.436Z","caller":"initialize/mysql.go:30","msg":"Initializing MySQL Successfully"}
{"level":"INFO","time":"2025-03-02T07:21:29.436Z","caller":"initialize/run.go:63","msg":"Server đang chạy trên cổng 8080"}
{"level":"INFO","time":"2025-03-02T07:22:03.404Z","caller":"initialize/mysql.go:30","msg":"Initializing MySQL Successfully"}
{"level":"INFO","time":"2025-03-02T07:22:03.405Z","caller":"initialize/run.go:63","msg":"Server đang chạy trên cổng 8080"}
{"level":"INFO","time":"2025-03-02T07:22:29.415Z","caller":"initialize/mysql.go:30","msg":"Initializing MySQL Successfully"}
{"level":"INFO","time":"2025-03-02T07:22:29.418Z","caller":"initialize/run.go:63","msg":"Server đang chạy trên cổng 8080"}