import { sqliteTable, text, integer, index } from "drizzle-orm/sqlite-core"; import { subscriptions } from "./subscription"; export const infoBrokers = sqliteTable("info_brokers", { id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()), name: text("name").notNull(), domain: text("domain").notNull().unique(), category: text("category").notNull(), removalMethod: text("removal_method").notNull(), removalUrl: text("removal_url"), requiresAccount: integer("requires_account", { mode: "boolean" }).default(false).notNull(), requiresVerification: integer("requires_verification", { mode: "boolean" }).default(false).notNull(), estimatedDays: integer("estimated_days").default(14).notNull(), isActive: integer("is_active", { mode: "boolean" }).default(true).notNull(), createdAt: integer("created_at", { mode: "timestamp_ms" }).defaultNow().notNull(), updatedAt: integer("updated_at", { mode: "timestamp_ms" }).defaultNow().notNull().$onUpdate(() => new Date()), }, (table) => ({ categoryIdx: index("info_brokers_category_idx").on(table.category), isActiveIdx: index("info_brokers_is_active_idx").on(table.isActive), removalMethodIdx: index("info_brokers_removal_method_idx").on(table.removalMethod), })); export const removalRequests = sqliteTable("removal_requests", { id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()), subscriptionId: text("subscription_id").notNull().references(() => subscriptions.id, { onDelete: "cascade" }), brokerId: text("broker_id").notNull().references(() => infoBrokers.id), status: text("status").default("PENDING").notNull(), personalInfo: text("personal_info", { mode: "json" }).notNull(), method: text("method").notNull(), attempts: integer("attempts").default(0).notNull(), nextRetryAt: integer("next_retry_at", { mode: "timestamp_ms" }), submittedAt: integer("submitted_at", { mode: "timestamp_ms" }), completedAt: integer("completed_at", { mode: "timestamp_ms" }), error: text("error"), notes: text("notes"), metadata: text("metadata", { mode: "json" }), createdAt: integer("created_at", { mode: "timestamp_ms" }).defaultNow().notNull(), updatedAt: integer("updated_at", { mode: "timestamp_ms" }).defaultNow().notNull().$onUpdate(() => new Date()), }, (table) => ({ subscriptionIdIdx: index("removal_requests_subscription_id_idx").on(table.subscriptionId), brokerIdIdx: index("removal_requests_broker_id_idx").on(table.brokerId), statusIdx: index("removal_requests_status_idx").on(table.status), submittedAtIdx: index("removal_requests_submitted_at_idx").on(table.submittedAt), subscriptionIdStatusIdx: index("removal_requests_sub_id_status_idx").on(table.subscriptionId, table.status), })); export const captchaEvents = sqliteTable("captcha_events", { id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()), removalRequestId: text("removal_request_id").references(() => removalRequests.id, { onDelete: "set null" }), brokerId: text("broker_id").notNull().references(() => infoBrokers.id, { onDelete: "cascade" }), captchaType: text("captcha_type").notNull(), // recaptcha_v2, recaptcha_v3, hcaptcha, image_challenge, turnstile status: text("status").notNull().default("detected"), // detected, solving, solved, failed, escalated solverProvider: text("solver_provider"), // 2captcha, anticaptcha, manual solveAttempts: integer("solve_attempts").default(0).notNull(), costInCents: integer("cost_in_cents"), error: text("error"), solvedAt: integer("solved_at", { mode: "timestamp_ms" }), createdAt: integer("created_at", { mode: "timestamp_ms" }).defaultNow().notNull(), updatedAt: integer("updated_at", { mode: "timestamp_ms" }).defaultNow().notNull().$onUpdate(() => new Date()), }, (table) => ({ removalRequestIdIdx: index("captcha_events_removal_request_id_idx").on(table.removalRequestId), brokerIdIdx: index("captcha_events_broker_id_idx").on(table.brokerId), statusIdx: index("captcha_events_status_idx").on(table.status), createdAtIdx: index("captcha_events_created_at_idx").on(table.createdAt), })); export const emailVerifications = sqliteTable("email_verifications", { id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()), removalRequestId: text("removal_request_id").references(() => removalRequests.id, { onDelete: "set null" }), brokerId: text("broker_id").notNull().references(() => infoBrokers.id, { onDelete: "cascade" }), emailTo: text("email_to").notNull(), emailFrom: text("email_from"), emailSubject: text("email_subject"), confirmationUrl: text("confirmation_url"), status: text("status").notNull().default("pending"), // pending, confirmed, expired, failed clickedAt: integer("clicked_at", { mode: "timestamp_ms" }), confirmedAt: integer("confirmed_at", { mode: "timestamp_ms" }), expiresAt: integer("expires_at", { mode: "timestamp_ms" }), createdAt: integer("created_at", { mode: "timestamp_ms" }).defaultNow().notNull(), updatedAt: integer("updated_at", { mode: "timestamp_ms" }).defaultNow().notNull().$onUpdate(() => new Date()), }, (table) => ({ removalRequestIdIdx: index("email_verifications_removal_request_id_idx").on(table.removalRequestId), brokerIdIdx: index("email_verifications_broker_id_idx").on(table.brokerId), statusIdx: index("email_verifications_status_idx").on(table.status), createdAtIdx: index("email_verifications_created_at_idx").on(table.createdAt), emailToIdx: index("email_verifications_email_to_idx").on(table.emailTo), })); export const reScanResults = sqliteTable("re_scan_results", { id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()), subscriptionId: text("subscription_id").notNull().references(() => subscriptions.id, { onDelete: "cascade" }), brokerId: text("broker_id").notNull(), removalRequestId: text("removal_request_id").references(() => removalRequests.id, { onDelete: "set null" }), wasRemoved: integer("was_removed", { mode: "boolean" }).notNull(), isReListed: integer("is_re_listed", { mode: "boolean" }).default(false).notNull(), profileUrl: text("profile_url"), scanType: text("scan_type").notNull(), // initial_scan, status_check, weekly_rescan, re_listing_detected createdAt: integer("created_at", { mode: "timestamp_ms" }).defaultNow().notNull(), }, (table) => ({ subscriptionIdIdx: index("re_scan_results_subscription_id_idx").on(table.subscriptionId), brokerIdIdx: index("re_scan_results_broker_id_idx").on(table.brokerId), isReListedIdx: index("re_scan_results_is_re_listed_idx").on(table.isReListed), scanTypeIdx: index("re_scan_results_scan_type_idx").on(table.scanType), createdAtIdx: index("re_scan_results_created_at_idx").on(table.createdAt), })); export const adapterHealth = sqliteTable("adapter_health", { id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()), brokerId: text("broker_id").notNull().references(() => infoBrokers.id, { onDelete: "cascade" }), brokerName: text("broker_name").notNull(), status: text("status").notNull().default("healthy"), // healthy, degraded, broken, disabled successCount: integer("success_count").default(0).notNull(), failureCount: integer("failure_count").default(0).notNull(), lastSuccessAt: integer("last_success_at", { mode: "timestamp_ms" }), lastFailureAt: integer("last_failure_at", { mode: "timestamp_ms" }), lastError: text("last_error"), failureRate24h: integer("failure_rate_24h"), totalOps24h: integer("total_ops_24h").default(0), isAutoDisabled: integer("is_auto_disabled", { mode: "boolean" }).default(false).notNull(), createdAt: integer("created_at", { mode: "timestamp_ms" }).defaultNow().notNull(), updatedAt: integer("updated_at", { mode: "timestamp_ms" }).defaultNow().notNull().$onUpdate(() => new Date()), }, (table) => ({ brokerIdIdx: index("adapter_health_broker_id_idx").on(table.brokerId), statusIdx: index("adapter_health_status_idx").on(table.status), isAutoDisabledIdx: index("adapter_health_is_auto_disabled_idx").on(table.isAutoDisabled), failureRate24hIdx: index("adapter_health_failure_rate_24h_idx").on(table.failureRate24h), })); export const costTracking = sqliteTable("cost_tracking", { id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()), subscriptionId: text("subscription_id").references(() => subscriptions.id, { onDelete: "set null" }), proxyProvider: text("proxy_provider"), captchaSolver: text("captcha_solver"), proxyRequests: integer("proxy_requests").default(0).notNull(), captchaSolves: integer("captcha_solves").default(0).notNull(), captchaCostCents: integer("captcha_cost_cents").default(0).notNull(), proxyCostCents: integer("proxy_cost_cents").default(0).notNull(), totalCostCents: integer("total_cost_cents").default(0).notNull(), periodStart: integer("period_start", { mode: "timestamp_ms" }).notNull(), periodEnd: integer("period_end", { mode: "timestamp_ms" }).notNull(), createdAt: integer("created_at", { mode: "timestamp_ms" }).defaultNow().notNull(), }, (table) => ({ subscriptionIdIdx: index("cost_tracking_subscription_id_idx").on(table.subscriptionId), periodStartIdx: index("cost_tracking_period_start_idx").on(table.periodStart), periodEndIdx: index("cost_tracking_period_end_idx").on(table.periodEnd), })); export const brokerListings = sqliteTable("broker_listings", { id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()), subscriptionId: text("subscription_id").notNull().references(() => subscriptions.id, { onDelete: "cascade" }), brokerId: text("broker_id").notNull(), removalRequestId: text("removal_request_id").references(() => removalRequests.id), url: text("url").notNull(), dataFound: text("data_found", { mode: "json" }).notNull(), screenshotUrl: text("screenshot_url"), isRemoved: integer("is_removed", { mode: "boolean" }).default(false).notNull(), removedAt: integer("removed_at", { mode: "timestamp_ms" }), scannedAt: integer("scanned_at", { mode: "timestamp_ms" }).defaultNow().notNull(), createdAt: integer("created_at", { mode: "timestamp_ms" }).defaultNow().notNull(), updatedAt: integer("updated_at", { mode: "timestamp_ms" }).defaultNow().notNull().$onUpdate(() => new Date()), }, (table) => ({ subscriptionIdIdx: index("broker_listings_subscription_id_idx").on(table.subscriptionId), brokerIdIdx: index("broker_listings_broker_id_idx").on(table.brokerId), removalRequestIdIdx: index("broker_listings_removal_request_id_idx").on(table.removalRequestId), isRemovedIdx: index("broker_listings_is_removed_idx").on(table.isRemoved), subscriptionIdIsRemovedIdx: index("broker_listings_sub_id_is_removed_idx").on(table.subscriptionId, table.isRemoved), }));