Files
OmniRoute/src/lib/db/oneproxy.ts
2026-05-01 08:41:19 -03:00

294 lines
8.7 KiB
TypeScript

import { randomUUID } from "crypto";
import { getDbInstance } from "./core";
import { backupDbFile } from "./backup";
type JsonRecord = Record<string, unknown>;
export interface OneproxyProxyRecord {
id: string;
name: string;
type: string;
host: string;
port: number;
region: string | null;
notes: string | null;
status: string;
source: string;
qualityScore: number | null;
latencyMs: number | null;
anonymity: string | null;
googleAccess: boolean;
lastValidated: string | null;
countryCode: string | null;
createdAt: string;
updatedAt: string;
}
export interface OneproxyStats {
total: number;
active: number;
avgQuality: number | null;
lastValidated: string | null;
byProtocol: Array<{ protocol: string; count: number }>;
byCountry: Array<{ countryCode: string; count: number }>;
}
interface OneproxyUpsertInput {
ip: string;
port: number;
protocol: string;
country?: string | null;
countryCode?: string | null;
anonymity?: string | null;
qualityScore?: number | null;
latencyMs?: number | null;
googleAccess?: boolean;
lastValidated?: string | null;
}
function toRecord(value: unknown): JsonRecord {
return value && typeof value === "object" ? (value as JsonRecord) : {};
}
function mapProxyRow(row: unknown): OneproxyProxyRecord {
const r = toRecord(row);
return {
id: typeof r.id === "string" ? r.id : "",
name: typeof r.name === "string" ? r.name : "",
type: typeof r.type === "string" ? r.type : "http",
host: typeof r.host === "string" ? r.host : "",
port: Number(r.port) || 0,
region: typeof r.region === "string" ? r.region : null,
notes: typeof r.notes === "string" ? r.notes : null,
status: typeof r.status === "string" ? r.status : "active",
source: typeof r.source === "string" ? r.source : "oneproxy",
qualityScore: typeof r.quality_score === "number" ? r.quality_score : null,
latencyMs: typeof r.latency_ms === "number" ? r.latency_ms : null,
anonymity: typeof r.anonymity === "string" ? r.anonymity : null,
googleAccess: r.google_access === 1 || r.google_access === true,
lastValidated: typeof r.last_validated === "string" ? r.last_validated : null,
countryCode: typeof r.country_code === "string" ? r.country_code : null,
createdAt: typeof r.created_at === "string" ? r.created_at : "",
updatedAt: typeof r.updated_at === "string" ? r.updated_at : "",
};
}
function mapStatsRow(row: unknown) {
const r = toRecord(row);
return {
total: Number(r.total) || 0,
active: Number(r.active) || 0,
avgQuality:
r.avg_quality !== null && r.avg_quality !== undefined
? Math.round(Number(r.avg_quality) * 100) / 100
: null,
lastValidated: typeof r.last_validated === "string" ? r.last_validated : null,
};
}
export async function listOneproxyProxies(options?: {
protocol?: string;
countryCode?: string;
minQuality?: number;
limit?: number;
}): Promise<OneproxyProxyRecord[]> {
const db = getDbInstance();
let sql = "SELECT * FROM proxy_registry WHERE source = 'oneproxy' AND status = 'active'";
const params: unknown[] = [];
if (options?.protocol) {
sql += " AND type = ?";
params.push(options.protocol);
}
if (options?.countryCode) {
sql += " AND country_code = ?";
params.push(options.countryCode);
}
if (options?.minQuality != null) {
sql += " AND quality_score >= ?";
params.push(options.minQuality);
}
sql += " ORDER BY quality_score DESC, last_validated DESC";
if (options?.limit) {
sql += " LIMIT ?";
params.push(options.limit);
}
const rows = db.prepare(sql).all(...params);
return rows.map(mapProxyRow);
}
export async function getOneproxyStats(): Promise<OneproxyStats> {
const db = getDbInstance();
const statsRow = db
.prepare(
`SELECT
COUNT(*) as total,
SUM(CASE WHEN status = 'active' THEN 1 ELSE 0 END) as active,
AVG(quality_score) as avg_quality,
MAX(last_validated) as last_validated
FROM proxy_registry WHERE source = 'oneproxy'`
)
.get();
const stats = mapStatsRow(statsRow);
const byProtocol = db
.prepare(
"SELECT type as protocol, COUNT(*) as count FROM proxy_registry WHERE source = 'oneproxy' GROUP BY type ORDER BY count DESC"
)
.all() as Array<JsonRecord>;
const byCountry = db
.prepare(
"SELECT country_code as countryCode, COUNT(*) as count FROM proxy_registry WHERE source = 'oneproxy' AND country_code IS NOT NULL GROUP BY country_code ORDER BY count DESC LIMIT 20"
)
.all() as Array<JsonRecord>;
return {
...stats,
byProtocol: byProtocol.map((r) => ({
protocol: String(r.protocol || "unknown"),
count: Number(r.count) || 0,
})),
byCountry: byCountry.map((r) => ({
countryCode: String(r.countryCode || "unknown"),
count: Number(r.count) || 0,
})),
};
}
export async function upsertOneproxyProxy(
input: OneproxyUpsertInput
): Promise<{ proxy: OneproxyProxyRecord | null; action: "created" | "updated" }> {
const db = getDbInstance();
const now = new Date().toISOString();
const name = `${input.protocol?.toUpperCase() || "HTTP"} - ${input.countryCode || "Unknown"} - ${input.ip}`;
const existing = db
.prepare("SELECT id FROM proxy_registry WHERE host = ? AND port = ? AND source = 'oneproxy'")
.get(input.ip, input.port) as { id?: string } | undefined;
if (existing?.id) {
db.prepare(
`UPDATE proxy_registry
SET status = ?, quality_score = ?, latency_ms = ?, anonymity = ?,
google_access = ?, last_validated = ?, country_code = ?, updated_at = ?
WHERE id = ?`
).run(
"active",
input.qualityScore ?? null,
input.latencyMs ?? null,
input.anonymity ?? null,
input.googleAccess ? 1 : 0,
input.lastValidated ?? now,
input.countryCode ?? null,
now,
existing.id
);
backupDbFile("pre-write");
const proxy = await getOneproxyProxyById(existing.id);
return { proxy, action: "updated" };
}
const id = randomUUID();
db.prepare(
`INSERT INTO proxy_registry
(id, name, type, host, port, region, notes, status, source,
quality_score, latency_ms, anonymity, google_access, last_validated, country_code,
created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
).run(
id,
name,
input.protocol || "http",
input.ip,
input.port,
input.countryCode ?? null,
null,
"active",
"oneproxy",
input.qualityScore ?? null,
input.latencyMs ?? null,
input.anonymity ?? null,
input.googleAccess ? 1 : 0,
input.lastValidated ?? now,
input.countryCode ?? null,
now,
now
);
backupDbFile("pre-write");
const proxy = await getOneproxyProxyById(id);
return { proxy, action: "created" };
}
export async function getOneproxyProxyById(id: string): Promise<OneproxyProxyRecord | null> {
const db = getDbInstance();
const row = db
.prepare("SELECT * FROM proxy_registry WHERE id = ? AND source = 'oneproxy'")
.get(id);
if (!row) return null;
return mapProxyRow(row);
}
export async function deleteOneproxyProxy(id: string): Promise<boolean> {
const db = getDbInstance();
const result = db
.prepare("DELETE FROM proxy_registry WHERE id = ? AND source = 'oneproxy'")
.run(id);
backupDbFile("pre-write");
return result.changes > 0;
}
export async function clearAllOneproxyProxies(): Promise<number> {
const db = getDbInstance();
const result = db.prepare("DELETE FROM proxy_registry WHERE source = 'oneproxy'").run();
backupDbFile("pre-write");
return result.changes;
}
export async function getOneproxyProxyForRotation(options?: {
strategy?: "random" | "quality" | "sequential";
}): Promise<OneproxyProxyRecord | null> {
const db = getDbInstance();
const strategy = options?.strategy || "quality";
let sql = "SELECT * FROM proxy_registry WHERE source = 'oneproxy' AND status = 'active'";
switch (strategy) {
case "quality":
sql += " ORDER BY quality_score DESC, latency_ms ASC LIMIT 1";
break;
case "random":
sql += " ORDER BY RANDOM() LIMIT 1";
break;
case "sequential":
sql += " ORDER BY last_validated ASC LIMIT 1";
break;
}
const row = db.prepare(sql).get();
if (!row) return null;
return mapProxyRow(row);
}
export async function markOneproxyProxyFailed(host: string, port: number): Promise<boolean> {
const db = getDbInstance();
const result = db
.prepare(
`UPDATE proxy_registry
SET quality_score = MAX(0, COALESCE(quality_score, 50) - 10),
status = CASE WHEN COALESCE(quality_score, 50) <= 10 THEN 'inactive' ELSE status END,
updated_at = datetime('now')
WHERE host = ? AND port = ? AND source = 'oneproxy'`
)
.run(host, port);
backupDbFile("pre-write");
return result.changes > 0;
}