template-monorepo/Makefile

358 lines
16 KiB
Makefile
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# go-zero 生成風格
GO_ZERO_STYLE := go_zero
GO ?= go
GOFMT ?= gofmt
GOFILES := $(shell find . -name '*.go' -not -path './generate/doc-generate/*')
GO_DOC_DIR := generate/doc-generate
GO_DOC_BIN := $(GO_DOC_DIR)/bin/go-doc
API_ENTRY := ./generate/api/gateway.api
DOC_OUT := ./docs/openapi
GOCTL ?= goctl
GOCTL_PKG := github.com/zeromicro/go-zero/tools/goctl@latest
GOLANGCI_PKG := github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.12.2
.DEFAULT_GOAL := help
help: ## 顯示可用指令
@echo "Gateway Makefile"
@echo ""
@echo "首次開發:"
@echo " make tools 安裝 goctl、goimports、golangci-lint寫入 \$$GOPATH/bin"
@echo " go mod download"
@echo ""
@echo "常用:"
@grep -E '^[a-zA-Z0-9_-]+:.*## ' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*## "}; {printf " make %-14s %s\n", $$1, $$2}'
tools: ## 安裝 goctl、goimports、golangci-lint需 Go且 GOPATH/bin 在 PATH
@command -v $(GOCTL) >/dev/null 2>&1 || (echo ">> installing goctl" && $(GO) install $(GOCTL_PKG))
@command -v goimports >/dev/null 2>&1 || (echo ">> installing goimports" && $(GO) install golang.org/x/tools/cmd/goimports@latest)
@if ! command -v golangci-lint >/dev/null 2>&1 || ! golangci-lint version 2>/dev/null | grep -q 'version 2\.'; then \
echo ">> installing golangci-lint v2"; \
$(GO) install $(GOLANGCI_PKG); \
fi
@echo "tools OK"
@echo " goctl: $$(goctl --version 2>/dev/null || echo missing)"
@echo " golangci-lint: $$(golangci-lint version 2>/dev/null | head -1 || echo missing)"
gen-api: tools ## 由 .api 生成 handler / logic / types自訂 handler 模板)
$(GOCTL) api go -api $(API_ENTRY) -dir . -style $(GO_ZERO_STYLE) -home generate/goctl
gen-mock: ## 依 go:generate 產生 internal/model/*/mockgomock
$(GO) generate ./internal/model/...
build-go-doc: ## 編譯 go-docOpenAPI 文件生成器)
@echo ">> building $(GO_DOC_BIN)"
@mkdir -p $(GO_DOC_DIR)/bin
@cd $(GO_DOC_DIR) && $(GO) build -o bin/go-doc ./cmd/go-doc
gen-doc: build-go-doc ## 從 .api 生成 OpenAPI 3.0 YAML
@mkdir -p $(DOC_OUT)
$(GO_DOC_BIN) -a $(API_ENTRY) -d $(DOC_OUT) -f gateway -s openapi3.0 -y
@echo "Generated: $(DOC_OUT)/gateway.yaml"
test: ## 執行測試
$(GO) test ./...
fmt: ## gofmt + goimports不含 lint
$(GOFMT) -s -w $(GOFILES)
@command -v goimports >/dev/null 2>&1 && goimports -w . || (echo "goimports not found; run: make tools" && exit 1)
lint: tools ## golangci-lint 靜態檢查
golangci-lint run ./...
lint-fix: tools ## 自動修正可修的 lint / formatter 問題(見 .golangci.yml
golangci-lint run --fix ./...
fix: fmt lint-fix lint ## 格式化 + 自動修 lint + 再檢查(提交前建議)
check: fix test ## 提交 / PR 前完整檢查fmt、lint、test
# ============================================================
# Docker compose本機依賴
# ============================================================
DOCKER_COMPOSE ?= docker compose
COMPOSE_FILE := deploy/docker-compose.yml
COMPOSE := $(DOCKER_COMPOSE) -f $(COMPOSE_FILE)
deps-up: ## 起 Mongo + Redis最小本機依賴
$(COMPOSE) up -d mongo redis
deps-up-smtp: ## 起 Mongo + Redis + MailHog
$(COMPOSE) --profile smtp up -d mongo redis mailhog
deps-down: ## 停服務(保留 volume
$(COMPOSE) --profile smtp --profile k6 --profile ldap down
deps-down-v: ## 停服務並刪 volume會清資料
$(COMPOSE) --profile smtp --profile k6 --profile ldap down -v
deps-logs: ## 看 compose log
$(COMPOSE) --profile smtp --profile k6 --profile ldap logs -f
# OpenLDAP本機 LDAP 測試,見 deploy/openldap/
LDAP_COMPOSE := $(DOCKER_COMPOSE) -f deploy/openldap/docker-compose.yml
ldap-up: ## 只起 OpenLDAPprofile ldapZITADEL 用 ldap://openldap:389
$(COMPOSE) --profile ldap up -d openldap
@echo "→ run 'make ldap-wait' to seed alice/bob"
@echo "→ 測試帳號見 deploy/openldap/README.md"
ldap-down: ## 停 OpenLDAP
$(COMPOSE) --profile ldap stop openldap
ldap-wait: ## 等 OpenLDAP ready 並 seed alice/bob
@$(MAKE) -s k6-wait-ldap
# ============================================================
# k6 測試test/k6/
# ============================================================
K6 ?= k6
K6_GATEWAY_CONFIG := etc/gateway.k6.yaml
K6_GATEWAY_BIN := bin/gateway-k6
K6_DIR := test/k6
K6_PAT_FILE := deploy/zitadel/machinekey/zitadel-admin-sa.token
K6_ENV_FILE := deploy/zitadel/machinekey/k6.env
K6_BOOTSTRAP_ENV := deploy/zitadel/machinekey/dev-bootstrap.env
ZITADEL_HEALTH_URL := http://localhost:8080/debug/healthz
# k6 安裝指引macOS / Linux
define K6_INSTALL_HINT
k6 not found in PATH.
Install:
macOS (Homebrew): brew install k6
Linux (apt): sudo gpg -k && sudo gpg --no-default-keyring --keyring /usr/share/keyrings/k6-archive-keyring.gpg --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys C5AD17C747E3415A3642D57D77C6C491D6AC1D69 \
&& echo 'deb [signed-by=/usr/share/keyrings/k6-archive-keyring.gpg] https://dl.k6.io/deb stable main' | sudo tee /etc/apt/sources.list.d/k6.list \
&& sudo apt update && sudo apt install k6
Other: https://grafana.com/docs/k6/latest/set-up/install-k6/
Or use docker (no install): K6='docker run --rm -i --network host -v $$PWD:/app -w /app grafana/k6:latest' make k6-smoke
endef
export K6_INSTALL_HINT
k6-check: ## 檢查 k6 是否安裝(沒裝會印 install 指引)
@command -v $(K6) >/dev/null 2>&1 || (echo "$$K6_INSTALL_HINT"; exit 1)
@echo "k6: $$($(K6) version 2>&1 | head -1)"
k6-up: ## 起 k6 全棧mongo + redis + mailhog + postgres + openldap + zitadel
$(COMPOSE) --profile k6 up -d mongo redis mailhog postgres openldap zitadel
@echo "OpenLDAP + ZITADEL bootstrapping (ZITADEL 首次約 3090s)…"
@echo "→ run 'make k6-wait' to block until ready"
@echo "→ LDAP 測試帳號見 deploy/openldap/README.mdalice / Password1!"
k6-wait-ldap: ## 等 OpenLDAP ready 並 seed alice/bob
@echo "waiting for OpenLDAP (gateway-openldap)…"
@for i in $$(seq 1 90); do \
if docker exec gateway-openldap ldapsearch -x -H ldap://localhost \
-b "dc=gateway,dc=local" \
-D "cn=admin,dc=gateway,dc=local" -w admin -LLL -s base "(objectClass=*)" dn 2>/dev/null | grep -q 'dc=gateway'; then \
echo "openldap ready ($$i s)"; \
$(MAKE) -s ldap-seed; exit 0; \
fi; \
sleep 1; \
done; \
echo "openldap did not become ready in 90s — check: docker logs gateway-openldap"; exit 1
ldap-seed: ## 將 deploy/openldap/bootstrap 測試帳號寫入目錄(可重複執行)
@docker cp deploy/openldap/bootstrap/10-people.ldif gateway-openldap:/tmp/10-people.ldif
@docker exec gateway-openldap ldapadd -x -H ldap://localhost \
-D "cn=admin,dc=gateway,dc=local" -w admin -c -f /tmp/10-people.ldif 2>&1 \
| grep -Ev '^(ldap_add: Already exists|adding new entry)' || true
@echo "ldap seed done (alice / bob @ ou=people,dc=gateway,dc=local)"
ldap-test: ## 列出 LDAP 測試使用者 alice / bob
@docker exec gateway-openldap ldapsearch -x -H ldap://localhost \
-b "ou=people,dc=gateway,dc=local" \
-D "cn=admin,dc=gateway,dc=local" -w admin \
"(|(uid=alice)(uid=bob))" uid mail cn
k6-bootstrap-zitadel: ## 自動建立 ZITADEL LDAP IdP + OIDC Appdev / k6
@if [ ! -s "$(K6_PAT_FILE)" ]; then echo "PAT missing — run 'make k6-wait' first"; exit 1; fi
@python3 deploy/zitadel/bootstrap_dev.py
@echo "→ OAuth / LDAP IdP 寫入 $(K6_BOOTSTRAP_ENV)"
k6-wait: k6-wait-ldap ## 等 OpenLDAP + ZITADEL ready + bootstrap IdP/OAuth + 寫 k6.env
@echo "waiting for ZITADEL at $(ZITADEL_HEALTH_URL)"
@for i in $$(seq 1 120); do \
if curl -fsS $(ZITADEL_HEALTH_URL) >/dev/null 2>&1; then \
echo "zitadel ready ($$i s)"; break; \
fi; \
sleep 1; \
if [ $$i -eq 120 ]; then echo "zitadel did not become ready in 120s"; exit 1; fi; \
done
@for i in $$(seq 1 30); do \
if [ -s "$(K6_PAT_FILE)" ]; then break; fi; \
sleep 1; \
done
@if [ ! -s "$(K6_PAT_FILE)" ]; then \
echo "PAT file $(K6_PAT_FILE) missing — check 'docker logs gateway-zitadel'"; \
exit 1; \
fi
@$(MAKE) -s k6-bootstrap-zitadel
@PAT=$$(tr -d '\n' < $(K6_PAT_FILE)); \
printf 'export ZITADEL_SERVICE_TOKEN=%s\nexport BASE_URL=http://localhost:8888\nexport MAILHOG_URL=http://localhost:8025\nexport REDIS_ADDR=localhost:6379\n' "$$PAT" > $(K6_ENV_FILE); \
if [ -s "$(K6_BOOTSTRAP_ENV)" ]; then cat $(K6_BOOTSTRAP_ENV) >> $(K6_ENV_FILE); fi; \
echo "wrote $(K6_ENV_FILE)"
@$(MAKE) -s k6-seed-fixtures
@echo "tip: 'source $(K6_ENV_FILE)' to load into your shell"
@echo "tip: run 'make dev-restart-gateway' if gateway was already running"
# k6-seed-fixtures idempotently upserts the k6-tenant + K6INVITE invite code
# into gateway_k6 so /auth/register resolves successfully. The invite hash is
# sha256("K6INVITE") — see internal/model/auth/domain/const.go HashInviteCode.
# Uses mongosh inside the container; no extra Go binary needed.
K6_TENANT_ID := k6-tenant
K6_INVITE_HASH := e62a291f0dcf88c50c91fdc78e3539c55e1e20ef645c640b1ec778fe4fabb4cb
k6-seed-fixtures: ## upsert k6-tenant + K6INVITE 到 mongo
@docker exec gateway-mongo mongosh --quiet --eval '\
db = db.getSiblingDB("gateway_k6"); \
var now = NumberLong(Date.now()); \
var t = db.tenants.updateOne( \
{tenant_id: "$(K6_TENANT_ID)"}, \
{$$set: {tenant_id: "$(K6_TENANT_ID)", slug: "$(K6_TENANT_ID)", name: "k6 Tenant", uid_prefix: "K6", status: "active", update_at: now}, $$setOnInsert: {create_at: now}}, \
{upsert: true} \
); \
var i = db.invite_codes.updateOne( \
{tenant_id: "$(K6_TENANT_ID)", code_hash: "$(K6_INVITE_HASH)"}, \
{$$set: {tenant_id: "$(K6_TENANT_ID)", code_hash: "$(K6_INVITE_HASH)", max_uses: NumberLong(1000000), expires_at: NumberLong(0), new_users_only: false, update_at: now}, $$setOnInsert: {used_count: NumberLong(0), create_at: now}}, \
{upsert: true} \
); \
print("tenant matched=" + t.matchedCount + " upserted=" + (t.upsertedId?1:0) + ", invite matched=" + i.matchedCount + " upserted=" + (i.upsertedId?1:0));' && \
echo "seeded fixtures (tenant=$(K6_TENANT_ID) invite=K6INVITE) into gateway_k6"
# Back-compat alias
k6-seed-tenant: k6-seed-fixtures ## (alias for k6-seed-fixtures)
k6-build: ## 建 gateway binary 給 k6 使用
@mkdir -p $(dir $(K6_GATEWAY_BIN))
$(GO) build -o $(K6_GATEWAY_BIN) ./gateway.go
k6-gateway: k6-build ## 前景啟 gateway吃 etc/gateway.k6.yaml + ZITADEL env
@if [ ! -s "$(K6_ENV_FILE)" ]; then echo "run 'make k6-wait' first"; exit 1; fi
@set -a; . $(K6_ENV_FILE); set +a; \
$(K6_GATEWAY_BIN) -f $(K6_GATEWAY_CONFIG)
k6-smoke: k6-check ## 跑 smoke 測試(每個端點一發)
@if [ ! -s "$(K6_ENV_FILE)" ]; then echo "run 'make k6-wait' first"; exit 1; fi
@set -a; . $(K6_ENV_FILE); set +a; \
for f in $(K6_DIR)/smoke/*.js; do \
echo "==> $$f"; $(K6) run "$$f" || exit 1; \
done
k6-journey: k6-check ## 跑 journey 測試(多步驟流程)
@if [ ! -s "$(K6_ENV_FILE)" ]; then echo "run 'make k6-wait' first"; exit 1; fi
@set -a; . $(K6_ENV_FILE); set +a; \
for f in $(K6_DIR)/journeys/*.js; do \
echo "==> $$f"; $(K6) run "$$f" || exit 1; \
done
k6-all: k6-smoke k6-journey ## smoke + journey
k6-seed-admin: k6-build ## 註冊 k6-admin 並 seed tenant_admin rolerbac journey 用)
@if [ ! -s "$(K6_ENV_FILE)" ]; then echo "run 'make k6-wait' first"; exit 1; fi
@mkdir -p bin
$(GO) build -o bin/k6-seed-admin ./cmd/k6-seed-admin
@set -a; . $(K6_ENV_FILE); set +a; \
./bin/k6-seed-admin > $(K6_ENV_FILE).admin || (echo "k6-seed-admin failed"; exit 1); \
cat $(K6_ENV_FILE).admin >> $(K6_ENV_FILE); \
echo "admin credentials appended to $(K6_ENV_FILE):"; \
cat $(K6_ENV_FILE).admin
k6-down: ## 停 k6 stack 並清 volume會清 Postgres / Mongo / Redis 資料)
$(COMPOSE) --profile k6 down -v
@rm -f $(K6_PAT_FILE) $(K6_ENV_FILE) $(K6_ENV_FILE).admin $(K6_ENV_FILE).tmp deploy/zitadel/machinekey/zitadel-admin-sa.json
@echo "k6 stack stopped, volumes & PAT removed"
# ============================================================
# 一鍵本機測試環境Docker + Gateway + 種子資料)
# ============================================================
DEV_DIR := .dev
DEV_GATEWAY_PID := $(DEV_DIR)/gateway.pid
DEV_GATEWAY_LOG := $(DEV_DIR)/gateway.log
.PHONY: dev-up dev-down dev-status dev-restart-gateway
dev-up: k6-up k6-wait k6-build dev-restart-gateway ## 一鍵起全套mongo/redis/mailhog/zitadel + seed + Gateway 背景
@echo ""
@echo "=========================================="
@echo " 本機測試環境已就緒"
@echo "=========================================="
@echo " Gateway API http://localhost:8888"
@echo " 前端(另開終端) make frontend-dev → http://localhost:5173"
@echo " MailHog 收信 http://localhost:8025 (註冊 OTP 在這裡看)"
@echo " ZITADEL 主控台 http://localhost:8080/ui/console"
@echo ""
@echo " 註冊預設:租戶 k6-tenant · 邀請碼 K6INVITE"
@echo " LDAP 登入alice / Password1!make k6-wait 已自動設定 ZITADEL IdP"
@echo ""
@echo " 管理後台make dev-seed-admin 後用輸出的帳密登入"
@echo " 關閉環境make dev-down"
@echo " 查看狀態make dev-status"
@echo " OAuth/LDAP 設定變更後make dev-restart-gateway"
@echo "=========================================="
dev-seed-admin: k6-seed-admin ## 建立具 tenant_admin 的管理員dev-up 之後執行)
dev-down: ## 停 Gateway 背景行程 + k6 docker stack
@if [ -f $(DEV_GATEWAY_PID) ]; then \
pid=$$(cat $(DEV_GATEWAY_PID)); \
if kill -0 $$pid 2>/dev/null; then kill $$pid && echo "stopped gateway (pid $$pid)"; fi; \
rm -f $(DEV_GATEWAY_PID); \
fi
@$(MAKE) -s k6-down
@rm -rf $(DEV_DIR)
@echo "dev environment stopped"
dev-status: ## 顯示 docker / gateway / health 狀態
@echo "=== docker (k6 profile) ==="
@$(COMPOSE) --profile k6 ps 2>/dev/null || true
@echo ""
@echo "=== gateway :8888 ==="
@if [ -f $(DEV_GATEWAY_PID) ] && kill -0 $$(cat $(DEV_GATEWAY_PID)) 2>/dev/null; then \
echo "running pid $$(cat $(DEV_GATEWAY_PID))"; \
curl -fsS http://localhost:8888/api/v1/health 2>/dev/null | head -c 200 || echo "(health check failed)"; \
echo ""; \
else \
echo "not running (run: make dev-up)"; \
fi
dev-restart-gateway: k6-build ## 重啟 Gateway載入 k6.env 的 OAuth / LDAP 設定)
@if [ ! -s "$(K6_ENV_FILE)" ]; then echo "run 'make k6-wait' first"; exit 1; fi
@if [ -f $(DEV_GATEWAY_PID) ]; then \
pid=$$(cat $(DEV_GATEWAY_PID)); \
kill -0 $$pid 2>/dev/null && kill $$pid || true; \
rm -f $(DEV_GATEWAY_PID); \
fi
@if command -v lsof >/dev/null 2>&1; then \
orphan=$$(lsof -ti :8888 2>/dev/null || true); \
if [ -n "$$orphan" ]; then kill $$orphan 2>/dev/null || true; sleep 1; fi; \
fi
@mkdir -p $(DEV_DIR)
@set -a; . $(K6_ENV_FILE); set +a; \
nohup $(K6_GATEWAY_BIN) -f $(K6_GATEWAY_CONFIG) > $(DEV_GATEWAY_LOG) 2>&1 & \
echo $$! > $(DEV_GATEWAY_PID); \
echo "gateway restarted (pid $$(cat $(DEV_GATEWAY_PID)))"
@for i in $$(seq 1 30); do \
if curl -fsS http://localhost:8888/api/v1/health >/dev/null 2>&1; then \
echo "gateway ready ($$i s)"; exit 0; \
fi; \
sleep 1; \
done; \
echo "gateway did not become ready — tail $(DEV_GATEWAY_LOG)"; tail -20 $(DEV_GATEWAY_LOG); exit 1
# ============================================================
# Frontend使用者前台 + 管理後台)
# ============================================================
frontend-install: ## 安裝 frontend 依賴
cd frontend && npm install
frontend-dev: frontend-install ## 啟動前端 dev server:5173proxy /api → :8888
cd frontend && npm run dev
frontend-build: frontend-install ## 建置前端靜態檔 → frontend/dist/
cd frontend && npm run build