import axios from 'axios' const http = axios.create({ baseURL: `${import.meta.env.BASE_URL}api/v1` }) // ------------------------------------------------------------------ // // Types // ------------------------------------------------------------------ // export interface Campaign { id: number name: string product: string platform: string cron_schedule: string | null active: number notes: string | null created_at: string updated_at: string } export interface CampaignCreate { name: string product: string platform?: string cron_schedule?: string | null notes?: string | null } export interface CampaignUpdate { name?: string product?: string cron_schedule?: string | null active?: boolean notes?: string | null } export interface Variant { id: number campaign_id: number sub_pattern: string title: string body: string flair: string | null notes: string | null link_url: string | null created_at: string updated_at: string } export interface VariantCreate { sub_pattern?: string title: string body: string flair?: string | null notes?: string | null link_url?: string | null } export interface CampaignSub { id: number campaign_id: number sub: string sort_order: number active: number } export interface Post { id: number campaign_id: number | null variant_id: number | null platform: string target: string status: string url: string | null error_msg: string | null screenshot_path: string | null triggered_by: string posted_at: string // Engagement snapshot (null when no data has been collected yet) score: number | null upvotes: number | null comment_count: number | null awards: number | null engagement_checked_at: string | null } export interface SubRules { id: number platform: string sub: string flair_required: number flair_to_use: string | null promo_allowed: number | null rule_warning: number notes: string | null post_url: string | null last_checked: string | null updated_at: string } export interface SubRulesUpsert { flair_required?: boolean flair_to_use?: string | null promo_allowed?: boolean | null rule_warning?: boolean notes?: string | null post_url?: string | null } export type OpportunityStatus = | 'pending_review' | 'approved' | 'posted' | 'manual_posted' | 'dismissed' export type PostType = 'reply_to_thread' | 'new_post' export interface Opportunity { id: number platform: string community: string thread_url: string thread_title: string | null thread_body: string | null signal_reason: string | null product: string | null draft_title: string | null draft_body: string post_type: PostType status: OpportunityStatus campaign_id: number | null dismiss_note: string | null created_at: string updated_at: string } export interface OpportunityCreate { platform?: string community: string thread_url: string thread_title?: string | null thread_body?: string | null signal_reason?: string | null product?: string | null draft_title?: string | null draft_body?: string post_type?: PostType campaign_id?: number | null } export interface ApproveResult { type: 'auto_post_ready' | 'manual_handoff' opportunity: Opportunity draft_body?: string thread_url?: string instructions: string } export type SignalMatchMode = 'any' | 'all' | 'regex' export type SignalLabel = 'pain-point' | 'feedback' | 'mention' | 'trust' export type SignalStatus = 'new' | 'saved' | 'dismissed' export interface SignalRule { id: number name: string platform: string sub: string | null keywords: string[] match_mode: SignalMatchMode min_score: number label: SignalLabel | null active: number created_at: string notes: string | null } export interface SignalRuleCreate { name: string platform?: string sub?: string | null keywords?: string[] match_mode?: SignalMatchMode min_score?: number label?: SignalLabel | null notes?: string | null } export interface Signal { id: number platform: string sub: string post_id: string title: string body_snippet: string | null score: number | null comment_count: number | null author: string | null url: string | null posted_at: string | null surfaced_at: string matched_keywords: string[] status: SignalStatus notes: string | null matched_rules?: SignalRule[] } export interface SessionStatus { target: string valid: boolean age_hours: number | null session_file: string } export interface RefreshResult { target: string ok: boolean message: string } // ------------------------------------------------------------------ // // Campaigns // ------------------------------------------------------------------ // export const api = { campaigns: { list: (activeOnly = false) => http.get('/campaigns', { params: { active_only: activeOnly } }).then(r => r.data), create: (data: CampaignCreate) => http.post('/campaigns', data).then(r => r.data), get: (id: number) => http.get(`/campaigns/${id}`).then(r => r.data), update: (id: number, data: CampaignUpdate) => http.patch(`/campaigns/${id}`, data).then(r => r.data), delete: (id: number) => http.delete(`/campaigns/${id}`), trigger: (id: number) => http.post<{ campaign_id: number; results: unknown[] }>(`/campaigns/${id}/trigger`).then(r => r.data), }, variants: { list: (campaignId: number) => http.get(`/campaigns/${campaignId}/variants`).then(r => r.data), create: (campaignId: number, data: VariantCreate) => http.post(`/campaigns/${campaignId}/variants`, data).then(r => r.data), update: (campaignId: number, variantId: number, data: Partial) => http.patch(`/campaigns/${campaignId}/variants/${variantId}`, data).then(r => r.data), delete: (campaignId: number, variantId: number) => http.delete(`/campaigns/${campaignId}/variants/${variantId}`), }, subs: { listForCampaign: (campaignId: number) => http.get(`/campaigns/${campaignId}/subs`).then(r => r.data), add: (campaignId: number, sub: string, sortOrder = 0) => http.post(`/campaigns/${campaignId}/subs`, { sub, sort_order: sortOrder }).then(r => r.data), remove: (campaignId: number, sub: string) => http.delete(`/campaigns/${campaignId}/subs/${sub}`), listRules: (platform = 'reddit') => http.get('/subs', { params: { platform } }).then(r => r.data), upsertRules: (sub: string, data: SubRulesUpsert, platform = 'reddit') => http.put(`/subs/${sub}`, data, { params: { platform } }).then(r => r.data), }, posts: { list: (campaignId?: number, target?: string, limit = 50) => http.get('/posts', { params: { campaign_id: campaignId, target, limit } }).then(r => r.data), triggerSingle: (campaignId: number, sub: string) => http.post('/posts/trigger', { campaign_id: campaignId, sub }).then(r => r.data), pollEngagement: () => http.post<{ polled: number; errors: number }>('/posts/poll-engagement').then(r => r.data), }, opportunities: { list: (status?: OpportunityStatus) => http.get('/opportunities', { params: status ? { status } : {} }).then(r => r.data), create: (data: OpportunityCreate) => http.post('/opportunities', data).then(r => r.data), get: (id: number) => http.get(`/opportunities/${id}`).then(r => r.data), update: (id: number, data: Partial>) => http.patch(`/opportunities/${id}`, data).then(r => r.data), approve: (id: number) => http.post(`/opportunities/${id}/approve`).then(r => r.data), markPosted: (id: number, manual = false, url?: string | null) => http.post(`/opportunities/${id}/mark-posted`, { url: url ?? null }, { params: { manual } }).then(r => r.data), dismiss: (id: number, note?: string) => http.post(`/opportunities/${id}/dismiss`, { note: note ?? null }).then(r => r.data), }, signalRules: { list: (activeOnly = false) => http.get('/signal-rules', { params: { active_only: activeOnly } }).then(r => r.data), create: (data: SignalRuleCreate) => http.post('/signal-rules', data).then(r => r.data), get: (id: number) => http.get(`/signal-rules/${id}`).then(r => r.data), update: (id: number, data: Partial & { active?: boolean }) => http.patch(`/signal-rules/${id}`, data).then(r => r.data), delete: (id: number) => http.delete(`/signal-rules/${id}`), }, signals: { list: (params?: { status?: SignalStatus; platform?: string; sub?: string; limit?: number }) => http.get('/signals', { params }).then(r => r.data), get: (id: number) => http.get(`/signals/${id}`).then(r => r.data), updateStatus: (id: number, status: SignalStatus, notes?: string) => http.patch(`/signals/${id}/status`, { status, notes: notes ?? null }).then(r => r.data), }, reddit: { sessionStatus: (target = 'magpie') => http.get('/reddit/session-status', { params: { target } }).then(r => r.data), refreshSession: (target = 'magpie') => http.post('/reddit/refresh-session', null, { params: { target } }).then(r => r.data), }, stats: () => http.get('/stats').then(r => r.data), }