/** * 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 { success: boolean data: T message?: string meta?: Record } 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 extends ApiResponse { pagination: Pagination } export interface ApiErrorPayload { success: false code: string message: string data?: { errors?: Record } & Record } 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 { 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 | null outline: Record | 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 | 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 | null image_style: Record | null publishing_rules: Record | 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 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> } > 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> 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 | null result?: Record | null }