init
This commit is contained in:
commit
d83f6c1145
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
|
41
.env.prod
Normal file
41
.env.prod
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
# App Configuration
|
||||||
|
PORT=8080
|
||||||
|
APP_ENV=production
|
||||||
|
|
||||||
|
# Database Configuration
|
||||||
|
BLUEPRINT_DB_HOST=mysql_bp
|
||||||
|
BLUEPRINT_DB_PORT=3306
|
||||||
|
BLUEPRINT_DB_DATABASE=blueprint
|
||||||
|
BLUEPRINT_DB_USERNAME=blueprint_user
|
||||||
|
BLUEPRINT_DB_PASSWORD=your_secure_password
|
||||||
|
BLUEPRINT_DB_ROOT_PASSWORD=your_secure_root_password
|
||||||
|
BLUEPRINT_DB_MAX_IDLE_CONNS=10
|
||||||
|
BLUEPRINT_DB_MAX_OPEN_CONNS=100
|
||||||
|
BLUEPRINT_DB_CONN_MAX_LIFETIME=1h
|
||||||
|
|
||||||
|
# Logger Configuration
|
||||||
|
LOGGER_LOG_LEVEL=info
|
||||||
|
LOGGER_FILE_LOG_NAME=logs/app.log
|
||||||
|
LOGGER_MAX_SIZE=1000
|
||||||
|
LOGGER_MAX_BACKUPS=7
|
||||||
|
LOGGER_MAX_AGE=30
|
||||||
|
LOGGER_COMPRESS=true
|
||||||
|
|
||||||
|
# Monitoring Configuration
|
||||||
|
GRAFANA_ADMIN_PASSWORD=admin
|
||||||
|
PROMETHEUS_RETENTION_TIME=15d
|
||||||
|
PROMETHEUS_SCRAPE_INTERVAL=15s
|
||||||
|
|
||||||
|
# Backup Configuration
|
||||||
|
BACKUP_RETENTION_DAYS=7
|
||||||
|
BACKUP_SCHEDULE="0 2 * * *"
|
||||||
|
|
||||||
|
# SSL Configuration
|
||||||
|
SSL_CERT_PATH=/etc/nginx/ssl/cert.pem
|
||||||
|
SSL_KEY_PATH=/etc/nginx/ssl/key.pem
|
||||||
|
|
||||||
|
# Network Configuration
|
||||||
|
NGINX_PORT=80
|
||||||
|
NGINX_SSL_PORT=443
|
||||||
|
GRAFANA_PORT=3000
|
||||||
|
PROMETHEUS_PORT=9090
|
35
.gitignore
vendored
Normal file
35
.gitignore
vendored
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
# Binaries for programs and plugins
|
||||||
|
*.exe
|
||||||
|
*.exe~
|
||||||
|
*.dll
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
|
||||||
|
# Test binary, built with "go test -c"
|
||||||
|
*.test
|
||||||
|
|
||||||
|
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||||
|
*.out
|
||||||
|
|
||||||
|
# Dependency directories (remove the comment below to include it)
|
||||||
|
# vendor/
|
||||||
|
|
||||||
|
# Go workspace file
|
||||||
|
go.work
|
||||||
|
tmp/
|
||||||
|
|
||||||
|
# IDE specific files
|
||||||
|
.vscode
|
||||||
|
.idea
|
||||||
|
|
||||||
|
# .env file
|
||||||
|
.env
|
||||||
|
|
||||||
|
# Project build
|
||||||
|
main
|
||||||
|
*templ.go
|
||||||
|
|
||||||
|
# OS X generated file
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
/logs/app.log
|
27
Dockerfile.dev
Normal file
27
Dockerfile.dev
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
FROM golang:1.23.3-alpine
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Install build dependencies and air
|
||||||
|
RUN apk add --no-cache git make && \
|
||||||
|
go install github.com/cosmtrek/air@v1.49.0
|
||||||
|
|
||||||
|
# Sao chép file go.mod và go.sum
|
||||||
|
COPY go.mod go.sum ./
|
||||||
|
|
||||||
|
# Tải các dependency
|
||||||
|
RUN go mod download
|
||||||
|
|
||||||
|
# Sao chép file .air.toml
|
||||||
|
COPY .air.toml ./
|
||||||
|
|
||||||
|
# Sao chép toàn bộ mã nguồn
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Expose port
|
||||||
|
ARG PORT=8080
|
||||||
|
ENV PORT=${PORT}
|
||||||
|
EXPOSE ${PORT}
|
||||||
|
|
||||||
|
# Chạy Air để hot-reload
|
||||||
|
CMD ["air"]
|
46
Dockerfile.prod
Normal file
46
Dockerfile.prod
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
# Build stage
|
||||||
|
FROM golang:1.23.3-alpine AS builder
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Install build dependencies
|
||||||
|
RUN apk add --no-cache git make
|
||||||
|
|
||||||
|
# Copy go mod and sum files
|
||||||
|
COPY go.mod go.sum ./
|
||||||
|
|
||||||
|
# Download dependencies
|
||||||
|
RUN go mod download
|
||||||
|
|
||||||
|
# Copy source code
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Build the application
|
||||||
|
RUN CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -o main ./cmd/api
|
||||||
|
|
||||||
|
# Production stage
|
||||||
|
FROM alpine:latest
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy binary from builder
|
||||||
|
COPY --from=builder /app/main .
|
||||||
|
|
||||||
|
# Copy SSL certificates
|
||||||
|
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
|
||||||
|
|
||||||
|
# Create necessary directories
|
||||||
|
RUN mkdir -p /app/logs
|
||||||
|
|
||||||
|
# Copy environment file
|
||||||
|
COPY .env.prod .env
|
||||||
|
|
||||||
|
# Expose port
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
# Set environment variables
|
||||||
|
ENV PORT=8080
|
||||||
|
ENV TZ=Asia/Ho_Chi_Minh
|
||||||
|
|
||||||
|
# Run the application
|
||||||
|
CMD ["./main"]
|
64
Makefile
Normal file
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
|
138
README.md
Normal file
138
README.md
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
## Cấu trúc dự án chi tiết
|
||||||
|
|
||||||
|
```
|
||||||
|
.
|
||||||
|
├── cmd/ # Điểm vào ứng dụng
|
||||||
|
│ └── api/ # API server
|
||||||
|
│ └── main.go # File main khởi động ứng dụng
|
||||||
|
├── global/ # Biến toàn cục và cấu trúc cấu hình
|
||||||
|
│ └── global.go # Định nghĩa các biến và cấu trúc toàn cục
|
||||||
|
├── internal/ # Mã nguồn nội bộ
|
||||||
|
│ ├── controllers/ # Xử lý request và response
|
||||||
|
│ │ └── user_controller.go # Controller xử lý các request liên quan đến user
|
||||||
|
│ ├── initialize/ # Khởi tạo các thành phần của ứng dụng
|
||||||
|
│ │ ├── loadconfig.go # Đọc cấu hình từ file .env
|
||||||
|
│ │ ├── logger.go # Khởi tạo logger
|
||||||
|
│ │ ├── mysql.go # Khởi tạo kết nối MySQL
|
||||||
|
│ │ └── run.go # Điểm khởi động chính của ứng dụng
|
||||||
|
│ ├── models/ # Định nghĩa các model dữ liệu
|
||||||
|
│ │ └── user.go # Model User
|
||||||
|
│ ├── repositories/ # Tương tác với cơ sở dữ liệu
|
||||||
|
│ │ └── user_repository.go # Repository xử lý dữ liệu user
|
||||||
|
│ ├── routers/ # Định nghĩa các router
|
||||||
|
│ │ ├── router_group.go # Nhóm các router
|
||||||
|
│ │ └── user/ # Router liên quan đến user
|
||||||
|
│ │ ├── router_group.go # Nhóm các router user
|
||||||
|
│ │ └── user_router.go # Định nghĩa các endpoint user
|
||||||
|
│ ├── services/ # Xử lý logic nghiệp vụ
|
||||||
|
│ │ └── user_service.go # Service xử lý logic liên quan đến user
|
||||||
|
│ └── wire/ # Dependency injection với Google Wire
|
||||||
|
│ ├── injector.go # Định nghĩa các injector
|
||||||
|
│ ├── wire.go # Định nghĩa các provider
|
||||||
|
│ └── wire_gen.go # File được tạo tự động bởi Wire
|
||||||
|
├── logs/ # Thư mục chứa log
|
||||||
|
├── .air.toml # Cấu hình cho Air (hot-reload)
|
||||||
|
├── .env # Biến môi trường
|
||||||
|
├── Dockerfile # Cấu hình Docker cho môi trường production
|
||||||
|
├── Dockerfile.dev # Cấu hình Docker cho môi trường phát triển
|
||||||
|
├── Makefile # Các lệnh make
|
||||||
|
└── docker-compose.yml, docker-compose.dev.yml # Cấu hình Docker Compose
|
||||||
|
```
|
||||||
|
|
||||||
|
## Quy tắc đặt tên file
|
||||||
|
|
||||||
|
Dự án tuân theo các quy tắc đặt tên file sau để đảm bảo tính nhất quán:
|
||||||
|
|
||||||
|
1. **Sử dụng dấu gạch dưới (_) để phân tách các từ** trong tên file, ví dụ: `user_controller.go`, `user_repository.go`.
|
||||||
|
|
||||||
|
2. **Tên file mô tả rõ chức năng** của file đó:
|
||||||
|
- Controllers: `<entity>_controller.go` (ví dụ: `user_controller.go`)
|
||||||
|
- Services: `<entity>_service.go` (ví dụ: `user_service.go`)
|
||||||
|
- Repositories: `<entity>_repository.go` (ví dụ: `user_repository.go`)
|
||||||
|
- Routers: `<entity>_router.go` (ví dụ: `user_router.go`)
|
||||||
|
- Models: `<entity>.go` (ví dụ: `user.go`)
|
||||||
|
|
||||||
|
3. **File khởi tạo và cấu hình** sử dụng tên mô tả chức năng:
|
||||||
|
- `loadconfig.go`: Đọc cấu hình
|
||||||
|
- `logger.go`: Khởi tạo logger
|
||||||
|
- `mysql.go`: Khởi tạo kết nối MySQL
|
||||||
|
- `run.go`: Điểm khởi động ứng dụng
|
||||||
|
|
||||||
|
4. **File Wire** tuân theo quy ước của Google Wire:
|
||||||
|
- `wire.go`: Định nghĩa các provider
|
||||||
|
- `injector.go`: Định nghĩa các injector
|
||||||
|
- `wire_gen.go`: File được tạo tự động bởi Wire
|
||||||
|
|
||||||
|
## Kiến trúc ứng dụng
|
||||||
|
|
||||||
|
Ứng dụng được tổ chức theo kiến trúc phân lớp:
|
||||||
|
|
||||||
|
1. **Controllers**: Xử lý request và response, gọi các service để thực hiện logic nghiệp vụ.
|
||||||
|
2. **Services**: Chứa logic nghiệp vụ, gọi các repository để tương tác với dữ liệu.
|
||||||
|
3. **Repositories**: Tương tác trực tiếp với cơ sở dữ liệu, thực hiện các thao tác CRUD.
|
||||||
|
4. **Models**: Định nghĩa cấu trúc dữ liệu.
|
||||||
|
5. **Routers**: Định nghĩa các endpoint API và kết nối với controllers.
|
||||||
|
6. **Wire**: Quản lý dependency injection, kết nối các thành phần lại với nhau.
|
||||||
|
|
||||||
|
## Dependency Injection với Google Wire
|
||||||
|
|
||||||
|
Dự án sử dụng Google Wire để quản lý dependency injection. Các thành phần chính:
|
||||||
|
|
||||||
|
1. **Provider**: Định nghĩa cách tạo các dependency (trong `wire.go`).
|
||||||
|
2. **Injector**: Định nghĩa cách kết nối các dependency (trong `injector.go`).
|
||||||
|
3. **Wire Gen**: File được tạo tự động bởi Wire, chứa code khởi tạo dependency (trong `wire_gen.go`).
|
||||||
|
|
||||||
|
Để cập nhật file `wire_gen.go` sau khi thay đổi các provider hoặc injector:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd internal/wire
|
||||||
|
go run github.com/google/wire/cmd/wire
|
||||||
|
```
|
||||||
|
|
||||||
|
## Môi trường phát triển (Development)
|
||||||
|
|
||||||
|
### Phương pháp 1: Sử dụng Docker Compose cho toàn bộ stack
|
||||||
|
|
||||||
|
Phương pháp này sử dụng Docker Compose để chạy cả ứng dụng Go và MySQL, với hot-reload được hỗ trợ bởi Air.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Khởi động môi trường phát triển
|
||||||
|
docker-compose -f docker-compose.dev.yml up -d
|
||||||
|
|
||||||
|
# Xem logs
|
||||||
|
docker-compose -f docker-compose.dev.yml logs -f
|
||||||
|
|
||||||
|
# Dừng môi trường phát triển
|
||||||
|
docker-compose -f docker-compose.dev.yml down
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phương pháp 2: Chạy ứng dụng Go trực tiếp, MySQL trong Docker
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Khởi động MySQL trong Docker
|
||||||
|
make docker-run
|
||||||
|
|
||||||
|
# Chạy ứng dụng với hot-reload
|
||||||
|
make watch
|
||||||
|
|
||||||
|
# Hoặc chạy ứng dụng thông thường
|
||||||
|
make run
|
||||||
|
|
||||||
|
# Dừng MySQL
|
||||||
|
make docker-down
|
||||||
|
```
|
||||||
|
|
||||||
|
## Môi trường sản xuất (Production)
|
||||||
|
|
||||||
|
### Phương pháp 1: Sử dụng Docker Compose
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Khởi động môi trường sản xuất
|
||||||
|
docker-compose up -d
|
||||||
|
|
||||||
|
# Xem logs
|
||||||
|
docker-compose logs -f
|
||||||
|
|
||||||
|
# Dừng môi trường sản xuất
|
||||||
|
docker-compose down
|
||||||
|
```
|
9
cmd/api/main.go
Normal file
9
cmd/api/main.go
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/dungnt11/todoms_golang/internal/initialize"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
initialize.Run()
|
||||||
|
}
|
92
docker-compose.dev.yml
Normal file
92
docker-compose.dev.yml
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
services:
|
||||||
|
app:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile.dev
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- ${PORT}:${PORT}
|
||||||
|
environment:
|
||||||
|
APP_ENV: ${APP_ENV}
|
||||||
|
PORT: ${PORT}
|
||||||
|
BLUEPRINT_DB_HOST: mysql_bp
|
||||||
|
BLUEPRINT_DB_PORT: ${BLUEPRINT_DB_PORT}
|
||||||
|
BLUEPRINT_DB_DATABASE: ${BLUEPRINT_DB_DATABASE}
|
||||||
|
BLUEPRINT_DB_USERNAME: ${BLUEPRINT_DB_USERNAME}
|
||||||
|
BLUEPRINT_DB_PASSWORD: ${BLUEPRINT_DB_PASSWORD}
|
||||||
|
BLUEPRINT_DB_MAX_IDLE_CONNS: ${BLUEPRINT_DB_MAX_IDLE_CONNS}
|
||||||
|
BLUEPRINT_DB_MAX_OPEN_CONNS: ${BLUEPRINT_DB_MAX_OPEN_CONNS}
|
||||||
|
BLUEPRINT_DB_CONN_MAX_LIFETIME: ${BLUEPRINT_DB_CONN_MAX_LIFETIME}
|
||||||
|
LOGGER_LOG_LEVEL: ${LOGGER_LOG_LEVEL}
|
||||||
|
LOGGER_FILE_LOG_NAME: ${LOGGER_FILE_LOG_NAME}
|
||||||
|
LOGGER_MAX_SIZE: ${LOGGER_MAX_SIZE}
|
||||||
|
LOGGER_MAX_BACKUPS: ${LOGGER_MAX_BACKUPS}
|
||||||
|
LOGGER_MAX_AGE: ${LOGGER_MAX_AGE}
|
||||||
|
LOGGER_COMPRESS: ${LOGGER_COMPRESS}
|
||||||
|
volumes:
|
||||||
|
- .:/app # Mount current directory to /app in container
|
||||||
|
command: air # Use air for hot-reload
|
||||||
|
depends_on:
|
||||||
|
mysql_bp:
|
||||||
|
condition: service_healthy
|
||||||
|
networks:
|
||||||
|
- blueprint
|
||||||
|
mysql_bp:
|
||||||
|
image: mysql:8.0
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
MYSQL_DATABASE: ${BLUEPRINT_DB_DATABASE}
|
||||||
|
MYSQL_USER: ${BLUEPRINT_DB_USERNAME}
|
||||||
|
MYSQL_PASSWORD: ${BLUEPRINT_DB_PASSWORD}
|
||||||
|
MYSQL_ROOT_PASSWORD: ${BLUEPRINT_DB_ROOT_PASSWORD}
|
||||||
|
MYSQL_ALLOW_EMPTY_PASSWORD: "no"
|
||||||
|
ports:
|
||||||
|
- "3306:3306"
|
||||||
|
volumes:
|
||||||
|
- mysql_volume_bp:/var/lib/mysql
|
||||||
|
- ./mysql/init:/docker-entrypoint-initdb.d
|
||||||
|
command: >
|
||||||
|
--default-authentication-plugin=mysql_native_password
|
||||||
|
--character-set-server=utf8mb4
|
||||||
|
--collation-server=utf8mb4_unicode_ci
|
||||||
|
--skip-name-resolve
|
||||||
|
--explicit_defaults_for_timestamp=1
|
||||||
|
--max_connections=1000
|
||||||
|
--innodb_buffer_pool_size=256M
|
||||||
|
--innodb_log_buffer_size=16M
|
||||||
|
--innodb_log_file_size=256M
|
||||||
|
--innodb_write_io_threads=8
|
||||||
|
--innodb_read_io_threads=8
|
||||||
|
--innodb_thread_concurrency=0
|
||||||
|
--innodb_flush_log_at_trx_commit=2
|
||||||
|
--innodb_flush_method=O_DIRECT
|
||||||
|
--innodb_file_per_table=1
|
||||||
|
--bind-address=0.0.0.0
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p${BLUEPRINT_DB_ROOT_PASSWORD}", "--connect-timeout=5"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
start_period: 180s
|
||||||
|
networks:
|
||||||
|
- blueprint
|
||||||
|
|
||||||
|
phpmyadmin:
|
||||||
|
image: phpmyadmin:latest
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "8081:80"
|
||||||
|
environment:
|
||||||
|
PMA_HOST: mysql_bp
|
||||||
|
PMA_PORT: 3306
|
||||||
|
MYSQL_ROOT_PASSWORD: ${BLUEPRINT_DB_ROOT_PASSWORD}
|
||||||
|
depends_on:
|
||||||
|
mysql_bp:
|
||||||
|
condition: service_healthy
|
||||||
|
networks:
|
||||||
|
- blueprint
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
mysql_volume_bp:
|
||||||
|
networks:
|
||||||
|
blueprint:
|
155
docker-compose.prod.yml
Normal file
155
docker-compose.prod.yml
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
services:
|
||||||
|
app:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile.prod
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "8083-8085:${PORT}"
|
||||||
|
environment:
|
||||||
|
APP_ENV: ${APP_ENV}
|
||||||
|
PORT: ${PORT}
|
||||||
|
GIN_MODE: release
|
||||||
|
BLUEPRINT_DB_HOST: mysql_bp
|
||||||
|
BLUEPRINT_DB_PORT: ${BLUEPRINT_DB_PORT}
|
||||||
|
BLUEPRINT_DB_DATABASE: ${BLUEPRINT_DB_DATABASE}
|
||||||
|
BLUEPRINT_DB_USERNAME: root
|
||||||
|
BLUEPRINT_DB_PASSWORD: ${BLUEPRINT_DB_ROOT_PASSWORD}
|
||||||
|
BLUEPRINT_DB_MAX_IDLE_CONNS: ${BLUEPRINT_DB_MAX_IDLE_CONNS}
|
||||||
|
BLUEPRINT_DB_MAX_OPEN_CONNS: ${BLUEPRINT_DB_MAX_OPEN_CONNS}
|
||||||
|
BLUEPRINT_DB_CONN_MAX_LIFETIME: ${BLUEPRINT_DB_CONN_MAX_LIFETIME}
|
||||||
|
LOGGER_LOG_LEVEL: ${LOGGER_LOG_LEVEL}
|
||||||
|
LOGGER_FILE_LOG_NAME: ${LOGGER_FILE_LOG_NAME}
|
||||||
|
LOGGER_MAX_SIZE: ${LOGGER_MAX_SIZE}
|
||||||
|
LOGGER_MAX_BACKUPS: ${LOGGER_MAX_BACKUPS}
|
||||||
|
LOGGER_MAX_AGE: ${LOGGER_MAX_AGE}
|
||||||
|
LOGGER_COMPRESS: ${LOGGER_COMPRESS}
|
||||||
|
depends_on:
|
||||||
|
mysql_bp:
|
||||||
|
condition: service_healthy
|
||||||
|
networks:
|
||||||
|
- blueprint
|
||||||
|
deploy:
|
||||||
|
replicas: 3
|
||||||
|
update_config:
|
||||||
|
parallelism: 1
|
||||||
|
delay: 10s
|
||||||
|
restart_policy:
|
||||||
|
condition: on-failure
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "wget", "--spider", "http://localhost:${PORT}/health"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
start_period: 40s
|
||||||
|
|
||||||
|
mysql_bp:
|
||||||
|
image: mysql:8.0
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
MYSQL_DATABASE: ${BLUEPRINT_DB_DATABASE}
|
||||||
|
MYSQL_ROOT_PASSWORD: ${BLUEPRINT_DB_ROOT_PASSWORD}
|
||||||
|
MYSQL_ALLOW_EMPTY_PASSWORD: "no"
|
||||||
|
ports:
|
||||||
|
- "3306:3306"
|
||||||
|
volumes:
|
||||||
|
- mysql_volume_bp:/var/lib/mysql
|
||||||
|
- mysql_backup:/backup
|
||||||
|
command: >
|
||||||
|
--default-authentication-plugin=mysql_native_password
|
||||||
|
--character-set-server=utf8mb4
|
||||||
|
--collation-server=utf8mb4_unicode_ci
|
||||||
|
--skip-name-resolve
|
||||||
|
--explicit_defaults_for_timestamp=1
|
||||||
|
--max_connections=1000
|
||||||
|
--innodb_buffer_pool_size=256M
|
||||||
|
--innodb_log_buffer_size=16M
|
||||||
|
--innodb_log_file_size=256M
|
||||||
|
--innodb_write_io_threads=8
|
||||||
|
--innodb_read_io_threads=8
|
||||||
|
--innodb_thread_concurrency=0
|
||||||
|
--innodb_flush_log_at_trx_commit=2
|
||||||
|
--innodb_flush_method=O_DIRECT
|
||||||
|
--innodb_file_per_table=1
|
||||||
|
--bind-address=0.0.0.0
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p${BLUEPRINT_DB_ROOT_PASSWORD}", "--connect-timeout=5"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
start_period: 180s
|
||||||
|
networks:
|
||||||
|
- blueprint
|
||||||
|
|
||||||
|
nginx:
|
||||||
|
image: nginx:alpine
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "80:80"
|
||||||
|
- "443:443"
|
||||||
|
volumes:
|
||||||
|
- ./nginx/conf.d:/etc/nginx/conf.d
|
||||||
|
- ./nginx/ssl:/etc/nginx/ssl
|
||||||
|
depends_on:
|
||||||
|
- app
|
||||||
|
networks:
|
||||||
|
- blueprint
|
||||||
|
|
||||||
|
prometheus:
|
||||||
|
image: prom/prometheus:latest
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "9090:9090"
|
||||||
|
volumes:
|
||||||
|
- ./prometheus:/etc/prometheus
|
||||||
|
- prometheus_data:/prometheus
|
||||||
|
command:
|
||||||
|
- '--config.file=/etc/prometheus/prometheus.yml'
|
||||||
|
- '--storage.tsdb.path=/prometheus'
|
||||||
|
- '--web.console.libraries=/usr/share/prometheus/console_libraries'
|
||||||
|
- '--web.console.templates=/usr/share/prometheus/consoles'
|
||||||
|
networks:
|
||||||
|
- blueprint
|
||||||
|
|
||||||
|
grafana:
|
||||||
|
image: grafana/grafana:latest
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "3000:3000"
|
||||||
|
volumes:
|
||||||
|
- grafana_data:/var/lib/grafana
|
||||||
|
environment:
|
||||||
|
- GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_ADMIN_PASSWORD}
|
||||||
|
depends_on:
|
||||||
|
- prometheus
|
||||||
|
networks:
|
||||||
|
- blueprint
|
||||||
|
|
||||||
|
mysql-backup:
|
||||||
|
image: mysql:8.0
|
||||||
|
volumes:
|
||||||
|
- mysql_backup:/backup
|
||||||
|
environment:
|
||||||
|
MYSQL_HOST: mysql_bp
|
||||||
|
MYSQL_USER: blueprint_user
|
||||||
|
MYSQL_PASSWORD: ${BLUEPRINT_DB_PASSWORD}
|
||||||
|
MYSQL_DATABASE: ${BLUEPRINT_DB_DATABASE}
|
||||||
|
entrypoint: |
|
||||||
|
/bin/sh -c '
|
||||||
|
while true; do
|
||||||
|
mysqldump -h$$MYSQL_HOST -u$$MYSQL_USER -p$$MYSQL_PASSWORD $$MYSQL_DATABASE > /backup/backup-$$(date +%Y%m%d-%H%M%S).sql;
|
||||||
|
find /backup -type f -mtime +7 -delete;
|
||||||
|
sleep 86400;
|
||||||
|
done
|
||||||
|
'
|
||||||
|
networks:
|
||||||
|
- blueprint
|
||||||
|
|
||||||
|
networks:
|
||||||
|
blueprint:
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
mysql_volume_bp:
|
||||||
|
mysql_backup:
|
||||||
|
prometheus_data:
|
||||||
|
grafana_data:
|
44
global/global.go
Normal file
44
global/global.go
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
package global
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
Config Configuration
|
||||||
|
Logger *zap.Logger
|
||||||
|
Mdb *gorm.DB
|
||||||
|
)
|
||||||
|
|
||||||
|
type Configuration struct {
|
||||||
|
Server ServerConfig
|
||||||
|
Database DatabaseConfig
|
||||||
|
Logger LoggerConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
type ServerConfig struct {
|
||||||
|
Port string
|
||||||
|
AppEnv string
|
||||||
|
}
|
||||||
|
|
||||||
|
type DatabaseConfig struct {
|
||||||
|
Host string
|
||||||
|
Port string
|
||||||
|
Database string
|
||||||
|
Username string
|
||||||
|
Password string
|
||||||
|
RootPassword string
|
||||||
|
MaxIdleConns int
|
||||||
|
MaxOpenConns int
|
||||||
|
ConnMaxLifetime int
|
||||||
|
}
|
||||||
|
|
||||||
|
type LoggerConfig struct {
|
||||||
|
LogLevel string
|
||||||
|
FileLogName string
|
||||||
|
MaxSize int
|
||||||
|
MaxBackups int
|
||||||
|
MaxAge int
|
||||||
|
Compress bool
|
||||||
|
}
|
52
go.mod
Normal file
52
go.mod
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
module github.com/dungnt11/todoms_golang
|
||||||
|
|
||||||
|
go 1.23.3
|
||||||
|
|
||||||
|
require go.uber.org/zap v1.27.0
|
||||||
|
|
||||||
|
require (
|
||||||
|
filippo.io/edwards25519 v1.1.0 // indirect
|
||||||
|
github.com/bytedance/sonic v1.12.9 // indirect
|
||||||
|
github.com/bytedance/sonic/loader v0.2.3 // indirect
|
||||||
|
github.com/cloudwego/base64x v0.1.5 // indirect
|
||||||
|
github.com/cloudwego/iasm v0.2.0 // indirect
|
||||||
|
github.com/fatih/color v1.18.0 // indirect
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
|
||||||
|
github.com/gin-contrib/sse v1.0.0 // indirect
|
||||||
|
github.com/gin-gonic/gin v1.10.0 // indirect
|
||||||
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
|
github.com/go-playground/validator/v10 v10.25.0 // indirect
|
||||||
|
github.com/go-sql-driver/mysql v1.9.0 // indirect
|
||||||
|
github.com/goccy/go-json v0.10.5 // indirect
|
||||||
|
github.com/google/subcommands v1.2.0 // indirect
|
||||||
|
github.com/google/wire v0.6.0 // indirect
|
||||||
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
|
github.com/jinzhu/now v1.1.5 // indirect
|
||||||
|
github.com/joho/godotenv v1.5.1 // indirect
|
||||||
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
|
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
|
||||||
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
|
github.com/natefinch/lumberjack v2.0.0+incompatible // indirect
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
|
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||||
|
go.uber.org/multierr v1.10.0 // indirect
|
||||||
|
golang.org/x/arch v0.14.0 // indirect
|
||||||
|
golang.org/x/crypto v0.35.0 // indirect
|
||||||
|
golang.org/x/mod v0.17.0 // indirect
|
||||||
|
golang.org/x/net v0.35.0 // indirect
|
||||||
|
golang.org/x/sync v0.11.0 // indirect
|
||||||
|
golang.org/x/sys v0.30.0 // indirect
|
||||||
|
golang.org/x/text v0.22.0 // indirect
|
||||||
|
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
|
||||||
|
google.golang.org/protobuf v1.36.5 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
gorm.io/driver/mysql v1.5.7 // indirect
|
||||||
|
gorm.io/gorm v1.25.12 // indirect
|
||||||
|
)
|
168
go.sum
Normal file
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/todoms_golang/internal/services"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProductController triển khai IProductController
|
||||||
|
type ProductController struct {
|
||||||
|
productService services.IProductService
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewProductController tạo một instance mới của ProductController
|
||||||
|
func NewProductController(productService services.IProductService) *ProductController {
|
||||||
|
return &ProductController{
|
||||||
|
productService: productService,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateProduct xử lý tạo sản phẩm mới
|
||||||
|
func (pc *ProductController) CreateProduct(c *gin.Context) {
|
||||||
|
// Gọi service để thực hiện logic tạo sản phẩm
|
||||||
|
result, err := pc.productService.CreateProduct(c)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(400, gin.H{
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(201, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetProduct xử lý lấy thông tin sản phẩm theo ID
|
||||||
|
func (pc *ProductController) GetProduct(c *gin.Context) {
|
||||||
|
// Lấy ID từ param
|
||||||
|
idStr := c.Param("id")
|
||||||
|
id, err := strconv.ParseUint(idStr, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(400, gin.H{
|
||||||
|
"error": "ID không hợp lệ",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gọi service để lấy thông tin sản phẩm
|
||||||
|
result, err := pc.productService.GetProduct(uint(id))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(404, gin.H{
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(200, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAllProducts xử lý lấy tất cả sản phẩm
|
||||||
|
func (pc *ProductController) GetAllProducts(c *gin.Context) {
|
||||||
|
// Gọi service để lấy tất cả sản phẩm
|
||||||
|
result, err := pc.productService.GetAllProducts()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(400, gin.H{
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(200, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateProduct xử lý cập nhật thông tin sản phẩm
|
||||||
|
func (pc *ProductController) UpdateProduct(c *gin.Context) {
|
||||||
|
// Lấy ID từ param
|
||||||
|
idStr := c.Param("id")
|
||||||
|
id, err := strconv.ParseUint(idStr, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(400, gin.H{
|
||||||
|
"error": "ID không hợp lệ",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gọi service để cập nhật thông tin sản phẩm
|
||||||
|
result, err := pc.productService.UpdateProduct(uint(id), c)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(400, gin.H{
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(200, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteProduct xử lý xóa sản phẩm
|
||||||
|
func (pc *ProductController) DeleteProduct(c *gin.Context) {
|
||||||
|
// Lấy ID từ param
|
||||||
|
idStr := c.Param("id")
|
||||||
|
id, err := strconv.ParseUint(idStr, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(400, gin.H{
|
||||||
|
"error": "ID không hợp lệ",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gọi service để xóa sản phẩm
|
||||||
|
result, err := pc.productService.DeleteProduct(uint(id))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(400, gin.H{
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(200, result)
|
||||||
|
}
|
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/todoms_golang/internal/services"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UserController triển khai IUserController
|
||||||
|
type UserController struct {
|
||||||
|
userService services.IUserService
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewUserController tạo một instance mới của UserController
|
||||||
|
func NewUserController(userService services.IUserService) *UserController {
|
||||||
|
return &UserController{
|
||||||
|
userService: userService,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register xử lý đăng ký người dùng
|
||||||
|
func (uc *UserController) Register(c *gin.Context) {
|
||||||
|
// Gọi service để thực hiện logic đăng ký
|
||||||
|
result, err := uc.userService.Register(c)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(400, gin.H{
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(200, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Login xử lý đăng nhập
|
||||||
|
func (uc *UserController) Login(c *gin.Context) {
|
||||||
|
// Gọi service để thực hiện logic đăng nhập
|
||||||
|
result, err := uc.userService.Login(c)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(400, gin.H{
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(200, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetProfile xử lý lấy thông tin profile
|
||||||
|
func (uc *UserController) GetProfile(c *gin.Context) {
|
||||||
|
// Lấy user ID từ token hoặc param (giả định)
|
||||||
|
userID := uint(1) // Trong thực tế, bạn sẽ lấy từ JWT token hoặc param
|
||||||
|
|
||||||
|
// Gọi service để lấy thông tin profile
|
||||||
|
result, err := uc.userService.GetProfile(userID)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(400, gin.H{
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(200, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateProfile xử lý cập nhật profile
|
||||||
|
func (uc *UserController) UpdateProfile(c *gin.Context) {
|
||||||
|
// Lấy user ID từ token hoặc param (giả định)
|
||||||
|
userID := uint(1) // Trong thực tế, bạn sẽ lấy từ JWT token hoặc param
|
||||||
|
|
||||||
|
// Đọc dữ liệu từ request
|
||||||
|
var data map[string]interface{}
|
||||||
|
if err := c.ShouldBindJSON(&data); err != nil {
|
||||||
|
c.JSON(400, gin.H{
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gọi service để cập nhật profile
|
||||||
|
result, err := uc.userService.UpdateProfile(userID, data)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(400, gin.H{
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(200, result)
|
||||||
|
}
|
65
internal/initialize/loadconfig.go
Normal file
65
internal/initialize/loadconfig.go
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
package initialize
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/dungnt11/todoms_golang/global"
|
||||||
|
"github.com/joho/godotenv"
|
||||||
|
)
|
||||||
|
|
||||||
|
func LoadConfig() error {
|
||||||
|
err := godotenv.Load()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cấu hình server
|
||||||
|
global.Config.Server.Port = os.Getenv("PORT")
|
||||||
|
global.Config.Server.AppEnv = os.Getenv("APP_ENV")
|
||||||
|
|
||||||
|
// Cấu hình database
|
||||||
|
global.Config.Database.Host = os.Getenv("BLUEPRINT_DB_HOST")
|
||||||
|
global.Config.Database.Port = os.Getenv("BLUEPRINT_DB_PORT")
|
||||||
|
global.Config.Database.Database = os.Getenv("BLUEPRINT_DB_DATABASE")
|
||||||
|
global.Config.Database.Username = os.Getenv("BLUEPRINT_DB_USERNAME")
|
||||||
|
global.Config.Database.Password = os.Getenv("BLUEPRINT_DB_PASSWORD")
|
||||||
|
global.Config.Database.RootPassword = os.Getenv("BLUEPRINT_DB_ROOT_PASSWORD")
|
||||||
|
|
||||||
|
// Chuyển đổi các giá trị cấu hình database từ string sang int
|
||||||
|
if maxIdleConns := os.Getenv("BLUEPRINT_DB_MAX_IDLE_CONNS"); maxIdleConns != "" {
|
||||||
|
fmt.Sscanf(maxIdleConns, "%d", &global.Config.Database.MaxIdleConns)
|
||||||
|
}
|
||||||
|
|
||||||
|
if maxOpenConns := os.Getenv("BLUEPRINT_DB_MAX_OPEN_CONNS"); maxOpenConns != "" {
|
||||||
|
fmt.Sscanf(maxOpenConns, "%d", &global.Config.Database.MaxOpenConns)
|
||||||
|
}
|
||||||
|
|
||||||
|
if connMaxLifetime := os.Getenv("BLUEPRINT_DB_CONN_MAX_LIFETIME"); connMaxLifetime != "" {
|
||||||
|
fmt.Sscanf(connMaxLifetime, "%d", &global.Config.Database.ConnMaxLifetime)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cấu hình logger
|
||||||
|
global.Config.Logger.LogLevel = os.Getenv("LOGGER_LOG_LEVEL")
|
||||||
|
global.Config.Logger.FileLogName = os.Getenv("LOGGER_FILE_LOG_NAME")
|
||||||
|
|
||||||
|
// Chuyển đổi từ string sang int và bool
|
||||||
|
if maxSize := os.Getenv("LOGGER_MAX_SIZE"); maxSize != "" {
|
||||||
|
fmt.Sscanf(maxSize, "%d", &global.Config.Logger.MaxSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
if maxBackups := os.Getenv("LOGGER_MAX_BACKUPS"); maxBackups != "" {
|
||||||
|
fmt.Sscanf(maxBackups, "%d", &global.Config.Logger.MaxBackups)
|
||||||
|
}
|
||||||
|
|
||||||
|
if maxAge := os.Getenv("LOGGER_MAX_AGE"); maxAge != "" {
|
||||||
|
fmt.Sscanf(maxAge, "%d", &global.Config.Logger.MaxAge)
|
||||||
|
}
|
||||||
|
|
||||||
|
if compress := os.Getenv("LOGGER_COMPRESS"); compress != "" {
|
||||||
|
global.Config.Logger.Compress = strings.ToLower(compress) == "true"
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
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/todoms_golang/global"
|
||||||
|
"github.com/natefinch/lumberjack"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
|
)
|
||||||
|
|
||||||
|
// InitLogger khởi tạo logger
|
||||||
|
func InitLogger() *zap.Logger {
|
||||||
|
// Đảm bảo thư mục logs tồn tại
|
||||||
|
ensureLogDir(global.Config.Logger.FileLogName)
|
||||||
|
|
||||||
|
// Khởi tạo logger
|
||||||
|
return newLogger(global.Config.Logger)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensureLogDir đảm bảo thư mục logs tồn tại
|
||||||
|
func ensureLogDir(logPath string) {
|
||||||
|
// Tìm vị trí thư mục cuối cùng trong đường dẫn
|
||||||
|
lastSlash := 0
|
||||||
|
for i := len(logPath) - 1; i >= 0; i-- {
|
||||||
|
if logPath[i] == '/' {
|
||||||
|
lastSlash = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nếu không có thư mục, không cần tạo
|
||||||
|
if lastSlash == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tạo thư mục nếu chưa tồn tại
|
||||||
|
dirPath := logPath[:lastSlash]
|
||||||
|
if _, err := os.Stat(dirPath); os.IsNotExist(err) {
|
||||||
|
os.MkdirAll(dirPath, 0755)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// newLogger tạo một logger mới
|
||||||
|
func newLogger(config global.LoggerConfig) *zap.Logger {
|
||||||
|
// Xác định level log
|
||||||
|
var level zapcore.Level
|
||||||
|
switch config.LogLevel {
|
||||||
|
case "debug":
|
||||||
|
level = zapcore.DebugLevel
|
||||||
|
case "info":
|
||||||
|
level = zapcore.InfoLevel
|
||||||
|
case "warn":
|
||||||
|
level = zapcore.WarnLevel
|
||||||
|
case "error":
|
||||||
|
level = zapcore.ErrorLevel
|
||||||
|
default:
|
||||||
|
level = zapcore.InfoLevel
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cấu hình encoder
|
||||||
|
encoder := getEncoder()
|
||||||
|
|
||||||
|
// Cấu hình lumberjack để xoay vòng file log
|
||||||
|
hook := lumberjack.Logger{
|
||||||
|
Filename: config.FileLogName,
|
||||||
|
MaxSize: config.MaxSize, // megabytes
|
||||||
|
MaxBackups: config.MaxBackups,
|
||||||
|
MaxAge: config.MaxAge, // days
|
||||||
|
Compress: config.Compress,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tạo core cho zap
|
||||||
|
core := zapcore.NewCore(
|
||||||
|
encoder,
|
||||||
|
zapcore.NewMultiWriteSyncer(zapcore.AddSync(os.Stdout), zapcore.AddSync(&hook)),
|
||||||
|
level,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Tạo logger với các tùy chọn
|
||||||
|
return zap.New(core,
|
||||||
|
zap.AddCaller(),
|
||||||
|
zap.AddStacktrace(zapcore.ErrorLevel),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getEncoder trả về encoder cho zap
|
||||||
|
func getEncoder() zapcore.Encoder {
|
||||||
|
encoderConfig := zap.NewProductionEncoderConfig()
|
||||||
|
|
||||||
|
// Cấu hình thời gian
|
||||||
|
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
|
||||||
|
encoderConfig.TimeKey = "time"
|
||||||
|
|
||||||
|
// Cấu hình level
|
||||||
|
encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
|
||||||
|
|
||||||
|
// Cấu hình caller
|
||||||
|
encoderConfig.EncodeCaller = zapcore.ShortCallerEncoder
|
||||||
|
|
||||||
|
// Trả về encoder JSON
|
||||||
|
return zapcore.NewJSONEncoder(encoderConfig)
|
||||||
|
}
|
47
internal/initialize/mysql.go
Normal file
47
internal/initialize/mysql.go
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
package initialize
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/dungnt11/todoms_golang/global"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"gorm.io/driver/mysql"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// checkErrorPanic kiểm tra lỗi và gọi panic nếu có lỗi, đồng thời ghi log chi tiết.
|
||||||
|
func checkErrorPanic(err error, errString string) {
|
||||||
|
if err != nil {
|
||||||
|
global.Logger.Error(errString, zap.Error(err))
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// InitMysql khởi tạo kết nối đến cơ sở dữ liệu MySQL và trả về đối tượng *gorm.DB.
|
||||||
|
func InitMysql() *gorm.DB {
|
||||||
|
m := global.Config.Database
|
||||||
|
|
||||||
|
dsn := "%s:%s@tcp(%s:%v)/%s?charset=utf8mb4&parseTime=True&loc=Local"
|
||||||
|
s := fmt.Sprintf(dsn, m.Username, m.Password, m.Host, m.Port, m.Database)
|
||||||
|
db, err := gorm.Open(mysql.Open(s), &gorm.Config{
|
||||||
|
SkipDefaultTransaction: false,
|
||||||
|
})
|
||||||
|
checkErrorPanic(err, "InitMysql initialization error")
|
||||||
|
global.Logger.Info("Initializing MySQL Successfully")
|
||||||
|
setPool(db)
|
||||||
|
return db
|
||||||
|
}
|
||||||
|
|
||||||
|
// setPool thiết lập các thông số cho connection pool của cơ sở dữ liệu.
|
||||||
|
func setPool(db *gorm.DB) {
|
||||||
|
m := global.Config.Database
|
||||||
|
sqlDb, err := db.DB()
|
||||||
|
if err != nil {
|
||||||
|
global.Logger.Error("Failed to get sql.DB from gorm.DB", zap.Error(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sqlDb.SetMaxIdleConns(m.MaxIdleConns)
|
||||||
|
sqlDb.SetMaxOpenConns(m.MaxOpenConns)
|
||||||
|
sqlDb.SetConnMaxLifetime(time.Duration(m.ConnMaxLifetime) * time.Second)
|
||||||
|
}
|
93
internal/initialize/run.go
Normal file
93
internal/initialize/run.go
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
package initialize
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/dungnt11/todoms_golang/global"
|
||||||
|
"github.com/dungnt11/todoms_golang/internal/routers"
|
||||||
|
"github.com/dungnt11/todoms_golang/internal/wire"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Run khởi động ứng dụng
|
||||||
|
func Run() {
|
||||||
|
// Tải cấu hình từ file .env
|
||||||
|
if err := LoadConfig(); err != nil {
|
||||||
|
fmt.Printf("Không thể tải cấu hình: %s\n", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Khởi tạo logger
|
||||||
|
global.Logger = InitLogger()
|
||||||
|
if global.Logger == nil {
|
||||||
|
fmt.Println("Khởi tạo logger thất bại")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Khởi tạo database
|
||||||
|
global.Mdb = InitMysql()
|
||||||
|
if global.Mdb == nil {
|
||||||
|
global.Logger.Error("Khởi tạo MySQL thất bại")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Khởi tạo router
|
||||||
|
r := provideRouter()
|
||||||
|
|
||||||
|
// Khởi tạo controllers thông qua wire
|
||||||
|
controllers, err := wire.InitializeControllers()
|
||||||
|
if err != nil {
|
||||||
|
global.Logger.Error("Khởi tạo controllers thất bại", zap.Error(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Khởi tạo routers
|
||||||
|
routers.InitRouters(r, controllers)
|
||||||
|
|
||||||
|
// Khởi động server
|
||||||
|
port := global.Config.Server.Port
|
||||||
|
server := &http.Server{
|
||||||
|
Addr: fmt.Sprintf(":%s", port),
|
||||||
|
Handler: r,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Khởi động server trong goroutine riêng
|
||||||
|
go func() {
|
||||||
|
global.Logger.Info(fmt.Sprintf("Server đang chạy trên cổng %s", port))
|
||||||
|
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||||
|
global.Logger.Error("Server gặp lỗi", zap.Error(err))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Graceful shutdown
|
||||||
|
quit := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
<-quit
|
||||||
|
global.Logger.Info("Đang tắt server...")
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
if err := server.Shutdown(ctx); err != nil {
|
||||||
|
global.Logger.Error("Server shutdown gặp lỗi", zap.Error(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
global.Logger.Info("Server đã tắt thành công.")
|
||||||
|
}
|
||||||
|
|
||||||
|
func provideRouter() *gin.Engine {
|
||||||
|
if global.Config.Server.AppEnv == "local" {
|
||||||
|
gin.SetMode(gin.DebugMode)
|
||||||
|
gin.ForceConsoleColor()
|
||||||
|
return gin.Default()
|
||||||
|
} else {
|
||||||
|
gin.SetMode(gin.ReleaseMode)
|
||||||
|
return gin.New()
|
||||||
|
}
|
||||||
|
}
|
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"`
|
||||||
|
Username string `json:"username" gorm:"uniqueIndex;not null"`
|
||||||
|
Password string `json:"-" gorm:"not null"` // Không trả về password trong JSON
|
||||||
|
Name string `json:"name"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
DeletedAt gorm.DeletedAt `json:"-" gorm:"index"`
|
||||||
|
}
|
23
internal/repositories/interfaces.go
Normal file
23
internal/repositories/interfaces.go
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package repositories
|
||||||
|
|
||||||
|
import "github.com/dungnt11/todoms_golang/internal/models"
|
||||||
|
|
||||||
|
// IUserRepository định nghĩa interface cho UserRepository
|
||||||
|
type IUserRepository interface {
|
||||||
|
Create(user *models.User) error
|
||||||
|
FindByID(id uint) (*models.User, error)
|
||||||
|
FindByUsername(username string) (*models.User, error)
|
||||||
|
Update(user *models.User) error
|
||||||
|
Delete(id uint) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// IProductRepository định nghĩa interface cho ProductRepository
|
||||||
|
type IProductRepository interface {
|
||||||
|
Create(product *models.Product) error
|
||||||
|
FindByID(id uint) (*models.Product, error)
|
||||||
|
FindAll() ([]*models.Product, error)
|
||||||
|
Update(product *models.Product) error
|
||||||
|
Delete(id uint) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Các interface khác có thể được thêm vào đây khi cần thiết
|
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/todoms_golang/internal/models"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProductRepository triển khai IProductRepository
|
||||||
|
type ProductRepository struct {
|
||||||
|
db *gorm.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewProductRepository tạo một instance mới của ProductRepository
|
||||||
|
func NewProductRepository(db *gorm.DB) *ProductRepository {
|
||||||
|
return &ProductRepository{
|
||||||
|
db: db,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create tạo một sản phẩm mới
|
||||||
|
func (r *ProductRepository) Create(product *models.Product) error {
|
||||||
|
return r.db.Create(product).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindByID tìm sản phẩm theo ID
|
||||||
|
func (r *ProductRepository) FindByID(id uint) (*models.Product, error) {
|
||||||
|
var product models.Product
|
||||||
|
err := r.db.First(&product, id).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &product, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindAll lấy tất cả sản phẩm
|
||||||
|
func (r *ProductRepository) FindAll() ([]*models.Product, error) {
|
||||||
|
var products []*models.Product
|
||||||
|
err := r.db.Find(&products).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return products, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update cập nhật thông tin sản phẩm
|
||||||
|
func (r *ProductRepository) Update(product *models.Product) error {
|
||||||
|
return r.db.Save(product).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete xóa sản phẩm theo ID
|
||||||
|
func (r *ProductRepository) Delete(id uint) error {
|
||||||
|
return r.db.Delete(&models.Product{}, id).Error
|
||||||
|
}
|
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/todoms_golang/internal/models"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UserRepository triển khai IUserRepository
|
||||||
|
type UserRepository struct {
|
||||||
|
db *gorm.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewUserRepository tạo một instance mới của UserRepository
|
||||||
|
func NewUserRepository(db *gorm.DB) *UserRepository {
|
||||||
|
return &UserRepository{
|
||||||
|
db: db,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create tạo một user mới trong database
|
||||||
|
func (ur *UserRepository) Create(user *models.User) error {
|
||||||
|
return ur.db.Create(user).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindByID tìm user theo ID
|
||||||
|
func (ur *UserRepository) FindByID(id uint) (*models.User, error) {
|
||||||
|
var user models.User
|
||||||
|
err := ur.db.First(&user, id).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &user, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindByUsername tìm user theo username
|
||||||
|
func (ur *UserRepository) FindByUsername(username string) (*models.User, error) {
|
||||||
|
var user models.User
|
||||||
|
err := ur.db.Where("username = ?", username).First(&user).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &user, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update cập nhật thông tin user
|
||||||
|
func (ur *UserRepository) Update(user *models.User) error {
|
||||||
|
return ur.db.Save(user).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete xóa user theo ID
|
||||||
|
func (ur *UserRepository) Delete(id uint) error {
|
||||||
|
return ur.db.Delete(&models.User{}, id).Error
|
||||||
|
}
|
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/todoms_golang/internal/controllers"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
// InitProductRoutes khởi tạo các routes cho product
|
||||||
|
func InitProductRoutes(router *gin.RouterGroup, productController controllers.IProductController) {
|
||||||
|
productRoutes := router.Group("/products")
|
||||||
|
{
|
||||||
|
productRoutes.POST("", productController.CreateProduct)
|
||||||
|
productRoutes.GET("", productController.GetAllProducts)
|
||||||
|
productRoutes.GET("/:id", productController.GetProduct)
|
||||||
|
productRoutes.PUT("/:id", productController.UpdateProduct)
|
||||||
|
productRoutes.DELETE("/:id", productController.DeleteProduct)
|
||||||
|
}
|
||||||
|
}
|
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/todoms_golang/internal/controllers"
|
||||||
|
"github.com/dungnt11/todoms_golang/internal/routers/product"
|
||||||
|
"github.com/dungnt11/todoms_golang/internal/routers/user"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
// InitRouters khởi tạo tất cả các routers
|
||||||
|
func InitRouters(r *gin.Engine, controllers *controllers.Controllers) {
|
||||||
|
// API v1
|
||||||
|
apiV1 := r.Group("/api/v1")
|
||||||
|
|
||||||
|
// Khởi tạo các routes
|
||||||
|
user.InitUserRoutes(apiV1, controllers.UserController)
|
||||||
|
product.InitProductRoutes(apiV1, controllers.ProductController)
|
||||||
|
|
||||||
|
// Các routes khác có thể được thêm vào đây
|
||||||
|
}
|
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/todoms_golang/internal/controllers"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
// InitUserRoutes khởi tạo các routes liên quan đến user
|
||||||
|
func InitUserRoutes(router *gin.RouterGroup, userController controllers.IUserController) {
|
||||||
|
userGroup := router.Group("/users")
|
||||||
|
{
|
||||||
|
userGroup.POST("/register", userController.Register)
|
||||||
|
userGroup.POST("/login", userController.Login)
|
||||||
|
userGroup.GET("/profile", userController.GetProfile)
|
||||||
|
userGroup.PUT("/profile", userController.UpdateProfile)
|
||||||
|
}
|
||||||
|
}
|
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/todoms_golang/internal/models"
|
||||||
|
"github.com/dungnt11/todoms_golang/internal/repositories"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProductService triển khai IProductService
|
||||||
|
type ProductService struct {
|
||||||
|
productRepo repositories.IProductRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewProductService tạo một instance mới của ProductService
|
||||||
|
func NewProductService(productRepo repositories.IProductRepository) *ProductService {
|
||||||
|
return &ProductService{
|
||||||
|
productRepo: productRepo,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateProduct tạo một sản phẩm mới
|
||||||
|
func (s *ProductService) CreateProduct(c *gin.Context) (map[string]interface{}, error) {
|
||||||
|
var product models.Product
|
||||||
|
if err := c.ShouldBindJSON(&product); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if product.Name == "" {
|
||||||
|
return nil, errors.New("tên sản phẩm không được để trống")
|
||||||
|
}
|
||||||
|
|
||||||
|
if product.Price <= 0 {
|
||||||
|
return nil, errors.New("giá sản phẩm phải lớn hơn 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.productRepo.Create(&product); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return map[string]interface{}{
|
||||||
|
"message": "tạo sản phẩm thành công",
|
||||||
|
"product": product,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetProduct lấy thông tin sản phẩm theo ID
|
||||||
|
func (s *ProductService) GetProduct(id uint) (map[string]interface{}, error) {
|
||||||
|
product, err := s.productRepo.FindByID(id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("không tìm thấy sản phẩm")
|
||||||
|
}
|
||||||
|
|
||||||
|
return map[string]interface{}{
|
||||||
|
"product": product,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAllProducts lấy tất cả sản phẩm
|
||||||
|
func (s *ProductService) GetAllProducts() (map[string]interface{}, error) {
|
||||||
|
products, err := s.productRepo.FindAll()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return map[string]interface{}{
|
||||||
|
"products": products,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateProduct cập nhật thông tin sản phẩm
|
||||||
|
func (s *ProductService) UpdateProduct(id uint, c *gin.Context) (map[string]interface{}, error) {
|
||||||
|
product, err := s.productRepo.FindByID(id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("không tìm thấy sản phẩm")
|
||||||
|
}
|
||||||
|
|
||||||
|
var updatedProduct models.Product
|
||||||
|
if err := c.ShouldBindJSON(&updatedProduct); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cập nhật thông tin
|
||||||
|
if updatedProduct.Name != "" {
|
||||||
|
product.Name = updatedProduct.Name
|
||||||
|
}
|
||||||
|
if updatedProduct.Description != "" {
|
||||||
|
product.Description = updatedProduct.Description
|
||||||
|
}
|
||||||
|
if updatedProduct.Price > 0 {
|
||||||
|
product.Price = updatedProduct.Price
|
||||||
|
}
|
||||||
|
if updatedProduct.Quantity >= 0 {
|
||||||
|
product.Quantity = updatedProduct.Quantity
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.productRepo.Update(product); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return map[string]interface{}{
|
||||||
|
"message": "cập nhật sản phẩm thành công",
|
||||||
|
"product": product,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteProduct xóa sản phẩm theo ID
|
||||||
|
func (s *ProductService) DeleteProduct(id uint) (map[string]interface{}, error) {
|
||||||
|
// Kiểm tra sản phẩm tồn tại
|
||||||
|
_, err := s.productRepo.FindByID(id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("không tìm thấy sản phẩm")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.productRepo.Delete(id); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return map[string]interface{}{
|
||||||
|
"message": "xóa sản phẩm thành công",
|
||||||
|
}, nil
|
||||||
|
}
|
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/todoms_golang/internal/models"
|
||||||
|
"github.com/dungnt11/todoms_golang/internal/repositories"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UserService triển khai IUserService
|
||||||
|
type UserService struct {
|
||||||
|
userRepo repositories.IUserRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewUserService tạo một instance mới của UserService
|
||||||
|
func NewUserService(userRepo repositories.IUserRepository) *UserService {
|
||||||
|
return &UserService{
|
||||||
|
userRepo: userRepo,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register xử lý logic đăng ký người dùng
|
||||||
|
func (us *UserService) Register(c *gin.Context) (map[string]interface{}, error) {
|
||||||
|
// Đọc dữ liệu từ request
|
||||||
|
var user models.User
|
||||||
|
if err := c.ShouldBindJSON(&user); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kiểm tra username đã tồn tại chưa
|
||||||
|
existingUser, _ := us.userRepo.FindByUsername(user.Username)
|
||||||
|
if existingUser != nil {
|
||||||
|
return nil, errors.New("username đã được sử dụng")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lưu vào database thông qua repository
|
||||||
|
err := us.userRepo.Create(&user)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trả về kết quả
|
||||||
|
return map[string]interface{}{
|
||||||
|
"message": "Đăng ký thành công",
|
||||||
|
"user_id": user.ID,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Login xử lý logic đăng nhập
|
||||||
|
func (us *UserService) Login(c *gin.Context) (map[string]interface{}, error) {
|
||||||
|
// Đọc dữ liệu từ request
|
||||||
|
var loginData struct {
|
||||||
|
Username string `json:"username"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
}
|
||||||
|
if err := c.ShouldBindJSON(&loginData); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tìm user theo username
|
||||||
|
user, err := us.userRepo.FindByUsername(loginData.Username)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("username hoặc mật khẩu không đúng")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kiểm tra mật khẩu (giả định)
|
||||||
|
if user.Password != loginData.Password {
|
||||||
|
return nil, errors.New("username hoặc mật khẩu không đúng")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trả về kết quả
|
||||||
|
return map[string]interface{}{
|
||||||
|
"message": "Đăng nhập thành công",
|
||||||
|
"user_id": user.ID,
|
||||||
|
"token": "jwt_token_here", // Trong thực tế, bạn sẽ tạo JWT token
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetProfile lấy thông tin profile của người dùng
|
||||||
|
func (us *UserService) GetProfile(userID uint) (map[string]interface{}, error) {
|
||||||
|
// Tìm user theo ID
|
||||||
|
user, err := us.userRepo.FindByID(userID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("không tìm thấy người dùng")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trả về kết quả
|
||||||
|
return map[string]interface{}{
|
||||||
|
"user": user,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateProfile cập nhật thông tin profile của người dùng
|
||||||
|
func (us *UserService) UpdateProfile(userID uint, data map[string]interface{}) (map[string]interface{}, error) {
|
||||||
|
// Tìm user theo ID
|
||||||
|
user, err := us.userRepo.FindByID(userID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("không tìm thấy người dùng")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cập nhật thông tin (giả định)
|
||||||
|
if name, ok := data["name"].(string); ok {
|
||||||
|
user.Name = name
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lưu vào database
|
||||||
|
err = us.userRepo.Update(user)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trả về kết quả
|
||||||
|
return map[string]interface{}{
|
||||||
|
"message": "Cập nhật thành công",
|
||||||
|
"user": user,
|
||||||
|
}, nil
|
||||||
|
}
|
17
internal/wire/injector.go
Normal file
17
internal/wire/injector.go
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
//go:build wireinject
|
||||||
|
// +build wireinject
|
||||||
|
|
||||||
|
package wire
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/dungnt11/todoms_golang/internal/controllers"
|
||||||
|
"github.com/google/wire"
|
||||||
|
)
|
||||||
|
|
||||||
|
// InitializeControllers khởi tạo tất cả các controllers
|
||||||
|
func InitializeControllers() (*controllers.Controllers, error) {
|
||||||
|
wire.Build(
|
||||||
|
AppSet,
|
||||||
|
)
|
||||||
|
return nil, nil
|
||||||
|
}
|
49
internal/wire/wire.go
Normal file
49
internal/wire/wire.go
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
// Package wire chứa các định nghĩa cho dependency injection sử dụng Google Wire
|
||||||
|
package wire
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/dungnt11/todoms_golang/global"
|
||||||
|
"github.com/dungnt11/todoms_golang/internal/controllers"
|
||||||
|
"github.com/dungnt11/todoms_golang/internal/repositories"
|
||||||
|
"github.com/dungnt11/todoms_golang/internal/services"
|
||||||
|
"github.com/google/wire"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RepositorySet là tập hợp các provider cho repositories
|
||||||
|
var RepositorySet = wire.NewSet(
|
||||||
|
repositories.NewUserRepository,
|
||||||
|
wire.Bind(new(repositories.IUserRepository), new(*repositories.UserRepository)),
|
||||||
|
repositories.NewProductRepository,
|
||||||
|
wire.Bind(new(repositories.IProductRepository), new(*repositories.ProductRepository)),
|
||||||
|
)
|
||||||
|
|
||||||
|
// ServiceSet là tập hợp các provider cho services
|
||||||
|
var ServiceSet = wire.NewSet(
|
||||||
|
services.NewUserService,
|
||||||
|
wire.Bind(new(services.IUserService), new(*services.UserService)),
|
||||||
|
services.NewProductService,
|
||||||
|
wire.Bind(new(services.IProductService), new(*services.ProductService)),
|
||||||
|
)
|
||||||
|
|
||||||
|
// ControllerSet là tập hợp các provider cho controllers
|
||||||
|
var ControllerSet = wire.NewSet(
|
||||||
|
controllers.NewUserController,
|
||||||
|
wire.Bind(new(controllers.IUserController), new(*controllers.UserController)),
|
||||||
|
controllers.NewProductController,
|
||||||
|
wire.Bind(new(controllers.IProductController), new(*controllers.ProductController)),
|
||||||
|
controllers.NewControllers,
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProvideDB cung cấp instance của *gorm.DB
|
||||||
|
func ProvideDB() *gorm.DB {
|
||||||
|
return global.Mdb
|
||||||
|
}
|
||||||
|
|
||||||
|
// AppSet là tập hợp tất cả các provider
|
||||||
|
var AppSet = wire.NewSet(
|
||||||
|
RepositorySet,
|
||||||
|
ServiceSet,
|
||||||
|
ControllerSet,
|
||||||
|
ProvideDB,
|
||||||
|
)
|
28
internal/wire/wire_gen.go
Normal file
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/todoms_golang/internal/controllers"
|
||||||
|
"github.com/dungnt11/todoms_golang/internal/repositories"
|
||||||
|
"github.com/dungnt11/todoms_golang/internal/services"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Injectors from injector.go:
|
||||||
|
|
||||||
|
// InitializeControllers khởi tạo tất cả các controllers
|
||||||
|
func InitializeControllers() (*controllers.Controllers, error) {
|
||||||
|
db := ProvideDB()
|
||||||
|
userRepository := repositories.NewUserRepository(db)
|
||||||
|
userService := services.NewUserService(userRepository)
|
||||||
|
userController := controllers.NewUserController(userService)
|
||||||
|
productRepository := repositories.NewProductRepository(db)
|
||||||
|
productService := services.NewProductService(productRepository)
|
||||||
|
productController := controllers.NewProductController(productService)
|
||||||
|
controllersControllers := controllers.NewControllers(userController, productController)
|
||||||
|
return controllersControllers, nil
|
||||||
|
}
|
30
nginx/conf.d/default.conf
Normal file
30
nginx/conf.d/default.conf
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
upstream app_servers {
|
||||||
|
server app:8083;
|
||||||
|
server app:8084;
|
||||||
|
server app:8085;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name localhost;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://app_servers;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_connect_timeout 60s;
|
||||||
|
proxy_send_timeout 60s;
|
||||||
|
proxy_read_timeout 60s;
|
||||||
|
proxy_buffering on;
|
||||||
|
proxy_buffer_size 128k;
|
||||||
|
proxy_buffers 4 256k;
|
||||||
|
proxy_busy_buffers_size 256k;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /health {
|
||||||
|
access_log off;
|
||||||
|
return 200 'healthy\n';
|
||||||
|
}
|
||||||
|
}
|
25
prometheus/prometheus.yml
Normal file
25
prometheus/prometheus.yml
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
global:
|
||||||
|
scrape_interval: 15s
|
||||||
|
evaluation_interval: 15s
|
||||||
|
scrape_timeout: 10s
|
||||||
|
|
||||||
|
scrape_configs:
|
||||||
|
- job_name: 'app'
|
||||||
|
static_configs:
|
||||||
|
- targets: ['app:8080']
|
||||||
|
metrics_path: '/metrics'
|
||||||
|
scrape_interval: 15s
|
||||||
|
scrape_timeout: 10s
|
||||||
|
|
||||||
|
- job_name: 'mysql'
|
||||||
|
static_configs:
|
||||||
|
- targets: ['mysql_bp:3306']
|
||||||
|
metrics_path: '/metrics'
|
||||||
|
scrape_interval: 15s
|
||||||
|
scrape_timeout: 10s
|
||||||
|
|
||||||
|
- job_name: 'prometheus'
|
||||||
|
static_configs:
|
||||||
|
- targets: ['localhost:9090']
|
||||||
|
scrape_interval: 15s
|
||||||
|
scrape_timeout: 10s
|
21
scripts/backup.sh
Normal file
21
scripts/backup.sh
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Thông tin kết nối
|
||||||
|
DB_HOST="mysql_bp"
|
||||||
|
DB_USER="root"
|
||||||
|
DB_PASS="root"
|
||||||
|
DB_NAME="blueprint"
|
||||||
|
BACKUP_DIR="/backup"
|
||||||
|
DATE=$(date +%Y%m%d_%H%M%S)
|
||||||
|
|
||||||
|
# Tạo backup
|
||||||
|
echo "Starting backup at $DATE"
|
||||||
|
mysqldump -h $DB_HOST -u $DB_USER -p$DB_PASS $DB_NAME > "$BACKUP_DIR/backup_$DATE.sql"
|
||||||
|
|
||||||
|
# Nén backup
|
||||||
|
gzip "$BACKUP_DIR/backup_$DATE.sql"
|
||||||
|
|
||||||
|
# Xóa backup cũ (giữ 7 ngày)
|
||||||
|
find $BACKUP_DIR -name "backup_*.sql.gz" -mtime +7 -delete
|
||||||
|
|
||||||
|
echo "Backup completed"
|
Loading…
x
Reference in New Issue
Block a user