feat(ECR-012–016): 合规、题库、时辰刷新、头像、MBTI OEJTS 与埋点

落地输入合规、探索题库、报告日/时辰刷新、账号头像、OEJTS 量表,并补齐 H5 埋点与 Admin 漏斗;同步 ESS 工件、切至自建 Git、清理 GitHub Actions。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
jackyu66git
2026-08-13 01:27:58 +08:00
co-authored by Cursor
parent 13860bf1ef
commit 89756f65b4
227 changed files with 7405 additions and 1407 deletions
+162 -9
View File
@@ -6,6 +6,8 @@ info:
愈心谷 P1 API 契约(冻结草案)。
用户可见文案遵循 .ai/product/lexicon.md。
统一信封:{ code, message, data }code=0 成功。
用户自由文字不合规(昵称/档案名/问答/备注等):HTTP 400 · code 40060 · message「文案不合规:…」
(见 feature-spec/input-compliance;后端 textsafe 权威校验)。
servers:
- url: http://127.0.0.1:8080
tags:
@@ -64,6 +66,44 @@ paths:
'200':
description: OK
/api/v1/auth/me/avatar:
post:
tags: [system]
summary: Upload account avatar (multipart file)
description: jpeg/png/webp · max 2MB · field name `file` · returns AuthUser with avatar_url
requestBody:
required: true
content:
multipart/form-data:
schema:
type: object
required: [file]
properties:
file: { type: string, format: binary }
responses:
'200':
description: OK · AuthUser with avatar_url
'400':
description: bad type / too large / empty (code 40114)
'401':
description: unauthorized
/api/v1/media/avatars/{file}:
get:
tags: [system]
summary: Serve account avatar image
description: Public read; `{uuid}.{jpg|jpeg|png|webp}` only; path traversal rejected
parameters:
- name: file
in: path
required: true
schema: { type: string }
responses:
'200':
description: image bytes
'404':
description: missing or invalid name
/api/v1/admin/auth/login:
post:
tags: [admin]
@@ -419,13 +459,17 @@ paths:
/api/v1/home/daily-tips:
get:
tags: [system]
summary: Homepage self-card daily tips (clothing / colors / wellness)
summary: Homepage self-card tips by 时辰 (clothing / colors / wellness)
description: >
Uses self profile birth + local time hexagram seed and DeepSeek when configured.
Falls back to local tips without LLM or birth profile. DeviceAuth required.
Cached per user × Chinese double-hour (时辰). Returns immediately from cache/fallback;
LLM warm runs in background. Includes shichen, shichen_name, valid_until for client refresh.
responses:
'200':
description: OK
description: OK · tips + valid_until
content:
application/json:
schema:
$ref: '#/components/schemas/EnvelopeHomeDailyTips'
/api/v1/admin/home/tools:
get:
@@ -639,7 +683,7 @@ paths:
profile_id: { type: string, format: uuid }
responses:
'200':
description: GrowthReport type=star(含 chart/planets/aspects_preview/outlook/transits
description: GrowthReport type=star(含 chart/planets/aspects_preview/outlook/transitssummary.as_of/valid_until 按自然日;GET 换日原地刷新运势
content:
application/json:
schema:
@@ -758,7 +802,7 @@ paths:
profile_id: { type: string, format: uuid }
responses:
'200':
description: GrowthReport type=rhythm
description: GrowthReport type=rhythmsummary.as_of/valid_until 按自然日;GET 换日原地刷新今日/本周建议)
content:
application/json:
schema:
@@ -853,6 +897,61 @@ paths:
schema:
$ref: '#/components/schemas/EnvelopeGrowthReport'
/api/v1/scale-bank/catalog:
get:
tags: [scale]
summary: curated 题库精选宫格 + 分类卡片(A 集非临床)
responses:
'200':
description: OK
content:
application/json:
schema:
type: object
properties:
code: { type: integer }
message: { type: string }
data:
type: object
properties:
featured:
type: array
items:
type: object
properties:
slug: { type: string }
title: { type: string }
description: { type: string }
category_key: { type: string }
icon: { type: string }
path: { type: string }
categories:
type: array
items:
type: object
properties:
key: { type: string }
title: { type: string }
description: { type: string }
icon: { type: string }
count: { type: integer }
path: { type: string }
/api/v1/scale-bank/categories/{key}:
get:
tags: [scale]
summary: curated 题库某一分类下的量表列表
parameters:
- name: key
in: path
required: true
schema: { type: string }
responses:
'200':
description: OK
'404':
description: category not found
/api/v1/scales:
get:
tags: [scale]
@@ -869,6 +968,9 @@ paths:
get:
tags: [scale]
summary: 探索测试题目
description: >
`mbti-full` 未开通成长会员时返回 access=membership · locked=true 且 questions 为空。
curated scalebank slug 从 embed 返回题目。
parameters:
- name: slug
in: path
@@ -883,6 +985,19 @@ paths:
$ref: '#/components/schemas/EnvelopeScaleDetail'
/api/v1/scales/{slug}/result:
get:
tags: [scale]
summary: 当前用户该量表最近一次结果
parameters:
- name: slug
in: path
required: true
schema: { type: string }
responses:
'200':
description: ScaleSubmitResult
'404':
description: 尚无结果
post:
tags: [scale]
summary: 提交探索测试并计分
@@ -904,8 +1019,8 @@ paths:
responses:
'200':
description: 探索结果
/api/v1/ask/quota:
'403':
description: mbti-full 需要成长会员(code 40310
get:
tags: [ask]
summary: 当前问答剩余次数(免费额度或成长会员)
@@ -1097,11 +1212,40 @@ components:
user_id: { type: string, format: uuid }
profile_id: { type: string, format: uuid }
type: { type: string }
summary: { type: object, additionalProperties: true }
summary:
type: object
additionalProperties: true
description: >
For type=star|rhythm include as_of (YYYY-MM-DD CST) and valid_until (next midnight CST RFC3339).
GET latest/id refreshes day-scoped outlook in place when as_of is stale; report id unchanged.
detail: { type: object, nullable: true, additionalProperties: true }
has_deep_access: { type: boolean }
created_at: { type: string, format: date-time }
HomeDailyTips:
type: object
required: [clothing_index, clothing, color_note, palette, wellness, source, shichen, shichen_name, valid_until]
properties:
clothing_index: { type: integer }
clothing: { type: string }
color_note: { type: string }
palette:
type: array
items:
type: object
properties:
name: { type: string }
hex: { type: string }
wellness: { type: string }
source: { type: string, description: llm | fallback }
gua_name: { type: string }
day_part: { type: string }
as_of: { type: string, format: date }
shichen: { type: integer, minimum: 0, maximum: 11 }
shichen_name: { type: string }
valid_until: { type: string, format: date-time, description: Next 时辰 start RFC3339 }
need_birth: { type: boolean }
MembershipMe:
type: object
required: [active, status]
@@ -1149,6 +1293,8 @@ components:
slug: { type: string }
title: { type: string }
description: { type: string }
access: { type: string, description: free | membership }
locked: { type: boolean, description: true when membership required and inactive }
questions:
type: array
items: { $ref: '#/components/schemas/ScaleQuestion' }
@@ -1223,6 +1369,13 @@ components:
properties:
data: { $ref: '#/components/schemas/GrowthReport' }
EnvelopeHomeDailyTips:
allOf:
- $ref: '#/components/schemas/Envelope'
- type: object
properties:
data: { $ref: '#/components/schemas/HomeDailyTips' }
EnvelopeMembershipMe:
allOf:
- $ref: '#/components/schemas/Envelope'