Spaces:
Running
Running
BRIDGE Platform Agent
feat(platform): BRIDGE Modules hub with /modules/e and /modules/s routed workspaces
1955a0f Download server.ts from sitcod3/BRIDGE_Modul-S_prod: direct link, hf CLI and curl.
- Browser
- Download file 44.1 kB
-
https://huggingface.co/spaces/sitcod3/BRIDGE_Modul-S_prod/resolve/main/server.ts
- Command line
-
hf download hf://spaces/sitcod3/BRIDGE_Modul-S_prod/server.ts
-
curl -L -o server.ts https://huggingface.co/spaces/sitcod3/BRIDGE_Modul-S_prod/resolve/main/server.ts
44.1 kB
| import express from "express"; | |
| import cors from "cors"; | |
| import morgan from "morgan"; | |
| import axios from "axios"; | |
| import path from "path"; | |
| import { getAiCompletionServer } from "./src/lib/aiServiceServer"; | |
| // ... | |
| // Initial realistic state for fallback / offline / testing mode | |
| let mockDb = { | |
| campaigns: [ | |
| { | |
| id: "camp_9901", | |
| name: "Enterprise SaaS Outbound Q3", | |
| status: "ACTIVE", | |
| created_at: "2026-07-15T10:00:00.000Z", | |
| lead_count: 1420, | |
| sent_count: 3840, | |
| open_count: 2150, | |
| reply_count: 168, | |
| bounce_count: 24, | |
| click_count: 430, | |
| timezone: "America/New_York", | |
| start_schedule: "09:00", | |
| end_schedule: "18:00", | |
| track_settings: "TRACK_EMAIL_OPEN_AND_CLICK", | |
| send_as_plain_text: false, | |
| follow_up_percentage: 85, | |
| unsubscribe_text: "Reply unsubscribe to stop receiving emails.", | |
| stop_lead_settings: "REPLIED", | |
| sequences: [ | |
| { | |
| id: "seq_1", | |
| seq_number: 1, | |
| delay_days: 0, | |
| subject: "Quick question regarding {{company_name}}'s outbound infrastructure", | |
| email_body: "<p>Hi {{first_name}},</p><p>Noticed you are scaling your growth team at {{company_name}}. Are you currently looking to optimize your cold outreach deliverability and warmup automation?</p><p>Best,<br>Alex</p>", | |
| variant_distribution_percentage: 50, | |
| variants: [ | |
| { | |
| id: "var_1a", | |
| variant_label: "Variant A", | |
| subject: "Quick question regarding {{company_name}}'s outbound infrastructure", | |
| email_body: "<p>Hi {{first_name}},</p><p>Noticed you are scaling your growth team at {{company_name}}. Are you currently looking to optimize your cold outreach deliverability and warmup automation?</p><p>Best,<br>Alex</p>" | |
| }, | |
| { | |
| id: "var_1b", | |
| variant_label: "Variant B (Shorter)", | |
| subject: "{{first_name}} - {{company_name}} outbound efficiency", | |
| email_body: "<p>Hey {{first_name}},</p><p>We helped similar SaaS leaders double their positive response rates without hitting spam filters. Open to a 3-min loom?</p><p>Alex</p>" | |
| } | |
| ] | |
| }, | |
| { | |
| id: "seq_2", | |
| seq_number: 2, | |
| delay_days: 3, | |
| subject: "Re: Quick question regarding {{company_name}}", | |
| email_body: "<p>Hi {{first_name}}, following up on my previous note. Did you have a chance to look at this?</p>", | |
| variant_distribution_percentage: 100, | |
| variants: [] | |
| } | |
| ], | |
| email_account_ids: ["acc_101", "acc_102"] | |
| }, | |
| { | |
| id: "camp_9902", | |
| name: "Fintech Growth & Compliance Sequence", | |
| status: "PAUSED", | |
| created_at: "2026-07-20T14:30:00.000Z", | |
| lead_count: 850, | |
| sent_count: 1200, | |
| open_count: 620, | |
| reply_count: 42, | |
| bounce_count: 11, | |
| click_count: 98, | |
| timezone: "Europe/London", | |
| start_schedule: "08:30", | |
| end_schedule: "17:30", | |
| track_settings: "TRACK_EMAIL_OPEN", | |
| send_as_plain_text: true, | |
| follow_up_percentage: 90, | |
| unsubscribe_text: "Click here to unsubscribe", | |
| stop_lead_settings: "REPLIED", | |
| sequences: [ | |
| { | |
| id: "seq_21", | |
| seq_number: 1, | |
| delay_days: 0, | |
| subject: "Compliance streamline for {{company_name}}", | |
| email_body: "<p>Hi {{first_name}}, wanted to share our automated compliance blueprint tailored for {{company_name}}.</p>", | |
| variant_distribution_percentage: 100, | |
| variants: [] | |
| } | |
| ], | |
| email_account_ids: ["acc_103"] | |
| }, | |
| { | |
| id: "camp_9903", | |
| name: "AI Tech Founders Pilot", | |
| status: "ACTIVE", | |
| created_at: "2026-08-01T09:15:00.000Z", | |
| lead_count: 2400, | |
| sent_count: 6100, | |
| open_count: 3890, | |
| reply_count: 310, | |
| bounce_count: 45, | |
| click_count: 820, | |
| timezone: "America/Los_Angeles", | |
| start_schedule: "09:00", | |
| end_schedule: "17:00", | |
| track_settings: "TRACK_EMAIL_OPEN_AND_CLICK", | |
| send_as_plain_text: false, | |
| follow_up_percentage: 100, | |
| unsubscribe_text: "Unsubscribe", | |
| stop_lead_settings: "REPLIED", | |
| sequences: [ | |
| { | |
| id: "seq_31", | |
| seq_number: 1, | |
| delay_days: 0, | |
| subject: "{{first_name}}, accelerating inference pipelines", | |
| email_body: "<p>Hi {{first_name}}, saw your launch on Product Hunt! Curious how {{company_name}} is handling model inference latency?</p>", | |
| variant_distribution_percentage: 100, | |
| variants: [] | |
| } | |
| ], | |
| email_account_ids: ["acc_101", "acc_102", "acc_104"] | |
| } | |
| ], | |
| leads: [ | |
| { | |
| id: "lead_1001", | |
| campaign_id: "camp_9901", | |
| first_name: "Sarah", | |
| last_name: "Jenkins", | |
| email: "sarah.jenkins@acmepartner.io", | |
| phone_number: "+1 415-555-0182", | |
| company_name: "Acme Partner Labs", | |
| website: "https://acmepartner.io", | |
| location: "San Francisco, CA", | |
| linkedin_profile: "https://linkedin.com/in/sarahjenkins-sample", | |
| status: "INTERESTED", | |
| open_count: 4, | |
| click_count: 2, | |
| created_at: "2026-07-16T11:20:00.000Z", | |
| custom_fields: { tech_stack: "Node/React", team_size: "45", funding_round: "Series B" } | |
| }, | |
| { | |
| id: "lead_1002", | |
| campaign_id: "camp_9901", | |
| first_name: "Marcus", | |
| last_name: "Vance", | |
| email: "m.vance@vanguardflow.co", | |
| phone_number: "+1 212-555-0199", | |
| company_name: "Vanguard Flow Systems", | |
| website: "https://vanguardflow.co", | |
| location: "New York, NY", | |
| linkedin_profile: "https://linkedin.com/in/marcusvance-sample", | |
| status: "MEETING_BOOKED", | |
| open_count: 6, | |
| click_count: 3, | |
| created_at: "2026-07-16T12:45:00.000Z", | |
| custom_fields: { tech_stack: "AWS/Python", team_size: "120", funding_round: "Series C" } | |
| }, | |
| { | |
| id: "lead_1003", | |
| campaign_id: "camp_9901", | |
| first_name: "Elena", | |
| last_name: "Rostova", | |
| email: "elena@cybercorebiotech.com", | |
| phone_number: "+44 20 7946 0912", | |
| company_name: "CyberCore Biotech", | |
| website: "https://cybercorebiotech.com", | |
| location: "London, UK", | |
| linkedin_profile: "https://linkedin.com/in/elenarostova", | |
| status: "REPLIED", | |
| open_count: 3, | |
| click_count: 1, | |
| created_at: "2026-07-17T09:10:00.000Z", | |
| custom_fields: { tech_stack: "NextJS", team_size: "80", funding_round: "Series A" } | |
| }, | |
| { | |
| id: "lead_1004", | |
| campaign_id: "camp_9902", | |
| first_name: "David", | |
| last_name: "Chen", | |
| email: "david.chen@horizonpay.net", | |
| phone_number: "+1 650-555-0144", | |
| company_name: "Horizon Pay", | |
| website: "https://horizonpay.net", | |
| location: "Palo Alto, CA", | |
| linkedin_profile: "https://linkedin.com/in/davidchen-sample", | |
| status: "NOT_INTERESTED", | |
| open_count: 1, | |
| click_count: 0, | |
| created_at: "2026-07-21T15:00:00.000Z", | |
| custom_fields: { tech_stack: "Go/Postgres", team_size: "200", funding_round: "Series D" } | |
| }, | |
| { | |
| id: "lead_1005", | |
| campaign_id: "camp_9903", | |
| first_name: "Amina", | |
| last_name: "Diallo", | |
| email: "amina@synapseai.tech", | |
| phone_number: "+1 312-555-0177", | |
| company_name: "Synapse AI Global", | |
| website: "https://synapseai.tech", | |
| location: "Chicago, IL", | |
| linkedin_profile: "https://linkedin.com/in/aminadiallo", | |
| status: "INTERESTED", | |
| open_count: 5, | |
| click_count: 4, | |
| created_at: "2026-08-02T10:14:00.000Z", | |
| custom_fields: { tech_stack: "PyTorch/Ray", team_size: "30", funding_round: "Seed" } | |
| } | |
| ], | |
| email_accounts: [ | |
| { | |
| id: "acc_101", | |
| from_name: "Alex Rivera", | |
| from_email: "alex@outbound-alchemist.io", | |
| provider: "GOOGLE", | |
| status: "CONNECTED", | |
| daily_limit: 50, | |
| sent_today: 34, | |
| warmup_enabled: true, | |
| warmup_reputation: "EXCELLENT", | |
| warmup_score: 98, | |
| warmup_daily_count: 25, | |
| ramp_day: 14, | |
| ramp_up_increment: 2, | |
| reply_rate_percentage: 45, | |
| campaigns_assigned: ["Enterprise SaaS Outbound Q3", "AI Tech Founders Pilot"], | |
| created_at: "2026-05-10T08:00:00.000Z" | |
| }, | |
| { | |
| id: "acc_102", | |
| from_name: "Alex Rivera (Secondary)", | |
| from_email: "alex.rivera@growthmatrix.cc", | |
| provider: "MICROSOFT", | |
| status: "CONNECTED", | |
| daily_limit: 45, | |
| sent_today: 28, | |
| warmup_enabled: true, | |
| warmup_reputation: "GOOD", | |
| warmup_score: 89, | |
| warmup_daily_count: 20, | |
| ramp_day: 9, | |
| ramp_up_increment: 3, | |
| reply_rate_percentage: 40, | |
| campaigns_assigned: ["Enterprise SaaS Outbound Q3", "AI Tech Founders Pilot"], | |
| created_at: "2026-06-01T08:00:00.000Z" | |
| }, | |
| { | |
| id: "acc_103", | |
| from_name: "Elena Rostova", | |
| from_email: "elena@fintechoutreach.com", | |
| provider: "SMTP", | |
| smtp_host: "mail.fintechoutreach.com", | |
| smtp_port: 587, | |
| status: "CONNECTED", | |
| daily_limit: 60, | |
| sent_today: 18, | |
| warmup_enabled: true, | |
| warmup_reputation: "EXCELLENT", | |
| warmup_score: 96, | |
| warmup_daily_count: 30, | |
| ramp_day: 22, | |
| ramp_up_increment: 2, | |
| reply_rate_percentage: 50, | |
| campaigns_assigned: ["Fintech Growth & Compliance Sequence"], | |
| created_at: "2026-06-15T08:00:00.000Z" | |
| }, | |
| { | |
| id: "acc_104", | |
| from_name: "Growth Ops Lead", | |
| from_email: "ops@aifoundryoutreach.io", | |
| provider: "GOOGLE", | |
| status: "RECONNECTING", | |
| daily_limit: 40, | |
| sent_today: 0, | |
| warmup_enabled: false, | |
| warmup_reputation: "POOR", | |
| warmup_score: 62, | |
| warmup_daily_count: 0, | |
| ramp_day: 1, | |
| ramp_up_increment: 1, | |
| reply_rate_percentage: 30, | |
| campaigns_assigned: ["AI Tech Founders Pilot"], | |
| created_at: "2026-07-28T08:00:00.000Z" | |
| } | |
| ], | |
| inbox: [ | |
| { | |
| id: "msg_501", | |
| campaign_id: "camp_9901", | |
| lead_id: "lead_1001", | |
| lead_name: "Sarah Jenkins", | |
| lead_email: "sarah.jenkins@acmepartner.io", | |
| company_name: "Acme Partner Labs", | |
| subject: "Re: Quick question regarding Acme Partner Labs's outbound infrastructure", | |
| category: "INTERESTED", | |
| unread: true, | |
| received_at: "2026-08-14T19:30:00.000Z", | |
| messages: [ | |
| { | |
| id: "m_sub_1", | |
| from: "alex@outbound-alchemist.io", | |
| to: "sarah.jenkins@acmepartner.io", | |
| direction: "OUTBOUND", | |
| timestamp: "2026-08-14T14:00:00.000Z", | |
| body_html: "<p>Hi Sarah, Noticed you are scaling your growth team at Acme Partner Labs. Are you currently looking to optimize your cold outreach deliverability and warmup automation?</p>" | |
| }, | |
| { | |
| id: "m_sub_2", | |
| from: "sarah.jenkins@acmepartner.io", | |
| to: "alex@outbound-alchemist.io", | |
| direction: "INBOUND", | |
| timestamp: "2026-08-14T19:30:00.000Z", | |
| body_html: "<p>Hi Alex,</p><p>Yes, perfect timing. We are actually evaluating mailbox warmup tools right now since our SDR team just doubled. Can you send over a brief demo or pricing overview?</p><p>Thanks,<br>Sarah Jenkins | Head of Growth</p>" | |
| } | |
| ] | |
| }, | |
| { | |
| id: "msg_502", | |
| campaign_id: "camp_9901", | |
| lead_id: "lead_1002", | |
| lead_name: "Marcus Vance", | |
| lead_email: "m.vance@vanguardflow.co", | |
| company_name: "Vanguard Flow Systems", | |
| subject: "Re: Quick question regarding Vanguard Flow Systems", | |
| category: "MEETING_BOOKED", | |
| unread: false, | |
| received_at: "2026-08-14T16:15:00.000Z", | |
| messages: [ | |
| { | |
| id: "m_sub_3", | |
| from: "alex@outbound-alchemist.io", | |
| to: "m.vance@vanguardflow.co", | |
| direction: "OUTBOUND", | |
| timestamp: "2026-08-13T10:00:00.000Z", | |
| body_html: "<p>Hey Marcus, We helped similar SaaS leaders double their positive response rates without hitting spam filters. Open to a 3-min loom?</p>" | |
| }, | |
| { | |
| id: "m_sub_4", | |
| from: "m.vance@vanguardflow.co", | |
| to: "alex@outbound-alchemist.io", | |
| direction: "INBOUND", | |
| timestamp: "2026-08-14T16:15:00.000Z", | |
| body_html: "<p>Alex, grabbed a slot on your calendar for Tuesday at 2 PM EST. Talk soon!</p>" | |
| } | |
| ] | |
| }, | |
| { | |
| id: "msg_503", | |
| campaign_id: "camp_9903", | |
| lead_id: "lead_1005", | |
| lead_name: "Amina Diallo", | |
| lead_email: "amina@synapseai.tech", | |
| company_name: "Synapse AI Global", | |
| subject: "Re: Amina, accelerating inference pipelines", | |
| category: "INTERESTED", | |
| unread: true, | |
| received_at: "2026-08-14T21:05:00.000Z", | |
| messages: [ | |
| { | |
| id: "m_sub_5", | |
| from: "ops@aifoundryoutreach.io", | |
| to: "amina@synapseai.tech", | |
| direction: "OUTBOUND", | |
| timestamp: "2026-08-14T15:00:00.000Z", | |
| body_html: "<p>Hi Amina, saw your launch on Product Hunt! Curious how Synapse AI Global is handling model inference latency?</p>" | |
| }, | |
| { | |
| id: "m_sub_6", | |
| from: "amina@synapseai.tech", | |
| to: "ops@aifoundryoutreach.io", | |
| direction: "INBOUND", | |
| timestamp: "2026-08-14T21:05:00.000Z", | |
| body_html: "<p>Thanks! We've built an in-house caching proxy, but always curious to learn about specialized acceleration clusters. Let's connect next week.</p>" | |
| } | |
| ] | |
| } | |
| ], | |
| webhooks: [ | |
| { | |
| id: "wh_801", | |
| name: "Slack Lead Alerts & Zapier Sync", | |
| webhook_url: "https://hooks.zapier.com/hooks/catch/912837/b8120a", | |
| event_types: ["REPLY", "LEAD_CATEGORY_UPDATED", "CAMPAIGN_COMPLETED"], | |
| campaign_id: null, | |
| status: "ACTIVE", | |
| last_triggered: "2026-08-14T21:05:12.000Z", | |
| created_at: "2026-06-20T10:00:00.000Z" | |
| }, | |
| { | |
| id: "wh_802", | |
| name: "HubSpot CRM Bi-directional Sync", | |
| webhook_url: "https://api.hubapi.com/webhooks/v3/custom-lead-sync", | |
| event_types: ["REPLY", "BOUNCE", "UNSUBSCRIBE", "LEAD_CATEGORY_UPDATED"], | |
| campaign_id: "camp_9901", | |
| status: "ACTIVE", | |
| last_triggered: "2026-08-14T19:30:22.000Z", | |
| created_at: "2026-07-01T12:00:00.000Z" | |
| } | |
| ], | |
| webhook_logs: [ | |
| { | |
| id: "whlog_1", | |
| webhook_id: "wh_801", | |
| event_type: "REPLY", | |
| status_code: 200, | |
| timestamp: "2026-08-14T21:05:12.000Z", | |
| payload_preview: { lead_email: "amina@synapseai.tech", category: "INTERESTED", sentiment: "positive" } | |
| }, | |
| { | |
| id: "whlog_2", | |
| webhook_id: "wh_802", | |
| event_type: "LEAD_CATEGORY_UPDATED", | |
| status_code: 200, | |
| timestamp: "2026-08-14T19:30:22.000Z", | |
| payload_preview: { lead_email: "sarah.jenkins@acmepartner.io", status: "INTERESTED" } | |
| } | |
| ], | |
| clients: [ | |
| { | |
| id: "cli_101", | |
| name: "Nexus AI Ventures", | |
| email: "growth@nexusventures.ai", | |
| company: "Nexus AI Ventures Inc.", | |
| campaigns_count: 2, | |
| leads_count: 3820, | |
| created_at: "2026-05-15T09:00:00.000Z" | |
| }, | |
| { | |
| id: "cli_102", | |
| name: "Apex Velocity B2B", | |
| email: "contact@apexvelocity.io", | |
| company: "Apex Velocity Marketing", | |
| campaigns_count: 1, | |
| leads_count: 1200, | |
| created_at: "2026-06-10T11:30:00.000Z" | |
| } | |
| ], | |
| block_list: { | |
| emails: [ | |
| { id: "bl_1", email: "competitor-bot@spamtrap.org", reason: "Spam trap detected", added_at: "2026-07-01T10:00:00.000Z" }, | |
| { id: "bl_2", email: "ceo@opt-out-forever.com", reason: "Direct legal opt-out", added_at: "2026-07-12T14:20:00.000Z" } | |
| ], | |
| domains: [ | |
| { id: "bld_1", domain: "mailinator.com", reason: "Disposable email service", added_at: "2026-06-10T09:00:00.000Z" }, | |
| { id: "bld_2", domain: "guerrillamail.com", reason: "Temporary inbox", added_at: "2026-06-11T11:00:00.000Z" }, | |
| { id: "bld_3", domain: "10minutemail.com", reason: "Temporary inbox", added_at: "2026-06-15T15:00:00.000Z" } | |
| ] | |
| }, | |
| billing: { | |
| credits: 0, | |
| auto_topup: { enabled: false, threshold: 500, amount: 10000 }, | |
| invoices: [] as any[], | |
| packages: [ | |
| { id: "pkg_2k", name: "2 000 cr", credits: 2000, price: 4.90, unit_price: 0.00245, features: ["Zero Expiry", "Verified results only consume credits"] }, | |
| { id: "pkg_10k", name: "10 000 cr", credits: 10000, price: 39.00, unit_price: 0.00390, features: ["Zero Expiry"] }, | |
| { id: "pkg_25k", name: "25 000 cr", credits: 25000, price: 59.00, unit_price: 0.00236, popular: true, features: ["Most Popular", "Zero Expiry"] }, | |
| { id: "pkg_50k", name: "50 000 cr", credits: 50000, price: 89.00, unit_price: 0.00178, popular: true, features: ["Most Popular", "Zero Expiry"] }, | |
| { id: "pkg_100k", name: "100 000 cr", credits: 100000, price: 149.00, unit_price: 0.00149, features: ["Zero Expiry"] }, | |
| { id: "pkg_500k", name: "500 000 cr", credits: 500000, price: 299.00, unit_price: 0.00060, features: ["Zero Expiry"] }, | |
| { id: "pkg_1m", name: "1 000 000 cr", credits: 1000000, price: 449.00, unit_price: 0.00045, features: ["Zero Expiry"] } | |
| ] | |
| } | |
| }; | |
| let stripeClient: any = null; | |
| function getStripe() { | |
| if (!stripeClient && process.env.STRIPE_SECRET_KEY) { | |
| import("stripe").then(({ default: Stripe }) => { | |
| stripeClient = new Stripe(process.env.STRIPE_SECRET_KEY as string); | |
| }); | |
| } | |
| return stripeClient; | |
| } | |
| async function startServer() { | |
| const app = express(); | |
| const PORT = Number(process.env.PORT) || 3000; | |
| app.use(cors({ | |
| origin: process.env.CORS_ORIGIN ? process.env.CORS_ORIGIN.split(',') : false, | |
| credentials: true | |
| })); | |
| app.use(morgan("dev")); | |
| app.use(express.json()); | |
| app.get(["/api/health", "/api/v1/health"], (req, res) => { | |
| res.json({ status: "ok", timestamp: new Date().toISOString(), uptime: process.uptime() }); | |
| }); | |
| app.get(["/api/ping", "/api/v1/ping"], (req, res) => { | |
| res.json({ pong: true, timestamp: new Date().toISOString() }); | |
| }); | |
| // Platform health probe (carried over from Module S prod) | |
| app.get("/api/health", (req, res) => { | |
| res.json({ status: "ok", platform: "BRIDGE Modules", modules: ["e", "s"], timestamp: new Date().toISOString() }); | |
| }); | |
| // Helper to check if real Smartlead API call should be attempted | |
| const shouldProxyToSmartlead = (apiKey: string | undefined) => { | |
| return Boolean(apiKey && apiKey.length > 10 && !apiKey.startsWith("demo_") && !apiKey.startsWith("mock_")); | |
| }; | |
| // ========================================== | |
| // MODULE 1: CAMPAIGNS API | |
| // ========================================== | |
| app.get("/api/campaigns", async (req, res) => { | |
| const apiKey = req.headers["x-api-key"] as string; | |
| if (shouldProxyToSmartlead(apiKey)) { | |
| try { | |
| const response = await axios.get("https://server.smartlead.ai/api/v1/campaigns", { | |
| params: { ...req.query, api_key: apiKey }, | |
| timeout: 8000 | |
| }); | |
| return res.json(response.data); | |
| } catch (err: any) { | |
| console.warn("Smartlead API live proxy failed, falling back to database mode:", err.message); | |
| } | |
| } | |
| return res.json(mockDb.campaigns); | |
| }); | |
| app.post("/api/campaigns", async (req, res) => { | |
| const apiKey = req.headers["x-api-key"] as string; | |
| if (shouldProxyToSmartlead(apiKey)) { | |
| try { | |
| const response = await axios.post("https://server.smartlead.ai/api/v1/campaigns", req.body, { | |
| params: { api_key: apiKey }, | |
| timeout: 8000 | |
| }); | |
| return res.status(201).json(response.data); | |
| } catch (err: any) { | |
| console.warn("Live create campaign failed, recording in local database:", err.message); | |
| } | |
| } | |
| const newCampaign = { | |
| id: "camp_" + Math.random().toString(36).substring(2, 9), | |
| name: req.body.name || "Untitled Campaign", | |
| status: "PAUSED", | |
| created_at: new Date().toISOString(), | |
| lead_count: 0, | |
| sent_count: 0, | |
| open_count: 0, | |
| reply_count: 0, | |
| bounce_count: 0, | |
| click_count: 0, | |
| timezone: req.body.timezone || "America/New_York", | |
| start_schedule: req.body.start_schedule || "09:00", | |
| end_schedule: req.body.end_schedule || "17:00", | |
| track_settings: req.body.track_settings || "TRACK_EMAIL_OPEN", | |
| send_as_plain_text: Boolean(req.body.send_as_plain_text), | |
| follow_up_percentage: req.body.follow_up_percentage || 80, | |
| unsubscribe_text: req.body.unsubscribe_text || "Unsubscribe", | |
| stop_lead_settings: req.body.stop_lead_settings || "REPLIED", | |
| sequences: [ | |
| { | |
| id: "seq_" + Math.random().toString(36).substring(2, 7), | |
| seq_number: 1, | |
| delay_days: 0, | |
| subject: req.body.initial_subject || "Connecting with {{first_name}}", | |
| email_body: req.body.initial_body || "<p>Hi {{first_name}}, let's connect!</p>", | |
| variant_distribution_percentage: 100, | |
| variants: [] | |
| } | |
| ], | |
| email_account_ids: req.body.email_account_ids || [] | |
| }; | |
| mockDb.campaigns.unshift(newCampaign); | |
| return res.status(201).json(newCampaign); | |
| }); | |
| app.get("/api/campaigns/:id", (req, res) => { | |
| const campaign = mockDb.campaigns.find(c => c.id === req.params.id); | |
| if (!campaign) return res.status(404).json({ error: "Campaign not found" }); | |
| return res.json(campaign); | |
| }); | |
| app.post("/api/campaigns/:id", (req, res) => { | |
| const index = mockDb.campaigns.findIndex(c => c.id === req.params.id); | |
| if (index === -1) return res.status(404).json({ error: "Campaign not found" }); | |
| mockDb.campaigns[index] = { ...mockDb.campaigns[index], ...req.body }; | |
| return res.json(mockDb.campaigns[index]); | |
| }); | |
| app.delete("/api/campaigns/:id", (req, res) => { | |
| mockDb.campaigns = mockDb.campaigns.filter(c => c.id !== req.params.id); | |
| return res.json({ success: true, message: "Campaign deleted successfully" }); | |
| }); | |
| app.post("/api/campaigns/:id/status", (req, res) => { | |
| const { status } = req.body; // START / PAUSE / STOP | |
| const campaign = mockDb.campaigns.find(c => c.id === req.params.id); | |
| if (!campaign) return res.status(404).json({ error: "Campaign not found" }); | |
| campaign.status = status === "START" ? "ACTIVE" : status === "PAUSE" ? "PAUSED" : "STOPPED"; | |
| return res.json({ success: true, status: campaign.status, campaign }); | |
| }); | |
| app.post("/api/campaigns/:id/schedule", (req, res) => { | |
| const campaign = mockDb.campaigns.find(c => c.id === req.params.id); | |
| if (!campaign) return res.status(404).json({ error: "Campaign not found" }); | |
| campaign.start_schedule = req.body.start_schedule || campaign.start_schedule; | |
| campaign.end_schedule = req.body.end_schedule || campaign.end_schedule; | |
| campaign.timezone = req.body.timezone || campaign.timezone; | |
| return res.json({ success: true, campaign }); | |
| }); | |
| app.get("/api/campaigns/:id/sequences", (req, res) => { | |
| const campaign = mockDb.campaigns.find(c => c.id === req.params.id); | |
| if (!campaign) return res.status(404).json({ error: "Campaign not found" }); | |
| return res.json(campaign.sequences || []); | |
| }); | |
| app.post("/api/campaigns/:id/sequences", (req, res) => { | |
| const campaign = mockDb.campaigns.find(c => c.id === req.params.id); | |
| if (!campaign) return res.status(404).json({ error: "Campaign not found" }); | |
| campaign.sequences = req.body.sequences || []; | |
| return res.json({ success: true, sequences: campaign.sequences }); | |
| }); | |
| app.get("/api/campaigns/:id/email-accounts", (req, res) => { | |
| const campaign = mockDb.campaigns.find(c => c.id === req.params.id); | |
| if (!campaign) return res.status(404).json({ error: "Campaign not found" }); | |
| const accounts = mockDb.email_accounts.filter(a => campaign.email_account_ids?.includes(a.id)); | |
| return res.json(accounts); | |
| }); | |
| app.post("/api/campaigns/:id/email-accounts", (req, res) => { | |
| const campaign = mockDb.campaigns.find(c => c.id === req.params.id); | |
| if (!campaign) return res.status(404).json({ error: "Campaign not found" }); | |
| const { email_account_id } = req.body; | |
| if (!campaign.email_account_ids) campaign.email_account_ids = []; | |
| if (!campaign.email_account_ids.includes(email_account_id)) { | |
| campaign.email_account_ids.push(email_account_id); | |
| } | |
| return res.json({ success: true, email_account_ids: campaign.email_account_ids }); | |
| }); | |
| app.delete("/api/campaigns/:id/email-accounts/:eid", (req, res) => { | |
| const campaign = mockDb.campaigns.find(c => c.id === req.params.id); | |
| if (!campaign) return res.status(404).json({ error: "Campaign not found" }); | |
| campaign.email_account_ids = (campaign.email_account_ids || []).filter(id => id !== req.params.eid); | |
| return res.json({ success: true, email_account_ids: campaign.email_account_ids }); | |
| }); | |
| app.get("/api/campaigns/:id/analytics", (req, res) => { | |
| const campaign = mockDb.campaigns.find(c => c.id === req.params.id); | |
| if (!campaign) return res.status(404).json({ error: "Campaign not found" }); | |
| return res.json({ | |
| sent: campaign.sent_count, | |
| opens: campaign.open_count, | |
| replies: campaign.reply_count, | |
| bounces: campaign.bounce_count, | |
| clicks: campaign.click_count, | |
| open_rate: ((campaign.open_count / (campaign.sent_count || 1)) * 100).toFixed(1), | |
| reply_rate: ((campaign.reply_count / (campaign.sent_count || 1)) * 100).toFixed(1), | |
| bounce_rate: ((campaign.bounce_count / (campaign.sent_count || 1)) * 100).toFixed(1) | |
| }); | |
| }); | |
| app.get("/api/campaigns/:id/analytics-by-date", (req, res) => { | |
| const days = 14; | |
| const history = []; | |
| const now = new Date(); | |
| for (let i = days; i >= 0; i--) { | |
| const d = new Date(now.getTime() - i * 24 * 60 * 60 * 1000); | |
| const dayLabel = d.toLocaleDateString("en-US", { month: "short", day: "numeric" }); | |
| history.push({ | |
| date: dayLabel, | |
| sent: Math.floor(120 + Math.random() * 80), | |
| opens: Math.floor(65 + Math.random() * 40), | |
| clicks: Math.floor(18 + Math.random() * 15), | |
| replies: Math.floor(8 + Math.random() * 10), | |
| bounces: Math.floor(1 + Math.random() * 3) | |
| }); | |
| } | |
| return res.json(history); | |
| }); | |
| app.get("/api/campaigns/:id/leads-export", (req, res) => { | |
| const campaignLeads = mockDb.leads.filter(l => l.campaign_id === req.params.id); | |
| let csv = "first_name,last_name,email,company_name,status,open_count,reply_count\n"; | |
| campaignLeads.forEach(l => { | |
| csv += `"${l.first_name}","${l.last_name}","${l.email}","${l.company_name}","${l.status}",${l.open_count},${l.click_count}\n`; | |
| }); | |
| res.setHeader("Content-Type", "text/csv"); | |
| res.setHeader("Content-Disposition", `attachment; filename=leads-${req.params.id}.csv`); | |
| return res.send(csv); | |
| }); | |
| // ========================================== | |
| // MODULE 2: LEADS API | |
| // ========================================== | |
| app.get("/api/campaigns/:id/leads", (req, res) => { | |
| const leads = mockDb.leads.filter(l => l.campaign_id === req.params.id); | |
| return res.json(leads); | |
| }); | |
| app.post("/api/campaigns/:id/leads", (req, res) => { | |
| const leadsToAdd = Array.isArray(req.body) ? req.body : req.body.leads || [req.body]; | |
| const created = leadsToAdd.map((item: any) => { | |
| const newLead = { | |
| id: "lead_" + Math.random().toString(36).substring(2, 9), | |
| campaign_id: req.params.id, | |
| first_name: item.first_name || "", | |
| last_name: item.last_name || "", | |
| email: item.email || "lead@sample.io", | |
| phone_number: item.phone_number || "", | |
| company_name: item.company_name || "", | |
| website: item.website || "", | |
| location: item.location || "", | |
| linkedin_profile: item.linkedin_profile || "", | |
| status: item.status || "INTERESTED", | |
| open_count: 0, | |
| click_count: 0, | |
| created_at: new Date().toISOString(), | |
| custom_fields: item.custom_fields || {} | |
| }; | |
| mockDb.leads.unshift(newLead); | |
| return newLead; | |
| }); | |
| const campaign = mockDb.campaigns.find(c => c.id === req.params.id); | |
| if (campaign) campaign.lead_count += created.length; | |
| return res.status(201).json({ success: true, count: created.length, leads: created }); | |
| }); | |
| app.get("/api/leads/search", (req, res) => { | |
| const query = (req.query.q as string || "").toLowerCase(); | |
| const status = req.query.status as string; | |
| let results = mockDb.leads; | |
| if (query) { | |
| results = results.filter(l => | |
| l.first_name.toLowerCase().includes(query) || | |
| l.last_name.toLowerCase().includes(query) || | |
| l.email.toLowerCase().includes(query) || | |
| l.company_name.toLowerCase().includes(query) | |
| ); | |
| } | |
| if (status && status !== "ALL") { | |
| results = results.filter(l => l.status === status); | |
| } | |
| return res.json(results); | |
| }); | |
| app.post("/api/campaigns/:id/leads/status", (req, res) => { | |
| const { lead_id, status } = req.body; | |
| const lead = mockDb.leads.find(l => l.id === lead_id); | |
| if (!lead) return res.status(404).json({ error: "Lead not found" }); | |
| lead.status = status; | |
| return res.json({ success: true, lead }); | |
| }); | |
| app.delete("/api/campaigns/:id/leads/:lead_id", (req, res) => { | |
| mockDb.leads = mockDb.leads.filter(l => l.id !== req.params.lead_id); | |
| const campaign = mockDb.campaigns.find(c => c.id === req.params.id); | |
| if (campaign && campaign.lead_count > 0) campaign.lead_count--; | |
| return res.json({ success: true }); | |
| }); | |
| // Blocklist routes | |
| app.get("/api/leads/block-list", (req, res) => res.json(mockDb.block_list.emails)); | |
| app.post("/api/leads/block-list", (req, res) => { | |
| const item = { id: "bl_" + Date.now(), email: req.body.email, reason: req.body.reason || "Manual add", added_at: new Date().toISOString() }; | |
| mockDb.block_list.emails.unshift(item); | |
| res.status(201).json(item); | |
| }); | |
| app.delete("/api/leads/block-list/:id", (req, res) => { | |
| mockDb.block_list.emails = mockDb.block_list.emails.filter(e => e.id !== req.params.id && e.email !== req.params.id); | |
| res.json({ success: true }); | |
| }); | |
| app.get("/api/leads/domain-block-list", (req, res) => res.json(mockDb.block_list.domains)); | |
| app.post("/api/leads/domain-block-list", (req, res) => { | |
| const item = { id: "bld_" + Date.now(), domain: req.body.domain, reason: req.body.reason || "Manual add", added_at: new Date().toISOString() }; | |
| mockDb.block_list.domains.unshift(item); | |
| res.status(201).json(item); | |
| }); | |
| app.delete("/api/leads/domain-block-list/:id", (req, res) => { | |
| mockDb.block_list.domains = mockDb.block_list.domains.filter(d => d.id !== req.params.id && d.domain !== req.params.id); | |
| res.json({ success: true }); | |
| }); | |
| // ========================================== | |
| // MODULE 3: EMAIL ACCOUNTS API | |
| // ========================================== | |
| app.get("/api/email-accounts", (req, res) => res.json(mockDb.email_accounts)); | |
| app.post("/api/email-accounts", (req, res) => { | |
| const newAcc = { | |
| id: "acc_" + Math.random().toString(36).substring(2, 8), | |
| from_name: req.body.from_name || "Outbound Rep", | |
| from_email: req.body.from_email || "rep@sampledomain.com", | |
| provider: req.body.provider || "SMTP", | |
| smtp_host: req.body.smtp_host, | |
| smtp_port: req.body.smtp_port, | |
| status: "CONNECTED", | |
| daily_limit: req.body.max_email_per_day || 50, | |
| sent_today: 0, | |
| warmup_enabled: true, | |
| warmup_reputation: "GOOD", | |
| warmup_score: 85, | |
| warmup_daily_count: 10, | |
| ramp_day: 1, | |
| ramp_up_increment: 2, | |
| reply_rate_percentage: 40, | |
| campaigns_assigned: [], | |
| created_at: new Date().toISOString() | |
| }; | |
| mockDb.email_accounts.push(newAcc); | |
| res.status(201).json(newAcc); | |
| }); | |
| app.post("/api/email-accounts/reconnect", (req, res) => { | |
| const { account_id } = req.body; | |
| const acc = mockDb.email_accounts.find(a => a.id === account_id); | |
| if (acc) acc.status = "CONNECTED"; | |
| res.json({ success: true, message: "Account reconnected successfully", account: acc }); | |
| }); | |
| app.delete("/api/email-accounts/:id", (req, res) => { | |
| mockDb.email_accounts = mockDb.email_accounts.filter(a => a.id !== req.params.id); | |
| res.json({ success: true }); | |
| }); | |
| // ========================================== | |
| // MODULE 4: EMAIL WARMUP API | |
| // ========================================== | |
| app.post("/api/email-accounts/:id/warmup/enable", (req, res) => { | |
| const acc = mockDb.email_accounts.find(a => a.id === req.params.id); | |
| if (!acc) return res.status(404).json({ error: "Account not found" }); | |
| acc.warmup_enabled = true; | |
| res.json({ success: true, account: acc }); | |
| }); | |
| app.post("/api/email-accounts/:id/warmup/disable", (req, res) => { | |
| const acc = mockDb.email_accounts.find(a => a.id === req.params.id); | |
| if (!acc) return res.status(404).json({ error: "Account not found" }); | |
| acc.warmup_enabled = false; | |
| res.json({ success: true, account: acc }); | |
| }); | |
| app.get("/api/email-accounts/:id/warmup/stats", (req, res) => { | |
| const acc = mockDb.email_accounts.find(a => a.id === req.params.id); | |
| if (!acc) return res.status(404).json({ error: "Account not found" }); | |
| res.json({ | |
| score: acc.warmup_score, | |
| reputation: acc.warmup_reputation, | |
| sent_warmup: acc.warmup_daily_count, | |
| received_warmup: Math.floor(acc.warmup_daily_count * 0.45), | |
| spam_rescued: 2, | |
| ramp_day: acc.ramp_day, | |
| history: [ | |
| { day: "Day 1", score: 70 }, | |
| { day: "Day 3", score: 78 }, | |
| { day: "Day 7", score: 85 }, | |
| { day: "Day 10", score: 92 }, | |
| { day: "Day 14", score: acc.warmup_score } | |
| ] | |
| }); | |
| }); | |
| app.post("/api/email-accounts/:id/warmup/settings", (req, res) => { | |
| const acc = mockDb.email_accounts.find(a => a.id === req.params.id); | |
| if (!acc) return res.status(404).json({ error: "Account not found" }); | |
| acc.ramp_up_increment = req.body.ramp_up_increment || acc.ramp_up_increment; | |
| acc.reply_rate_percentage = req.body.reply_rate_percentage || acc.reply_rate_percentage; | |
| res.json({ success: true, account: acc }); | |
| }); | |
| // ========================================== | |
| // MODULE 5: UNIFIED INBOX API | |
| // ========================================== | |
| app.get("/api/inbox", (req, res) => { | |
| const { category, campaign_id } = req.query; | |
| let threads = mockDb.inbox; | |
| if (category && category !== "ALL") { | |
| threads = threads.filter(t => t.category === category); | |
| } | |
| if (campaign_id) { | |
| threads = threads.filter(t => t.campaign_id === campaign_id); | |
| } | |
| res.json(threads); | |
| }); | |
| app.get("/api/inbox/unread-count", (req, res) => { | |
| const unread = mockDb.inbox.filter(t => t.unread).length; | |
| res.json({ count: unread }); | |
| }); | |
| app.post("/api/campaigns/:id/reply-email-thread", (req, res) => { | |
| const { thread_id, reply_body } = req.body; | |
| const thread = mockDb.inbox.find(t => t.id === thread_id || t.campaign_id === req.params.id); | |
| if (thread) { | |
| thread.unread = false; | |
| thread.messages.push({ | |
| id: "m_rep_" + Date.now(), | |
| from: "alex@outbound-alchemist.io", | |
| to: thread.lead_email, | |
| direction: "OUTBOUND", | |
| timestamp: new Date().toISOString(), | |
| body_html: `<p>${reply_body}</p>` | |
| }); | |
| return res.json({ success: true, message: "Reply sent successfully", thread }); | |
| } | |
| res.json({ success: true, message: "Reply delivered" }); | |
| }); | |
| app.post("/api/inbox/categorize", (req, res) => { | |
| const { thread_id, category } = req.body; | |
| const thread = mockDb.inbox.find(t => t.id === thread_id); | |
| if (thread) { | |
| thread.category = category; | |
| const lead = mockDb.leads.find(l => l.email === thread.lead_email); | |
| if (lead) lead.status = category; | |
| } | |
| res.json({ success: true, category }); | |
| }); | |
| // ========================================== | |
| // MODULE 6: WEBHOOKS API | |
| // ========================================== | |
| app.get("/api/webhooks", (req, res) => res.json(mockDb.webhooks)); | |
| app.post("/api/webhooks", (req, res) => { | |
| const newWh = { | |
| id: "wh_" + Math.random().toString(36).substring(2, 8), | |
| name: req.body.name || "Custom Webhook Endpoint", | |
| webhook_url: req.body.webhook_url || "https://api.domain.com/webhook", | |
| event_types: req.body.event_types || ["REPLY"], | |
| campaign_id: req.body.campaign_id || null, | |
| status: "ACTIVE", | |
| last_triggered: null, | |
| created_at: new Date().toISOString() | |
| }; | |
| mockDb.webhooks.unshift(newWh); | |
| res.status(201).json(newWh); | |
| }); | |
| app.delete("/api/webhooks/:id", (req, res) => { | |
| mockDb.webhooks = mockDb.webhooks.filter(w => w.id !== req.params.id); | |
| res.json({ success: true }); | |
| }); | |
| app.post("/api/webhooks/:id/test", (req, res) => { | |
| const wh = mockDb.webhooks.find(w => w.id === req.params.id); | |
| if (!wh) return res.status(404).json({ error: "Webhook not found" }); | |
| const logItem = { | |
| id: "whlog_" + Date.now(), | |
| webhook_id: wh.id, | |
| event_type: wh.event_types[0] || "REPLY", | |
| status_code: 200, | |
| timestamp: new Date().toISOString(), | |
| payload_preview: { lead_email: "test.lead@domain.com", category: "INTERESTED", sentiment: "positive" } | |
| } as const; | |
| mockDb.webhook_logs.unshift(logItem); | |
| wh.last_triggered = new Date().toISOString(); | |
| res.json({ success: true, message: "Test payload sent to endpoint", log: logItem }); | |
| }); | |
| app.get("/api/webhooks/logs", (req, res) => res.json(mockDb.webhook_logs)); | |
| // ========================================== | |
| // MODULE 7: ANALYTICS API | |
| // ========================================== | |
| app.get("/api/analytics/campaigns", (req, res) => { | |
| const totalSent = mockDb.campaigns.reduce((acc, c) => acc + c.sent_count, 0); | |
| const totalOpens = mockDb.campaigns.reduce((acc, c) => acc + c.open_count, 0); | |
| const totalReplies = mockDb.campaigns.reduce((acc, c) => acc + c.reply_count, 0); | |
| const totalBounces = mockDb.campaigns.reduce((acc, c) => acc + c.bounce_count, 0); | |
| const totalClicks = mockDb.campaigns.reduce((acc, c) => acc + c.click_count, 0); | |
| const totalLeads = mockDb.campaigns.reduce((acc, c) => acc + c.lead_count, 0); | |
| res.json({ | |
| total_campaigns: mockDb.campaigns.length, | |
| total_leads: totalLeads, | |
| total_sent: totalSent, | |
| total_opens: totalOpens, | |
| total_replies: totalReplies, | |
| total_bounces: totalBounces, | |
| total_clicks: totalClicks, | |
| open_rate: ((totalOpens / (totalSent || 1)) * 100).toFixed(1), | |
| reply_rate: ((totalReplies / (totalSent || 1)) * 100).toFixed(1), | |
| click_rate: ((totalClicks / (totalSent || 1)) * 100).toFixed(1), | |
| bounce_rate: ((totalBounces / (totalSent || 1)) * 100).toFixed(1) | |
| }); | |
| }); | |
| // ========================================== | |
| // MODULE 8: CLIENT MANAGEMENT API | |
| // ========================================== | |
| app.get("/api/clients", (req, res) => res.json(mockDb.clients)); | |
| // ========================================== | |
| // MODULE 9: BILLING & STRIPE API | |
| // ========================================== | |
| app.get("/api/billing", (req, res) => res.json(mockDb.billing)); | |
| app.post("/api/billing/purchase", (req, res) => { | |
| const { package_id } = req.body; | |
| const pkg = mockDb.billing.packages.find(p => p.id === package_id); | |
| if (!pkg) return res.status(404).json({ error: "Package not found" }); | |
| // In a real app, this would be handled via Stripe Webhook after payment | |
| // For this environment, we'll simulate the credit addition | |
| mockDb.billing.credits += pkg.credits; | |
| const invoice = { | |
| id: "inv_" + Math.random().toString(36).substring(2, 9), | |
| description: `${pkg.name} Package Purchase`, | |
| credits: pkg.credits, | |
| amount: pkg.price, | |
| status: "PAID", | |
| created_at: new Date().toISOString() | |
| }; | |
| mockDb.billing.invoices.unshift(invoice); | |
| res.json({ success: true, message: `Successfully added ${pkg.credits} credits.`, credits: mockDb.billing.credits }); | |
| }); | |
| app.post("/api/billing/autotopup", (req, res) => { | |
| mockDb.billing.auto_topup = { ...mockDb.billing.auto_topup, ...req.body }; | |
| res.json({ success: true, auto_topup: mockDb.billing.auto_topup }); | |
| }); | |
| app.post("/api/billing/create-checkout-session", async (req, res) => { | |
| const { package_id } = req.body; | |
| const pkg = mockDb.billing.packages.find(p => p.id === package_id); | |
| if (!pkg) return res.status(404).json({ error: "Package not found" }); | |
| const stripe = getStripe(); | |
| if (!stripe) { | |
| // Fallback for demo mode if no key is set | |
| return res.json({ | |
| url: `${req.headers.origin}/pricing?success=true&package_id=${package_id}`, | |
| mode: "demo" | |
| }); | |
| } | |
| try { | |
| const session = await stripe.checkout.sessions.create({ | |
| payment_method_types: ['card'], | |
| line_items: [{ | |
| price_data: { | |
| currency: 'usd', | |
| product_data: { | |
| name: `BRIDGEModulE - ${pkg.name}`, | |
| description: `${pkg.credits.toLocaleString()} credits for email verification.`, | |
| }, | |
| unit_amount: Math.round(pkg.price * 100), | |
| }, | |
| quantity: 1, | |
| }], | |
| mode: 'payment', | |
| success_url: `${req.headers.origin}/pricing?success=true&session_id={CHECKOUT_SESSION_ID}`, | |
| cancel_url: `${req.headers.origin}/pricing?canceled=true`, | |
| }); | |
| res.json({ url: session.url }); | |
| } catch (error: any) { | |
| res.status(500).json({ error: error.message }); | |
| } | |
| }); | |
| app.post("/api/ai/completion", async (req, res) => { | |
| try { | |
| const { prompt } = req.body; | |
| if (!prompt) { | |
| return res.status(400).json({ error: "Prompt is required" }); | |
| } | |
| const response = await getAiCompletionServer(prompt); | |
| res.json({ response }); | |
| } catch (error) { | |
| res.status(500).json({ error: "Failed to get AI completion" }); | |
| } | |
| }); | |
| app.post("/api/clients", (req, res) => { | |
| const newClient = { | |
| id: "cli_" + Math.random().toString(36).substring(2, 7), | |
| name: req.body.name || "New Client", | |
| email: req.body.email || "client@company.com", | |
| company: req.body.company || "Client Company Ltd", | |
| campaigns_count: 0, | |
| leads_count: 0, | |
| created_at: new Date().toISOString() | |
| }; | |
| mockDb.clients.push(newClient); | |
| res.status(201).json(newClient); | |
| }); | |
| app.delete("/api/clients/:id", (req, res) => { | |
| mockDb.clients = mockDb.clients.filter(c => c.id !== req.params.id); | |
| res.json({ success: true }); | |
| }); | |
| // Proxy Catch-All for any other direct Smartlead API calls | |
| app.use("/api", async (req, res) => { | |
| const apiKey = req.headers["x-api-key"] as string; | |
| if (!apiKey) { | |
| return res.status(401).json({ error: "Missing x-api-key header" }); | |
| } | |
| try { | |
| const targetUrl = `https://server.smartlead.ai/api/v1${req.path}`; | |
| const response = await axios({ | |
| method: req.method, | |
| url: targetUrl, | |
| data: Object.keys(req.body).length > 0 ? req.body : undefined, | |
| params: { ...req.query, api_key: apiKey }, | |
| timeout: 10000 | |
| }); | |
| res.status(response.status).json(response.data); | |
| } catch (error: any) { | |
| if (error.response) { | |
| res.status(error.response.status).json(error.response.data); | |
| } else { | |
| res.status(500).json({ error: "Proxy connection error", details: error.message }); | |
| } | |
| } | |
| }); | |
| // Vite development middleware or production static serving | |
| if (process.env.NODE_ENV !== "production") { | |
| const { createServer: createViteServer } = await import("vite"); | |
| const vite = await createViteServer({ | |
| server: { middlewareMode: true }, | |
| appType: "spa", | |
| }); | |
| app.use(vite.middlewares); | |
| } else { | |
| const distPath = path.join(process.cwd(), "dist"); | |
| app.use(express.static(distPath)); | |
| app.get("*", (req, res) => { | |
| res.sendFile(path.join(distPath, "index.html")); | |
| }); | |
| } | |
| app.listen(PORT, "0.0.0.0", () => { | |
| console.log(`Smartlead Command Hub Server listening on http://0.0.0.0:${PORT}`); | |
| }); | |
| } | |
| startServer(); | |