init project
This commit is contained in:
commit
e2a219cacd
46
.air.toml
Normal file
46
.air.toml
Normal 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
34
.gitignore
vendored
Normal 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
18
Dockerfile
Normal 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
29
Dockerfile.dev
Normal 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
64
Makefile
Normal 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
410
README.md
Normal 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
10
cmd/api/main.go
Normal 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
49
docker-compose.dev.yml
Normal 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
47
docker-compose.yml
Normal 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
48
global/global.go
Normal 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
52
go.mod
Normal 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
168
go.sum
Normal 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=
|
15
internal/controllers/controllers.go
Normal file
15
internal/controllers/controllers.go
Normal 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,
|
||||
}
|
||||
}
|
22
internal/controllers/interfaces.go
Normal file
22
internal/controllers/interfaces.go
Normal 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
|
125
internal/controllers/product_controller.go
Normal file
125
internal/controllers/product_controller.go
Normal 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)
|
||||
}
|
93
internal/controllers/user_controller.go
Normal file
93
internal/controllers/user_controller.go
Normal 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)
|
||||
}
|
119
internal/initialize/loadconfig.go
Normal file
119
internal/initialize/loadconfig.go
Normal 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
|
||||
}
|
103
internal/initialize/logger.go
Normal file
103
internal/initialize/logger.go
Normal 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)
|
||||
}
|
46
internal/initialize/mysql.go
Normal file
46
internal/initialize/mysql.go
Normal 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
82
internal/initialize/run.go
Executable 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.")
|
||||
}
|
16
internal/models/product.go
Normal file
16
internal/models/product.go
Normal 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
18
internal/models/user.go
Normal 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"`
|
||||
}
|
23
internal/repositories/interfaces.go
Normal file
23
internal/repositories/interfaces.go
Normal 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
|
53
internal/repositories/product_repository.go
Normal file
53
internal/repositories/product_repository.go
Normal 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
|
||||
}
|
53
internal/repositories/user_repository.go
Normal file
53
internal/repositories/user_repository.go
Normal 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
|
||||
}
|
18
internal/routers/product/product_router.go
Normal file
18
internal/routers/product/product_router.go
Normal 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)
|
||||
}
|
||||
}
|
20
internal/routers/router_group.go
Normal file
20
internal/routers/router_group.go
Normal 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
|
||||
}
|
17
internal/routers/user/user_router.go
Normal file
17
internal/routers/user/user_router.go
Normal 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)
|
||||
}
|
||||
}
|
22
internal/services/interfaces.go
Normal file
22
internal/services/interfaces.go
Normal 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
|
123
internal/services/product_service.go
Normal file
123
internal/services/product_service.go
Normal 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
|
||||
}
|
118
internal/services/user_service.go
Normal file
118
internal/services/user_service.go
Normal 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
17
internal/wire/injector.go
Normal 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
62
internal/wire/wire.go
Normal 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
28
internal/wire/wire_gen.go
Normal 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
101
logs/app.log
Normal 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"}
|
Loading…
x
Reference in New Issue
Block a user