diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..28bd7a2
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,58 @@
+name: ci
+
+on:
+ push:
+ branches: [main]
+ pull_request:
+ branches: [main]
+
+jobs:
+ h5:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-node@v4
+ with:
+ node-version: '20'
+ cache: npm
+ - run: npm ci
+ - run: npm run build:h5
+ - run: npm run test:h5
+ - name: Install Playwright Chromium
+ working-directory: apps/user-h5
+ run: npx playwright install --with-deps chromium
+ - run: npm run test:e2e -w @yuxingu/user-h5
+ env:
+ CI: true
+
+ api:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-go@v5
+ with:
+ go-version: '1.22'
+ - name: Unit tests (no integration DB)
+ working-directory: apps/api
+ run: go test $(go list ./... | grep -v /internal/integration) -count=1
+
+ ess-docs:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - name: ESS artifact presence (ECR-004)
+ run: |
+ test -f docs/ECR/ECR-004-hygiene-followup.md
+ test -f docs/TASKS/TASK-20260805-ECR004.yaml
+ test -f docs/TEST_REPORT/ECR-004.md
+ test -f docs/STATE/ECR-004.md
+ echo "ESS docs gate OK"
+ - name: ess-validate when ESS_ROOT available
+ env:
+ ESS_ROOT: ${{ vars.ESS_ROOT }}
+ run: |
+ if [ -n "$ESS_ROOT" ] && [ -f "$ESS_ROOT/scripts/ess-validate.py" ]; then
+ python scripts/ess-validate.py --phase coding --ecr ECR-004 --task docs/TASKS/TASK-20260805-ECR004.yaml
+ else
+ echo "ESS_ROOT not configured in CI — skipped full ess-validate"
+ fi
diff --git a/apps/api/internal/handler/report.go b/apps/api/internal/handler/report.go
index 117d994..3e01a8d 100644
--- a/apps/api/internal/handler/report.go
+++ b/apps/api/internal/handler/report.go
@@ -19,6 +19,14 @@ type ReportHandler struct {
Membership *membership.Service
}
+func (h *ReportHandler) requireMembership(c *gin.Context) (*membership.Service, bool) {
+ if h.Membership == nil {
+ response.Fail(c, http.StatusInternalServerError, 50000, "membership service unavailable")
+ return nil, false
+ }
+ return h.Membership, true
+}
+
// Register mounts report/commerce routes.
func (h *ReportHandler) Register(rg *gin.RouterGroup) {
rg.POST("/reports/portrait", h.CreatePortrait)
@@ -202,7 +210,11 @@ func (h *ReportHandler) GetMembership(c *gin.Context) {
response.Fail(c, http.StatusUnauthorized, 40100, "unauthorized")
return
}
- me, err := h.Membership.Get(c.Request.Context(), userID)
+ msvc, ok := h.requireMembership(c)
+ if !ok {
+ return
+ }
+ me, err := msvc.Get(c.Request.Context(), userID)
if err != nil {
response.Fail(c, http.StatusInternalServerError, 50000, err.Error())
return
@@ -217,6 +229,10 @@ func (h *ReportHandler) CreateOrder(c *gin.Context) {
response.Fail(c, http.StatusUnauthorized, 40100, "unauthorized")
return
}
+ msvc, ok := h.requireMembership(c)
+ if !ok {
+ return
+ }
var req struct {
Kind string `json:"kind" binding:"required"`
Plan string `json:"plan"`
@@ -235,7 +251,7 @@ func (h *ReportHandler) CreateOrder(c *gin.Context) {
}
rid = &id
}
- oid, err := h.Membership.CreateOrder(c.Request.Context(), userID, membership.CreateOrderInput{
+ oid, err := msvc.CreateOrder(c.Request.Context(), userID, membership.CreateOrderInput{
Kind: req.Kind, Plan: req.Plan, ReportID: rid,
})
if err != nil {
@@ -252,12 +268,16 @@ func (h *ReportHandler) PayMock(c *gin.Context) {
response.Fail(c, http.StatusUnauthorized, 40100, "unauthorized")
return
}
+ msvc, ok := h.requireMembership(c)
+ if !ok {
+ return
+ }
oid, err := uuid.Parse(c.Param("id"))
if err != nil {
response.Fail(c, http.StatusBadRequest, 10000, "invalid id")
return
}
- if err := h.Membership.PayMock(c.Request.Context(), userID, oid); err != nil {
+ if err := msvc.PayMock(c.Request.Context(), userID, oid); err != nil {
response.Fail(c, http.StatusBadRequest, 30004, err.Error())
return
}
diff --git a/apps/user-h5/src/components/synastry/SynastryClassicSelects.vue b/apps/user-h5/src/components/synastry/SynastryClassicSelects.vue
new file mode 100644
index 0000000..354e7e2
--- /dev/null
+++ b/apps/user-h5/src/components/synastry/SynastryClassicSelects.vue
@@ -0,0 +1,62 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/user-h5/src/components/synastry/SynastryDualPick.vue b/apps/user-h5/src/components/synastry/SynastryDualPick.vue
new file mode 100644
index 0000000..674be5b
--- /dev/null
+++ b/apps/user-h5/src/components/synastry/SynastryDualPick.vue
@@ -0,0 +1,180 @@
+
+
+
+
+ {{ selfInitial }}
+
+
自己
+
{{ profileAName }}
+
+
×
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/user-h5/src/components/synastry/SynastryInviteNearby.vue b/apps/user-h5/src/components/synastry/SynastryInviteNearby.vue
new file mode 100644
index 0000000..eadd4e9
--- /dev/null
+++ b/apps/user-h5/src/components/synastry/SynastryInviteNearby.vue
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+
邀请链接:{{ invitePath }}(可复制分享)
+
{{ nearbyHint }}
+
+ 需要至少两个档案。可先去
+ 档案页
+ 添加 TA,或点「添加档案」快速创建。
+
+
+
+
+
+
+
diff --git a/apps/user-h5/src/components/synastry/SynastryLanding.vue b/apps/user-h5/src/components/synastry/SynastryLanding.vue
index 3d71931..1c83274 100644
--- a/apps/user-h5/src/components/synastry/SynastryLanding.vue
+++ b/apps/user-h5/src/components/synastry/SynastryLanding.vue
@@ -2,75 +2,35 @@
看看你和 TA 的默契指数
-
-
-
+
-
-
-
- {{ selfInitial }}
-
-
自己
-
{{ profileAName }}
-
-
×
-
-
-
-
-
-
-
-
+
-
+