From d83f6c11456f25cf0235c2e8fdcf404888fbff7e Mon Sep 17 00:00:00 2001 From: koh Date: Sat, 12 Apr 2025 15:25:34 +0700 Subject: [PATCH] init --- .air.toml | 46 ++++++ .env.prod | 41 +++++ .gitignore | 35 ++++ Dockerfile.dev | 27 ++++ Dockerfile.prod | 46 ++++++ Makefile | 64 ++++++++ README.md | 138 ++++++++++++++++ cmd/api/main.go | 9 ++ docker-compose.dev.yml | 92 +++++++++++ docker-compose.prod.yml | 155 ++++++++++++++++++ global/global.go | 44 +++++ go.mod | 52 ++++++ go.sum | 168 ++++++++++++++++++++ internal/controllers/controllers.go | 15 ++ internal/controllers/interfaces.go | 22 +++ internal/controllers/product_controller.go | 125 +++++++++++++++ internal/controllers/user_controller.go | 93 +++++++++++ internal/initialize/loadconfig.go | 65 ++++++++ internal/initialize/logger.go | 103 ++++++++++++ internal/initialize/mysql.go | 47 ++++++ internal/initialize/run.go | 93 +++++++++++ internal/models/product.go | 16 ++ internal/models/user.go | 18 +++ internal/repositories/interfaces.go | 23 +++ internal/repositories/product_repository.go | 53 ++++++ internal/repositories/user_repository.go | 53 ++++++ internal/routers/product/product_router.go | 18 +++ internal/routers/router_group.go | 20 +++ internal/routers/user/user_router.go | 17 ++ internal/services/interfaces.go | 22 +++ internal/services/product_service.go | 123 ++++++++++++++ internal/services/user_service.go | 118 ++++++++++++++ internal/wire/injector.go | 17 ++ internal/wire/wire.go | 49 ++++++ internal/wire/wire_gen.go | 28 ++++ nginx/conf.d/default.conf | 30 ++++ prometheus/prometheus.yml | 25 +++ scripts/backup.sh | 21 +++ 38 files changed, 2131 insertions(+) create mode 100644 .air.toml create mode 100644 .env.prod create mode 100644 .gitignore create mode 100644 Dockerfile.dev create mode 100644 Dockerfile.prod create mode 100644 Makefile create mode 100644 README.md create mode 100644 cmd/api/main.go create mode 100644 docker-compose.dev.yml create mode 100644 docker-compose.prod.yml create mode 100644 global/global.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 internal/controllers/controllers.go create mode 100644 internal/controllers/interfaces.go create mode 100644 internal/controllers/product_controller.go create mode 100644 internal/controllers/user_controller.go create mode 100644 internal/initialize/loadconfig.go create mode 100644 internal/initialize/logger.go create mode 100644 internal/initialize/mysql.go create mode 100644 internal/initialize/run.go create mode 100644 internal/models/product.go create mode 100644 internal/models/user.go create mode 100644 internal/repositories/interfaces.go create mode 100644 internal/repositories/product_repository.go create mode 100644 internal/repositories/user_repository.go create mode 100644 internal/routers/product/product_router.go create mode 100644 internal/routers/router_group.go create mode 100644 internal/routers/user/user_router.go create mode 100644 internal/services/interfaces.go create mode 100644 internal/services/product_service.go create mode 100644 internal/services/user_service.go create mode 100644 internal/wire/injector.go create mode 100644 internal/wire/wire.go create mode 100644 internal/wire/wire_gen.go create mode 100644 nginx/conf.d/default.conf create mode 100644 prometheus/prometheus.yml create mode 100644 scripts/backup.sh diff --git a/.air.toml b/.air.toml new file mode 100644 index 0000000..5a3c060 --- /dev/null +++ b/.air.toml @@ -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 diff --git a/.env.prod b/.env.prod new file mode 100644 index 0000000..0a7b279 --- /dev/null +++ b/.env.prod @@ -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 \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..18e5f2a --- /dev/null +++ b/.gitignore @@ -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 diff --git a/Dockerfile.dev b/Dockerfile.dev new file mode 100644 index 0000000..d642437 --- /dev/null +++ b/Dockerfile.dev @@ -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"] \ No newline at end of file diff --git a/Dockerfile.prod b/Dockerfile.prod new file mode 100644 index 0000000..942b83e --- /dev/null +++ b/Dockerfile.prod @@ -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"] \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..9d76e0e --- /dev/null +++ b/Makefile @@ -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 diff --git a/README.md b/README.md new file mode 100644 index 0000000..feee80e --- /dev/null +++ b/README.md @@ -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: `_controller.go` (ví dụ: `user_controller.go`) + - Services: `_service.go` (ví dụ: `user_service.go`) + - Repositories: `_repository.go` (ví dụ: `user_repository.go`) + - Routers: `_router.go` (ví dụ: `user_router.go`) + - Models: `.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 +``` diff --git a/cmd/api/main.go b/cmd/api/main.go new file mode 100644 index 0000000..8882c2c --- /dev/null +++ b/cmd/api/main.go @@ -0,0 +1,9 @@ +package main + +import ( + "github.com/dungnt11/todoms_golang/internal/initialize" +) + +func main() { + initialize.Run() +} diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml new file mode 100644 index 0000000..e21e9a1 --- /dev/null +++ b/docker-compose.dev.yml @@ -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: \ No newline at end of file diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml new file mode 100644 index 0000000..870a189 --- /dev/null +++ b/docker-compose.prod.yml @@ -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: \ No newline at end of file diff --git a/global/global.go b/global/global.go new file mode 100644 index 0000000..c33b439 --- /dev/null +++ b/global/global.go @@ -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 +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..e170076 --- /dev/null +++ b/go.mod @@ -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 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..3494cef --- /dev/null +++ b/go.sum @@ -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= diff --git a/internal/controllers/controllers.go b/internal/controllers/controllers.go new file mode 100644 index 0000000..d95d464 --- /dev/null +++ b/internal/controllers/controllers.go @@ -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, + } +} diff --git a/internal/controllers/interfaces.go b/internal/controllers/interfaces.go new file mode 100644 index 0000000..acfdd34 --- /dev/null +++ b/internal/controllers/interfaces.go @@ -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 diff --git a/internal/controllers/product_controller.go b/internal/controllers/product_controller.go new file mode 100644 index 0000000..0734d38 --- /dev/null +++ b/internal/controllers/product_controller.go @@ -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) +} diff --git a/internal/controllers/user_controller.go b/internal/controllers/user_controller.go new file mode 100644 index 0000000..fa3a7e3 --- /dev/null +++ b/internal/controllers/user_controller.go @@ -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) +} diff --git a/internal/initialize/loadconfig.go b/internal/initialize/loadconfig.go new file mode 100644 index 0000000..3fdbfa8 --- /dev/null +++ b/internal/initialize/loadconfig.go @@ -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 +} diff --git a/internal/initialize/logger.go b/internal/initialize/logger.go new file mode 100644 index 0000000..da74a26 --- /dev/null +++ b/internal/initialize/logger.go @@ -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) +} diff --git a/internal/initialize/mysql.go b/internal/initialize/mysql.go new file mode 100644 index 0000000..2eef30a --- /dev/null +++ b/internal/initialize/mysql.go @@ -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) +} diff --git a/internal/initialize/run.go b/internal/initialize/run.go new file mode 100644 index 0000000..5a3478e --- /dev/null +++ b/internal/initialize/run.go @@ -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() + } +} diff --git a/internal/models/product.go b/internal/models/product.go new file mode 100644 index 0000000..4eb1879 --- /dev/null +++ b/internal/models/product.go @@ -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"` +} diff --git a/internal/models/user.go b/internal/models/user.go new file mode 100644 index 0000000..3837b88 --- /dev/null +++ b/internal/models/user.go @@ -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"` +} diff --git a/internal/repositories/interfaces.go b/internal/repositories/interfaces.go new file mode 100644 index 0000000..e485066 --- /dev/null +++ b/internal/repositories/interfaces.go @@ -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 diff --git a/internal/repositories/product_repository.go b/internal/repositories/product_repository.go new file mode 100644 index 0000000..77440f2 --- /dev/null +++ b/internal/repositories/product_repository.go @@ -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 +} diff --git a/internal/repositories/user_repository.go b/internal/repositories/user_repository.go new file mode 100644 index 0000000..ebceb28 --- /dev/null +++ b/internal/repositories/user_repository.go @@ -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 +} diff --git a/internal/routers/product/product_router.go b/internal/routers/product/product_router.go new file mode 100644 index 0000000..3476181 --- /dev/null +++ b/internal/routers/product/product_router.go @@ -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) + } +} diff --git a/internal/routers/router_group.go b/internal/routers/router_group.go new file mode 100644 index 0000000..2eb8727 --- /dev/null +++ b/internal/routers/router_group.go @@ -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 +} diff --git a/internal/routers/user/user_router.go b/internal/routers/user/user_router.go new file mode 100644 index 0000000..e423763 --- /dev/null +++ b/internal/routers/user/user_router.go @@ -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) + } +} diff --git a/internal/services/interfaces.go b/internal/services/interfaces.go new file mode 100644 index 0000000..08587ac --- /dev/null +++ b/internal/services/interfaces.go @@ -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 diff --git a/internal/services/product_service.go b/internal/services/product_service.go new file mode 100644 index 0000000..194ff24 --- /dev/null +++ b/internal/services/product_service.go @@ -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 +} diff --git a/internal/services/user_service.go b/internal/services/user_service.go new file mode 100644 index 0000000..18012c5 --- /dev/null +++ b/internal/services/user_service.go @@ -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 +} diff --git a/internal/wire/injector.go b/internal/wire/injector.go new file mode 100644 index 0000000..81c9db9 --- /dev/null +++ b/internal/wire/injector.go @@ -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 +} diff --git a/internal/wire/wire.go b/internal/wire/wire.go new file mode 100644 index 0000000..e4b2208 --- /dev/null +++ b/internal/wire/wire.go @@ -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, +) diff --git a/internal/wire/wire_gen.go b/internal/wire/wire_gen.go new file mode 100644 index 0000000..e9d1523 --- /dev/null +++ b/internal/wire/wire_gen.go @@ -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 +} diff --git a/nginx/conf.d/default.conf b/nginx/conf.d/default.conf new file mode 100644 index 0000000..95a6d4b --- /dev/null +++ b/nginx/conf.d/default.conf @@ -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'; + } +} \ No newline at end of file diff --git a/prometheus/prometheus.yml b/prometheus/prometheus.yml new file mode 100644 index 0000000..1ee3f5e --- /dev/null +++ b/prometheus/prometheus.yml @@ -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 \ No newline at end of file diff --git a/scripts/backup.sh b/scripts/backup.sh new file mode 100644 index 0000000..538a111 --- /dev/null +++ b/scripts/backup.sh @@ -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" \ No newline at end of file