template-monorepo/Makefile

358 lines
16 KiB
Makefile
Raw Normal View History

2026-05-19 11:00:28 +00:00
# go-zero 生成風格
2026-05-19 13:15:18 +00:00
GO_ZERO_STYLE := go_zero
2026-05-19 11:00:28 +00:00
GO ?= go
2026-05-19 13:15:18 +00:00
GOFMT ?= gofmt
GOFILES := $(shell find . -name '*.go' -not -path './generate/doc-generate/*')
2026-05-19 11:00:28 +00:00
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
2026-05-19 13:15:18 +00:00
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
2026-05-19 13:15:18 +00:00
@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
2026-05-19 11:00:28 +00:00
gen-mock: ## 依 go:generate 產生 internal/model/*/mockgomock
$(GO) generate ./internal/model/...
2026-05-19 11:00:28 +00:00
build-go-doc: ## 編譯 go-docOpenAPI 文件生成器)
2026-05-19 13:15:18 +00:00
@echo ">> building $(GO_DOC_BIN)"
@mkdir -p $(GO_DOC_DIR)/bin
@cd $(GO_DOC_DIR) && $(GO) build -o bin/go-doc ./cmd/go-doc
2026-05-19 11:00:28 +00:00
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
2026-05-19 13:15:18 +00:00
@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 靜態檢查
2026-05-19 13:15:18 +00:00
golangci-lint run ./...
lint-fix: tools ## 自動修正可修的 lint / formatter 問題(見 .golangci.yml
2026-05-19 13:15:18 +00:00
golangci-lint run --fix ./...
fix: fmt lint-fix lint ## 格式化 + 自動修 lint + 再檢查(提交前建議)
check: fix test ## 提交 / PR 前完整檢查fmt、lint、test
2026-05-26 06:05:33 +00:00
# ============================================================
# 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
2026-05-28 05:53:33 +00:00
$(COMPOSE) --profile smtp --profile k6 --profile ldap down
2026-05-26 06:05:33 +00:00
deps-down-v: ## 停服務並刪 volume會清資料
2026-05-28 05:53:33 +00:00
$(COMPOSE) --profile smtp --profile k6 --profile ldap down -v
2026-05-26 06:05:33 +00:00
deps-logs: ## 看 compose log
2026-05-28 05:53:33 +00:00
$(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
2026-05-26 06:05:33 +00:00
# ============================================================
# 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
2026-05-28 05:53:33 +00:00
K6_BOOTSTRAP_ENV := deploy/zitadel/machinekey/dev-bootstrap.env
2026-05-26 06:05:33 +00:00
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)"
2026-05-27 09:28:13 +00:00
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
2026-05-28 05:53:33 +00:00
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
2026-05-26 06:05:33 +00:00
@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
2026-05-28 05:53:33 +00:00
@$(MAKE) -s k6-bootstrap-zitadel
2026-05-26 06:05:33 +00:00
@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); \
2026-05-28 05:53:33 +00:00
if [ -s "$(K6_BOOTSTRAP_ENV)" ]; then cat $(K6_BOOTSTRAP_ENV) >> $(K6_ENV_FILE); fi; \
2026-05-26 06:05:33 +00:00
echo "wrote $(K6_ENV_FILE)"
@$(MAKE) -s k6-seed-fixtures
@echo "tip: 'source $(K6_ENV_FILE)' to load into your shell"
2026-05-28 05:53:33 +00:00
@echo "tip: run 'make dev-restart-gateway' if gateway was already running"
2026-05-26 06:05:33 +00:00
# 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
2026-05-26 09:32:32 +00:00
@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
2026-05-28 05:53:33 +00:00
dev-up: k6-up k6-wait k6-build dev-restart-gateway ## 一鍵起全套mongo/redis/mailhog/zitadel + seed + Gateway 背景
2026-05-26 09:32:32 +00:00
@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"
2026-05-28 05:53:33 +00:00
@echo " LDAP 登入alice / Password1!make k6-wait 已自動設定 ZITADEL IdP"
2026-05-26 09:32:32 +00:00
@echo ""
@echo " 管理後台make dev-seed-admin 後用輸出的帳密登入"
@echo " 關閉環境make dev-down"
@echo " 查看狀態make dev-status"
2026-05-28 05:53:33 +00:00
@echo " OAuth/LDAP 設定變更後make dev-restart-gateway"
2026-05-26 09:32:32 +00:00
@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
2026-05-28 05:53:33 +00:00
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
2026-05-26 09:32:32 +00:00
@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
2026-05-28 05:53:33 +00:00
@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
2026-05-26 09:32:32 +00:00
@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)))"
2026-05-28 05:53:33 +00:00
@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
2026-05-26 09:32:32 +00:00
# ============================================================
# 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