First Commit

This commit is contained in:
phattt2901 2025-05-02 15:12:18 +07:00
parent 05426244ed
commit a77e811d24
49 changed files with 2124 additions and 272 deletions

37
.air.toml Normal file
View 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
View 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
View 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
View File

@ -0,0 +1 @@
- Return Answer in Vietnamese

59
0001_init.sql.bck Normal file
View 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
View 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
View 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
View 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. Healthcheck endpoint
curl http://localhost:8080/healthz # hoặc GET /campaign/config
# 7. Khi cần tắt
docker-compose down

332
Zee.md
View File

@ -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
# 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.
This document provides the high-level architectural blueprint for the Matchmaking Microsite project based on our collaborative design process.

BIN
bin/server Normal file

Binary file not shown.

56
build/coverage.out Normal file
View 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
View 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
View 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
View 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
View 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
View 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

View File

@ -1,5 +1,48 @@
# 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
## 1. External Connections

View File

@ -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
- **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
- `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
@ -16,6 +25,26 @@
- `Helper`: Các thư viện, tiện ích dùng chung
- `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)
- `ubit`: Đơn vị logic nhỏ nhất (hàm, type, hằng số)
- `ubrick`: Tập hợp các ubit liên quan
@ -26,8 +55,8 @@
- **Backend**: Go
- **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ế
- **Deployment**: Docker, triển khai trên Cloud (AWS/GCP/Azure)
- **CI/CD Integration**: GitHub Actions
- **Deployment**: Docker, triển khai trên Cloud (VPS Storage)
- **CI/CD Integration**: DroneCI
- **Database**: Managed PostgreSQL Service
- **Frontend Serving**: Backend render HTML fragments, CDN cho static assets

View File

@ -3,5 +3,19 @@
---
# 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 14:01] : md : Created all documentation template files

16
docs/docker_rule.md Normal file
View 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

View File

@ -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.
# 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:
# - Đăng ký hồ sơ người dùng
# - 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
# - **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
# - **CI/CD Integration:** Sử dụng GitHub Actions
# - **CI/CD Integration:** Sử dụng DroneCI
## Core Technologies
- Go
@ -22,7 +21,32 @@
- HTMX
- Docker
- 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

View File

@ -7,63 +7,76 @@
### 1. Infrastructure & Environment Setup
Thiết lập cơ sở hạ tầng và môi trường cho dự án.
* **Tasks:**
* [ ] Khởi tạo project Go và cấu hình module (Completed: )
* [ ] Thiết lập Docker/Docker Compose (Completed: )
* [ ] Cấu hình PostgreSQL cho dev/staging/prod (Completed: )
* [ ] Thiết lập CI/CD với GitHub Actions (Completed: )
* [X] Khởi tạo project Go và cấu hình module (Completed: 2025-04-25)
* [X] Thiết lập Docker/Docker Compose (Completed: 2025-04-25)
* [X] Cấu hình PostgreSQL cho dev/staging/prod (Completed: 2025-04-26)
* [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ơ.
* **Tasks:**
* [ ] Thiết kế schema và repository cho `Profile` (Completed: )
* [ ] Triển khai API `POST /profiles` và handler (Completed: )
* [ ] Tích hợp upload ảnh lên S3 (Completed: )
* [ ] Gửi email xác nhận đăng ký (Completed: )
* [ ] Thiết kế schema và repository cho `Profile`
* [ ] Triển khai API `POST /profiles` và handler
* [ ] Tích hợp upload ảnh lên S3
* [ ] 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ả.
* **Tasks:**
* [ ] Xây dựng ubit/ubrick tính toán điểm và filter (Completed: )
* [ ] Triển khai saga `MatchingTransaction` (Completed: )
* [ ] Tạo và lưu `MatchOutcome` (Completed: )
* [ ] Xây dựng batch job hoặc trigger real-time (Completed: )
* [ ] Xây dựng ubit/ubrick tính toán điểm và filter
* [ ] Triển khai saga `MatchingTransaction`
* [ ] Tạo và lưu `MatchOutcome`
* [ ] 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.
* **Tasks:**
* [ ] Tạo Result Page với HTMX (Completed: )
* [ ] Triển khai `GET /results/{token}` (Completed: )
* [ ] API `POST /results/select``POST /results/decline` (Completed: )
* [ ] Cập nhật state trong `MatchOutcome` (Completed: )
* [ ] Tạo Result Page với HTMX
* [ ] Triển khai `GET /results/{token}`
* [ ] API `POST /results/select``POST /results/decline`
* [ ] 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.
* **Tasks:**
* [ ] Saga `ResultNotificationTransaction` email kết quả (Completed: )
* [ ] Saga `GiftAllocationTransaction` quay thưởng và lưu `GiftAllocation` (Completed: )
* [ ] Saga `GiftNotificationTransaction` email trúng thưởng (Completed: )
* [ ] Thiết kế và seed dữ liệu `Gift` (Completed: )
* [ ] Saga `ResultNotificationTransaction` email kết quả
* [ ] Saga `GiftAllocationTransaction` quay thưởng và lưu `GiftAllocation`
* [ ] Saga `GiftNotificationTransaction` email trúng thưởng
* [ ] 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.
* **Tasks:**
* [ ] Implement auth endpoint `POST /admin/auth/login` (Completed: )
* [ ] Reporting Dashboard summary và báo cáo theo team (Completed: )
* [ ] Campaign Management UI & API (Completed: )
* [ ] Gift Management CRUD (Completed: )
* [ ] Implement auth endpoint `POST /admin/auth/login`
* [ ] Reporting Dashboard summary và báo cáo theo team
* [ ] Campaign Management UI & API
* [ ] Gift Management CRUD
### 7. QA, Testing & Monitoring
### 8. QA, Testing & Monitoring
Đảm bảo chất lượng và vận hành.
* **Tasks:**
* [ ] Viết unit/integration tests (Completed: )
* [ ] Thiết lập monitoring và alert (Completed: )
* [ ] Load testing ≥100 req/s (Completed: )
* [ ] Viết unit/integration tests
* [ ] Thiết lập monitoring và alert
* [ ] 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.
* **Tasks:**
* [ ] Build & deploy Docker containers lên Cloud (Completed: )
* [ ] Cấu hình load balancer, CDN (Completed: )
* [ ] Cập nhật README và hướng dẫn vận hành (Completed: )
* [ ] Bàn giao tài liệu và training (Completed: )
* [ ] Build & deploy Docker containers lên Cloud
* [ ] Cấu hình load balancer, CDN
* [ ] Cập nhật README và hướng dẫn vận hành
* [ ] Bàn giao tài liệu và training
---

View File

@ -1,5 +1,83 @@
# 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
## 1. Database Schema

View File

@ -53,6 +53,18 @@
5. **GiftAllocationTransaction**: Quay thưởng → Tạo GiftAllocation
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:
```mermaid
sequenceDiagram

56
docs/test_steps.md Normal file
View 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
View 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``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
View 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
View 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=

View 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
}

View 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")
}

View File

@ -0,0 +1 @@
package database

View File

@ -0,0 +1 @@
package database

View 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)
}

View 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
View 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
}

View 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)
})
}

View 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: ""}
}
}

View 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",
})
}

View 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")
}

View 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)
}

View 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
View 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
}

View 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;

1
query Normal file
View File

@ -0,0 +1 @@
DockerDesktopService

22
scripts/dev-test.sh Normal file
View 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
View 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
View 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"]

View 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
View File

@ -0,0 +1 @@
exit status 1