Files
ProxyIOS/ProxyCore/Sources/DataLayer/Database/DatabaseManager.swift
2026-04-06 11:28:40 -05:00

135 lines
5.9 KiB
Swift

import Foundation
import GRDB
public final class DatabaseManager: Sendable {
public let dbPool: DatabasePool
public static let shared: DatabaseManager = {
let url = FileManager.default
.containerURL(forSecurityApplicationGroupIdentifier: "group.com.treyt.proxyapp")!
.appendingPathComponent("proxy.sqlite")
return try! DatabaseManager(path: url.path)
}()
public init(path: String) throws {
var config = Configuration()
config.prepareDatabase { db in
// WAL mode for cross-process concurrent access
try db.execute(sql: "PRAGMA journal_mode = WAL")
try db.execute(sql: "PRAGMA synchronous = NORMAL")
}
dbPool = try DatabasePool(path: path, configuration: config)
try migrate()
}
private func migrate() throws {
var migrator = DatabaseMigrator()
migrator.registerMigration("v1_create_tables") { db in
try db.create(table: "captured_traffic") { t in
t.autoIncrementedPrimaryKey("id")
t.column("requestId", .text).notNull().unique()
t.column("domain", .text).notNull().indexed()
t.column("url", .text).notNull()
t.column("method", .text).notNull()
t.column("scheme", .text).notNull()
t.column("statusCode", .integer)
t.column("statusText", .text)
t.column("requestHeaders", .text)
t.column("requestBody", .blob)
t.column("requestBodySize", .integer).notNull().defaults(to: 0)
t.column("requestContentType", .text)
t.column("queryParameters", .text)
t.column("responseHeaders", .text)
t.column("responseBody", .blob)
t.column("responseBodySize", .integer).notNull().defaults(to: 0)
t.column("responseContentType", .text)
t.column("startedAt", .double).notNull()
t.column("completedAt", .double)
t.column("durationMs", .integer)
t.column("isSslDecrypted", .boolean).notNull().defaults(to: false)
t.column("isPinned", .boolean).notNull().defaults(to: false)
t.column("isWebsocket", .boolean).notNull().defaults(to: false)
t.column("isHidden", .boolean).notNull().defaults(to: false)
t.column("createdAt", .double).notNull()
}
try db.create(index: "idx_traffic_started_at", on: "captured_traffic", columns: ["startedAt"])
try db.create(index: "idx_traffic_pinned", on: "captured_traffic", columns: ["isPinned"])
try db.create(table: "ssl_proxying_entries") { t in
t.autoIncrementedPrimaryKey("id")
t.column("domainPattern", .text).notNull()
t.column("isInclude", .boolean).notNull()
t.column("createdAt", .double).notNull()
}
try db.create(table: "block_list_entries") { t in
t.autoIncrementedPrimaryKey("id")
t.column("name", .text)
t.column("urlPattern", .text).notNull()
t.column("method", .text).notNull().defaults(to: "ANY")
t.column("includeSubpaths", .boolean).notNull().defaults(to: true)
t.column("blockAction", .text).notNull().defaults(to: "block_and_hide")
t.column("isEnabled", .boolean).notNull().defaults(to: true)
t.column("createdAt", .double).notNull()
}
try db.create(table: "breakpoint_rules") { t in
t.autoIncrementedPrimaryKey("id")
t.column("name", .text)
t.column("urlPattern", .text).notNull()
t.column("method", .text).notNull().defaults(to: "ANY")
t.column("interceptRequest", .boolean).notNull().defaults(to: true)
t.column("interceptResponse", .boolean).notNull().defaults(to: true)
t.column("isEnabled", .boolean).notNull().defaults(to: true)
t.column("createdAt", .double).notNull()
}
try db.create(table: "map_local_rules") { t in
t.autoIncrementedPrimaryKey("id")
t.column("name", .text)
t.column("urlPattern", .text).notNull()
t.column("method", .text).notNull().defaults(to: "ANY")
t.column("responseStatus", .integer).notNull().defaults(to: 200)
t.column("responseHeaders", .text)
t.column("responseBody", .blob)
t.column("responseContentType", .text)
t.column("isEnabled", .boolean).notNull().defaults(to: true)
t.column("createdAt", .double).notNull()
}
try db.create(table: "dns_spoof_rules") { t in
t.autoIncrementedPrimaryKey("id")
t.column("sourceDomain", .text).notNull()
t.column("targetDomain", .text).notNull()
t.column("isEnabled", .boolean).notNull().defaults(to: true)
t.column("createdAt", .double).notNull()
}
try db.create(table: "compose_requests") { t in
t.autoIncrementedPrimaryKey("id")
t.column("name", .text).notNull().defaults(to: "New Request")
t.column("method", .text).notNull().defaults(to: "GET")
t.column("url", .text)
t.column("headers", .text)
t.column("queryParameters", .text)
t.column("body", .text)
t.column("bodyContentType", .text)
t.column("responseStatus", .integer)
t.column("responseHeaders", .text)
t.column("responseBody", .blob)
t.column("lastSentAt", .double)
t.column("createdAt", .double).notNull()
}
}
try migrator.migrate(dbPool)
}
}