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:
+45
-122
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user