refactor(ECR-001): Phase F types/sdk 对齐并拆分超标 H5 页

抽出 OpenAPI DTO;将 Ask/Report/Profile/Star 等 8 页拆到 ≤400 行,并修 ScaleSummary、fortuneKey、Scale 选答与单测。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
jackyu66git
2026-08-05 18:19:34 +08:00
co-authored by Cursor
parent 19d3cd5945
commit e735aa43e3
64 changed files with 6198 additions and 4157 deletions
+45 -122
View File
@@ -1,11 +1,30 @@
import type {
AcceptSynastryInviteBody,
ApiResponse,
AskMessage,
AskQuota,
AskThread,
CreateOrderBody,
CreateProfileBody,
ExploreCategory,
GrowthCheckin,
GrowthPlan,
GrowthReport,
ImageCardDrawn,
ImageCardQuota,
ImageCardScene,
MembershipMe,
MoodEntry,
Profile,
RelationInsightResult,
ScaleDetail,
ScaleSubmitResult,
ScaleSummary,
SolarTermToday,
SynastryInviteCreated,
SynastryInviteView,
SynastryNearbyItem,
UpdateProfileBody,
} from '@yuxingu/types'
/** Platform adapters so H5 and mini-program share one client. */
@@ -29,7 +48,7 @@ export interface CreateClientOptions {
}
/**
* createClient builds a typed API facade.
* createClient builds a typed API facade aligned with proto/openapi.yaml paths.
*/
export function createClient(opts: CreateClientOptions) {
const { baseURL, adapters } = opts
@@ -60,27 +79,10 @@ export function createClient(opts: CreateClientOptions) {
healthz: () => call<{ status: string }>('/api/v1/healthz'),
ping: () => call<{ pong: boolean }>('/api/v1/ping'),
listProfiles: () => call<{ items: Profile[] }>('/api/v1/profiles'),
createProfile: (body: {
relation: 'self' | 'other'
birth_date: string
display_name?: string
relation_type?: string
birth_time?: string
birth_place?: string
}) => call<Profile>('/api/v1/profiles', { method: 'POST', body }),
updateProfile: (
id: string,
body: {
display_name?: string
birth_date?: string
relation_type?: string
birth_time?: string
birth_place?: string
geo_lat?: number
geo_lng?: number
geo_visible?: boolean
},
) => call<Profile>(`/api/v1/profiles/${id}`, { method: 'PATCH', body }),
createProfile: (body: CreateProfileBody) =>
call<Profile>('/api/v1/profiles', { method: 'POST', body }),
updateProfile: (id: string, body: UpdateProfileBody) =>
call<Profile>(`/api/v1/profiles/${id}`, { method: 'PATCH', body }),
deleteProfile: (id: string) =>
call<{ deleted: boolean }>(`/api/v1/profiles/${id}`, { method: 'DELETE' }),
createPortrait: (profile_id: string) =>
@@ -93,136 +95,57 @@ export function createClient(opts: CreateClientOptions) {
body: { profile_id_a, profile_id_b, ...(as_of ? { as_of } : {}) },
}),
listSynastryNearby: (lat: number, lng: number, radius_km = 50) =>
call<{ items: { profile: Profile; distance_km: number }[] }>(
call<{ items: SynastryNearbyItem[] }>(
`/api/v1/synastry/nearby?lat=${lat}&lng=${lng}&radius_km=${radius_km}`,
),
createSynastryInvite: (profile_id: string) =>
call<{ token: string; expires_at: string; path: string }>('/api/v1/synastry/invites', {
call<SynastryInviteCreated>('/api/v1/synastry/invites', {
method: 'POST',
body: { profile_id },
}),
getSynastryInvite: (token: string) =>
call<{
token: string
expires_at: string
host_name: string
already_accepted: boolean
}>(`/api/v1/synastry/invites/${token}`),
acceptSynastryInvite: (token: string, body: {
display_name?: string
birth_date: string
birth_time?: string
birth_place?: string
}) =>
call<SynastryInviteView>(`/api/v1/synastry/invites/${token}`),
acceptSynastryInvite: (token: string, body: AcceptSynastryInviteBody) =>
call<GrowthReport>(`/api/v1/synastry/invites/${token}/accept`, { method: 'POST', body }),
createRhythm: (profile_id: string) =>
call<GrowthReport>('/api/v1/reports/rhythm', { method: 'POST', body: { profile_id } }),
listImageCardScenes: () =>
call<{ items: { key: string; label: string }[] }>('/api/v1/image-cards/scenes'),
getImageCardQuota: () =>
call<{ remaining: number; daily_free: number; unlimited: boolean }>('/api/v1/image-cards/quota'),
listImageCardScenes: () => call<{ items: ImageCardScene[] }>('/api/v1/image-cards/scenes'),
getImageCardQuota: () => call<ImageCardQuota>('/api/v1/image-cards/quota'),
drawImageCard: (body: { profile_id: string; scene?: string; depth?: boolean }) =>
call<{
report: GrowthReport
scene: string
cards: { id: string; title: string; imagery: string; prompt: string; tip: string }[]
quota_left: number
}>('/api/v1/image-cards/draw', { method: 'POST', body }),
getSolarTermsToday: () =>
call<{ name: string; tip: string; date: string }>('/api/v1/solar-terms/today'),
call<ImageCardDrawn>('/api/v1/image-cards/draw', { method: 'POST', body }),
getSolarTermsToday: () => call<SolarTermToday>('/api/v1/solar-terms/today'),
saveMood: (body: { score?: number; note?: string; day?: string }) =>
call<{ id: string; day: string; score?: number; note?: string }>('/api/v1/moods', {
method: 'POST',
body,
}),
getMoodToday: () =>
call<{ mood: { id: string; day: string; score?: number; note?: string } | null }>(
'/api/v1/moods/today',
),
getMoodsRecent: () =>
call<{ items: { id: string; day: string; score?: number; note?: string }[] }>(
'/api/v1/moods/recent',
),
getExploreCatalog: () =>
call<{
categories: {
key: string
title: string
description: string
icon: string
tone: string
items: {
key: string
title: string
description: string
path: string
icon: string
tone: string
badge?: string
}[]
}[]
}>('/api/v1/explore/catalog'),
getExploreCategory: (key: string) =>
call<{
key: string
title: string
description: string
icon: string
tone: string
items: {
key: string
title: string
description: string
path: string
icon: string
tone: string
badge?: string
}[]
}>(`/api/v1/explore/catalog/${key}`),
listGrowthPlans: () =>
call<{ items: { id: string; title: string; focus: string; status: string }[] }>(
'/api/v1/growth/plans',
),
call<MoodEntry>('/api/v1/moods', { method: 'POST', body }),
getMoodToday: () => call<{ mood: MoodEntry | null }>('/api/v1/moods/today'),
getMoodsRecent: () => call<{ items: MoodEntry[] }>('/api/v1/moods/recent'),
getExploreCatalog: () => call<{ categories: ExploreCategory[] }>('/api/v1/explore/catalog'),
getExploreCategory: (key: string) => call<ExploreCategory>(`/api/v1/explore/catalog/${key}`),
listGrowthPlans: () => call<{ items: GrowthPlan[] }>('/api/v1/growth/plans'),
createGrowthPlan: (body: { title: string; focus?: string }) =>
call<{ id: string; title: string; focus: string }>('/api/v1/growth/plans', {
method: 'POST',
body,
}),
call<GrowthPlan>('/api/v1/growth/plans', { method: 'POST', body }),
growthCheckin: (planId: string, body?: { note?: string }) =>
call<{ id: string; day: string }>(`/api/v1/growth/plans/${planId}/checkin`, {
method: 'POST',
body: body || {},
}),
listGrowthCheckins: (planId: string) =>
call<{ items: { id: string; day: string; note?: string }[] }>(
`/api/v1/growth/plans/${planId}/checkins`,
),
call<{ items: GrowthCheckin[] }>(`/api/v1/growth/plans/${planId}/checkins`),
listReports: () => call<{ items: GrowthReport[] }>('/api/v1/reports'),
getReport: (id: string) => call<GrowthReport>(`/api/v1/reports/${id}`),
getMembership: () => call<MembershipMe>('/api/v1/membership/me'),
createOrder: (body: { kind: 'membership' | 'deep_access'; plan?: string; report_id?: string }) =>
createOrder: (body: CreateOrderBody) =>
call<{ order_id: string }>('/api/v1/orders', { method: 'POST', body }),
payMock: (orderId: string) =>
call<{ paid: boolean }>(`/api/v1/orders/${orderId}/pay-mock`, { method: 'POST' }),
createRelationInsight: (profile_a_id: string, profile_b_id: string) =>
call<{
insight: { id: string; report_id?: string }
report: GrowthReport
}>('/api/v1/relation/insight', {
call<RelationInsightResult>('/api/v1/relation/insight', {
method: 'POST',
body: { profile_a_id, profile_b_id },
}),
listScales: () =>
call<{ items: { slug: string; title: string; description: string }[] }>('/api/v1/scales'),
getScale: (slug: string) =>
call<{
slug: string
title: string
description: string
questions: { id: string; sort: number; body: { prompt: string; options: { key: string; text: string }[] } }[]
}>(`/api/v1/scales/${slug}`),
listScales: () => call<{ items: ScaleSummary[] }>('/api/v1/scales'),
getScale: (slug: string) => call<ScaleDetail>(`/api/v1/scales/${slug}`),
submitScale: (slug: string, profile_id: string, answers: Record<string, string>) =>
call<{ id: string; result: Record<string, unknown> }>(`/api/v1/scales/${slug}/result`, {
call<ScaleSubmitResult>(`/api/v1/scales/${slug}/result`, {
method: 'POST',
body: { profile_id, answers },
}),
+145 -5
View File
@@ -21,6 +21,26 @@ export interface Profile {
created_at: string
}
export interface CreateProfileBody {
relation: 'self' | 'other'
birth_date: string
display_name?: string
relation_type?: string
birth_time?: string
birth_place?: string
}
export interface UpdateProfileBody {
display_name?: string
birth_date?: string
relation_type?: string
birth_time?: string
birth_place?: string
geo_lat?: number
geo_lng?: number
geo_visible?: boolean
}
/** 成长报告(detail 可能因权益被剥离) */
export interface GrowthReport {
id: string
@@ -33,13 +53,34 @@ export interface GrowthReport {
created_at: string
}
/** 探索测试列表项 */
/** 探索测试列表项 — 对齐 Go ScaleListItem / listScales 响应 */
export interface ScaleSummary {
slug: string
name: string
category: string
questionCount: number
description?: string
title: string
description: string
}
export interface ScaleQuestionOption {
key: string
text: string
}
export interface ScaleQuestion {
id: string
sort: number
body: { prompt: string; options: ScaleQuestionOption[] }
}
export interface ScaleDetail {
slug: string
title: string
description: string
questions: ScaleQuestion[]
}
export interface ScaleSubmitResult {
id: string
result: Record<string, unknown>
}
/** 成长会员当前权益 */
@@ -51,6 +92,12 @@ export interface MembershipMe {
ask_quota_left?: number
}
export interface CreateOrderBody {
kind: 'membership' | 'deep_access'
plan?: string
report_id?: string
}
/** 问答线程 */
export interface AskThread {
id: string
@@ -76,3 +123,96 @@ export interface AskQuota {
free_limit: number
source: 'membership' | 'free' | string
}
export interface SynastryNearbyItem {
profile: Profile
distance_km: number
}
export interface SynastryInviteCreated {
token: string
expires_at: string
path: string
}
export interface SynastryInviteView {
token: string
expires_at: string
host_name: string
already_accepted: boolean
}
export interface AcceptSynastryInviteBody {
display_name?: string
birth_date: string
birth_time?: string
birth_place?: string
}
export interface ImageCardScene {
key: string
label: string
}
export interface ImageCardQuota {
remaining: number
daily_free: number
unlimited: boolean
}
export interface ImageCardDrawn {
report: GrowthReport
scene: string
cards: { id: string; title: string; imagery: string; prompt: string; tip: string }[]
quota_left: number
}
export interface SolarTermToday {
name: string
tip: string
date: string
}
export interface MoodEntry {
id: string
day: string
score?: number
note?: string
}
export interface ExploreItem {
key: string
title: string
description: string
path: string
icon: string
tone: string
badge?: string
}
export interface ExploreCategory {
key: string
title: string
description: string
icon: string
tone: string
items: ExploreItem[]
}
export interface GrowthPlan {
id: string
title: string
focus: string
status?: string
}
export interface GrowthCheckin {
id: string
day: string
note?: string
}
export interface RelationInsightResult {
insight: { id: string; report_id?: string }
report: GrowthReport
}