Files
gig-admin/src/api/types.ts
T

834 lines
19 KiB
TypeScript

/**
* Hand-written types for the gig-platform admin API.
*
* The scribe-generated `types.gen.ts` covers paths/params, but its response
* schemas are example-based and incomplete — the envelope and entities below
* are the source of truth for data shapes (mirrors CLAUDE.md API design).
*/
// ---------- envelope ----------
export interface ApiResponse<T = unknown> {
success: boolean
data: T
message?: string
meta?: Record<string, unknown>
}
export interface Pagination {
current_page: number
next_page: number | null
page_size: number
prev_page: number | null
total_count: number
total_pages: number
}
export interface PaginatedResponse<T> extends ApiResponse<T[]> {
pagination: Pagination
}
export interface ApiErrorPayload {
success: false
code: string
message: string
data?: { errors?: Record<string, string[]> } & Record<string, unknown>
}
export interface ListParams {
page?: number
page_size?: number
[key: string]: string | number | boolean | undefined
}
// ---------- auth ----------
export interface TokenGrant {
token_type: string
expires_in: number
access_token: string
refresh_token?: string
}
export interface AdminUser {
kind: 'user'
uid: string
sid?: string | null
username: string | null
email: string | null
phone: string | null
display_name: string | null
avatar: string | null
status: boolean | string
is_robot?: boolean
}
export interface PlatformUser {
kind: 'user'
uid: string
sid?: string | null
sid_display?: string | null
username: string | null
display_name: string | null
email: string | null
phone: string | null
country_code?: string | null
avatar: string | null
expertise?: string | null
city?: string | null
timezone?: string | null
timezone_offset?: number | null
locale?: string | null
status: boolean
is_robot: boolean
email_verified_at?: string | null
phone_verified_at?: string | null
roles: string[]
created_at: string
updated_at?: string
}
export interface UserListParams extends ListParams {
q?: string
status?: boolean
is_robot?: boolean
role?: string
}
export interface UserAddress {
kind?: string
contact_name?: string
contact_phone_number?: string
country_code?: string
province?: string
city?: string
district?: string
address_line?: string
}
export interface UserCareer {
id?: number | string
company_name?: string
position?: string
department?: string
start_date?: string
end_date?: string
is_current?: boolean
description?: string
}
export interface UserEducation {
id?: number | string
school_name?: string
major?: string
degree?: string
start_date?: string
end_date?: string
is_current?: boolean
}
export interface UserHonor {
id?: number | string
title?: string
issuer?: string
date?: string
description?: string
}
export interface UserProfileInfo {
user?: PlatformUser
profile?: {
kind?: string
user_uid?: string
computed_fullname?: string
headline?: string
bio?: string
gender?: string
}
careers?: UserCareer[]
educations?: UserEducation[]
honors?: UserHonor[]
addresses?: UserAddress[]
}
export interface UserExpertiseService {
id?: number
kind?: string
service_type?: string
charge_type?: string
price?: number
currency_code?: string
duration?: number
enabled?: boolean
}
export interface UserExpertise {
id?: number
kind?: string
name?: string
description?: string
tags?: string[]
user_uid?: string
services?: UserExpertiseService[]
}
export interface UserServicesResponse {
expertises?: UserExpertise[]
}
export interface MeResponse extends ApiResponse<AdminUser> {
meta: {
roles: string[]
permissions: string[]
}
}
// ---------- category ----------
export interface CategoryTranslation {
locale: string
name: string
description: string | null
}
export interface Category {
kind: 'category'
id: string
parent_id: string | null
level: number
slug: string
name: string | null
description: string | null
sort_order: number
is_active: boolean
visibility?: string
approval_status?: 'pending' | 'approved' | 'rejected' | 'merged'
source?: 'platform' | 'admin' | 'user'
created_by_uid?: string | null
approved_by_uid?: string | null
approved_at?: string | null
merged_into_category_id?: string | null
translations?: CategoryTranslation[]
children?: Category[]
created_at?: string
updated_at?: string
}
// ---------- studio ----------
export type ArticleStatus =
| 'planned'
| 'drafting'
| 'drafted'
| 'published'
| 'scheduled'
export interface StudioArticle {
kind: 'studio_article'
id: string
series_id: string
robot_author_id: string
category_id: string | null
subtopic_id: string | null
article_number: number
plan: Record<string, unknown> | null
outline: Record<string, unknown> | null
title: string | null
summary: string | null
key_takeaways: string[] | null
cta: string | null
tags: string[] | null
seo_keywords: string[] | null
cover_image_prompt: string | null
cover_asset_id: string | null
cover_image: { url: string; alt?: string | null } | null
inline_images: Array<{ url: string; alt?: string | null }> | null
inline_image_prompts: unknown[] | null
status: ArticleStatus
/** LLM used to draft the body, e.g. { provider, model, params }. */
drafting_model: { provider?: string; model?: string; [k: string]: unknown } | null
publish_schedule: Record<string, unknown> | null
dimzou_document_id: number | null
dimzou_publication_id: number | null
created_at: string
updated_at: string
}
export type RobotAuthorStatus = 'pending' | 'profiling' | 'ready' | 'paused'
export interface StudioRobotAuthor {
kind: 'studio_robot_author'
id: string
user_uid: string
/** Bound robot account's public name (index listing only). */
display_name?: string | null
avatar?: string | null
category_id: string
subtopic_id: string | null
/** Account-level voice — stored on the shared profile, flattened into the author. */
persona: string | null
article_style: Record<string, unknown> | null
image_style: Record<string, unknown> | null
publishing_rules: Record<string, unknown> | null
status: RobotAuthorStatus
source: 'user' | 'category'
created_at: string
updated_at: string
}
export type SeriesStatus = 'planned' | 'in_progress' | 'complete'
export interface StudioSeries {
kind: 'studio_series'
id: string
category_id: string
subtopic_id: string | null
robot_author_id: string
goal: string | null
/** Series-level angle/audience (moved off the author). */
positioning: string | null
target_readers: string | null
reader_journey: string | null
target_article_count: number | null
planned_articles: unknown[] | null
status: SeriesStatus
created_at: string
updated_at: string
}
export interface StudioSubtopic {
kind: 'studio_subtopic'
id: string
category_id: string
title: string
target_audience: string | null
pain_points: string | null
content_value: string | null
expandability_score: number | null
status: 'active' | 'archived'
created_at: string
updated_at: string
}
export interface StudioCategoryBrief {
kind: 'studio_category_brief'
id: string
category_id: string
description: string | null
target_audience: string | null
content_angles: string[] | null
risk_notes: string | null
subtopic_count: number
author_count: number
article_count: number
created_at: string
updated_at: string
}
export interface StudioStylePreset {
kind: 'studio_style_preset'
id: string
type: 'article' | 'image' | 'bundle'
name: string
payload: Record<string, unknown>
created_at: string
updated_at: string
}
export interface StudioAgentToken {
kind: 'studio_agent_token'
id: string
scopes: string[] | null
created_at: string | null
expires_at: string | null
}
export interface StudioAgentTokenGrant extends TokenGrant {
kind: 'studio_agent_token_grant'
connect: { endpoint: string; header: string }
}
// ---------- sid / locales / devices ----------
export interface SidRule {
id?: number
key: string
description: string | null
enabled: boolean
created_at?: string
updated_at?: string
}
export interface LuckyNumber {
id: number
number: string
category: string | null
enabled: boolean
created_by?: string | null
created_at?: string
updated_at?: string
}
export interface SidCheckResult {
sid: string
reserved: boolean
reasons?: string[]
}
export interface LocaleRow {
kind?: string
code: string
name: string
native_name: string
is_active: boolean
sort_order: number
created_at?: string
updated_at?: string
}
export interface NotificationDevice {
id: number | string
device_id?: string
user_uid?: string | null
platform?: string
model?: string | null
is_online?: boolean
active_sessions_count?: number
last_seen_at?: string | null
created_at?: string
}
export type ReportStatus = 'open' | 'resolved' | 'dismissed'
export interface ReportUser {
uid: string
display_name?: string | null
}
export interface ResourceReport {
id: number
reason: string
message?: string | null
status: ReportStatus
reporter?: ReportUser | null
created_at: string
handled_at?: string | null
}
interface ReportedResourceBase {
kind?: string
id: number
is_blocked: boolean
blocked_at?: string | null
blocked_by_uid?: string | null
author?: ReportUser | null
open_reports_count: number
latest_report_at?: string | null
reports: ResourceReport[]
created_at?: string
}
export interface ReportedComment extends ReportedResourceBase {
body?: string | null
body_format?: string
status?: string
}
export interface ReportedEvent extends ReportedResourceBase {
title?: string | null
visibility?: string
}
export type DimzouTranslationStatus = 'draft' | 'awaiting_publish' | 'published'
export interface DimzouTranslation {
kind?: string
id: number
language_code: string
status: DimzouTranslationStatus
source_document?: { id: number; title?: string | null } | null
translated_document_id: number
created_by_uid: string
creator?: ReportUser | null
draft_version_id?: number | null
blocks: {
total: number
needs_translation: number
translation_failed: number
}
created_at?: string
updated_at?: string
}
export interface RetranslateResult {
translation_version_id: number
queued_blocks: number
}
export interface AdminRole {
kind?: string
name: string
permissions: string[]
users_count: number
builtin: boolean
}
/** module → { module, groups: { group → { permissionKey → description } } } */
export type PermissionCatalog = Record<
string,
{ module: string; groups: Record<string, Record<string, string>> }
>
export interface RoleUser {
kind?: string
uid: string
display_name?: string | null
email?: string | null
username?: string | null
roles: string[]
}
export type DimzouDocumentState = 'active' | 'archived' | 'trashed'
export interface DimzouDocumentVersion {
id: number
document_id: number
version_number: number
status: 'draft' | 'published' | 'superseded'
visibility?: 'public' | 'group_only'
structured_title?: unknown
language?: string | null
publication_id?: number | null
published_at?: string | null
frozen_at?: string | null
created_at?: string
updated_at?: string
}
export interface DimzouDocument {
kind?: string
id: number
title?: string | null
owner?: ReportUser | null
category?: { id: string; name?: string | null } | null
state: DimzouDocumentState
versions_count: number
published_version?: number | null
draft_version?: number | null
archived_at?: string | null
deleted_at?: string | null
created_at?: string
updated_at?: string
// detail-only
versions?: DimzouDocumentVersion[]
translations?: Array<{
id: number
language_code: string
status: DimzouTranslationStatus
translated_document_id: number
created_by_uid: string
created_at?: string
}>
pending_revisions_count?: number
pending_join_requests_count?: number
}
export interface DimzouPublication {
kind?: string
id: number
document_id: number
document_version_id: number
version_number: number
visibility: 'public' | 'group_only'
title?: string | null
summary?: string | null
cover_image?: unknown
language?: string | null
keywords?: string[]
feed_card?: unknown
author_headline?: string | null
published_at?: string | null
block_count?: number
author?: ReportUser | null
category?: { id: string; name?: string | null } | null
}
// --- Exc 专长交易(只读监控)---
export interface ExcUser {
uid: string
display_name?: string | null
}
export type ExcDemandStatus =
| 'draft'
| 'open'
| 'quotation_closed'
| 'order_placed'
| 'fulfilled'
| 'broadcasting'
| 'matched'
| 'converted'
| 'expired'
| 'canceled'
export type ExcDispatchMode = 'instant_accept' | 'quote' | 'scheduled_booking' | 'manual_selection'
export interface ExcDemand {
kind?: string
id: number
title?: string | null
status: ExcDemandStatus
dispatch_mode?: ExcDispatchMode | null
service_type?: string | null
buyer?: ExcUser | null
category_id?: string | null
currency_code?: string | null
ref_amount?: number | null
matched_provider_uid?: string | null
matched_at?: string | null
quotes_count: number
dispatches_count: number
required_at?: string | null
published_at?: string | null
created_at?: string
// detail-only
summary?: string | null
content?: string | null
address?: unknown
lat?: number | null
lng?: number | null
duration?: number | null
quote_deadline?: string | null
related_services?: Array<{ id: number; title?: string | null; category_name?: string | null }>
quotes?: Array<{
id: number
user_uid: string
price: number
currency_code?: string
message?: string | null
user?: ExcUser | null
created_at?: string
}>
dispatches?: ExcDemandDispatchRow[]
}
export interface ExcDemandDispatchRow {
id: number
provider?: ExcUser | null
round_no: number
status: ExcDispatchStatus
score?: number | null
distance_meters?: number | null
eta_minutes?: number | null
responded_at?: string | null
}
export type ExcDispatchStatus =
| 'sent'
| 'delivered'
| 'viewed'
| 'accepted'
| 'rejected'
| 'ignored'
| 'expired'
| 'taken_by_other'
export interface ExcDispatch {
kind?: string
id: number
demand?: { id: number; title?: string | null } | null
provider?: ExcUser | null
round_no: number
status: ExcDispatchStatus
score?: number | null
similarity?: number | null
distance_meters?: number | null
eta_minutes?: number | null
auto_grab_eligible: boolean
sent_at?: string | null
viewed_at?: string | null
responded_at?: string | null
expires_at?: string | null
}
export type ExcOrderStatus =
| 'created'
| 'confirmed'
| 'started'
| 'consumed'
| 'paid'
| 'fulfilled'
| 'canceled'
export interface ExcOrder {
kind?: string
id: number
title?: string | null
status: ExcOrderStatus
payment_status?: 'UNPAID' | 'PAID' | null
buyer?: ExcUser | null
provider?: ExcUser | null
currency_code?: string | null
total_amount?: number | null
actual_total_amount?: number | null
platform_fee_amount?: number | null
provider_net_amount?: number | null
commission_rate?: number | null
cancellation_fee_amount?: number | null
paid_at?: string | null
settled_at?: string | null
escrow_release_at?: string | null
escrow_released_at?: string | null
order_date?: string | null
created_at?: string
// detail-only
buyer_remarks?: string | null
pay_order_no?: string | null
paid_channel?: string | null
cancellation_fee_due_date?: string | null
items?: Array<{
id: number
name?: string | null
status?: string
price?: number
quantity?: number
line_total?: number
service_type?: string
}>
transactions?: Array<{
id: number
previous_status?: string | null
new_status?: string
changed_by?: string | null
changed_by_type?: string | null
reason?: string | null
changed_at?: string
}>
}
export type ExcProviderStatus = 'offline' | 'online' | 'inactive' | 'busy'
export interface ExcProvider {
kind?: string
user?: ExcUser | null
user_uid: string
status: ExcProviderStatus
is_disabled: boolean
is_verified: boolean
rating?: number | null
completed_jobs?: number | null
online_at?: string | null
offline_at?: string | null
last_location_at?: string | null
current_service_order_id?: number | null
created_at?: string
// detail-only
last_lat?: number | null
last_lng?: number | null
location_accuracy_m?: number | null
dispatch_setting?: {
auto_grab_enabled?: boolean
service_whitelist?: string[]
min_price?: number | null
max_radius_meters?: number | null
max_eta_minutes?: number | null
daily_accept_cap?: number | null
accepted_today?: number
} | null
status_logs?: Array<{
from_status?: string | null
to_status?: string
reason?: string | null
created_at?: string
}>
}
// ---------- resume bulk import ----------
export type ResumeImportBatchStatus =
| 'queued'
| 'unpacking'
| 'processing'
| 'completed'
| 'failed'
/**
* What happened to one resume. Independent of the parse status: a resume can
* parse perfectly and still end up `skipped_duplicate`.
*/
export type ResumeImportOutcome =
| 'created'
| 'would_create'
| 'skipped_duplicate'
| 'reused_import'
| 'needs_contact'
| 'duplicate_file'
| 'unsupported_entry'
| 'entry_too_large'
| 'parse_failed'
| 'failed'
/** `cached` / `reused` mean no provider call was made for that step. */
export type ResumeImportStepStatus =
| 'pending'
| 'processing'
| 'completed'
| 'cached'
| 'reused'
| 'skipped'
| 'failed'
export interface ResumeImportBatch {
id: string
kind: 'resume-import-batch'
status: ResumeImportBatchStatus
dryRun: boolean
progress: {
/** 0 until unpacking finishes — render a preparing state, not "0 / 0". */
total: number
processed: number
percent: number
}
counts: Partial<Record<ResumeImportOutcome, number>>
archive: { filename: string | null; size: number | null }
options: {
countryCode: string | null
languageHint: string | null
schemaVersion: string | null
}
error: { code: string; message: string } | null
createdBy: string | null
pollUrl: string
createdAt: string | null
startedAt: string | null
finishedAt: string | null
}
export interface ResumeImportItem {
id: string
kind: 'resume-import'
status: 'queued' | 'processing' | 'completed' | 'failed'
outcome: ResumeImportOutcome | null
steps: { extraction: ResumeImportStepStatus; structure: ResumeImportStepStatus }
document: {
filename: string | null
archiveEntry: string | null
mimeType: string | null
size: number | null
extractionMethod: string | null
}
identityType: 'phone' | 'email' | null
matchedUserUid: string | null
duplicateOfImportId: string | null
error: { code: string; message: string } | null
createdAt: string | null
finishedAt: string | null
// detail-only — never present on the list
identityValue?: string | null
metrics?: Record<string, number> | null
result?: Record<string, unknown> | null
}