Files
Kordant/web/src/server/db/schema/removebrokers.ts

168 lines
10 KiB
TypeScript

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),
}));