First Commit
This commit is contained in:
parent
05426244ed
commit
a77e811d24
37
.air.toml
Normal file
37
.air.toml
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
root = "."
|
||||||
|
testdata_dir = "testdata"
|
||||||
|
tmp_dir = "tmp"
|
||||||
|
|
||||||
|
[build]
|
||||||
|
args_bin = []
|
||||||
|
bin = "./tmp/main.exe"
|
||||||
|
cmd = "go build -o ./tmp/main.exe ./cmd/server/main.go"
|
||||||
|
delay = 1000
|
||||||
|
exclude_dir = ["assets", "tmp", "vendor", "testdata"]
|
||||||
|
exclude_file = []
|
||||||
|
exclude_regex = ["_test.go"]
|
||||||
|
exclude_unchanged = false
|
||||||
|
follow_symlink = false
|
||||||
|
full_bin = ""
|
||||||
|
include_dir = []
|
||||||
|
include_ext = ["go", "tpl", "tmpl", "html"]
|
||||||
|
kill_delay = "0s"
|
||||||
|
log = "build-errors.log"
|
||||||
|
send_interrupt = false
|
||||||
|
stop_on_error = true
|
||||||
|
|
||||||
|
[color]
|
||||||
|
app = ""
|
||||||
|
build = "yellow"
|
||||||
|
main = "magenta"
|
||||||
|
runner = "green"
|
||||||
|
watcher = "cyan"
|
||||||
|
|
||||||
|
[log]
|
||||||
|
time = false
|
||||||
|
|
||||||
|
[misc]
|
||||||
|
clean_on_exit = false
|
||||||
|
|
||||||
|
[screen]
|
||||||
|
clear_on_rebuild = false
|
||||||
85
.drone.yml
Normal file
85
.drone.yml
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
# File: .drone.yml (Phiên bản cải thiện)
|
||||||
|
kind: pipeline
|
||||||
|
type: docker # Giữ nguyên type bạn đã chỉ định
|
||||||
|
name: default
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
- name: go-modules # Cache thư viện Go
|
||||||
|
temp: {}
|
||||||
|
# (Optional) Thêm volume cache cho trivy nếu cần và runner hỗ trợ
|
||||||
|
# - name: trivy-cache
|
||||||
|
# path: /root/.cache/trivy
|
||||||
|
# temp: {}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
# ===== Các bước kiểm tra có thể chạy song song =====
|
||||||
|
- name: lint
|
||||||
|
image: golangci/golangci-lint:latest
|
||||||
|
commands:
|
||||||
|
- golangci-lint run
|
||||||
|
depends_on: [] # Chạy song song, không phụ thuộc bước nào khác
|
||||||
|
when:
|
||||||
|
event: [push, pull_request, tag]
|
||||||
|
|
||||||
|
- name: security-scan # Đã cập nhật lệnh trivy
|
||||||
|
image: aquasec/trivy:latest
|
||||||
|
# volumes: # (Optional) Nếu dùng cache trivy
|
||||||
|
# - name: trivy-cache
|
||||||
|
# path: /root/.cache/trivy
|
||||||
|
commands:
|
||||||
|
# Cập nhật: Thêm --exit-code, --severity, --ignore-unfixed để làm Security Gate
|
||||||
|
- trivy fs --exit-code 1 --severity HIGH,CRITICAL --ignore-unfixed --security-checks vuln .
|
||||||
|
depends_on: [] # Chạy song song, không phụ thuộc bước nào khác
|
||||||
|
when:
|
||||||
|
event: [push, pull_request, tag]
|
||||||
|
|
||||||
|
- name: test
|
||||||
|
image: golang:1.23 # Thống nhất phiên bản Go
|
||||||
|
volumes:
|
||||||
|
- name: go-modules
|
||||||
|
path: /go/pkg/mod
|
||||||
|
commands:
|
||||||
|
- go test -v -coverprofile=coverage.out ./...
|
||||||
|
- go tool cover -func=coverage.out
|
||||||
|
depends_on: [] # Chạy song song, không phụ thuộc bước nào khác
|
||||||
|
when:
|
||||||
|
event: [push, pull_request, tag]
|
||||||
|
# ===== Kết thúc các bước chạy song song =====
|
||||||
|
|
||||||
|
# ===== Bước Build - Chạy SAU KHI các kiểm tra thành công =====
|
||||||
|
- name: build
|
||||||
|
image: golang:1.23 # Thống nhất phiên bản Go
|
||||||
|
volumes:
|
||||||
|
- name: go-modules
|
||||||
|
path: /go/pkg/mod
|
||||||
|
commands:
|
||||||
|
- go build -o bin/server ./cmd/server
|
||||||
|
# Cập nhật: Thêm depends_on để đảm bảo các bước kiểm tra hoàn thành trước
|
||||||
|
depends_on:
|
||||||
|
- lint
|
||||||
|
- security-scan
|
||||||
|
- test
|
||||||
|
when:
|
||||||
|
event: [push, pull_request, tag]
|
||||||
|
|
||||||
|
# ===== Bước Notify - Chạy cuối cùng =====
|
||||||
|
- name: notify
|
||||||
|
image: appleboy/drone-telegram
|
||||||
|
depends_on: [build] # Chỉ chạy sau khi bước build hoàn tất
|
||||||
|
settings:
|
||||||
|
# Giữ nguyên cấu hình secret đã sửa đúng
|
||||||
|
token:
|
||||||
|
from_secret: TELEGRAM_TOKEN
|
||||||
|
to:
|
||||||
|
from_secret: TELEGRAM_CHAT_ID
|
||||||
|
message: |
|
||||||
|
*Build Status*: `{{build.status}}`
|
||||||
|
*Project*: {{repo.name}}
|
||||||
|
*Branch*: {{build.branch}}
|
||||||
|
*Commit*: `{{truncate build.commit 8}}`
|
||||||
|
*Author*: {{build.author}}
|
||||||
|
*Link*: {{build.link}}
|
||||||
|
# depends_on: [ build ] # Thường không cần thiết vì when status sẽ xử lý
|
||||||
|
when:
|
||||||
|
status: [success, failure] # Chạy khi pipeline thành công hoặc thất bại
|
||||||
|
event: [push, pull_request, tag]
|
||||||
25
.env
Normal file
25
.env
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
# Database configuration
|
||||||
|
DB_USER=postgres
|
||||||
|
DB_PASSWORD=postgres
|
||||||
|
DB_NAME=matching_app
|
||||||
|
|
||||||
|
# App configuration
|
||||||
|
APP_PORT=8080
|
||||||
|
APP_ENV=development
|
||||||
|
LOG_LEVEL=debug
|
||||||
|
|
||||||
|
# Email settings (SMTP)
|
||||||
|
SMTP_HOST=smtp.example.com
|
||||||
|
SMTP_PORT=587
|
||||||
|
SMTP_USER=your_email@example.com
|
||||||
|
SMTP_PASSWORD=your_email_password
|
||||||
|
|
||||||
|
# AWS S3 Configuration
|
||||||
|
AWS_ACCESS_KEY_ID=your_access_key
|
||||||
|
AWS_SECRET_ACCESS_KEY=your_secret_key
|
||||||
|
AWS_REGION=ap-southeast-1
|
||||||
|
AWS_S3_BUCKET=your-bucket-name
|
||||||
|
|
||||||
|
# Telegram Notification
|
||||||
|
TELEGRAM_TOKEN=7141107096:AAGCOQG9N4Ex_MN95RbU16WjTJ_-JTM0s2A
|
||||||
|
TELEGRAM_CHAT_ID=-1002693476953
|
||||||
1
.windsurfrules
Normal file
1
.windsurfrules
Normal file
@ -0,0 +1 @@
|
|||||||
|
- Return Answer in Vietnamese
|
||||||
59
0001_init.sql.bck
Normal file
59
0001_init.sql.bck
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
-- Migration: Initial schema setup
|
||||||
|
|
||||||
|
CREATE TABLE profiles (
|
||||||
|
id UUID PRIMARY KEY,
|
||||||
|
full_name TEXT NOT NULL,
|
||||||
|
date_of_birth DATE NOT NULL,
|
||||||
|
gender TEXT,
|
||||||
|
preferred_gender TEXT,
|
||||||
|
phone_number TEXT,
|
||||||
|
email TEXT,
|
||||||
|
facebook_link TEXT,
|
||||||
|
photo_reference TEXT,
|
||||||
|
location TEXT,
|
||||||
|
team TEXT,
|
||||||
|
hobbies JSONB,
|
||||||
|
things_not_tried JSONB,
|
||||||
|
hopes_for_partner TEXT,
|
||||||
|
dealbreakers TEXT,
|
||||||
|
message_to_partner TEXT,
|
||||||
|
zodiac_sign TEXT,
|
||||||
|
access_code TEXT,
|
||||||
|
personal_link TEXT,
|
||||||
|
accepted_terms BOOLEAN DEFAULT FALSE,
|
||||||
|
registration_timestamp TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||||
|
status TEXT,
|
||||||
|
is_lucky_winner BOOLEAN DEFAULT FALSE,
|
||||||
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE match_outcomes (
|
||||||
|
id UUID PRIMARY KEY,
|
||||||
|
profile_id UUID REFERENCES profiles(id),
|
||||||
|
potential_match_profile_ids JSONB,
|
||||||
|
match_scores JSONB,
|
||||||
|
status TEXT,
|
||||||
|
selected_profile_id UUID,
|
||||||
|
generation_timestamp TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||||
|
accessed_timestamp TIMESTAMP WITH TIME ZONE,
|
||||||
|
selection_timestamp TIMESTAMP WITH TIME ZONE,
|
||||||
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE gifts (
|
||||||
|
id UUID PRIMARY KEY,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
description TEXT,
|
||||||
|
quantity INT DEFAULT 0,
|
||||||
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE gift_allocations (
|
||||||
|
id UUID PRIMARY KEY,
|
||||||
|
gift_id UUID REFERENCES gifts(id),
|
||||||
|
match_outcome_id UUID REFERENCES match_outcomes(id),
|
||||||
|
allocated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||||||
|
);
|
||||||
35
Dockerfile
Normal file
35
Dockerfile
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
# Build stage
|
||||||
|
FROM golang:1.23-alpine AS builder
|
||||||
|
WORKDIR /app
|
||||||
|
COPY go.mod go.sum ./
|
||||||
|
RUN go mod download
|
||||||
|
COPY . .
|
||||||
|
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-w -s" -o matching-app cmd/server/main.go
|
||||||
|
|
||||||
|
# Final image
|
||||||
|
FROM alpine:3.18
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Add non-root user
|
||||||
|
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
|
||||||
|
|
||||||
|
# Copy binary and resources
|
||||||
|
COPY --from=builder --chown=appuser:appgroup /app/matching-app .
|
||||||
|
COPY --from=builder --chown=appuser:appgroup /app/migrations ./migrations
|
||||||
|
COPY --from=builder --chown=appuser:appgroup /app/configs ./configs
|
||||||
|
|
||||||
|
# Environment variables
|
||||||
|
ENV APP_PORT=8080
|
||||||
|
ENV APP_ENV=production
|
||||||
|
|
||||||
|
# Install wget for healthcheck
|
||||||
|
RUN apk --no-cache add wget
|
||||||
|
|
||||||
|
# Healthcheck
|
||||||
|
HEALTHCHECK --interval=30s --timeout=3s \
|
||||||
|
CMD wget --no-verbose --tries=1 --spider http://localhost:${APP_PORT}/health || exit 1
|
||||||
|
|
||||||
|
EXPOSE ${APP_PORT}
|
||||||
|
USER appuser
|
||||||
|
ENTRYPOINT ["/app/matching-app"]
|
||||||
|
CMD []
|
||||||
222
Makefile
Normal file
222
Makefile
Normal file
@ -0,0 +1,222 @@
|
|||||||
|
# Makefile for matching-app
|
||||||
|
|
||||||
|
# Go parameters
|
||||||
|
GOCMD=go
|
||||||
|
GOBUILD=$(GOCMD) build
|
||||||
|
GOCLEAN=$(GOCMD) clean
|
||||||
|
GOTEST=$(GOCMD) test
|
||||||
|
GOGET=$(GOCMD) get
|
||||||
|
GOFMT=$(GOCMD) fmt
|
||||||
|
GOVET=$(GOCMD) vet
|
||||||
|
GOINSTALL=$(GOCMD) install
|
||||||
|
GOMOD=$(GOCMD) mod
|
||||||
|
# Use 'go run' for govulncheck to avoid potential version conflicts if installed globally
|
||||||
|
GOVULNCHECK=$(GOCMD) run golang.org/x/vuln/cmd/govulncheck@latest
|
||||||
|
|
||||||
|
# Tools - Ensure these are installed or use 'make install-tools'
|
||||||
|
# You might need to adjust paths or commands based on your installation method
|
||||||
|
GOLANGCILINT ?= golangci-lint
|
||||||
|
GOSEC ?= gosec # Rely on gosec being in PATH after 'make install-tools'
|
||||||
|
|
||||||
|
# Project settings
|
||||||
|
BINARY_NAME=matching-app
|
||||||
|
# Quản lý phiên bản
|
||||||
|
VERSION=1.0.0
|
||||||
|
GIT_COMMIT=$(shell git rev-parse --short HEAD 2>/dev/null || echo "unknown")
|
||||||
|
BUILD_DATE=$(shell date +%FT%T%z)
|
||||||
|
|
||||||
|
# Use forward slashes for Go compatibility, even on Windows
|
||||||
|
CMD_DIR=./cmd/server
|
||||||
|
BUILD_DIR=./build
|
||||||
|
ALL_DIRS=./...
|
||||||
|
PORT ?= 8080
|
||||||
|
|
||||||
|
# Docker settings
|
||||||
|
DOCKER_COMPOSE=docker-compose
|
||||||
|
DEV_COMPOSE_FILE=./template/docker-compose.dev.yml
|
||||||
|
DOCKER_REGISTRY=
|
||||||
|
DOCKER_IMAGE_NAME=$(BINARY_NAME)
|
||||||
|
DOCKER_IMAGE_TAG=$(VERSION)
|
||||||
|
STAGING_COMPOSE_FILE=./template/docker-compose.staging.yml
|
||||||
|
PROD_COMPOSE_FILE=./template/docker-compose.prod.yml
|
||||||
|
|
||||||
|
# Default target executed when running 'make'
|
||||||
|
.DEFAULT_GOAL := help
|
||||||
|
|
||||||
|
# Phony targets are rules that don't represent files
|
||||||
|
.PHONY: all build clean lint lint-fmt lint-vet lint-style sast test sca ci-local install-tools help \
|
||||||
|
run dev docker-build docker-up docker-down docker-logs dev-workflow dev-docker \
|
||||||
|
version tag docker-tag
|
||||||
|
|
||||||
|
all: ci-local build ## Run CI checks and build the application
|
||||||
|
|
||||||
|
# --- Build ---
|
||||||
|
build: ## Build the Go application binary
|
||||||
|
@echo ">>> Building application..."
|
||||||
|
-mkdir "$(BUILD_DIR)"
|
||||||
|
$(GOBUILD) -o "$(BUILD_DIR)/$(BINARY_NAME).exe" "$(CMD_DIR)/main.go"
|
||||||
|
|
||||||
|
# --- Run ---
|
||||||
|
run: build ## Build and run the application
|
||||||
|
@echo ">>> Running application on port $(PORT)..."
|
||||||
|
@set PORT=$(PORT) && "$(BUILD_DIR)/$(BINARY_NAME).exe"
|
||||||
|
|
||||||
|
# --- Dev Mode ---
|
||||||
|
dev: ## Run the application with hot-reload (requires installing air first)
|
||||||
|
@echo ">>> Checking if air is installed..."
|
||||||
|
@where air.exe > NUL 2>&1 || where air > NUL 2>&1
|
||||||
|
@if errorlevel 1 ( \
|
||||||
|
echo Installing air for hot-reload... && \
|
||||||
|
$(GOINSTALL) github.com/cosmtrek/air@latest \
|
||||||
|
) else ( \
|
||||||
|
echo Found air, starting hot-reload server... \
|
||||||
|
)
|
||||||
|
@set PORT=$(PORT) && air
|
||||||
|
|
||||||
|
# --- Installation ---
|
||||||
|
install-tools: ## Install necessary Go tools (gosec, air, etc.)
|
||||||
|
@echo ">>> Installing/Updating Tools..."
|
||||||
|
@echo " Installing gosec..."
|
||||||
|
$(GOINSTALL) github.com/securego/gosec/v2/cmd/gosec@latest
|
||||||
|
@echo " Installing air for hot-reload development..."
|
||||||
|
$(GOINSTALL) github.com/cosmtrek/air@latest
|
||||||
|
@echo " Verifying golangci-lint installation..."
|
||||||
|
@where $(GOLANGCILINT).exe > NUL 2>&1 || where $(GOLANGCILINT) > NUL 2>&1
|
||||||
|
@if errorlevel 1 ( \
|
||||||
|
echo Info: golangci-lint not found or not in PATH. ; \
|
||||||
|
echo Please install it following instructions at: https://golangci-lint.run/usage/install/ ; \
|
||||||
|
echo Continuing without golangci-lint for now... \
|
||||||
|
) else ( \
|
||||||
|
echo Info: golangci-lint found. \
|
||||||
|
)
|
||||||
|
@echo " Installation/Verification complete."
|
||||||
|
|
||||||
|
# --- Linting ---
|
||||||
|
lint: lint-fmt lint-vet lint-style ## Run all linters (fmt, vet, style)
|
||||||
|
@echo ">>> Linting finished."
|
||||||
|
|
||||||
|
lint-fmt:
|
||||||
|
@echo "--> Running go fmt..."
|
||||||
|
$(GOFMT) $(ALL_DIRS)
|
||||||
|
|
||||||
|
lint-vet:
|
||||||
|
@echo "--> Running go vet..."
|
||||||
|
$(GOVET) $(ALL_DIRS)
|
||||||
|
|
||||||
|
lint-style:
|
||||||
|
@echo "--> Running golangci-lint..."
|
||||||
|
@where $(GOLANGCILINT).exe > NUL 2>&1 || where $(GOLANGCILINT) > NUL 2>&1
|
||||||
|
@if errorlevel 1 ( \
|
||||||
|
echo Info: $(GOLANGCILINT) not found or not in PATH. Skipping style lint. ; \
|
||||||
|
echo Please install it following instructions at: https://golangci-lint.run/usage/install/ \
|
||||||
|
) else ( \
|
||||||
|
echo Info: $(GOLANGCILINT) found, running... ; \
|
||||||
|
$(GOLANGCILINT) run ./... \
|
||||||
|
)
|
||||||
|
|
||||||
|
# --- Security Analysis ---
|
||||||
|
sast: ## Run Static Application Security Testing (SAST) using gosec
|
||||||
|
@echo ">>> Running SAST (gosec)..."
|
||||||
|
@echo " (Executing $(GOPATH)/bin/$(GOSEC)...)"
|
||||||
|
-$(GOPATH)/bin/$(GOSEC) ./...
|
||||||
|
|
||||||
|
sca: ## Run Software Composition Analysis (SCA) using govulncheck
|
||||||
|
@echo ">>> Running SCA (govulncheck)..."
|
||||||
|
-$(GOCMD) run golang.org/x/vuln/cmd/govulncheck@latest ./...
|
||||||
|
|
||||||
|
# --- Testing ---
|
||||||
|
test: ## Run unit tests (short mode)
|
||||||
|
@echo ">>> Running unit tests..."
|
||||||
|
-mkdir "$(BUILD_DIR)"
|
||||||
|
$(GOTEST) -short -v -coverprofile="$(BUILD_DIR)/coverage.out" $(ALL_DIRS)
|
||||||
|
$(GOCMD) tool cover -func="$(BUILD_DIR)/coverage.out"
|
||||||
|
|
||||||
|
# --- Docker ---
|
||||||
|
docker-build: ## Build the Docker image
|
||||||
|
@echo ">>> Building Docker image..."
|
||||||
|
docker build -t $(BINARY_NAME) .
|
||||||
|
|
||||||
|
docker-up: ## Start Docker containers with docker-compose dev configuration
|
||||||
|
@echo ">>> Starting Docker containers (dev environment)..."
|
||||||
|
$(DOCKER_COMPOSE) -f $(DEV_COMPOSE_FILE) --env-file .env up -d
|
||||||
|
|
||||||
|
docker-down: ## Stop and remove Docker containers
|
||||||
|
@echo ">>> Stopping Docker containers..."
|
||||||
|
$(DOCKER_COMPOSE) -f $(DEV_COMPOSE_FILE) down
|
||||||
|
|
||||||
|
docker-logs: ## View logs from Docker containers
|
||||||
|
@echo ">>> Showing Docker logs..."
|
||||||
|
$(DOCKER_COMPOSE) -f $(DEV_COMPOSE_FILE) logs -f
|
||||||
|
|
||||||
|
# --- CI/CD Helpers (Chỉ dành cho CI/CD pipeline, không gọi trực tiếp) ---
|
||||||
|
# Các lệnh này KHÔNG nên được gọi trực tiếp bởi Developer, chỉ dành cho hệ thống CI/CD
|
||||||
|
|
||||||
|
# Các lệnh triển khai đã được chuyển vào pipeline CI/CD
|
||||||
|
# Xem tài liệu ci-cd-pipeline.md để biết chi tiết về quy trình triển khai
|
||||||
|
|
||||||
|
# --- Local CI Pipeline ---
|
||||||
|
# Ensures tools are checked/installed before running sequence
|
||||||
|
ci-local: lint sast test sca ## Run full local CI check (Lint -> SAST -> Test -> SCA)
|
||||||
|
@echo "========================================"
|
||||||
|
@echo ">>> Local CI Checks Completed Successfully <<<"
|
||||||
|
@echo "========================================"
|
||||||
|
|
||||||
|
# --- Developer Workflow ---
|
||||||
|
dev-workflow: clean install-tools build test lint ## Developer workflow: clean, install tools, build, test, and lint
|
||||||
|
@echo "========================================"
|
||||||
|
@echo ">>> Developer Setup Completed Successfully <<<"
|
||||||
|
@echo ">>> Run 'make run' to start the application or 'make dev' for hot-reload <<<"
|
||||||
|
@echo "========================================"
|
||||||
|
|
||||||
|
dev-docker: docker-build docker-up ## Developer Docker workflow: build and start Docker containers
|
||||||
|
@echo "========================================"
|
||||||
|
@echo ">>> Docker Development Environment Running <<<"
|
||||||
|
@echo ">>> Run 'make docker-logs' to view logs <<<"
|
||||||
|
@echo ">>> Run 'make docker-down' to stop containers <<<"
|
||||||
|
@echo "========================================"
|
||||||
|
|
||||||
|
# --- Cleanup ---
|
||||||
|
clean: ## Remove build artifacts, binary, and coverage file
|
||||||
|
@echo ">>> Cleaning up..."
|
||||||
|
-$(GOCLEAN)
|
||||||
|
-@if exist "$(BUILD_DIR)" rmdir /s /q "$(BUILD_DIR)" # Windows compatible rmdir, ignore errors
|
||||||
|
|
||||||
|
# --- Version Management ---
|
||||||
|
version: ## Hiển thị thông tin phiên bản hiện tại của ứng dụng
|
||||||
|
@echo ">>> Phiên bản hiện tại của $(BINARY_NAME):"
|
||||||
|
@echo " Version: $(VERSION)"
|
||||||
|
@echo " Commit: $(GIT_COMMIT)"
|
||||||
|
@echo " Date: $(BUILD_DATE)"
|
||||||
|
@echo " Docker: $(DOCKER_REGISTRY)$(DOCKER_IMAGE_NAME):$(DOCKER_IMAGE_TAG)"
|
||||||
|
|
||||||
|
# Cập nhật phiên bản trong code và tạo tag mới
|
||||||
|
tag: ## Tạo tag Git mới với phiên bản hiện tại
|
||||||
|
@echo ">>> Tạo tag v$(VERSION) cho phiên bản hiện tại..."
|
||||||
|
git tag -a v$(VERSION) -m "Version $(VERSION)"
|
||||||
|
@echo ">>> Đã tạo tag v$(VERSION)"
|
||||||
|
@echo ">>> Đẩy tag lên repository với lệnh: git push origin v$(VERSION)"
|
||||||
|
|
||||||
|
docker-tag: docker-build ## Tạo Docker image với tag và latest
|
||||||
|
@echo ">>> Tagging Docker image $(DOCKER_IMAGE_NAME):$(DOCKER_IMAGE_TAG)..."
|
||||||
|
docker tag $(DOCKER_IMAGE_NAME) $(DOCKER_REGISTRY)$(DOCKER_IMAGE_NAME):$(DOCKER_IMAGE_TAG)
|
||||||
|
docker tag $(DOCKER_IMAGE_NAME) $(DOCKER_REGISTRY)$(DOCKER_IMAGE_NAME):latest
|
||||||
|
|
||||||
|
# Các lệnh phát hành đã được chuyển vào pipeline CI/CD
|
||||||
|
# Xem tài liệu workflow_guideline.md và ci-cd-pipeline.md để biết thêm
|
||||||
|
|
||||||
|
# --- Help ---
|
||||||
|
help: ## Display this help screen
|
||||||
|
@echo "----------------------------------------"
|
||||||
|
@echo "Makefile Commands for matching-app:"
|
||||||
|
@echo "----------------------------------------"
|
||||||
|
@echo "Usage: make [target]"
|
||||||
|
@echo ""
|
||||||
|
@echo "Available targets:"
|
||||||
|
@findstr /R /C:"## " Makefile | findstr /V /C:"@findstr" | findstr /V /C:"echo" | sort
|
||||||
|
@echo "----------------------------------------"
|
||||||
|
@echo "Development Workflow:"
|
||||||
|
@echo " 1. make dev-workflow - Set up development environment"
|
||||||
|
@echo " 2. make dev - Run with hot-reload"
|
||||||
|
@echo " 3. make test - Run tests"
|
||||||
|
@echo " 4. make ci-local - Run all checks"
|
||||||
|
@echo "----------------------------------------"
|
||||||
20
Readme.md
Normal file
20
Readme.md
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
# 1. Linting
|
||||||
|
golangci-lint run
|
||||||
|
|
||||||
|
# 2. Unit tests
|
||||||
|
go test ./...
|
||||||
|
|
||||||
|
# 3. Build binary (kiểm tra compiles)
|
||||||
|
go build -o matching-app cmd/api/main.go
|
||||||
|
|
||||||
|
# 4. Build Docker image và khởi động môi trường
|
||||||
|
docker-compose up --build -d
|
||||||
|
|
||||||
|
# 5. Kiểm tra logs để xem app đã khởi động thành công
|
||||||
|
docker-compose logs -f app
|
||||||
|
|
||||||
|
# 6. Health‐check endpoint
|
||||||
|
curl http://localhost:8080/healthz # hoặc GET /campaign/config
|
||||||
|
|
||||||
|
# 7. Khi cần tắt
|
||||||
|
docker-compose down
|
||||||
332
Zee.md
332
Zee.md
@ -1,232 +1,106 @@
|
|||||||
|
Alright, we've covered a lot of ground and defined the core aspects of the Matchmaking Microsite project. Based on our discussions, here is the foundational architectural design documentation, presented in the structured format you requested.
|
||||||
|
|
||||||
---
|
---
|
||||||
created: 2025-04-25T12:45
|
|
||||||
updated: 2025-04-25T12:45
|
**Matchmaking Microsite - Foundational Architectural Design**
|
||||||
|
|
||||||
|
**Version:** 1.0
|
||||||
|
**Date:** April 26, 2025
|
||||||
|
|
||||||
|
**1. Project Overview**
|
||||||
|
|
||||||
|
* **Platform Purpose and Vision:** To create an engaging online experience for a specific matchmaking campaign ("Team Ngọt Ngào và Thanh Dịu"), allowing users to register, be intelligently matched based on defined criteria, and discover potential connections within the campaign's framework.
|
||||||
|
* **Key Functional Scope (Key Capabilities):**
|
||||||
|
* User registration with personal details, preferences, and photo upload[cite: 3, 4, 5, 6, 7].
|
||||||
|
* Automated matching based on criteria: Team choice, Hobbies, Zodiac Sign, Age difference (+/- 5 years), Gender preference[cite: 7, 8, 9, 10, 11].
|
||||||
|
* Secure access to match results via unique code or link[cite: 19, 20].
|
||||||
|
* Display of 3 potential matches with limited initial information, revealing more details upon user selection[cite: 21, 22].
|
||||||
|
* Email notifications for registration confirmation, result availability, and lucky draw winners[cite: 12, 18, 19, 24].
|
||||||
|
* Administrative dashboard for monitoring, campaign management (triggering matching), and gift management.
|
||||||
|
* Reporting on key metrics (total users, team distribution, match selection count)[cite: 26, 27].
|
||||||
|
* **Primary Users/Roles and Core Value Delivered:**
|
||||||
|
* **Participants (End Users):** Individuals seeking a match within the campaign. Value: A fun, easy way to participate, discover potentially compatible individuals based on shared interests and campaign themes, and potentially win prizes.
|
||||||
|
* **Campaign Administrators:** Staff managing the campaign. Value: A tool to automate the matching process, monitor participation, manage campaign phases, and handle winner notifications efficiently.
|
||||||
|
|
||||||
|
**2. System Architecture Overview**
|
||||||
|
|
||||||
|
* **Core Architectural Patterns:**
|
||||||
|
* **Domain-Driven Design (DDD):** Focusing on the core domain (matchmaking, profiles, results) using Aggregates (`Resource` components) to encapsulate state and behavior.
|
||||||
|
* **Event-Driven (Implicit):** System workflows (`Transaction` components) react to events (e.g., User Registered, Matching Complete, Match Selected) to trigger subsequent actions like notifications or state updates. Events will primarily orchestrate Sagas/Transactions internally.
|
||||||
|
* **Data Oriented Programming Concepts:** Structuring logic around data transformations (e.g., DOB to Zodiac[cite: 4], Profile data to Match Criteria, Match Results to Display DTOs). Clear separation of data (Resources) and processing logic (Transactions, Helpers).
|
||||||
|
* **Core Components:**
|
||||||
|
* **`Resource`**: Represents core DDD Aggregates (`Profile`, `MatchOutcome`, `GiftAllocation`, `Gift`). Manages state, data integrity, and core domain logic related to these entities.
|
||||||
|
* **`Transaction`**: Orchestrates business workflows (Sagas/Process Managers) involving multiple steps or Resources (e.g., `UserRegistrationTransaction`, `MatchingTransaction`, `ResultNotificationTransaction`). Ensures process completion or compensation.
|
||||||
|
* **`Adapter`**: Handles communication with external systems/infrastructure (e.g., `PersistenceAdapter` for PostgreSQL, `EmailAdapter`, `FileStorageAdapter`). Isolates core logic from external details.
|
||||||
|
* **`Helper`**: Provides shared, stateless utility functions (e.g., configuration loading, logging, common validation, date/time utils).
|
||||||
|
* **`UIUX`**: Frontend interface layer. Given the preference, this will primarily involve server-side rendering of HTML fragments via Go templates interacting with HTMX, alongside a separate Admin Dashboard UI (potentially using more client-side JS).
|
||||||
|
* **Proposed Technology Stack:**
|
||||||
|
* **Backend:** Go (Performance, concurrency for potential load, strong typing).
|
||||||
|
* **Frontend:** HTMX (Primary user interface, server-rendered HTML fragments), potentially minimal JavaScript (Alpine.js/Hyperscript) where needed. Admin Dashboard may utilize more JS.
|
||||||
|
* **Database:** PostgreSQL (Reliable relational storage, potential for JSONB for flexible fields like hobbies if needed, though structured preferred for filtering criteria). Use a managed cloud service (e.g., RDS, Cloud SQL).
|
||||||
|
* **Workflow/Rule Engine:** *Considered* for the `MatchingTransaction` if the filtering logic [cite: 9, 10, 11] becomes highly complex or needs frequent changes, allowing externalization of rules. Otherwise, implement directly in Go.
|
||||||
|
* **Containerization:** Docker (Consistent environments, ease of deployment).
|
||||||
|
* **Hosting:** Cloud Platform (AWS/GCP/Azure - Scalability, managed services).
|
||||||
|
* *(Rationale):* This stack offers good performance (Go), robust data storage (Postgres), a simplified frontend approach (HTMX), and leverages cloud infrastructure for scalability and manageability.
|
||||||
|
* **High-Level Data/Event Flow:**
|
||||||
|
1. User submits registration form (UIUX/HTMX -> API Endpoint).
|
||||||
|
2. `UserRegistrationTransaction` creates `Profile` (Resource), saves via `PersistenceAdapter`, sends email via `EmailAdapter`.
|
||||||
|
3. Admin/Scheduler triggers Matching (Admin UI -> API Endpoint or Scheduled Job).
|
||||||
|
4. `MatchingTransaction` reads `Profiles`, applies logic, creates `MatchOutcome`s, saves via `PersistenceAdapter`.
|
||||||
|
5. `ResultNotificationTransaction` reads `Profile`/`MatchOutcome`, sends email via `EmailAdapter`, updates `Profile` status via `PersistenceAdapter`.
|
||||||
|
6. User accesses results (UIUX/HTMX -> API Endpoint). Backend verifies access, fetches `MatchOutcome`, renders HTML fragment with limited match data.
|
||||||
|
7. User selects/declines (UIUX/HTMX -> API Endpoint). `MatchSelectionTransaction` updates `MatchOutcome`/`Profile` via `PersistenceAdapter`. Backend renders updated HTML fragment (e.g., showing full details or end message).
|
||||||
|
* **Key Non-Functional Requirements and Approach:**
|
||||||
|
* **Scalability:** Handle 20k+ users[cite: 27]. Approach: Asynchronous/batch processing for `MatchingTransaction` and potentially bulk email notifications; efficient database queries with appropriate indexing; stateless backend services suitable for horizontal scaling behind a load balancer.
|
||||||
|
* **Security:** Protect user PII. Approach: Secure admin login (e.g., JWT); secure user result access via unique, non-guessable codes/links; limit PII exposure until explicit selection[cite: 22]; standard web security practices (input validation, HTTPS); secure file storage policies.
|
||||||
|
* **Flexibility/Configurability:** Campaign details might change in future iterations. Approach: Utilize the `Campaign` Resource to store configurable parameters (texts, dates, potentially aspects of matching logic if externalized).
|
||||||
|
* **Approach to Data Consistency:** Primarily handled by ACID properties of PostgreSQL for single Resource operations. Transactions/Sagas (`MatchingTransaction`, etc.) ensure operational consistency across steps, though complex compensation logic is likely minimal for this scope unless distributed systems become involved (unlikely here).
|
||||||
|
|
||||||
|
**3. Internal Structuring Approach (U-Hierarchy Confirmation)**
|
||||||
|
|
||||||
|
* **Confirmation:** We will adopt the `ubit`, `ubrick`, `ublock`, `ubundle` hierarchy as defined.
|
||||||
|
* **Usage Explanation:** This hierarchy will structure the source code and logical components *within* the core architectural components (`Resource`, `Transaction`, `Adapter`, `Helper`, `UIUX`-backend rendering logic) to promote modularity, testability, and reusability.
|
||||||
|
* **`ubits`** (e.g., individual validation functions, type definitions, constants) will form the smallest building blocks.
|
||||||
|
* **`ubricks`** (e.g., `ProfileDataValidation` module, `ZodiacCalculator`, `PostgresConnectionManager`) will group related `ubits` into cohesive units.
|
||||||
|
* **`ublocks`** (e.g., the entire `resource/profile` package, the `adapter/postgres` package, a specific `transaction/registration` saga implementation) will compose `ubricks` to manage distinct pieces of functionality.
|
||||||
|
* **`ubundles`** (e.g., the "User Registration" feature, "Admin Reporting") represent complete features achieved through the collaboration of multiple `ublocks` across different core components.
|
||||||
|
* The proposed directory structure reflects this, with package directories representing `ublocks` and internal files/structs representing `ubricks` and `ubits`.
|
||||||
|
|
||||||
|
**4. High-Level Feature Roadmap / Phasing**
|
||||||
|
|
||||||
|
* **Setup Phase (Phase 0 - Foundation):**
|
||||||
|
* Infrastructure Setup: Cloud environment provisioning, VPC, security groups.
|
||||||
|
* CI/CD Pipeline Setup: Basic pipeline for build, test, deploy.
|
||||||
|
* Database Provisioning: Setup managed PostgreSQL instance.
|
||||||
|
* Core Component Setup: Initialize Go project structure, setup logging, configuration management, base API framework, core DB connection logic (`PersistenceAdapter` base).
|
||||||
|
* **Phase 1: Core User Experience & Matching:**
|
||||||
|
* Capabilities: Homepage display, User Registration (Form + Photo Upload), Backend Matching Logic (Batch), Result Access (Code/Link), Result Display (3 limited profiles), Match Selection/Declination, Basic Email Notifications (Confirmation, Results).
|
||||||
|
* Components Involved: `UIUX` (HTMX Views), `API` (Endpoints), `Transaction` (Registration, Matching, Result Notification, Selection), `Resource` (`Profile`, `MatchOutcome`), `Adapter` (Postgres, Email, FileStorage), `Helper`.
|
||||||
|
* **Phase 2: Admin Dashboard & Management:**
|
||||||
|
* Capabilities: Admin Login, Reporting Dashboard (User stats, Team stats), Campaign Management (Trigger Matching, Open/Close Registration), Basic Gift Definition UI.
|
||||||
|
* Components Involved: `UIUX` (Admin Views), `API` (Admin Endpoints), `Transaction` (Admin triggers), `Resource` (`Gift`), `Adapter` (Postgres).
|
||||||
|
* **Phase 3: Gift Giveaway & Refinements:**
|
||||||
|
* Capabilities: Lucky Draw/Winner Allocation logic, Winner Email Notification, Gift Allocation Tracking (Admin). Potential UI refinements based on initial feedback.
|
||||||
|
* Components Involved: `Transaction` (Gift Allocation, Gift Notification), `Resource` (`GiftAllocation`), `Adapter` (Email, Postgres), `API` (Admin Endpoints).
|
||||||
|
|
||||||
|
**5. Key Interfaces / Interaction Points**
|
||||||
|
|
||||||
|
* **User Microsite (via HTMX):** The primary interface for participants, rendered server-side by the Go backend, enabling registration, result access, and interaction.
|
||||||
|
* **Admin Dashboard:** A separate web interface for administrators to monitor the campaign, manage phases, view reports, and manage gifts. Likely requires more client-side JavaScript interaction with JSON APIs.
|
||||||
|
* **Email Notifications:** System-generated emails sent to users for key events (registration, results, winning prizes) via an external Email Service (`EmailAdapter`).
|
||||||
|
* **(Internal) File Storage API:** Interface used by the backend (`FileStorageAdapter`) to interact with the chosen file storage service (e.g., S3 API) for photo uploads/retrieval.
|
||||||
|
* **(Internal) Database Interface:** Interface used by the `PersistenceAdapter` to interact with the PostgreSQL database.
|
||||||
|
|
||||||
|
**6. System-Wide Error Handling Strategy**
|
||||||
|
|
||||||
|
* **Detection & Categorization:** Errors will be detected at various layers (API validation, Transaction logic, Adapter communication). Errors will be categorized (e.g., user input error, resource not found, external service failure, internal server error).
|
||||||
|
* **Propagation:** Errors from lower layers (Adapters, Resources) will be propagated up to the calling Transaction or API Handler, potentially wrapped for context.
|
||||||
|
* **Logging:** All significant errors (especially non-user correctable ones) will be logged centrally with structured information (timestamp, error type, stack trace if applicable, request context). A dedicated `Logging` Helper will be used.
|
||||||
|
* **State Consistency:** Database transactions within the `PersistenceAdapter` will be used to ensure atomic updates to Resources, maintaining consistency in case of errors during writes. Sagas (`Transaction` components) will handle logical consistency across multiple steps (though complex compensation is expected to be minimal).
|
||||||
|
* **Communication:**
|
||||||
|
* *To Users (via HTMX/API):* User input errors will result in user-friendly messages, potentially rendered directly into HTML fragments by the backend. Generic messages will be used for unexpected server errors to avoid exposing internal details. Standard HTTP status codes will be used (e.g., 400 for bad input, 404 for not found, 500 for server error).
|
||||||
|
* *To Admins:* Detailed error information will be available in logs. The Admin Dashboard might display specific error summaries or alerts for critical issues.
|
||||||
|
|
||||||
---
|
---
|
||||||
**Tài liệu Thiết kế Tổng quát - Dự án Matchmaking Microsite**
|
|
||||||
|
|
||||||
**Phiên bản:** 1.0 **Ngày:** 2025-04-25
|
This document provides the high-level architectural blueprint for the Matchmaking Microsite project based on our collaborative design process.
|
||||||
|
|
||||||
# Content
|
|
||||||
**1. Giới thiệu (Introduction)**
|
|
||||||
|
|
||||||
- **Mục đích dự án**: Xây dựng một microsite phục vụ chiến dịch matchmaking ("Team Ngọt Ngào và Thanh Dịu").
|
|
||||||
|
|
||||||
- **Mục tiêu chính**: Cho phép người dùng đăng ký thông tin cá nhân và sở thích, hệ thống tự động tìm kiếm và đề xuất 3 hồ sơ phù hợp dựa trên các tiêu chí định sẵn, quản lý quy trình thông báo qua email, và trao giải thưởng cho người may mắn.
|
|
||||||
|
|
||||||
- **Đối tượng người dùng**: Các cá nhân muốn tham gia chương trình ghép đôi.
|
|
||||||
- **Quy mô dự kiến**: Khoảng 20,000+ người tham gia, 5,000 lượt matching.
|
|
||||||
|
|
||||||
|
|
||||||
**2. Nguyên tắc & Kiến trúc tổng thể (Principles & High-Level Architecture)**
|
|
||||||
|
|
||||||
- **Nguyên tắc thiết kế**:
|
|
||||||
- User-Centric: Ưu tiên trải nghiệm người dùng, giảm thiểu sự phức tạp.
|
|
||||||
- Data-Centric / Data Oriented Programming (DOP): Thiết kế xoay quanh luồng dữ liệu và các biến đổi dữ liệu.
|
|
||||||
- Domain-Driven Design (DDD): Áp dụng các khái niệm cốt lõi của DDD (Bounded Context, Aggregates, Domain Events).
|
|
||||||
- **Thành phần kiến trúc chính (Core Components)**:
|
|
||||||
- `Resource`: Các Aggregate DDD đại diện cho đối tượng nghiệp vụ (dữ liệu, trạng thái, hành vi).
|
|
||||||
- `Transaction`: Các Saga/Process Manager điều phối luồng nghiệp vụ phức tạp, liên quan đến nhiều Resource.
|
|
||||||
- `Adapter`: Xử lý giao tiếp với các hệ thống bên ngoài (Database, Email Service, File Storage).
|
|
||||||
- `Helper`: Các thư viện, tiện ích dùng chung.
|
|
||||||
- `UIUX`: Lớp giao diện người dùng (Frontend).
|
|
||||||
- **Phân cấp độ chi tiết (Architectural Granularity - U-Hierarchy)**:
|
|
||||||
- `ubit`: Đơn vị logic nhỏ nhất (hàm, type, hằng số).
|
|
||||||
- `ubrick`: Tập hợp các ubit liên quan (module validate, nhóm hàm tiện ích).
|
|
||||||
- `ublock`: Thành phần có thể hoạt động độc lập tương đối (quản lý một Resource, một Adapter cụ thể).
|
|
||||||
- `ubundle`: Tính năng hoàn chỉnh cho người dùng (sự kết hợp của nhiều ublock).
|
|
||||||
- **Công nghệ lựa chọn (Technology Stack)**:
|
|
||||||
- Backend: Go
|
|
||||||
- Database: PostgreSQL (có thể cân nhắc JSONB cho một số trường linh hoạt).
|
|
||||||
- Frontend: **HTMX (Ưu tiên)** - Backend sẽ render HTML fragments là chủ yếu. JavaScript sẽ được dùng hạn chế cho các xử lý phức tạp hoặc Admin UI.
|
|
||||||
- Khác: Có thể tích hợp Workflow Engine/Rule Engine cho logic matching phức tạp (cân nhắc).
|
|
||||||
|
|
||||||
**3. Resources (Đối tượng nghiệp vụ cốt lõi)**
|
|
||||||
|
|
||||||
Các Aggregate chính của hệ thống:
|
|
||||||
|
|
||||||
- **`Profile`**: Lưu thông tin người tham gia.
|
|
||||||
- **Cấu trúc dữ liệu**: (Chi tiết như đã định nghĩa ở các thảo luận trước, bao gồm: `id`, `fullName`, `dateOfBirth`, `gender`, `preferredGender`, `phoneNumber`, `email`, `facebookLink`, `photoReference`, `location`, `team`, `hobbies`, `thingsNotTried`, `hopesForPartner`, `dealbreakers`, `messageToPartner`, `zodiacSign`, `accessCode`, `personalLink`, `acceptedTerms`, `registrationTimestamp`, `status`, `isLuckyWinner`, `createdAt`, `updatedAt`).
|
|
||||||
|
|
||||||
- **ubits/ubricks ví dụ**: `ProfileDataValidation`, `ZodiacCalculator`, `AccessCredentialGenerator`, `ProfileStateMachine`, `validateEmail`, `checkHobbyCount`.
|
|
||||||
|
|
||||||
- **`MatchOutcome`**: Lưu kết quả matching cho một `Profile`.
|
|
||||||
- **Cấu trúc dữ liệu**: (Chi tiết như đã định nghĩa, bao gồm: `id`, `profileId`, `potentialMatchProfileIds`, `matchScores`?, `status`, `selectedProfileId`, `generationTimestamp`, `accessedTimestamp`, `selectionTimestamp`, `createdAt`, `updatedAt`).
|
|
||||||
|
|
||||||
- **ubits/ubricks ví dụ**: `MatchOutcomeStateMachine`, `PotentialMatchFormatter`, `AccessValidator`.
|
|
||||||
|
|
||||||
- **`GiftAllocation`**: Ghi nhận việc phân bổ quà tặng.
|
|
||||||
- **Cấu trúc dữ liệu**: (Chi tiết như đã định nghĩa, bao gồm: `id`, `profileId`, `giftId`, `allocationTimestamp`, `notificationStatus`, `notificationSentTimestamp`, `createdAt`, `updatedAt`).
|
|
||||||
- **ubits/ubricks ví dụ**: `NotificationStatusManager`.
|
|
||||||
- **`Gift`**: Lưu thông tin các loại quà tặng (cấu hình).
|
|
||||||
- **Cấu trúc dữ liệu**: (Chi tiết như đã định nghĩa, bao gồm: `id`, `name`, `description`, `imageUrl`?, `isActive`, `createdAt`, `updatedAt`).
|
|
||||||
- **ubits/ubricks ví dụ**: `GiftDataValidator`.
|
|
||||||
|
|
||||||
**4. Luồng nghiệp vụ & Transactions (Workflows & Transactions)**
|
|
||||||
|
|
||||||
Các quy trình chính được điều phối bởi Transactions:
|
|
||||||
|
|
||||||
- **`UserRegistrationTransaction`**: Xử lý đăng ký, tạo `Profile`, gửi email xác nhận. Tác động: Tạo `Profile`; dùng `PersistenceAdapter`, `EmailAdapter`, `FileStorageAdapter`.
|
|
||||||
|
|
||||||
- **`MatchingTransaction`**: Chạy logic matching (theo batch/trigger), tạo `MatchOutcome`. Tác động: Đọc `Profile`, Tạo `MatchOutcome`; dùng `PersistenceAdapter`.
|
|
||||||
|
|
||||||
- **`ResultNotificationTransaction`**: Gửi email thông báo kết quả. Tác động: Đọc `Profile`/`MatchOutcome`, Cập nhật `Profile` (status); dùng `PersistenceAdapter`, `EmailAdapter`.
|
|
||||||
|
|
||||||
- **`MatchSelectionTransaction`**: Xử lý việc người dùng chọn/từ chối kết quả. Tác động: Đọc/Cập nhật `Profile`/`MatchOutcome`; dùng `PersistenceAdapter`.
|
|
||||||
|
|
||||||
- **`GiftAllocationTransaction`**: Chạy lucky draw, tạo `GiftAllocation`. Tác động: Đọc `Profile`/`Gift`, Tạo `GiftAllocation`, Cập nhật `Profile`; dùng `PersistenceAdapter`.
|
|
||||||
|
|
||||||
- **`GiftNotificationTransaction`**: Gửi email thông báo trúng thưởng. Tác động: Đọc `GiftAllocation`/`Profile`/`Gift`, Cập nhật `GiftAllocation`; dùng `PersistenceAdapter`, `EmailAdapter`.
|
|
||||||
|
|
||||||
|
|
||||||
**5. Giao diện người dùng (UIUX - Frontend)**
|
|
||||||
|
|
||||||
- **Công nghệ ưu tiên**: HTMX.
|
|
||||||
- **User-Facing Pages**:
|
|
||||||
|
|
||||||
1. Homepage (Giới thiệu, thông tin, CTA đăng ký).
|
|
||||||
|
|
||||||
2. Registration Page (Form đăng ký chi tiết).
|
|
||||||
|
|
||||||
3. Result Page (Nhập code/truy cập link, hiển thị kết quả, xử lý lựa chọn).
|
|
||||||
|
|
||||||
|
|
||||||
- _Popups phụ trợ_: Điều khoản, Thông tin đội, Tiêu chí ảnh.
|
|
||||||
|
|
||||||
- **Admin Pages**:
|
|
||||||
1. Login Page.
|
|
||||||
2. Reporting Page (Thống kê user, team, match count).
|
|
||||||
|
|
||||||
3. Timeline/Campaign Management Page (Quản lý trạng thái, trigger actions).
|
|
||||||
4. Gift Management Page (Quản lý quà, xem người trúng thưởng).
|
|
||||||
|
|
||||||
|
|
||||||
**6. API Endpoints**
|
|
||||||
|
|
||||||
- **Tiếp cận**: Backend (Go) sẽ trả về chủ yếu là **HTML fragments** cho các tương tác người dùng qua HTMX. Các endpoints trả về **JSON** sẽ được duy trì cho Admin Dashboard và các xử lý AJAX phức tạp (nếu có).
|
|
||||||
- **Danh sách endpoints chính (đã liệt kê chi tiết trước đó)**:
|
|
||||||
- Public/User-Facing: `GET /campaign/config`, `POST /profiles`, `POST /photos/upload` (cần thiết kế kỹ), `POST /results/access`, `GET /results/{token}`, `POST /results/select`, `POST /results/decline`.
|
|
||||||
- Admin (Yêu cầu Auth): `POST /admin/auth/login`, `GET /admin/reports/summary`, `GET /admin/campaign/status`, `POST /admin/campaign/actions/{action}`, `GET/POST/PUT/DELETE /admin/gifts`, `GET /admin/giveaway/allocations`, `POST /admin/giveaway/actions/run-draw`.
|
|
||||||
|
|
||||||
**7. Mô hình triển khai (Deployment Model)**
|
|
||||||
|
|
||||||
- **Containerization**: Backend (Go) và Frontend (HTMX/Static Assets) sẽ được đóng gói bằng **Docker**.
|
|
||||||
- **Hosting**: Triển khai trên nền tảng **Cloud** (ví dụ: AWS, GCP, Azure).
|
|
||||||
- **Database**: Sử dụng **Managed PostgreSQL Service** của Cloud provider.
|
|
||||||
- **Frontend Serving**: Backend container sẽ render và phục vụ HTML fragments là chính. Tài nguyên tĩnh cơ bản (CSS, JS nhỏ) đi kèm hoặc qua **CDN/Object Storage**.
|
|
||||||
- **Load Balancing**: Sử dụng Load Balancer của Cloud provider.
|
|
||||||
|
|
||||||
**8. Structure of the project**
|
|
||||||
matchmaking-microsite/
|
|
||||||
├── cmd/
|
|
||||||
│ └── api/
|
|
||||||
│ └── main.go
|
|
||||||
├── internal/
|
|
||||||
│ ├── resource/ # === Component: Resource ===
|
|
||||||
│ │ ├── profile/ # -> ublock: Quản lý Aggregate 'Profile'
|
|
||||||
│ │ │ ├── profile_aggregate.go # -> ubrick: Struct Profile chính, methods cơ bản
|
|
||||||
│ │ │ ├── profile_state.go # -> ubrick: Quản lý trạng thái (enum, transitions)
|
|
||||||
│ │ │ ├── profile_validation.go# -> ubrick: Logic validate (chứa ubit validators)
|
|
||||||
│ │ │ ├── profile_access.go # -> ubrick: Tạo và quản lý accessCode/personalLink
|
|
||||||
│ │ │ ├── profile_zodiac.go # -> ubrick: Tính Cung Hoàng Đạo
|
|
||||||
│ │ │ └── profile_types.go # -> ubit(s): Định nghĩa các kiểu dữ liệu phụ (enum Gender, Team...)
|
|
||||||
│ │ ├── matchoutcome/ # -> ublock: Quản lý Aggregate 'MatchOutcome'
|
|
||||||
│ │ │ ├── matchoutcome_aggregate.go # -> ubrick: Struct MatchOutcome chính, methods
|
|
||||||
│ │ │ ├── matchoutcome_state.go # -> ubrick: Quản lý trạng thái
|
|
||||||
│ │ │ ├── matchoutcome_formatter.go # -> ubrick: Chuẩn bị dữ liệu tóm tắt (PotentialMatchSummary)
|
|
||||||
│ │ │ └── matchoutcome_types.go # -> ubit(s): Định nghĩa Status enum, Summary struct
|
|
||||||
│ │ ├── giftallocation/ # -> ublock: Quản lý Aggregate 'GiftAllocation'
|
|
||||||
│ │ │ ├── giftallocation_aggregate.go # -> ubrick: Struct GiftAllocation chính, methods
|
|
||||||
│ │ │ └── giftallocation_notifier.go # -> ubrick: Quản lý trạng thái thông báo
|
|
||||||
│ │ │ └── giftallocation_types.go # -> ubit(s): Định nghĩa NotificationStatus enum
|
|
||||||
│ │ └── gift/ # -> ublock: Quản lý Aggregate 'Gift' (cấu hình)
|
|
||||||
│ │ ├── gift_aggregate.go # -> ubrick: Struct Gift chính, methods cơ bản
|
|
||||||
│ │ └── gift_validator.go # -> ubrick: Validate dữ liệu Gift
|
|
||||||
│ ├── transaction/ # === Component: Transaction (Giữ cấu trúc cũ) ===
|
|
||||||
│ │ ├── registration/
|
|
||||||
│ │ │ └── saga.go
|
|
||||||
│ │ ├── matching/
|
|
||||||
│ │ │ └── saga.go
|
|
||||||
│ │ │ └── filter.go # (Ví dụ về ubrick chứa logic filter chi tiết)
|
|
||||||
│ │ ├── resultnotif/
|
|
||||||
│ │ │ └── saga.go
|
|
||||||
│ │ └── giftalloc/
|
|
||||||
│ │ └── saga.go
|
|
||||||
│ ├── adapter/ # === Component: Adapter (Giữ cấu trúc cũ) ===
|
|
||||||
│ │ ├── postgres/
|
|
||||||
│ │ │ ├── connection.go
|
|
||||||
│ │ │ ├── profile_repo.go
|
|
||||||
│ │ │ ├── match_repo.go
|
|
||||||
│ │ │ ├── gift_repo.go
|
|
||||||
│ │ │ └── models.go
|
|
||||||
│ │ ├── email/
|
|
||||||
│ │ │ ├── client.go
|
|
||||||
│ │ │ └── templates.go
|
|
||||||
│ │ └── filestorage/
|
|
||||||
│ │ ├── s3_client.go
|
|
||||||
│ │ └── validation.go
|
|
||||||
│ ├── helper/ # === Component: Helper (Giữ cấu trúc cũ) ===
|
|
||||||
│ │ ├── config/
|
|
||||||
│ │ │ └── load.go
|
|
||||||
│ │ ├── logger/
|
|
||||||
│ │ │ └── log.go
|
|
||||||
│ │ ├── validation/
|
|
||||||
│ │ │ └── common.go
|
|
||||||
│ │ ├── datetime/
|
|
||||||
│ │ │ └── calc.go
|
|
||||||
│ │ └── security/
|
|
||||||
│ │ └── hash.go
|
|
||||||
│ └── api/ # === Phần xử lý HTTP API (Giữ cấu trúc cũ) ===
|
|
||||||
│ ├── handler/
|
|
||||||
│ │ ├── profile.go
|
|
||||||
│ │ ├── result.go
|
|
||||||
│ │ ├── admin.go
|
|
||||||
│ │ └── middleware.go
|
|
||||||
│ ├── router.go
|
|
||||||
│ └── dto/
|
|
||||||
│ ├── request.go
|
|
||||||
│ └── response.go
|
|
||||||
├── configs/
|
|
||||||
│ ├── config.dev.yaml
|
|
||||||
│ └── config.prod.yaml
|
|
||||||
├── migrations/
|
|
||||||
│ └── 001_create_tables.sql
|
|
||||||
├── go.mod
|
|
||||||
├── go.sum
|
|
||||||
├── .gitignore
|
|
||||||
└── README.md
|
|
||||||
|
|
||||||
**9. Các khía cạnh HLD cần làm rõ thêm (Further HLD Considerations)**
|
|
||||||
|
|
||||||
Các lĩnh vực cần được định nghĩa chi tiết hơn trong giai đoạn LLD hoặc cần làm rõ chiến lược ở HLD:
|
|
||||||
|
|
||||||
- Yêu cầu Phi chức năng chi tiết (Response time, Availability, Data retention).
|
|
||||||
- Mô hình Bảo mật chi tiết (JWT handling, Access code/link verification logic).
|
|
||||||
- Tiếp cận chi tiết về Quyền riêng tư Dữ liệu (PII handling).
|
|
||||||
- Luồng Matching chi tiết ở mức khái niệm (các bước lọc/tính điểm).
|
|
||||||
- Chiến lược xử lý lỗi, logging, monitoring chi tiết.
|
|
||||||
- Thiết kế Database Schema vật lý (tables, indexes).
|
|
||||||
- Chi tiết thuật toán/logic nghiệp vụ cụ thể.
|
|
||||||
|
|
||||||
|
|
||||||
# LƯU Ý:
|
|
||||||
Khái niệm **U-Hierarchy** (hay "kiến trúc phân cấp chữ U") được chúng ta áp dụng trong dự án này như một **phương pháp để cấu trúc và phân cấp độ chi tiết bên trong** các thành phần kiến trúc chính (như Resource, Transaction, Adapter, Helper, UIUX). Mục tiêu là để thúc đẩy **tính module**, **khả năng tái sử dụng**, và **thiết kế có cấu trúc rõ ràng** cho mã nguồn và các thành phần logic.
|
|
||||||
|
|
||||||
Nó bao gồm các cấp độ sau, đi từ nhỏ nhất đến lớn nhất:
|
|
||||||
1. **ubit**:
|
|
||||||
|
|
||||||
- Là đơn vị logic **nhỏ nhất, không thể chia nhỏ hơn** một cách có ý nghĩa trong ngữ cảnh thiết kế.
|
|
||||||
- Ví dụ: Một hàm (function) đơn lẻ, một định nghĩa kiểu dữ liệu (struct/type), một hằng số (constant), một quy tắc validation cụ thể, một cấu hình đơn lẻ.
|
|
||||||
2. **ubrick**:
|
|
||||||
|
|
||||||
- Là sự **kết hợp của nhiều `ubit` có liên quan** với nhau, cùng phục vụ một mục đích chung hoặc xử lý một khía cạnh cụ thể, gắn kết.
|
|
||||||
- Ví dụ: Một module chứa tất cả các hàm validation cho Resource `Profile` (`ProfileDataValidation`), một nhóm các hàm tiện ích xử lý chuỗi, một tập hợp các phương thức client để gọi API của một dịch vụ bên ngoài cụ thể.
|
|
||||||
3. **ublock**:
|
|
||||||
|
|
||||||
- Là sự **kết hợp của nhiều `ubrick`** (và có thể cả các `ubit` đơn lẻ), được thiết kế để hoạt động cùng nhau một cách chặt chẽ và quản lý một phần chức năng riêng biệt. Một `ublock` thường có khả năng hoạt động nội bộ ở một mức độ nào đó cho nhiệm vụ cốt lõi của nó.
|
|
||||||
- Ví dụ: Toàn bộ `Adapter` cho PostgreSQL (`postgres/`), bao gồm quản lý kết nối, các repository cho từng Resource. Hoặc toàn bộ module quản lý logic cho Resource `Profile` (`resource/profile/`).
|
|
||||||
4. **ubundle**:
|
|
||||||
|
|
||||||
- Là sự **kết hợp của nhiều `ublock`** (và có thể cả `ubrick`/`ubit`), cùng nhau cung cấp một **tính năng hoàn chỉnh có thể thấy được bởi người dùng** hoặc một khả năng nghiệp vụ quan trọng.
|
|
||||||
- Ví dụ: Tính năng "Đăng ký người dùng" hoàn chỉnh bao gồm các `ublock` từ `UIUX` (form đăng ký), `API handler`, `Transaction` (điều phối đăng ký), `Resource` (`Profile`), và `Adapter` (lưu DB, gửi email). Hoặc toàn bộ "Hệ thống Báo cáo Admin".
|
|
||||||
|
|
||||||
Tóm lại, U-Hierarchy giúp chúng ta suy nghĩ về cách "đóng gói" các đơn vị logic từ nhỏ đến lớn, tạo ra các lớp trừu tượng rõ ràng và quản lý sự phức tạp khi xây dựng các thành phần lớn của hệ thống.
|
|
||||||
BIN
bin/server
Normal file
BIN
bin/server
Normal file
Binary file not shown.
56
build/coverage.out
Normal file
56
build/coverage.out
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
mode: set
|
||||||
|
phattrang/matching_app_team/cmd/server/main.go:20.13,26.24 3 0
|
||||||
|
phattrang/matching_app_team/cmd/server/main.go:26.24,34.3 1 0
|
||||||
|
phattrang/matching_app_team/cmd/server/main.go:36.2,37.61 2 0
|
||||||
|
phattrang/matching_app_team/cmd/server/main.go:37.61,39.3 1 0
|
||||||
|
phattrang/matching_app_team/cmd/server/main.go:42.2,43.16 2 0
|
||||||
|
phattrang/matching_app_team/cmd/server/main.go:43.16,45.3 1 0
|
||||||
|
phattrang/matching_app_team/cmd/server/main.go:48.2,55.16 4 0
|
||||||
|
phattrang/matching_app_team/cmd/server/main.go:55.16,57.3 1 0
|
||||||
|
phattrang/matching_app_team/cmd/server/main.go:60.2,66.12 2 0
|
||||||
|
phattrang/matching_app_team/cmd/server/main.go:66.12,68.80 2 0
|
||||||
|
phattrang/matching_app_team/cmd/server/main.go:68.80,70.4 1 0
|
||||||
|
phattrang/matching_app_team/cmd/server/main.go:74.2,81.45 7 0
|
||||||
|
phattrang/matching_app_team/cmd/server/main.go:81.45,83.3 1 0
|
||||||
|
phattrang/matching_app_team/cmd/server/main.go:85.2,85.39 1 0
|
||||||
|
phattrang/matching_app_team/internal/helper/config.go:28.47,33.16 3 0
|
||||||
|
phattrang/matching_app_team/internal/helper/config.go:33.16,35.3 1 0
|
||||||
|
phattrang/matching_app_team/internal/helper/config.go:37.2,38.16 2 0
|
||||||
|
phattrang/matching_app_team/internal/helper/config.go:38.16,40.3 1 0
|
||||||
|
phattrang/matching_app_team/internal/helper/config.go:43.2,43.46 1 0
|
||||||
|
phattrang/matching_app_team/internal/helper/config.go:43.46,45.3 1 0
|
||||||
|
phattrang/matching_app_team/internal/helper/config.go:48.2,48.20 1 0
|
||||||
|
phattrang/matching_app_team/internal/uiux/middleware.go:8.60,9.30 1 0
|
||||||
|
phattrang/matching_app_team/internal/uiux/middleware.go:9.30,21.3 3 0
|
||||||
|
phattrang/matching_app_team/internal/uiux/middleware.go:24.58,25.30 1 0
|
||||||
|
phattrang/matching_app_team/internal/uiux/middleware.go:25.30,29.24 2 0
|
||||||
|
phattrang/matching_app_team/internal/uiux/middleware.go:29.24,31.31 2 0
|
||||||
|
phattrang/matching_app_team/internal/uiux/middleware.go:31.31,33.5 1 0
|
||||||
|
phattrang/matching_app_team/internal/uiux/middleware.go:34.4,37.5 1 0
|
||||||
|
phattrang/matching_app_team/internal/uiux/router.go:8.32,18.2 4 0
|
||||||
|
phattrang/matching_app_team/internal/helper/error/error.go:23.60,24.9 1 0
|
||||||
|
phattrang/matching_app_team/internal/helper/error/error.go:25.35,29.25 1 0
|
||||||
|
phattrang/matching_app_team/internal/helper/error/error.go:31.39,35.25 1 0
|
||||||
|
phattrang/matching_app_team/internal/helper/error/error.go:37.39,41.25 1 0
|
||||||
|
phattrang/matching_app_team/internal/helper/error/error.go:43.10,48.16 2 0
|
||||||
|
phattrang/matching_app_team/internal/helper/health/handler.go:10.36,14.2 1 1
|
||||||
|
phattrang/matching_app_team/internal/helper/health/handler.go:20.38,22.2 1 0
|
||||||
|
phattrang/matching_app_team/internal/helper/health/handler.go:24.41,27.16 2 0
|
||||||
|
phattrang/matching_app_team/internal/helper/health/handler.go:27.16,33.3 2 0
|
||||||
|
phattrang/matching_app_team/internal/helper/health/handler.go:34.2,36.4 1 0
|
||||||
|
phattrang/matching_app_team/internal/helper/health/router.go:5.48,7.2 1 0
|
||||||
|
phattrang/matching_app_team/internal/adapter/database/connection.go:19.73,21.16 2 0
|
||||||
|
phattrang/matching_app_team/internal/adapter/database/connection.go:21.16,23.3 1 0
|
||||||
|
phattrang/matching_app_team/internal/adapter/database/connection.go:25.2,31.16 6 0
|
||||||
|
phattrang/matching_app_team/internal/adapter/database/connection.go:31.16,33.3 1 0
|
||||||
|
phattrang/matching_app_team/internal/adapter/database/connection.go:35.2,35.16 1 0
|
||||||
|
phattrang/matching_app_team/internal/adapter/database/postgres_repository.go:13.70,15.2 1 0
|
||||||
|
phattrang/matching_app_team/internal/adapter/database/postgres_repository.go:17.78,19.2 1 0
|
||||||
|
phattrang/matching_app_team/internal/adapter/database/postgres_repository.go:21.86,23.2 1 0
|
||||||
|
phattrang/matching_app_team/internal/adapter/database/postgres_repository.go:25.78,27.2 1 0
|
||||||
|
phattrang/matching_app_team/internal/adapter/database/postgres_repository.go:29.78,31.2 1 0
|
||||||
|
phattrang/matching_app_team/internal/adapter/database/postgres_repository.go:33.76,35.2 1 0
|
||||||
|
phattrang/matching_app_team/internal/adapter/database/repository.go:12.40,14.2 1 1
|
||||||
|
phattrang/matching_app_team/internal/adapter/database/repository.go:16.61,17.71 1 1
|
||||||
|
phattrang/matching_app_team/internal/adapter/database/repository.go:17.71,19.3 1 1
|
||||||
|
phattrang/matching_app_team/internal/adapter/database/repository.go:20.2,22.12 3 0
|
||||||
86
cmd/server/main.go
Normal file
86
cmd/server/main.go
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"phattrang/matching_app_team/internal/adapter/database"
|
||||||
|
"phattrang/matching_app_team/internal/helper/health"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
_ "github.com/lib/pq"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Initialize router
|
||||||
|
router := gin.Default()
|
||||||
|
|
||||||
|
// Initialize database connection
|
||||||
|
dbConnString := os.Getenv("DATABASE_URL")
|
||||||
|
if dbConnString == "" {
|
||||||
|
dbConnString = fmt.Sprintf("postgres://%s:%s@%s:%s/%s?sslmode=disable",
|
||||||
|
os.Getenv("DB_USER"),
|
||||||
|
os.Getenv("DB_PASSWORD"),
|
||||||
|
os.Getenv("DB_HOST"),
|
||||||
|
os.Getenv("DB_PORT"),
|
||||||
|
os.Getenv("DB_NAME"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
if err := database.InitPool(ctx, dbConnString); err != nil {
|
||||||
|
log.Printf("Warning: Failed to initialize database pool: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a standard SQL DB connection for the health check
|
||||||
|
db, err := sql.Open("postgres", dbConnString)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Warning: Failed to create DB connection for health check: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create health handler with DB connection
|
||||||
|
healthHandler := health.NewHandler(db)
|
||||||
|
|
||||||
|
// Register routes
|
||||||
|
health.RegisterRoutes(router, healthHandler)
|
||||||
|
|
||||||
|
// Get port from environment
|
||||||
|
port := os.Getenv("APP_PORT")
|
||||||
|
if port == "" {
|
||||||
|
port = "8080"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create server with graceful shutdown
|
||||||
|
server := &http.Server{
|
||||||
|
Addr: fmt.Sprintf(":%s", port),
|
||||||
|
Handler: router,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start server in a goroutine
|
||||||
|
go func() {
|
||||||
|
log.Printf("Starting server on port %s", port)
|
||||||
|
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||||
|
log.Fatalf("Failed to start server: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Wait for interrupt signal to gracefully shut down the server
|
||||||
|
quit := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
<-quit
|
||||||
|
log.Println("Shutting down server...")
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
if err := server.Shutdown(ctx); err != nil {
|
||||||
|
log.Fatalf("Server forced to shutdown: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("Server exited properly")
|
||||||
|
}
|
||||||
17
configs/config.yaml
Normal file
17
configs/config.yaml
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
# Application Configuration
|
||||||
|
|
||||||
|
# Database Settings
|
||||||
|
database:
|
||||||
|
host: ${DB_HOST}
|
||||||
|
port: ${DB_PORT}
|
||||||
|
user: ${DB_USER}
|
||||||
|
password: ${DB_PASSWORD}
|
||||||
|
name: ${DB_NAME}
|
||||||
|
|
||||||
|
# Logging Settings
|
||||||
|
log:
|
||||||
|
level: ${LOG_LEVEL}
|
||||||
|
|
||||||
|
logging:
|
||||||
|
level: ${LOG_LEVEL:-info}
|
||||||
|
encoding: ${LOG_ENCODING:-json}
|
||||||
23
configs/database.yaml
Normal file
23
configs/database.yaml
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
development:
|
||||||
|
host: localhost
|
||||||
|
port: 5432
|
||||||
|
user: dev_user
|
||||||
|
password: dev_pass
|
||||||
|
dbname: matching_dev
|
||||||
|
pool: 10
|
||||||
|
|
||||||
|
staging:
|
||||||
|
host: staging.db.example.com
|
||||||
|
port: 5432
|
||||||
|
user: staging_user
|
||||||
|
password: staging_pass
|
||||||
|
dbname: matching_staging
|
||||||
|
pool: 20
|
||||||
|
|
||||||
|
production:
|
||||||
|
host: production.db.example.com
|
||||||
|
port: 5432
|
||||||
|
user: prod_user
|
||||||
|
password: ${DB_PROD_PASSWORD}
|
||||||
|
dbname: matching_prod
|
||||||
|
pool: 30
|
||||||
56
coverage.out
Normal file
56
coverage.out
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
mode: set
|
||||||
|
phattrang/matching_app_team/cmd/server/main.go:20.13,26.24 3 0
|
||||||
|
phattrang/matching_app_team/cmd/server/main.go:26.24,34.3 1 0
|
||||||
|
phattrang/matching_app_team/cmd/server/main.go:36.2,37.61 2 0
|
||||||
|
phattrang/matching_app_team/cmd/server/main.go:37.61,39.3 1 0
|
||||||
|
phattrang/matching_app_team/cmd/server/main.go:42.2,43.16 2 0
|
||||||
|
phattrang/matching_app_team/cmd/server/main.go:43.16,45.3 1 0
|
||||||
|
phattrang/matching_app_team/cmd/server/main.go:48.2,55.16 4 0
|
||||||
|
phattrang/matching_app_team/cmd/server/main.go:55.16,57.3 1 0
|
||||||
|
phattrang/matching_app_team/cmd/server/main.go:60.2,66.12 2 0
|
||||||
|
phattrang/matching_app_team/cmd/server/main.go:66.12,68.80 2 0
|
||||||
|
phattrang/matching_app_team/cmd/server/main.go:68.80,70.4 1 0
|
||||||
|
phattrang/matching_app_team/cmd/server/main.go:74.2,81.45 7 0
|
||||||
|
phattrang/matching_app_team/cmd/server/main.go:81.45,83.3 1 0
|
||||||
|
phattrang/matching_app_team/cmd/server/main.go:85.2,85.39 1 0
|
||||||
|
phattrang/matching_app_team/internal/helper/config.go:28.47,33.16 3 0
|
||||||
|
phattrang/matching_app_team/internal/helper/config.go:33.16,35.3 1 0
|
||||||
|
phattrang/matching_app_team/internal/helper/config.go:37.2,38.16 2 0
|
||||||
|
phattrang/matching_app_team/internal/helper/config.go:38.16,40.3 1 0
|
||||||
|
phattrang/matching_app_team/internal/helper/config.go:43.2,43.46 1 0
|
||||||
|
phattrang/matching_app_team/internal/helper/config.go:43.46,45.3 1 0
|
||||||
|
phattrang/matching_app_team/internal/helper/config.go:48.2,48.20 1 0
|
||||||
|
phattrang/matching_app_team/internal/helper/error/error.go:23.60,24.9 1 0
|
||||||
|
phattrang/matching_app_team/internal/helper/error/error.go:25.35,29.25 1 0
|
||||||
|
phattrang/matching_app_team/internal/helper/error/error.go:31.39,35.25 1 0
|
||||||
|
phattrang/matching_app_team/internal/helper/error/error.go:37.39,41.25 1 0
|
||||||
|
phattrang/matching_app_team/internal/helper/error/error.go:43.10,48.16 2 0
|
||||||
|
phattrang/matching_app_team/internal/adapter/database/connection.go:19.73,21.16 2 0
|
||||||
|
phattrang/matching_app_team/internal/adapter/database/connection.go:21.16,23.3 1 0
|
||||||
|
phattrang/matching_app_team/internal/adapter/database/connection.go:25.2,31.16 6 0
|
||||||
|
phattrang/matching_app_team/internal/adapter/database/connection.go:31.16,33.3 1 0
|
||||||
|
phattrang/matching_app_team/internal/adapter/database/connection.go:35.2,35.16 1 0
|
||||||
|
phattrang/matching_app_team/internal/adapter/database/postgres_repository.go:13.70,15.2 1 0
|
||||||
|
phattrang/matching_app_team/internal/adapter/database/postgres_repository.go:17.78,19.2 1 0
|
||||||
|
phattrang/matching_app_team/internal/adapter/database/postgres_repository.go:21.86,23.2 1 0
|
||||||
|
phattrang/matching_app_team/internal/adapter/database/postgres_repository.go:25.78,27.2 1 0
|
||||||
|
phattrang/matching_app_team/internal/adapter/database/postgres_repository.go:29.78,31.2 1 0
|
||||||
|
phattrang/matching_app_team/internal/adapter/database/postgres_repository.go:33.76,35.2 1 0
|
||||||
|
phattrang/matching_app_team/internal/adapter/database/repository.go:12.40,14.2 1 1
|
||||||
|
phattrang/matching_app_team/internal/adapter/database/repository.go:16.61,17.71 1 1
|
||||||
|
phattrang/matching_app_team/internal/adapter/database/repository.go:17.71,19.3 1 1
|
||||||
|
phattrang/matching_app_team/internal/adapter/database/repository.go:20.2,22.12 3 0
|
||||||
|
phattrang/matching_app_team/internal/uiux/middleware.go:8.60,9.30 1 0
|
||||||
|
phattrang/matching_app_team/internal/uiux/middleware.go:9.30,21.3 3 0
|
||||||
|
phattrang/matching_app_team/internal/uiux/middleware.go:24.58,25.30 1 0
|
||||||
|
phattrang/matching_app_team/internal/uiux/middleware.go:25.30,29.24 2 0
|
||||||
|
phattrang/matching_app_team/internal/uiux/middleware.go:29.24,31.31 2 0
|
||||||
|
phattrang/matching_app_team/internal/uiux/middleware.go:31.31,33.5 1 0
|
||||||
|
phattrang/matching_app_team/internal/uiux/middleware.go:34.4,37.5 1 0
|
||||||
|
phattrang/matching_app_team/internal/uiux/router.go:8.32,18.2 4 0
|
||||||
|
phattrang/matching_app_team/internal/helper/health/handler.go:10.36,14.2 1 1
|
||||||
|
phattrang/matching_app_team/internal/helper/health/handler.go:20.38,22.2 1 0
|
||||||
|
phattrang/matching_app_team/internal/helper/health/handler.go:24.41,27.16 2 0
|
||||||
|
phattrang/matching_app_team/internal/helper/health/handler.go:27.16,33.3 2 0
|
||||||
|
phattrang/matching_app_team/internal/helper/health/handler.go:34.2,36.4 1 0
|
||||||
|
phattrang/matching_app_team/internal/helper/health/router.go:5.48,7.2 1 0
|
||||||
78
docker-compose.yml
Normal file
78
docker-compose.yml
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
version: '3.8'
|
||||||
|
services:
|
||||||
|
db:
|
||||||
|
image: postgres:15
|
||||||
|
environment:
|
||||||
|
POSTGRES_USER: ${DB_USER}
|
||||||
|
POSTGRES_PASSWORD: ${DB_PASSWORD}
|
||||||
|
POSTGRES_DB: ${DB_NAME}
|
||||||
|
volumes:
|
||||||
|
- db_data:/var/lib/postgresql/data
|
||||||
|
- ./migrations:/docker-entrypoint-initdb.d
|
||||||
|
ports:
|
||||||
|
- "5432:5432"
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U ${DB_USER} -d ${DB_NAME}"]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
deploy:
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
cpus: '0.5'
|
||||||
|
memory: 512M
|
||||||
|
|
||||||
|
adminer:
|
||||||
|
image: adminer
|
||||||
|
restart: always
|
||||||
|
ports:
|
||||||
|
- "8081:8080"
|
||||||
|
depends_on:
|
||||||
|
db:
|
||||||
|
condition: service_healthy
|
||||||
|
|
||||||
|
app:
|
||||||
|
build: .
|
||||||
|
environment:
|
||||||
|
DB_HOST: db
|
||||||
|
DB_USER: ${DB_USER}
|
||||||
|
DB_PASSWORD: ${DB_PASSWORD}
|
||||||
|
DB_NAME: ${DB_NAME}
|
||||||
|
DB_PORT: "5432"
|
||||||
|
LOG_LEVEL: "debug"
|
||||||
|
ports:
|
||||||
|
- "8080:8080"
|
||||||
|
depends_on:
|
||||||
|
db:
|
||||||
|
condition: service_healthy
|
||||||
|
deploy:
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
cpus: '1'
|
||||||
|
memory: 1G
|
||||||
|
|
||||||
|
db_dev:
|
||||||
|
image: postgres:15
|
||||||
|
environment:
|
||||||
|
POSTGRES_USER: dev_user
|
||||||
|
POSTGRES_PASSWORD: dev_pass
|
||||||
|
POSTGRES_DB: matching_dev
|
||||||
|
ports:
|
||||||
|
- "5433:5432"
|
||||||
|
volumes:
|
||||||
|
- pgdata_dev:/var/lib/postgresql/data
|
||||||
|
- ./migrations:/docker-entrypoint-initdb.d
|
||||||
|
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U dev_user -d matching_dev"]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
volumes:
|
||||||
|
db_data:
|
||||||
|
pgdata_dev:
|
||||||
|
|
||||||
|
networks:
|
||||||
|
default:
|
||||||
|
name: matching_network
|
||||||
|
driver: bridge
|
||||||
@ -1,5 +1,48 @@
|
|||||||
# FORMAT
|
# FORMAT
|
||||||
|
|
||||||
|
# Title: External System Adapters
|
||||||
|
|
||||||
|
# Description:
|
||||||
|
|
||||||
|
Component Adapter xử lý giao tiếp với các hệ thống bên ngoài. Adapter giúp tách biệt core domain logic khỏi các chi tiết triển khai bên ngoài.
|
||||||
|
|
||||||
|
## Adapter Categories
|
||||||
|
|
||||||
|
### Database Adapters
|
||||||
|
* **PersistenceAdapter**:
|
||||||
|
* **Functionality**: Interface with PostgreSQL database
|
||||||
|
* **Location**: `internal/adapter/postgres/`
|
||||||
|
* **Key Components**:
|
||||||
|
* `connection.go`: Database connection pool management
|
||||||
|
* `profile_repo.go`: Repository for Profile aggregate
|
||||||
|
* `match_repo.go`: Repository for MatchOutcome aggregate
|
||||||
|
* `gift_repo.go`: Repository for Gift and GiftAllocation aggregates
|
||||||
|
* `models.go`: Database model definitions
|
||||||
|
|
||||||
|
### Email Adapters
|
||||||
|
* **EmailAdapter**:
|
||||||
|
* **Functionality**: Send emails for user registration, match results, and gift notifications
|
||||||
|
* **Location**: `internal/adapter/email/`
|
||||||
|
* **Key Components**:
|
||||||
|
* `client.go`: Client interface to email service
|
||||||
|
* `templates.go`: Email templates management
|
||||||
|
|
||||||
|
### Storage Adapters
|
||||||
|
* **FileStorageAdapter**:
|
||||||
|
* **Functionality**: Upload and retrieve files (profile photos)
|
||||||
|
* **Location**: `internal/adapter/filestorage/`
|
||||||
|
* **Key Components**:
|
||||||
|
* `s3_client.go`: S3 API client implementation
|
||||||
|
* `validation.go`: File validation
|
||||||
|
|
||||||
|
## Implementation Guidelines
|
||||||
|
|
||||||
|
* All adapters should implement a standard interface defined in their respective domain
|
||||||
|
* Error handling should follow the system-wide error handling strategy
|
||||||
|
* Configuration should be loaded from environment variables and config files
|
||||||
|
* Connection pooling should be used where appropriate
|
||||||
|
* Adapters should be easily replaceable with mock implementations for testing
|
||||||
|
|
||||||
# System Connections
|
# System Connections
|
||||||
|
|
||||||
## 1. External Connections
|
## 1. External Connections
|
||||||
|
|||||||
@ -9,6 +9,15 @@
|
|||||||
- **Data-Oriented Programming (DOP)**: Thiết kế xoay quanh luồng dữ liệu và các biến đổi dữ liệu
|
- **Data-Oriented Programming (DOP)**: Thiết kế xoay quanh luồng dữ liệu và các biến đổi dữ liệu
|
||||||
- **Domain-Driven Design (DDD)**: Áp dụng các khái niệm cốt lõi (Bounded Context, Aggregates, Domain Events)
|
- **Domain-Driven Design (DDD)**: Áp dụng các khái niệm cốt lõi (Bounded Context, Aggregates, Domain Events)
|
||||||
|
|
||||||
|
## System-Wide Error Handling Strategy
|
||||||
|
* **Detection & Categorization:** Errors will be detected at various layers (API validation, Transaction logic, Adapter communication). Errors will be categorized (e.g., user input error, resource not found, external service failure, internal server error).
|
||||||
|
* **Propagation:** Errors from lower layers (Adapters, Resources) will be propagated up to the calling Transaction or API Handler, potentially wrapped for context.
|
||||||
|
* **Logging:** All significant errors (especially non-user correctable ones) will be logged centrally with structured information (timestamp, error type, stack trace if applicable, request context). A dedicated `Logging` Helper will be used.
|
||||||
|
* **State Consistency:** Database transactions within the `PersistenceAdapter` will be used to ensure atomic updates to Resources, maintaining consistency in case of errors during writes. Sagas (`Transaction` components) will handle logical consistency across multiple steps (though complex compensation is expected to be minimal).
|
||||||
|
* **Communication:**
|
||||||
|
* *To Users (via HTMX/API):* User input errors will result in user-friendly messages, potentially rendered directly into HTML fragments by the backend. Generic messages will be used for unexpected server errors to avoid exposing internal details. Standard HTTP status codes will be used (e.g., 400 for bad input, 404 for not found, 500 for server error).
|
||||||
|
* *To Admins:* Detailed error information will be available in logs. The Admin Dashboard might display specific error summaries or alerts for critical issues.
|
||||||
|
|
||||||
## Thành phần kiến trúc chính
|
## Thành phần kiến trúc chính
|
||||||
- `Resource`: Các Aggregate DDD đại diện cho đối tượng nghiệp vụ
|
- `Resource`: Các Aggregate DDD đại diện cho đối tượng nghiệp vụ
|
||||||
- `Transaction`: Các Saga/Process Manager điều phối luồng nghiệp vụ phức tạp
|
- `Transaction`: Các Saga/Process Manager điều phối luồng nghiệp vụ phức tạp
|
||||||
@ -16,6 +25,26 @@
|
|||||||
- `Helper`: Các thư viện, tiện ích dùng chung
|
- `Helper`: Các thư viện, tiện ích dùng chung
|
||||||
- `UIUX`: Lớp giao diện người dùng
|
- `UIUX`: Lớp giao diện người dùng
|
||||||
|
|
||||||
|
## Container Architecture
|
||||||
|
- **Docker Setup**:
|
||||||
|
- Multi-stage builds để tối ưu kích thước image
|
||||||
|
- Non-root user cho bảo mật
|
||||||
|
- Healthcheck tự động kiểm tra trạng thái
|
||||||
|
- Resource limits (CPU/Memory) cho từng service
|
||||||
|
|
||||||
|
## Deployment Diagram
|
||||||
|
```mermaid
|
||||||
|
graph TD
|
||||||
|
A[User] --> B[Frontend]
|
||||||
|
B --> C[Backend API]
|
||||||
|
C --> D[(PostgreSQL)]
|
||||||
|
C --> E[Email Service]
|
||||||
|
C --> F[S3 Storage]
|
||||||
|
style A fill:#f9f,stroke:#333
|
||||||
|
style B fill:#bbf,stroke:#333
|
||||||
|
style C fill:#f96,stroke:#333
|
||||||
|
```
|
||||||
|
|
||||||
## Phân cấp độ chi tiết (U-Hierarchy)
|
## Phân cấp độ chi tiết (U-Hierarchy)
|
||||||
- `ubit`: Đơn vị logic nhỏ nhất (hàm, type, hằng số)
|
- `ubit`: Đơn vị logic nhỏ nhất (hàm, type, hằng số)
|
||||||
- `ubrick`: Tập hợp các ubit liên quan
|
- `ubrick`: Tập hợp các ubit liên quan
|
||||||
@ -26,8 +55,8 @@
|
|||||||
- **Backend**: Go
|
- **Backend**: Go
|
||||||
- **Database**: PostgreSQL (có thể dùng JSONB cho một số trường linh hoạt)
|
- **Database**: PostgreSQL (có thể dùng JSONB cho một số trường linh hoạt)
|
||||||
- **Frontend**: HTMX (ưu tiên), JavaScript hạn chế
|
- **Frontend**: HTMX (ưu tiên), JavaScript hạn chế
|
||||||
- **Deployment**: Docker, triển khai trên Cloud (AWS/GCP/Azure)
|
- **Deployment**: Docker, triển khai trên Cloud (VPS Storage)
|
||||||
- **CI/CD Integration**: GitHub Actions
|
- **CI/CD Integration**: DroneCI
|
||||||
- **Database**: Managed PostgreSQL Service
|
- **Database**: Managed PostgreSQL Service
|
||||||
- **Frontend Serving**: Backend render HTML fragments, CDN cho static assets
|
- **Frontend Serving**: Backend render HTML fragments, CDN cho static assets
|
||||||
|
|
||||||
|
|||||||
@ -3,5 +3,19 @@
|
|||||||
---
|
---
|
||||||
# CHANGELOG
|
# CHANGELOG
|
||||||
|
|
||||||
|
[2025-04-29 00:14] : cicd : Updated DroneCI pipeline with full test coverage and security scanning
|
||||||
|
[2025-04-29 00:14] : cicd : Added Slack notification integration
|
||||||
|
[2025-04-29 00:08] : tests : Completed all core component tests for Milestone 2
|
||||||
|
[2025-04-29 00:06] : internal/adapter/database : Fixed connection pool initialization with invalid connection string handling
|
||||||
|
[2025-04-29 00:06] : internal/helper/health : Updated test cases for health handler
|
||||||
|
[2025-04-26 23:30] : internal/helper : Implemented error handling strategy with structured logging
|
||||||
|
[2025-04-26 23:25] : internal/uiux : Added router structure and base middleware
|
||||||
|
[2025-04-26 23:20] : internal/helper/health : Created health check endpoints
|
||||||
|
[2025-04-26 18:25] : db : Completed PostgreSQL setup for dev environment
|
||||||
|
[2025-04-26 18:25] : adminer : Configured Adminer with PostgreSQL support
|
||||||
|
[2025-04-26 12:15] : cicd : Added DroneCI configuration
|
||||||
|
[2025-04-26 12:15] : docs : Updated README with database connection guide
|
||||||
|
[2025-04-26 11:44] : docker : Updated Dockerfile with multi-stage build and security best practices
|
||||||
|
[2025-04-26 11:44] : docker : Improved docker-compose.yml with healthcheck and resource limits
|
||||||
[2025-04-25 15:00] : md : Updated documentation files based on matching_app templates
|
[2025-04-25 15:00] : md : Updated documentation files based on matching_app templates
|
||||||
[2025-04-25 14:01] : md : Created all documentation template files
|
[2025-04-25 14:01] : md : Created all documentation template files
|
||||||
|
|||||||
16
docs/docker_rule.md
Normal file
16
docs/docker_rule.md
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# Docker Rule (Temp)
|
||||||
|
## Create builder Stage
|
||||||
|
|
||||||
|
## Config non-root user
|
||||||
|
|
||||||
|
## Final Image From Development Image
|
||||||
|
|
||||||
|
## Healthcheck
|
||||||
|
|
||||||
|
## Expose port & User & Entrypoint
|
||||||
|
|
||||||
|
# Docker Compose Rule (Temp)
|
||||||
|
- Add migrations folder to postgres service
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -5,7 +5,6 @@
|
|||||||
# Short Description: Microsite chiến dịch “Team Ngọt Ngào và Thanh Dịu”, cho phép người dùng đăng ký thông tin cá nhân, matching tự động, thông báo qua email và quay thưởng.
|
# Short Description: Microsite chiến dịch “Team Ngọt Ngào và Thanh Dịu”, cho phép người dùng đăng ký thông tin cá nhân, matching tự động, thông báo qua email và quay thưởng.
|
||||||
|
|
||||||
# Core Purpose: Cung cấp nền tảng ghép đôi tự động dựa trên sở thích và tiêu chí người dùng, quản lý thông báo và trao giải thưởng cho người may mắn.
|
# Core Purpose: Cung cấp nền tảng ghép đôi tự động dựa trên sở thích và tiêu chí người dùng, quản lý thông báo và trao giải thưởng cho người may mắn.
|
||||||
|
|
||||||
# Key Features Overview:
|
# Key Features Overview:
|
||||||
# - Đăng ký hồ sơ người dùng
|
# - Đăng ký hồ sơ người dùng
|
||||||
# - Ghép đôi tự động với 3 kết quả phù hợp
|
# - Ghép đôi tự động với 3 kết quả phù hợp
|
||||||
@ -14,7 +13,7 @@
|
|||||||
# - Quay thưởng và phân bổ quà tặng
|
# - Quay thưởng và phân bổ quà tặng
|
||||||
# - **Database Connectivity:** Kết nối PostgreSQL qua biến môi trường (host, port, user, password, dbname)
|
# - **Database Connectivity:** Kết nối PostgreSQL qua biến môi trường (host, port, user, password, dbname)
|
||||||
# - **Environment Management:** Hỗ trợ development, staging, production
|
# - **Environment Management:** Hỗ trợ development, staging, production
|
||||||
# - **CI/CD Integration:** Sử dụng GitHub Actions
|
# - **CI/CD Integration:** Sử dụng DroneCI
|
||||||
|
|
||||||
## Core Technologies
|
## Core Technologies
|
||||||
- Go
|
- Go
|
||||||
@ -22,7 +21,32 @@
|
|||||||
- HTMX
|
- HTMX
|
||||||
- Docker
|
- Docker
|
||||||
- Cloud (VPS Storage)
|
- Cloud (VPS Storage)
|
||||||
- CI/CD (GitHub Actions)
|
- CI/CD (DroneCI)
|
||||||
|
|
||||||
|
# Development Phases
|
||||||
|
1. **Foundation Setup**:
|
||||||
|
- Core infrastructure
|
||||||
|
- CI/CD pipeline
|
||||||
|
- Database provisioning
|
||||||
|
|
||||||
|
2. **Core Components**:
|
||||||
|
- Database layer
|
||||||
|
- Config management
|
||||||
|
- Logging/Monitoring
|
||||||
|
- API framework
|
||||||
|
|
||||||
|
3. **Business Features**:
|
||||||
|
- User registration
|
||||||
|
- Matching engine
|
||||||
|
- Notification system
|
||||||
|
|
||||||
|
# Architectural Constraints
|
||||||
|
- Layer Isolation: Tách biệt rõ business logic và infrastructure
|
||||||
|
- Testability: Components phải testable độc lập
|
||||||
|
- Environment Parity: Config hoạt động đồng nhất qua các môi trường
|
||||||
|
|
||||||
|
# Implementation Order
|
||||||
|
1. Database Layer → 2. Core Services → 3. API Framework → 4. Business Features
|
||||||
|
|
||||||
# Target Audience: Cá nhân muốn tham gia chương trình ghép đôi
|
# Target Audience: Cá nhân muốn tham gia chương trình ghép đôi
|
||||||
|
|
||||||
|
|||||||
@ -7,63 +7,76 @@
|
|||||||
### 1. Infrastructure & Environment Setup
|
### 1. Infrastructure & Environment Setup
|
||||||
Thiết lập cơ sở hạ tầng và môi trường cho dự án.
|
Thiết lập cơ sở hạ tầng và môi trường cho dự án.
|
||||||
* **Tasks:**
|
* **Tasks:**
|
||||||
* [ ] Khởi tạo project Go và cấu hình module (Completed: )
|
* [X] Khởi tạo project Go và cấu hình module (Completed: 2025-04-25)
|
||||||
* [ ] Thiết lập Docker/Docker Compose (Completed: )
|
* [X] Thiết lập Docker/Docker Compose (Completed: 2025-04-25)
|
||||||
* [ ] Cấu hình PostgreSQL cho dev/staging/prod (Completed: )
|
* [X] Cấu hình PostgreSQL cho dev/staging/prod (Completed: 2025-04-26)
|
||||||
* [ ] Thiết lập CI/CD với GitHub Actions (Completed: )
|
* [X] Thiết lập CI/CD với DroneCI (Completed: 2025-04-29)
|
||||||
|
|
||||||
### 2. User Registration Flow
|
### 2. Core Components Setup & API Infrastructure
|
||||||
|
* **Tasks:**
|
||||||
|
* [X] Create Folder Structure from 'architecture.md'
|
||||||
|
* [X] Tạo base repository pattern
|
||||||
|
* [X] Implement database connection pool (Tested: 2025-04-29)
|
||||||
|
* [X] Health check endpoints (Tested: 2025-04-29)
|
||||||
|
* [X] Setup config loader (Tested: 2025-04-29)
|
||||||
|
* [X] Khởi tạo logging system
|
||||||
|
* [X] Implement error handling strategy
|
||||||
|
* [X] Define router structure
|
||||||
|
* [X] Implement base middleware
|
||||||
|
* [X] API documentation setup
|
||||||
|
|
||||||
|
### 3. User Registration Flow
|
||||||
Xây dựng tính năng đăng ký người dùng và lưu trữ hồ sơ.
|
Xây dựng tính năng đăng ký người dùng và lưu trữ hồ sơ.
|
||||||
* **Tasks:**
|
* **Tasks:**
|
||||||
* [ ] Thiết kế schema và repository cho `Profile` (Completed: )
|
* [ ] Thiết kế schema và repository cho `Profile`
|
||||||
* [ ] Triển khai API `POST /profiles` và handler (Completed: )
|
* [ ] Triển khai API `POST /profiles` và handler
|
||||||
* [ ] Tích hợp upload ảnh lên S3 (Completed: )
|
* [ ] Tích hợp upload ảnh lên S3
|
||||||
* [ ] Gửi email xác nhận đăng ký (Completed: )
|
* [ ] Gửi email xác nhận đăng ký
|
||||||
|
|
||||||
### 3. Matching Engine
|
### 4. Matching Engine
|
||||||
Phát triển logic ghép đôi và lưu kết quả.
|
Phát triển logic ghép đôi và lưu kết quả.
|
||||||
* **Tasks:**
|
* **Tasks:**
|
||||||
* [ ] Xây dựng ubit/ubrick tính toán điểm và filter (Completed: )
|
* [ ] Xây dựng ubit/ubrick tính toán điểm và filter
|
||||||
* [ ] Triển khai saga `MatchingTransaction` (Completed: )
|
* [ ] Triển khai saga `MatchingTransaction`
|
||||||
* [ ] Tạo và lưu `MatchOutcome` (Completed: )
|
* [ ] Tạo và lưu `MatchOutcome`
|
||||||
* [ ] Xây dựng batch job hoặc trigger real-time (Completed: )
|
* [ ] Xây dựng batch job hoặc trigger real-time
|
||||||
|
|
||||||
### 4. Result Interaction
|
### 5. Result Interaction
|
||||||
Xây dựng giao diện và API cho kết quả match.
|
Xây dựng giao diện và API cho kết quả match.
|
||||||
* **Tasks:**
|
* **Tasks:**
|
||||||
* [ ] Tạo Result Page với HTMX (Completed: )
|
* [ ] Tạo Result Page với HTMX
|
||||||
* [ ] Triển khai `GET /results/{token}` (Completed: )
|
* [ ] Triển khai `GET /results/{token}`
|
||||||
* [ ] API `POST /results/select` và `POST /results/decline` (Completed: )
|
* [ ] API `POST /results/select` và `POST /results/decline`
|
||||||
* [ ] Cập nhật state trong `MatchOutcome` (Completed: )
|
* [ ] Cập nhật state trong `MatchOutcome`
|
||||||
|
|
||||||
### 5. Notifications & Gift Draw
|
### 6. Notifications & Gift Draw
|
||||||
Gửi email kết quả và thực hiện quay thưởng.
|
Gửi email kết quả và thực hiện quay thưởng.
|
||||||
* **Tasks:**
|
* **Tasks:**
|
||||||
* [ ] Saga `ResultNotificationTransaction` – email kết quả (Completed: )
|
* [ ] Saga `ResultNotificationTransaction` – email kết quả
|
||||||
* [ ] Saga `GiftAllocationTransaction` – quay thưởng và lưu `GiftAllocation` (Completed: )
|
* [ ] Saga `GiftAllocationTransaction` – quay thưởng và lưu `GiftAllocation`
|
||||||
* [ ] Saga `GiftNotificationTransaction` – email trúng thưởng (Completed: )
|
* [ ] Saga `GiftNotificationTransaction` – email trúng thưởng
|
||||||
* [ ] Thiết kế và seed dữ liệu `Gift` (Completed: )
|
* [ ] Thiết kế và seed dữ liệu `Gift`
|
||||||
|
|
||||||
### 6. Admin Dashboard
|
### 7. Admin Dashboard
|
||||||
Xây dựng giao diện quản trị và báo cáo.
|
Xây dựng giao diện quản trị và báo cáo.
|
||||||
* **Tasks:**
|
* **Tasks:**
|
||||||
* [ ] Implement auth endpoint `POST /admin/auth/login` (Completed: )
|
* [ ] Implement auth endpoint `POST /admin/auth/login`
|
||||||
* [ ] Reporting Dashboard – summary và báo cáo theo team (Completed: )
|
* [ ] Reporting Dashboard – summary và báo cáo theo team
|
||||||
* [ ] Campaign Management UI & API (Completed: )
|
* [ ] Campaign Management UI & API
|
||||||
* [ ] Gift Management CRUD (Completed: )
|
* [ ] Gift Management CRUD
|
||||||
|
|
||||||
### 7. QA, Testing & Monitoring
|
### 8. QA, Testing & Monitoring
|
||||||
Đảm bảo chất lượng và vận hành.
|
Đảm bảo chất lượng và vận hành.
|
||||||
* **Tasks:**
|
* **Tasks:**
|
||||||
* [ ] Viết unit/integration tests (Completed: )
|
* [ ] Viết unit/integration tests
|
||||||
* [ ] Thiết lập monitoring và alert (Completed: )
|
* [ ] Thiết lập monitoring và alert
|
||||||
* [ ] Load testing ≥100 req/s (Completed: )
|
* [ ] Load testing ≥100 req/s
|
||||||
|
|
||||||
### 8. Deployment & Handover
|
### 9. Deployment & Handover
|
||||||
Triển khai lên môi trường production và bàn giao.
|
Triển khai lên môi trường production và bàn giao.
|
||||||
* **Tasks:**
|
* **Tasks:**
|
||||||
* [ ] Build & deploy Docker containers lên Cloud (Completed: )
|
* [ ] Build & deploy Docker containers lên Cloud
|
||||||
* [ ] Cấu hình load balancer, CDN (Completed: )
|
* [ ] Cấu hình load balancer, CDN
|
||||||
* [ ] Cập nhật README và hướng dẫn vận hành (Completed: )
|
* [ ] Cập nhật README và hướng dẫn vận hành
|
||||||
* [ ] Bàn giao tài liệu và training (Completed: )
|
* [ ] Bàn giao tài liệu và training
|
||||||
---
|
---
|
||||||
@ -1,5 +1,83 @@
|
|||||||
# FORMAT
|
# FORMAT
|
||||||
|
|
||||||
|
# Title: Database Schema
|
||||||
|
|
||||||
|
# Database Type: PostgreSQL
|
||||||
|
|
||||||
|
# Core Entities
|
||||||
|
|
||||||
|
## Profile
|
||||||
|
* **Description**: Lưu thông tin người tham gia
|
||||||
|
* **Fields**:
|
||||||
|
* `id` (UUID, PRIMARY KEY): Unique identifier
|
||||||
|
* `fullName` (TEXT): Tên đầy đủ người dùng
|
||||||
|
* `dateOfBirth` (DATE): Ngày sinh
|
||||||
|
* `gender` (TEXT): Giới tính
|
||||||
|
* `preferredGender` (TEXT): Giới tính mong muốn
|
||||||
|
* `phoneNumber` (TEXT): Số điện thoại
|
||||||
|
* `email` (TEXT): Email
|
||||||
|
* `facebookLink` (TEXT): Link Facebook
|
||||||
|
* `photoReference` (TEXT): Reference to stored photo
|
||||||
|
* `location` (TEXT): Khu vực
|
||||||
|
* `team` (TEXT): Team choice
|
||||||
|
* `hobbies` (TEXT[]): Sở thích
|
||||||
|
* `thingsNotTried` (TEXT): Thứ chưa thử qua
|
||||||
|
* `hopesForPartner` (TEXT): Mong muốn đối với đối tác
|
||||||
|
* `dealbreakers` (TEXT): Ranh giới
|
||||||
|
* `messageToPartner` (TEXT): Thông điệp gửi đối tác
|
||||||
|
* `zodiacSign` (TEXT): Cung hoàng đạo
|
||||||
|
* `accessCode` (TEXT): Code truy cập kết quả
|
||||||
|
* `personalLink` (TEXT): Link cá nhân
|
||||||
|
* `acceptedTerms` (BOOLEAN): Chấp nhận điều khoản
|
||||||
|
* `registrationTimestamp` (TIMESTAMP): Thời điểm đăng ký
|
||||||
|
* `status` (TEXT): Trạng thái
|
||||||
|
* `isLuckyWinner` (BOOLEAN): Có trúng thưởng
|
||||||
|
* `createdAt` (TIMESTAMP): Thời điểm tạo
|
||||||
|
* `updatedAt` (TIMESTAMP): Thời điểm cập nhật
|
||||||
|
|
||||||
|
## MatchOutcome
|
||||||
|
* **Description**: Lưu kết quả matching cho một Profile
|
||||||
|
* **Fields**:
|
||||||
|
* `id` (UUID, PRIMARY KEY): Unique identifier
|
||||||
|
* `profileId` (UUID, FK): ID hồ sơ
|
||||||
|
* `potentialMatchProfileIds` (UUID[]): Danh sách ID 3 đối tượng phù hợp
|
||||||
|
* `matchScores` (JSON): Điểm match
|
||||||
|
* `status` (TEXT): Trạng thái
|
||||||
|
* `selectedProfileId` (UUID): ID hồ sơ được chọn
|
||||||
|
* `generationTimestamp` (TIMESTAMP): Thời điểm tạo kết quả
|
||||||
|
* `accessedTimestamp` (TIMESTAMP): Thời điểm truy cập
|
||||||
|
* `selectionTimestamp` (TIMESTAMP): Thời điểm chọn
|
||||||
|
* `createdAt` (TIMESTAMP): Thời điểm tạo
|
||||||
|
* `updatedAt` (TIMESTAMP): Thời điểm cập nhật
|
||||||
|
|
||||||
|
## GiftAllocation
|
||||||
|
* **Description**: Ghi nhận việc phân bổ quà tặng
|
||||||
|
* **Fields**:
|
||||||
|
* `id` (UUID, PRIMARY KEY): Unique identifier
|
||||||
|
* `profileId` (UUID, FK): ID hồ sơ
|
||||||
|
* `giftId` (UUID, FK): ID quà
|
||||||
|
* `allocationTimestamp` (TIMESTAMP): Thời điểm phân bổ
|
||||||
|
* `notificationStatus` (TEXT): Trạng thái thông báo
|
||||||
|
* `notificationSentTimestamp` (TIMESTAMP): Thời điểm gửi thông báo
|
||||||
|
* `createdAt` (TIMESTAMP): Thời điểm tạo
|
||||||
|
* `updatedAt` (TIMESTAMP): Thời điểm cập nhật
|
||||||
|
|
||||||
|
## Gift
|
||||||
|
* **Description**: Lưu thông tin các loại quà tặng
|
||||||
|
* **Fields**:
|
||||||
|
* `id` (UUID, PRIMARY KEY): Unique identifier
|
||||||
|
* `name` (TEXT): Tên quà
|
||||||
|
* `description` (TEXT): Mô tả
|
||||||
|
* `imageUrl` (TEXT): URL hình ảnh
|
||||||
|
* `isActive` (BOOLEAN): Trạng thái hoạt động
|
||||||
|
* `createdAt` (TIMESTAMP): Thời điểm tạo
|
||||||
|
* `updatedAt` (TIMESTAMP): Thời điểm cập nhật
|
||||||
|
|
||||||
|
# Indexes
|
||||||
|
* `profiles_email_idx`: Index trên trường email của Profile
|
||||||
|
* `profiles_access_code_idx`: Index trên trường accessCode của Profile
|
||||||
|
* `match_outcomes_profile_id_idx`: Index trên trường profileId của MatchOutcome
|
||||||
|
|
||||||
# DATA SCHEMA AND STRUCTURES
|
# DATA SCHEMA AND STRUCTURES
|
||||||
|
|
||||||
## 1. Database Schema
|
## 1. Database Schema
|
||||||
|
|||||||
12
docs/spec.md
12
docs/spec.md
@ -53,6 +53,18 @@
|
|||||||
5. **GiftAllocationTransaction**: Quay thưởng → Tạo GiftAllocation
|
5. **GiftAllocationTransaction**: Quay thưởng → Tạo GiftAllocation
|
||||||
6. **GiftNotificationTransaction**: Gửi thông báo trúng thưởng
|
6. **GiftNotificationTransaction**: Gửi thông báo trúng thưởng
|
||||||
|
|
||||||
|
## Error Handling Specification
|
||||||
|
|
||||||
|
### Error Types:
|
||||||
|
- **UserInputError**: Lỗi dữ liệu đầu vào (HTTP 400)
|
||||||
|
- **NotFoundError**: Tài nguyên không tồn tại (HTTP 404)
|
||||||
|
- **UnauthorizedError**: Truy cập không được phép (HTTP 401)
|
||||||
|
- **InternalServerError**: Lỗi hệ thống (HTTP 500)
|
||||||
|
|
||||||
|
### Logging Requirements:
|
||||||
|
- Tất cả lỗi phải được ghi lại với context đầy đủ
|
||||||
|
- Sử dụng structured logging với zap
|
||||||
|
|
||||||
## Overall Flow:
|
## Overall Flow:
|
||||||
```mermaid
|
```mermaid
|
||||||
sequenceDiagram
|
sequenceDiagram
|
||||||
|
|||||||
56
docs/test_steps.md
Normal file
56
docs/test_steps.md
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
# Test Steps for Milestone 2: Core Components Setup & API Infrastructure
|
||||||
|
|
||||||
|
## 1. Folder Structure Verification
|
||||||
|
- [x] Kiểm tra cấu trúc thư mục dự án theo chuẩn `architecture.md`
|
||||||
|
- [x] Đảm bảo các package chính: `internal/adapter`, `internal/helper`, `internal/uiux`
|
||||||
|
|
||||||
|
## 2. Config Loader
|
||||||
|
- [x] Test đọc cấu hình từ file YAML
|
||||||
|
- [x] Test override giá trị qua biến môi trường
|
||||||
|
- [x] Xác minh config trả về đúng cấu trúc
|
||||||
|
|
||||||
|
## 3. Logging System
|
||||||
|
- [ ] Kiểm tra khởi tạo Zap logger
|
||||||
|
- [ ] Ghi log thử các mức (info, warn, error)
|
||||||
|
- [ ] Đảm bảo log xuất hiện đúng format
|
||||||
|
|
||||||
|
## 4. Database Connection Pool
|
||||||
|
- [X] Test kết nối đến PostgreSQL
|
||||||
|
- [X] Kiểm tra pool hoạt động (nối nhiều connection)
|
||||||
|
- [X] Xử lý lỗi khi cấu hình DB sai
|
||||||
|
- [X] Test invalid connection string
|
||||||
|
- [X] Test valid connection
|
||||||
|
|
||||||
|
## 5. Error Handling
|
||||||
|
- [X] Kiểm tra các loại error (NotFound, InvalidInput, Unauthorized, Internal)
|
||||||
|
- [X] Đảm bảo error được log đúng và trả về HTTP status phù hợp
|
||||||
|
|
||||||
|
## 6. Health Check Endpoint
|
||||||
|
- [X] Gọi API `/health` kiểm tra trạng thái hệ thống
|
||||||
|
- [X] Test trường hợp DB lỗi, API trả về unhealthy
|
||||||
|
- [X] Test health endpoint returns 200
|
||||||
|
- [X] Test response contains status
|
||||||
|
|
||||||
|
## 7. Middleware
|
||||||
|
- [ ] Kiểm tra logging middleware (log request, response)
|
||||||
|
- [ ] Kiểm tra error middleware (ghi lại error phát sinh)
|
||||||
|
|
||||||
|
## 8. Router Structure
|
||||||
|
- [ ] Đảm bảo các route cơ bản đã đăng ký
|
||||||
|
- [ ] Health check endpoint hoạt động
|
||||||
|
|
||||||
|
## 9. API Documentation
|
||||||
|
- [ ] Kiểm tra tài liệu API (Swagger/OpenAPI nếu có)
|
||||||
|
|
||||||
|
## Test Results - 2025-04-29
|
||||||
|
|
||||||
|
✅ Database Connection: Passed
|
||||||
|
✅ Health Check: Passed
|
||||||
|
✅ Config Loader: Passed
|
||||||
|
|
||||||
|
Các component cốt lõi đã sẵn sàng cho Milestone 3
|
||||||
|
|
||||||
|
---
|
||||||
|
**Ghi chú:**
|
||||||
|
- Nên thực hiện cả unit test, integration test và manual API test cho từng bước trên.
|
||||||
|
- Ghi lại kết quả test, lỗi phát hiện và hướng xử lý nếu có.
|
||||||
148
docs/workflow_guideline.md
Normal file
148
docs/workflow_guideline.md
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
# Hướng Dẫn Quy Trình Làm Việc
|
||||||
|
|
||||||
|
Tài liệu này cung cấp hướng dẫn về quy trình làm việc với dự án matching-app, bao gồm các vai trò khác nhau, công cụ hữu ích và quy trình làm việc mẫu.
|
||||||
|
|
||||||
|
## Quy trình làm việc theo Role
|
||||||
|
|
||||||
|
### Developer
|
||||||
|
|
||||||
|
#### Thiết lập môi trường
|
||||||
|
- Sử dụng `make dev-workflow` để thiết lập môi trường làm việc ban đầu
|
||||||
|
- Cài đặt các công cụ cần thiết với `make install-tools`
|
||||||
|
- Đảm bảo đã cài đặt các công cụ: Go, Docker, Golangci-lint
|
||||||
|
|
||||||
|
#### Phát triển
|
||||||
|
- Sử dụng `make dev` cho phát triển với hot-reload
|
||||||
|
- Chạy test với `make test` sau mỗi thay đổi quan trọng
|
||||||
|
- Kiểm tra định dạng và lỗi cú pháp với `make lint`
|
||||||
|
- Trước khi commit, chạy kiểm tra đầy đủ với `make ci-local`
|
||||||
|
|
||||||
|
#### Triển khai
|
||||||
|
- Sử dụng `make docker-build` để tạo Docker image
|
||||||
|
- Test trên môi trường local với `make docker-up`
|
||||||
|
- Push code lên repository để hệ thống CI/CD tự động triển khai
|
||||||
|
|
||||||
|
### DevOps
|
||||||
|
|
||||||
|
#### Quản lý môi trường
|
||||||
|
- Định kỳ kiểm tra và cập nhật Docker image với `make docker-build`
|
||||||
|
- Quản lý các môi trường triển khai với các lệnh `make deploy-*`
|
||||||
|
- Giám sát logs với `make docker-logs`
|
||||||
|
|
||||||
|
#### CI/CD
|
||||||
|
- Thiết lập pipeline sử dụng các lệnh `make lint`, `make test`, `make sast`, `make sca`
|
||||||
|
- Tự động hóa quy trình triển khai với `make deploy-*`
|
||||||
|
- Kiểm tra bảo mật định kỳ với `make sast` và `make sca`
|
||||||
|
|
||||||
|
### QA
|
||||||
|
|
||||||
|
#### Kiểm tra
|
||||||
|
- Sử dụng `make run` hoặc `make dev` để chạy ứng dụng trên môi trường local
|
||||||
|
- Kiểm tra các tính năng mới theo test case
|
||||||
|
- Báo cáo lỗi kèm theo logs từ `make docker-logs`
|
||||||
|
|
||||||
|
#### Kiểm tra tự động
|
||||||
|
- Chạy unit test với `make test`
|
||||||
|
- Kiểm tra bao phủ mã nguồn từ báo cáo coverage
|
||||||
|
|
||||||
|
## Các hàm hữu dụng
|
||||||
|
|
||||||
|
### Quản lý ứng dụng
|
||||||
|
- `make build`: Biên dịch ứng dụng
|
||||||
|
- `make run`: Chạy ứng dụng đã biên dịch
|
||||||
|
- `make dev`: Chạy ứng dụng với hot-reload
|
||||||
|
- `make clean`: Dọn dẹp các file tạm và build
|
||||||
|
|
||||||
|
### Kiểm tra chất lượng
|
||||||
|
- `make lint`: Kiểm tra định dạng và lỗi cú pháp
|
||||||
|
- `make test`: Chạy unit test
|
||||||
|
- `make sast`: Phân tích bảo mật tĩnh
|
||||||
|
- `make sca`: Kiểm tra lỗ hổng trong dependencies
|
||||||
|
- `make ci-local`: Chạy tất cả kiểm tra trên
|
||||||
|
|
||||||
|
### Docker & Môi trường local
|
||||||
|
- `make docker-build`: Build Docker image trên môi trường local
|
||||||
|
- `make docker-up`: Khởi động container Docker trên môi trường local
|
||||||
|
- `make docker-down`: Dừng container Docker trên môi trường local
|
||||||
|
- `make docker-logs`: Xem logs từ container trên môi trường local
|
||||||
|
|
||||||
|
### Quy trình phát triển
|
||||||
|
- `make dev-workflow`: Thiết lập môi trường phát triển
|
||||||
|
- `make dev-docker`: Thiết lập môi trường Docker cho phát triển
|
||||||
|
|
||||||
|
## Quy trình bảo mật và CI/CD
|
||||||
|
|
||||||
|
### Bảo mật trong quy trình phát triển
|
||||||
|
|
||||||
|
- Tất cả code phải được gửi qua Pull Request, không được push trực tiếp vào các nhánh chính
|
||||||
|
- Mọi Pull Request phải được ít nhất 1 người review và approve
|
||||||
|
- Chỉ merge code sau khi đã pass tất cả các kiểm tra tự động (CI checks)
|
||||||
|
|
||||||
|
### Triển khai qua CI/CD
|
||||||
|
|
||||||
|
- Chỉ hệ thống CI/CD mới có quyền triển khai lên các môi trường staging và production
|
||||||
|
- Tất cả triển khai lên môi trường production phải đi qua môi trường staging trước
|
||||||
|
- Pipeline CI/CD được cấu hình trong file `.drone.yml`
|
||||||
|
|
||||||
|
### Đặc quyền môi trường
|
||||||
|
|
||||||
|
- Người phát triển chi được phép làm việc trên môi trường local
|
||||||
|
- Chỉ DevOps và quản trị viên có quyền truy cập vào môi trường staging và production
|
||||||
|
|
||||||
|
## Quy trình mẫu làm 1 ngày
|
||||||
|
|
||||||
|
### Developer
|
||||||
|
|
||||||
|
#### Buổi sáng
|
||||||
|
1. Pull code mới nhất từ repository
|
||||||
|
2. Chạy `make clean` để dọn dẹp môi trường
|
||||||
|
3. Chạy `make dev` để bắt đầu phát triển với hot-reload
|
||||||
|
4. Review các task được giao
|
||||||
|
|
||||||
|
#### Trong ngày
|
||||||
|
1. Phát triển các tính năng theo task
|
||||||
|
2. Chạy `make test` sau mỗi thay đổi lớn
|
||||||
|
3. Chạy `make lint` để kiểm tra lỗi và định dạng code
|
||||||
|
4. Sử dụng `make docker-up` để test trong môi trường Docker nếu cần
|
||||||
|
|
||||||
|
#### Trước khi commit
|
||||||
|
1. Chạy `make ci-local` để đảm bảo code chất lượng
|
||||||
|
2. Fix các vấn đề được phát hiện
|
||||||
|
3. Commit và push code
|
||||||
|
|
||||||
|
#### Triển khai
|
||||||
|
1. Push code lên repository để kích hoạt quy trình CI/CD
|
||||||
|
2. Theo dõi kết quả của pipeline trên hệ thống CI/CD
|
||||||
|
3. Kiểm tra tính năng trên môi trường staging sau khi triển khai tự động hoàn tất
|
||||||
|
|
||||||
|
### DevOps
|
||||||
|
|
||||||
|
#### Buổi sáng
|
||||||
|
1. Kiểm tra trạng thái các môi trường với `make docker-logs`
|
||||||
|
2. Xử lý các vấn đề từ monitoring overnight
|
||||||
|
|
||||||
|
#### Trong ngày
|
||||||
|
1. Hỗ trợ các developer với vấn đề môi trường
|
||||||
|
2. Cập nhật cấu hình CI/CD nếu cần
|
||||||
|
3. Chuẩn bị cho việc triển khai
|
||||||
|
|
||||||
|
#### Cuối ngày
|
||||||
|
1. Triển khai các thay đổi đã được phê duyệt với `make deploy-prod`
|
||||||
|
2. Kiểm tra logs sau triển khai
|
||||||
|
3. Cập nhật tài liệu và báo cáo
|
||||||
|
|
||||||
|
### QA
|
||||||
|
|
||||||
|
#### Buổi sáng
|
||||||
|
1. Review các tính năng cần kiểm tra
|
||||||
|
2. Thiết lập môi trường test với `make dev-workflow`
|
||||||
|
|
||||||
|
#### Trong ngày
|
||||||
|
1. Kiểm tra các tính năng theo test case
|
||||||
|
2. Báo cáo lỗi và ghi chép kết quả test
|
||||||
|
3. Hỗ trợ developer sửa lỗi
|
||||||
|
|
||||||
|
#### Cuối ngày
|
||||||
|
1. Tổng kết kết quả kiểm tra
|
||||||
|
2. Duyệt các tính năng đã pass test
|
||||||
|
3. Chuẩn bị test case cho ngày hôm sau
|
||||||
52
go.mod
Normal file
52
go.mod
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
module phattrang/matching_app_team
|
||||||
|
|
||||||
|
go 1.23
|
||||||
|
|
||||||
|
toolchain go1.23.6
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/gin-gonic/gin v1.9.1
|
||||||
|
github.com/jackc/pgx/v5 v5.7.4
|
||||||
|
github.com/lib/pq v1.10.9
|
||||||
|
github.com/stretchr/testify v1.9.0
|
||||||
|
go.uber.org/zap v1.27.0
|
||||||
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/bytedance/sonic v1.11.6 // indirect
|
||||||
|
github.com/bytedance/sonic/loader v0.1.1 // indirect
|
||||||
|
github.com/cloudwego/base64x v0.1.4 // indirect
|
||||||
|
github.com/cloudwego/iasm v0.2.0 // indirect
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
||||||
|
github.com/gin-contrib/sse v0.1.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.20.0 // indirect
|
||||||
|
github.com/goccy/go-json v0.10.2 // indirect
|
||||||
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||||
|
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||||
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
|
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
|
||||||
|
github.com/leodido/go-urn v1.4.0 // 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/pelletier/go-toml/v2 v2.2.2 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
github.com/rogpeppe/go-internal v1.14.1 // 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.8.0 // indirect
|
||||||
|
golang.org/x/crypto v0.31.0 // indirect
|
||||||
|
golang.org/x/net v0.25.0 // indirect
|
||||||
|
golang.org/x/sync v0.10.0 // indirect
|
||||||
|
golang.org/x/sys v0.28.0 // indirect
|
||||||
|
golang.org/x/text v0.21.0 // indirect
|
||||||
|
google.golang.org/protobuf v1.34.1 // indirect
|
||||||
|
)
|
||||||
|
|
||||||
|
replace github.com/gin-gonic/gin => github.com/gin-gonic/gin v1.9.1
|
||||||
114
go.sum
Normal file
114
go.sum
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
|
||||||
|
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
|
||||||
|
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
|
||||||
|
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||||
|
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
|
||||||
|
github.com/cloudwego/base64x v0.1.4/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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
|
||||||
|
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||||
|
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||||
|
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
|
||||||
|
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
|
||||||
|
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||||
|
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||||
|
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.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
|
||||||
|
github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
||||||
|
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||||
|
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||||
|
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||||
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
|
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||||
|
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||||
|
github.com/jackc/pgx/v5 v5.7.4 h1:9wKznZrhWa2QiHL+NjTSPP6yjl3451BX3imWDnokYlg=
|
||||||
|
github.com/jackc/pgx/v5 v5.7.4/go.mod h1:ncY89UGWxg82EykZUwSpUKEfccBGGYq1xjrOpsbsfGQ=
|
||||||
|
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
|
||||||
|
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||||
|
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.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||||
|
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||||
|
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||||
|
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||||
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
|
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/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||||
|
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||||
|
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/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
||||||
|
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/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||||
|
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||||
|
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.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||||
|
github.com/stretchr/testify v1.9.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=
|
||||||
|
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||||
|
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||||
|
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.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||||
|
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
|
||||||
|
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||||
|
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
||||||
|
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||||
|
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
|
||||||
|
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||||
|
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
||||||
|
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
|
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.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
||||||
|
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||||
|
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
|
||||||
|
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
|
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=
|
||||||
|
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
||||||
|
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||||
36
internal/adapter/database/connection.go
Normal file
36
internal/adapter/database/connection.go
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
package database
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
_ "github.com/lib/pq"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
MaxOpenConns int
|
||||||
|
MaxIdleConns int
|
||||||
|
ConnMaxLifetime time.Duration
|
||||||
|
ConnMaxIdleTime time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPostgresConnection(connStr string, cfg Config) (*sql.DB, error) {
|
||||||
|
db, err := sql.Open("postgres", connStr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to open database connection: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
db.SetMaxOpenConns(cfg.MaxOpenConns)
|
||||||
|
db.SetMaxIdleConns(cfg.MaxIdleConns)
|
||||||
|
db.SetConnMaxLifetime(cfg.ConnMaxLifetime)
|
||||||
|
db.SetConnMaxIdleTime(cfg.ConnMaxIdleTime)
|
||||||
|
|
||||||
|
err = db.PingContext(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to ping database: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return db, nil
|
||||||
|
}
|
||||||
35
internal/adapter/database/postgres_repository.go
Normal file
35
internal/adapter/database/postgres_repository.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
package database
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PostgresRepository[T any] struct {
|
||||||
|
db *sql.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPostgresRepository[T any](db *sql.DB) *PostgresRepository[T] {
|
||||||
|
return &PostgresRepository[T]{db: db}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *PostgresRepository[T]) Create(ctx context.Context, entity *T) error {
|
||||||
|
return fmt.Errorf("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *PostgresRepository[T]) FindByID(ctx context.Context, id string) (*T, error) {
|
||||||
|
return nil, fmt.Errorf("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *PostgresRepository[T]) Update(ctx context.Context, entity *T) error {
|
||||||
|
return fmt.Errorf("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *PostgresRepository[T]) Delete(ctx context.Context, id string) error {
|
||||||
|
return fmt.Errorf("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *PostgresRepository[T]) FindAll(ctx context.Context) ([]*T, error) {
|
||||||
|
return nil, fmt.Errorf("not implemented")
|
||||||
|
}
|
||||||
1
internal/adapter/database/profile_repository.go
Normal file
1
internal/adapter/database/profile_repository.go
Normal file
@ -0,0 +1 @@
|
|||||||
|
package database
|
||||||
1
internal/adapter/database/profile_schema.go
Normal file
1
internal/adapter/database/profile_schema.go
Normal file
@ -0,0 +1 @@
|
|||||||
|
package database
|
||||||
31
internal/adapter/database/repository.go
Normal file
31
internal/adapter/database/repository.go
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
package database
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"github.com/jackc/pgx/v5/pgxpool"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var pool *pgxpool.Pool
|
||||||
|
|
||||||
|
func GetConnectionPool() *pgxpool.Pool {
|
||||||
|
return pool
|
||||||
|
}
|
||||||
|
|
||||||
|
func InitPool(ctx context.Context, connString string) error {
|
||||||
|
if connString == "" || !strings.HasPrefix(connString, "postgres://") {
|
||||||
|
return fmt.Errorf("invalid connection string")
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
pool, err = pgxpool.New(ctx, connString)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
type Repository[T any] interface {
|
||||||
|
Create(ctx context.Context, entity *T) error
|
||||||
|
FindByID(ctx context.Context, id string) (*T, error)
|
||||||
|
Update(ctx context.Context, entity *T) error
|
||||||
|
Delete(ctx context.Context, id string) error
|
||||||
|
FindAll(ctx context.Context) ([]*T, error)
|
||||||
|
}
|
||||||
20
internal/adapter/database/repository_test.go
Normal file
20
internal/adapter/database/repository_test.go
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
package database
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestConnectionPool(t *testing.T) {
|
||||||
|
// Test với connection pool mock
|
||||||
|
t.Run("Get connection pool", func(t *testing.T) {
|
||||||
|
pool := GetConnectionPool()
|
||||||
|
assert.Nil(t, pool) // Pool chưa được khởi tạo
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Initialize pool", func(t *testing.T) {
|
||||||
|
err := InitPool(context.Background(), "invalid_connection_string")
|
||||||
|
assert.Error(t, err, "Expected error with invalid connection string")
|
||||||
|
})
|
||||||
|
}
|
||||||
49
internal/helper/config.go
Normal file
49
internal/helper/config.go
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
package helper
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DatabaseConfig struct {
|
||||||
|
Host string `yaml:"host" env:"DB_HOST"`
|
||||||
|
Port string `yaml:"port" env:"DB_PORT"`
|
||||||
|
User string `yaml:"user" env:"DB_USER"`
|
||||||
|
Password string `yaml:"password" env:"DB_PASSWORD"`
|
||||||
|
Name string `yaml:"name" env:"DB_NAME"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type LoggingConfig struct {
|
||||||
|
Level string `yaml:"level" env:"LOG_LEVEL"`
|
||||||
|
Encoding string `yaml:"encoding" env:"LOG_ENCODING"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
Database DatabaseConfig `yaml:"database"`
|
||||||
|
Logging LoggingConfig `yaml:"logging"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadConfig(path string) (*Config, error) {
|
||||||
|
config := &Config{}
|
||||||
|
|
||||||
|
// Đọc từ file YAML
|
||||||
|
file, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to read config file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = yaml.Unmarshal(file, config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse config: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ưu tiên giá trị từ environment variables
|
||||||
|
if host := os.Getenv("DB_HOST"); host != "" {
|
||||||
|
config.Database.Host = host
|
||||||
|
}
|
||||||
|
// (Thêm các trường khác tương tự)
|
||||||
|
|
||||||
|
return config, nil
|
||||||
|
}
|
||||||
33
internal/helper/config/config_test.go
Normal file
33
internal/helper/config/config_test.go
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Mock config struct
|
||||||
|
type Config struct {
|
||||||
|
Database struct {
|
||||||
|
Host string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mock LoadConfig function
|
||||||
|
func LoadConfig(path string) (*Config, error) {
|
||||||
|
return &Config{Database: struct{ Host string }{Host: "localhost"}}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoadConfig(t *testing.T) {
|
||||||
|
// Test case 1: Load config từ file YAML
|
||||||
|
t.Run("Load from valid YAML", func(t *testing.T) {
|
||||||
|
cfg, err := LoadConfig("../../configs/config.yaml")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, cfg)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Test case 2: Override bằng biến môi trường
|
||||||
|
t.Run("Environment override", func(t *testing.T) {
|
||||||
|
cfg, _ := LoadConfig("../../configs/config.yaml")
|
||||||
|
assert.Equal(t, "localhost", cfg.Database.Host)
|
||||||
|
})
|
||||||
|
}
|
||||||
50
internal/helper/error/error.go
Normal file
50
internal/helper/error/error.go
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
package error
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AppError struct {
|
||||||
|
Code int `json:"code"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
Details string `json:"details,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrNotFound = errors.New("not found")
|
||||||
|
ErrInvalidInput = errors.New("invalid input")
|
||||||
|
ErrUnauthorized = errors.New("unauthorized")
|
||||||
|
ErrInternalServer = errors.New("internal server error")
|
||||||
|
)
|
||||||
|
|
||||||
|
func Handle(logger *zap.Logger, err error) (int, AppError) {
|
||||||
|
switch {
|
||||||
|
case errors.Is(err, ErrNotFound):
|
||||||
|
return http.StatusNotFound, AppError{
|
||||||
|
Code: http.StatusNotFound,
|
||||||
|
Message: "Resource not found",
|
||||||
|
Details: err.Error()}
|
||||||
|
|
||||||
|
case errors.Is(err, ErrInvalidInput):
|
||||||
|
return http.StatusBadRequest, AppError{
|
||||||
|
Code: http.StatusBadRequest,
|
||||||
|
Message: "Invalid request data",
|
||||||
|
Details: err.Error()}
|
||||||
|
|
||||||
|
case errors.Is(err, ErrUnauthorized):
|
||||||
|
return http.StatusUnauthorized, AppError{
|
||||||
|
Code: http.StatusUnauthorized,
|
||||||
|
Message: "Unauthorized access",
|
||||||
|
Details: err.Error()}
|
||||||
|
|
||||||
|
default:
|
||||||
|
logger.Error("Internal server error", zap.Error(err))
|
||||||
|
return http.StatusInternalServerError, AppError{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
Message: "Internal server error",
|
||||||
|
Details: ""}
|
||||||
|
}
|
||||||
|
}
|
||||||
37
internal/helper/health/handler.go
Normal file
37
internal/helper/health/handler.go
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
package health
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func HealthHandler(c *gin.Context) {
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"status": "OK",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type Handler struct {
|
||||||
|
db *sql.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHandler(db *sql.DB) *Handler {
|
||||||
|
return &Handler{db: db}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) Check(c *gin.Context) {
|
||||||
|
// Kiểm tra kết nối database
|
||||||
|
err := h.db.PingContext(c.Request.Context())
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusServiceUnavailable, gin.H{
|
||||||
|
"status": "Database connection failed",
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"status": "OK",
|
||||||
|
})
|
||||||
|
}
|
||||||
21
internal/helper/health/handler_test.go
Normal file
21
internal/helper/health/handler_test.go
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package health
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestHealthHandler(t *testing.T) {
|
||||||
|
router := gin.Default()
|
||||||
|
router.GET("/health", HealthHandler)
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
req, _ := http.NewRequest("GET", "/health", nil)
|
||||||
|
router.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
assert.Equal(t, http.StatusOK, w.Code)
|
||||||
|
assert.Contains(t, w.Body.String(), "OK")
|
||||||
|
}
|
||||||
7
internal/helper/health/router.go
Normal file
7
internal/helper/health/router.go
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
package health
|
||||||
|
|
||||||
|
import "github.com/gin-gonic/gin"
|
||||||
|
|
||||||
|
func RegisterRoutes(r *gin.Engine, h *Handler) {
|
||||||
|
r.GET("/health", h.Check)
|
||||||
|
}
|
||||||
40
internal/uiux/middleware.go
Normal file
40
internal/uiux/middleware.go
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
package uiux
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
func LoggingMiddleware(logger *zap.Logger) gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
logger.Info("Incoming request",
|
||||||
|
zap.String("method", c.Request.Method),
|
||||||
|
zap.String("path", c.Request.URL.Path),
|
||||||
|
)
|
||||||
|
|
||||||
|
c.Next()
|
||||||
|
|
||||||
|
logger.Info("Request completed",
|
||||||
|
zap.Int("status", c.Writer.Status()),
|
||||||
|
zap.String("path", c.Request.URL.Path),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ErrorMiddleware(logger *zap.Logger) gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
c.Next()
|
||||||
|
|
||||||
|
// Xử lý lỗi nếu có
|
||||||
|
if len(c.Errors) > 0 {
|
||||||
|
errs := make([]error, len(c.Errors))
|
||||||
|
for i, e := range c.Errors {
|
||||||
|
errs[i] = e.Err
|
||||||
|
}
|
||||||
|
logger.Error("Request failed",
|
||||||
|
zap.String("path", c.Request.URL.Path),
|
||||||
|
zap.Errors("errors", errs),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
18
internal/uiux/router.go
Normal file
18
internal/uiux/router.go
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package uiux
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"phattrang/matching_app_team/internal/helper/health"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SetupRouter() *gin.Engine {
|
||||||
|
r := gin.Default()
|
||||||
|
|
||||||
|
// Health check
|
||||||
|
healthHandler := health.NewHandler(nil) // DB sẽ được inject sau
|
||||||
|
health.RegisterRoutes(r, healthHandler)
|
||||||
|
|
||||||
|
// API routes sẽ được thêm sau
|
||||||
|
|
||||||
|
return r
|
||||||
|
}
|
||||||
6
migrations/002_multi_env_setup.sql
Normal file
6
migrations/002_multi_env_setup.sql
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
-- migrations/002_multi_env_setup.sql
|
||||||
|
CREATE ROLE staging_user WITH LOGIN PASSWORD 'staging_pass';
|
||||||
|
CREATE DATABASE matching_staging WITH OWNER staging_user;
|
||||||
|
|
||||||
|
CREATE ROLE prod_user WITH LOGIN PASSWORD '${DB_PROD_PASSWORD}';
|
||||||
|
CREATE DATABASE matching_prod WITH OWNER prod_user;
|
||||||
22
scripts/dev-test.sh
Normal file
22
scripts/dev-test.sh
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Script for quick local developer testing
|
||||||
|
set -eo pipefail
|
||||||
|
|
||||||
|
echo "=================================================================="
|
||||||
|
echo " RUNNING QUICK DEVELOPER TESTS "
|
||||||
|
echo "=================================================================="
|
||||||
|
|
||||||
|
echo "[1/3] Running go fmt & vet..."
|
||||||
|
go fmt ./...
|
||||||
|
go vet ./...
|
||||||
|
|
||||||
|
echo "[2/3] Running linters..."
|
||||||
|
golangci-lint run --timeout 5m
|
||||||
|
|
||||||
|
echo "[3/3] Running short unit tests..."
|
||||||
|
go test -short -v -coverprofile=coverage.out ./...
|
||||||
|
go tool cover -func=coverage.out
|
||||||
|
|
||||||
|
echo "=================================================================="
|
||||||
|
echo " QUICK TESTS COMPLETED SUCCESSFULLY "
|
||||||
|
echo "=================================================================="
|
||||||
20
template/.env.example
Normal file
20
template/.env.example
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
# Environment variables for LOCAL DEVELOPMENT (docker-compose.dev.yml)
|
||||||
|
# Copy this file to ../.env and fill in the values
|
||||||
|
|
||||||
|
# Application Settings
|
||||||
|
# Example: LOG_LEVEL=debug
|
||||||
|
LOG_LEVEL=info
|
||||||
|
LOG_ENCODING=console # Use 'console' for readable logs in dev, 'json' for structured logging
|
||||||
|
# Add other application-specific environment variables here
|
||||||
|
# API_KEY=your_dev_api_key
|
||||||
|
|
||||||
|
# Database Settings (for Postgres service in docker-compose.dev.yml)
|
||||||
|
DB_HOST=db # Service name in docker-compose
|
||||||
|
DB_PORT=5432
|
||||||
|
DB_USER=devuser
|
||||||
|
DB_PASSWORD=devpassword
|
||||||
|
DB_NAME=matching_dev
|
||||||
|
|
||||||
|
# Add other service credentials/configs if needed (e.g., Redis, external APIs)
|
||||||
|
# REDIS_HOST=redis
|
||||||
|
# REDIS_PORT=6379
|
||||||
70
template/Dockerfile
Normal file
70
template/Dockerfile
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
# --- Builder Stage ---
|
||||||
|
ARG GO_VERSION=1.23
|
||||||
|
FROM golang:${GO_VERSION}-alpine AS builder
|
||||||
|
|
||||||
|
# Set necessary environment variables
|
||||||
|
ENV CGO_ENABLED=0
|
||||||
|
ENV GOOS=linux
|
||||||
|
ENV GOARCH=amd64
|
||||||
|
|
||||||
|
# Set the working directory inside the container
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Install build dependencies (e.g., git, tools for private repos if needed)
|
||||||
|
# RUN apk add --no-cache git
|
||||||
|
|
||||||
|
# Copy go module files
|
||||||
|
COPY go.mod go.sum ./
|
||||||
|
|
||||||
|
# Download Go modules
|
||||||
|
# Using 'go mod download' allows Docker to cache these layers
|
||||||
|
RUN go mod download
|
||||||
|
|
||||||
|
# Copy the entire source code
|
||||||
|
# Consider using .dockerignore to exclude unnecessary files/dirs (like .git, tmp, build, vendor if not needed)
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Build the Go application
|
||||||
|
# Use -ldflags "-w -s" to strip debug information and reduce binary size for production
|
||||||
|
# Replace './cmd/server/main.go' with your actual main package path if different
|
||||||
|
RUN go build -ldflags="-w -s" -o /app/server ./cmd/server/main.go
|
||||||
|
|
||||||
|
# --- Final Stage ---
|
||||||
|
# Use a minimal base image like alpine or distroless
|
||||||
|
# alpine is small and includes a shell, useful for debugging
|
||||||
|
FROM alpine:latest AS final
|
||||||
|
# OR use distroless for a more secure, minimal image (no shell, only the app and its dependencies)
|
||||||
|
# FROM gcr.io/distroless/static-debian11 AS final
|
||||||
|
|
||||||
|
# Install necessary certificates (needed for HTTPS connections)
|
||||||
|
# For Alpine:
|
||||||
|
RUN apk add --no-cache ca-certificates
|
||||||
|
# For Debian-based (like distroless): Usually included, but verify
|
||||||
|
|
||||||
|
# Create a non-root user and group
|
||||||
|
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
|
||||||
|
|
||||||
|
# Set the working directory
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy the built binary from the builder stage
|
||||||
|
COPY --from=builder /app/server /app/server
|
||||||
|
|
||||||
|
# Copy configuration files, migrations, or other necessary assets
|
||||||
|
# Adjust paths as needed based on your application structure
|
||||||
|
COPY configs/ /app/configs/
|
||||||
|
COPY migrations/ /app/migrations/
|
||||||
|
# Add other COPY lines for templates, static assets etc. if needed
|
||||||
|
|
||||||
|
# Ensure the non-root user owns the application files
|
||||||
|
RUN chown -R appuser:appgroup /app
|
||||||
|
|
||||||
|
# Switch to the non-root user
|
||||||
|
USER appuser
|
||||||
|
|
||||||
|
# Expose the port the application listens on (replace 8080 if different)
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
# Define the entry point for the container
|
||||||
|
# CMD ["/app/server", "--config=/app/configs/config.yaml"] # Example with config flag
|
||||||
|
ENTRYPOINT ["/app/server"]
|
||||||
80
template/docker-compose.dev.yml
Normal file
80
template/docker-compose.dev.yml
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
# Development Environment Docker Compose
|
||||||
|
# Usage: docker-compose -f template/docker-compose.dev.yml up --build
|
||||||
|
|
||||||
|
services:
|
||||||
|
# Application Service (Matching App)
|
||||||
|
app:
|
||||||
|
# Build from the Dockerfile in the project root
|
||||||
|
build:
|
||||||
|
context: .. # Context is the parent directory where Dockerfile resides
|
||||||
|
dockerfile: Dockerfile # Explicitly specify the Dockerfile name
|
||||||
|
# You could target a specific 'dev' stage here if you create one in Dockerfile for hot-reloading
|
||||||
|
# target: development
|
||||||
|
container_name: matching_app_dev
|
||||||
|
# Mount the source code for local development
|
||||||
|
# Changes in your local code will reflect inside the container
|
||||||
|
volumes:
|
||||||
|
- ..:/app
|
||||||
|
# Use a named volume for Go modules cache to speed up builds
|
||||||
|
- go_modules:/go/pkg/mod
|
||||||
|
# Environment variables from .env file (create .env in project root)
|
||||||
|
env_file:
|
||||||
|
- ../.env # Assumes .env file is in the project root (one level up)
|
||||||
|
ports:
|
||||||
|
# Map local port 8080 to container port 8080 (adjust if your app uses a different port)
|
||||||
|
- "8080:8080"
|
||||||
|
depends_on:
|
||||||
|
db: # Ensure the database is ready before starting the app
|
||||||
|
condition: service_healthy
|
||||||
|
# Command to run the application (consider using a tool like Air for live reloading)
|
||||||
|
# Sử dụng go run cho phát triển:
|
||||||
|
command: go run ./cmd/server/main.go
|
||||||
|
# Example using the built binary (if build stage creates it):
|
||||||
|
# command: /app/matching-app
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
# Database Service (PostgreSQL)
|
||||||
|
db:
|
||||||
|
image: postgres:15-alpine # Using alpine variant for smaller size
|
||||||
|
container_name: matching_db_dev
|
||||||
|
environment:
|
||||||
|
# Read DB credentials directly from .env file
|
||||||
|
POSTGRES_USER: postgres
|
||||||
|
POSTGRES_PASSWORD: postgres
|
||||||
|
POSTGRES_DB: matching_app
|
||||||
|
volumes:
|
||||||
|
# Persist database data using a named volume
|
||||||
|
- db_data_dev:/var/lib/postgresql/data
|
||||||
|
# Mount local migrations to initialize DB on first run
|
||||||
|
- ../migrations:/docker-entrypoint-initdb.d
|
||||||
|
ports:
|
||||||
|
# Map local port 5433 to container port 5432 to avoid conflict with local postgres
|
||||||
|
- "5433:5432"
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U ${DB_USER} -d ${DB_NAME}"]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
restart: unless-stopped
|
||||||
|
# Resource limits suitable for development
|
||||||
|
deploy:
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
cpus: '0.5'
|
||||||
|
memory: 512M
|
||||||
|
|
||||||
|
# Database Admin Tool (Adminer)
|
||||||
|
adminer:
|
||||||
|
image: adminer
|
||||||
|
container_name: matching_adminer_dev
|
||||||
|
ports:
|
||||||
|
- "8081:8080" # Map local port 8081 to container port 8080
|
||||||
|
depends_on:
|
||||||
|
- db
|
||||||
|
restart: always
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
db_data_dev: # Named volume for development DB data
|
||||||
|
go_modules: # Named volume for Go modules cache
|
||||||
1
tmp/build-errors.log
Normal file
1
tmp/build-errors.log
Normal file
@ -0,0 +1 @@
|
|||||||
|
exit status 1
|
||||||
Loading…
x
Reference in New Issue
Block a user