Initial project setup - Phases 1-3 complete
This commit is contained in:
134
ProxyCore/Sources/DataLayer/Database/DatabaseManager.swift
Normal file
134
ProxyCore/Sources/DataLayer/Database/DatabaseManager.swift
Normal file
@@ -0,0 +1,134 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
57
ProxyCore/Sources/DataLayer/Models/BlockListEntry.swift
Normal file
57
ProxyCore/Sources/DataLayer/Models/BlockListEntry.swift
Normal file
@@ -0,0 +1,57 @@
|
||||
import Foundation
|
||||
import GRDB
|
||||
|
||||
public enum BlockAction: String, Codable, Sendable, CaseIterable {
|
||||
case blockAndHide = "block_and_hide"
|
||||
case blockAndDisplay = "block_and_display"
|
||||
case hideOnly = "hide_only"
|
||||
|
||||
public var displayName: String {
|
||||
switch self {
|
||||
case .blockAndHide: "Block & Hide Request"
|
||||
case .blockAndDisplay: "Block & Display"
|
||||
case .hideOnly: "Hide but not Block"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct BlockListEntry: Codable, FetchableRecord, MutablePersistableRecord, Identifiable, Sendable {
|
||||
public var id: Int64?
|
||||
public var name: String?
|
||||
public var urlPattern: String
|
||||
public var method: String
|
||||
public var includeSubpaths: Bool
|
||||
public var blockAction: String
|
||||
public var isEnabled: Bool
|
||||
public var createdAt: Double
|
||||
|
||||
public static let databaseTableName = "block_list_entries"
|
||||
|
||||
public mutating func didInsert(_ inserted: InsertionSuccess) {
|
||||
id = inserted.rowID
|
||||
}
|
||||
|
||||
public init(
|
||||
id: Int64? = nil,
|
||||
name: String? = nil,
|
||||
urlPattern: String,
|
||||
method: String = "ANY",
|
||||
includeSubpaths: Bool = true,
|
||||
blockAction: BlockAction = .blockAndHide,
|
||||
isEnabled: Bool = true,
|
||||
createdAt: Double = Date().timeIntervalSince1970
|
||||
) {
|
||||
self.id = id
|
||||
self.name = name
|
||||
self.urlPattern = urlPattern
|
||||
self.method = method
|
||||
self.includeSubpaths = includeSubpaths
|
||||
self.blockAction = blockAction.rawValue
|
||||
self.isEnabled = isEnabled
|
||||
self.createdAt = createdAt
|
||||
}
|
||||
|
||||
public var action: BlockAction {
|
||||
BlockAction(rawValue: blockAction) ?? .blockAndHide
|
||||
}
|
||||
}
|
||||
39
ProxyCore/Sources/DataLayer/Models/BreakpointRule.swift
Normal file
39
ProxyCore/Sources/DataLayer/Models/BreakpointRule.swift
Normal file
@@ -0,0 +1,39 @@
|
||||
import Foundation
|
||||
import GRDB
|
||||
|
||||
public struct BreakpointRule: Codable, FetchableRecord, MutablePersistableRecord, Identifiable, Sendable {
|
||||
public var id: Int64?
|
||||
public var name: String?
|
||||
public var urlPattern: String
|
||||
public var method: String
|
||||
public var interceptRequest: Bool
|
||||
public var interceptResponse: Bool
|
||||
public var isEnabled: Bool
|
||||
public var createdAt: Double
|
||||
|
||||
public static let databaseTableName = "breakpoint_rules"
|
||||
|
||||
public mutating func didInsert(_ inserted: InsertionSuccess) {
|
||||
id = inserted.rowID
|
||||
}
|
||||
|
||||
public init(
|
||||
id: Int64? = nil,
|
||||
name: String? = nil,
|
||||
urlPattern: String,
|
||||
method: String = "ANY",
|
||||
interceptRequest: Bool = true,
|
||||
interceptResponse: Bool = true,
|
||||
isEnabled: Bool = true,
|
||||
createdAt: Double = Date().timeIntervalSince1970
|
||||
) {
|
||||
self.id = id
|
||||
self.name = name
|
||||
self.urlPattern = urlPattern
|
||||
self.method = method
|
||||
self.interceptRequest = interceptRequest
|
||||
self.interceptResponse = interceptResponse
|
||||
self.isEnabled = isEnabled
|
||||
self.createdAt = createdAt
|
||||
}
|
||||
}
|
||||
140
ProxyCore/Sources/DataLayer/Models/CapturedTraffic.swift
Normal file
140
ProxyCore/Sources/DataLayer/Models/CapturedTraffic.swift
Normal file
@@ -0,0 +1,140 @@
|
||||
import Foundation
|
||||
import GRDB
|
||||
|
||||
public struct CapturedTraffic: Codable, FetchableRecord, MutablePersistableRecord, Identifiable, Sendable {
|
||||
public var id: Int64?
|
||||
public var requestId: String
|
||||
public var domain: String
|
||||
public var url: String
|
||||
public var method: String
|
||||
public var scheme: String
|
||||
public var statusCode: Int?
|
||||
public var statusText: String?
|
||||
|
||||
// Request
|
||||
public var requestHeaders: String?
|
||||
public var requestBody: Data?
|
||||
public var requestBodySize: Int
|
||||
public var requestContentType: String?
|
||||
public var queryParameters: String?
|
||||
|
||||
// Response
|
||||
public var responseHeaders: String?
|
||||
public var responseBody: Data?
|
||||
public var responseBodySize: Int
|
||||
public var responseContentType: String?
|
||||
|
||||
// Timing
|
||||
public var startedAt: Double
|
||||
public var completedAt: Double?
|
||||
public var durationMs: Int?
|
||||
|
||||
// Metadata
|
||||
public var isSslDecrypted: Bool
|
||||
public var isPinned: Bool
|
||||
public var isWebsocket: Bool
|
||||
public var isHidden: Bool
|
||||
|
||||
public var createdAt: Double
|
||||
|
||||
public static let databaseTableName = "captured_traffic"
|
||||
|
||||
public mutating func didInsert(_ inserted: InsertionSuccess) {
|
||||
id = inserted.rowID
|
||||
}
|
||||
|
||||
public init(
|
||||
id: Int64? = nil,
|
||||
requestId: String = UUID().uuidString,
|
||||
domain: String,
|
||||
url: String,
|
||||
method: String,
|
||||
scheme: String,
|
||||
statusCode: Int? = nil,
|
||||
statusText: String? = nil,
|
||||
requestHeaders: String? = nil,
|
||||
requestBody: Data? = nil,
|
||||
requestBodySize: Int = 0,
|
||||
requestContentType: String? = nil,
|
||||
queryParameters: String? = nil,
|
||||
responseHeaders: String? = nil,
|
||||
responseBody: Data? = nil,
|
||||
responseBodySize: Int = 0,
|
||||
responseContentType: String? = nil,
|
||||
startedAt: Double = Date().timeIntervalSince1970,
|
||||
completedAt: Double? = nil,
|
||||
durationMs: Int? = nil,
|
||||
isSslDecrypted: Bool = false,
|
||||
isPinned: Bool = false,
|
||||
isWebsocket: Bool = false,
|
||||
isHidden: Bool = false,
|
||||
createdAt: Double = Date().timeIntervalSince1970
|
||||
) {
|
||||
self.id = id
|
||||
self.requestId = requestId
|
||||
self.domain = domain
|
||||
self.url = url
|
||||
self.method = method
|
||||
self.scheme = scheme
|
||||
self.statusCode = statusCode
|
||||
self.statusText = statusText
|
||||
self.requestHeaders = requestHeaders
|
||||
self.requestBody = requestBody
|
||||
self.requestBodySize = requestBodySize
|
||||
self.requestContentType = requestContentType
|
||||
self.queryParameters = queryParameters
|
||||
self.responseHeaders = responseHeaders
|
||||
self.responseBody = responseBody
|
||||
self.responseBodySize = responseBodySize
|
||||
self.responseContentType = responseContentType
|
||||
self.startedAt = startedAt
|
||||
self.completedAt = completedAt
|
||||
self.durationMs = durationMs
|
||||
self.isSslDecrypted = isSslDecrypted
|
||||
self.isPinned = isPinned
|
||||
self.isWebsocket = isWebsocket
|
||||
self.isHidden = isHidden
|
||||
self.createdAt = createdAt
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Computed Properties
|
||||
|
||||
extension CapturedTraffic {
|
||||
public var decodedRequestHeaders: [String: String] {
|
||||
guard let data = requestHeaders?.data(using: .utf8),
|
||||
let dict = try? JSONDecoder().decode([String: String].self, from: data) else {
|
||||
return [:]
|
||||
}
|
||||
return dict
|
||||
}
|
||||
|
||||
public var decodedResponseHeaders: [String: String] {
|
||||
guard let data = responseHeaders?.data(using: .utf8),
|
||||
let dict = try? JSONDecoder().decode([String: String].self, from: data) else {
|
||||
return [:]
|
||||
}
|
||||
return dict
|
||||
}
|
||||
|
||||
public var decodedQueryParameters: [String: String] {
|
||||
guard let data = queryParameters?.data(using: .utf8),
|
||||
let dict = try? JSONDecoder().decode([String: String].self, from: data) else {
|
||||
return [:]
|
||||
}
|
||||
return dict
|
||||
}
|
||||
|
||||
public var startDate: Date {
|
||||
Date(timeIntervalSince1970: startedAt)
|
||||
}
|
||||
|
||||
public var formattedDuration: String {
|
||||
guard let ms = durationMs else { return "-" }
|
||||
if ms < 1000 {
|
||||
return "\(ms) ms"
|
||||
} else {
|
||||
return String(format: "%.1f s", Double(ms) / 1000.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
54
ProxyCore/Sources/DataLayer/Models/ComposeRequest.swift
Normal file
54
ProxyCore/Sources/DataLayer/Models/ComposeRequest.swift
Normal file
@@ -0,0 +1,54 @@
|
||||
import Foundation
|
||||
import GRDB
|
||||
|
||||
public struct ComposeRequest: Codable, FetchableRecord, MutablePersistableRecord, Identifiable, Sendable {
|
||||
public var id: Int64?
|
||||
public var name: String
|
||||
public var method: String
|
||||
public var url: String?
|
||||
public var headers: String?
|
||||
public var queryParameters: String?
|
||||
public var body: String?
|
||||
public var bodyContentType: String?
|
||||
public var responseStatus: Int?
|
||||
public var responseHeaders: String?
|
||||
public var responseBody: Data?
|
||||
public var lastSentAt: Double?
|
||||
public var createdAt: Double
|
||||
|
||||
public static let databaseTableName = "compose_requests"
|
||||
|
||||
public mutating func didInsert(_ inserted: InsertionSuccess) {
|
||||
id = inserted.rowID
|
||||
}
|
||||
|
||||
public init(
|
||||
id: Int64? = nil,
|
||||
name: String = "New Request",
|
||||
method: String = "GET",
|
||||
url: String? = nil,
|
||||
headers: String? = nil,
|
||||
queryParameters: String? = nil,
|
||||
body: String? = nil,
|
||||
bodyContentType: String? = nil,
|
||||
responseStatus: Int? = nil,
|
||||
responseHeaders: String? = nil,
|
||||
responseBody: Data? = nil,
|
||||
lastSentAt: Double? = nil,
|
||||
createdAt: Double = Date().timeIntervalSince1970
|
||||
) {
|
||||
self.id = id
|
||||
self.name = name
|
||||
self.method = method
|
||||
self.url = url
|
||||
self.headers = headers
|
||||
self.queryParameters = queryParameters
|
||||
self.body = body
|
||||
self.bodyContentType = bodyContentType
|
||||
self.responseStatus = responseStatus
|
||||
self.responseHeaders = responseHeaders
|
||||
self.responseBody = responseBody
|
||||
self.lastSentAt = lastSentAt
|
||||
self.createdAt = createdAt
|
||||
}
|
||||
}
|
||||
30
ProxyCore/Sources/DataLayer/Models/DNSSpoofRule.swift
Normal file
30
ProxyCore/Sources/DataLayer/Models/DNSSpoofRule.swift
Normal file
@@ -0,0 +1,30 @@
|
||||
import Foundation
|
||||
import GRDB
|
||||
|
||||
public struct DNSSpoofRule: Codable, FetchableRecord, MutablePersistableRecord, Identifiable, Sendable {
|
||||
public var id: Int64?
|
||||
public var sourceDomain: String
|
||||
public var targetDomain: String
|
||||
public var isEnabled: Bool
|
||||
public var createdAt: Double
|
||||
|
||||
public static let databaseTableName = "dns_spoof_rules"
|
||||
|
||||
public mutating func didInsert(_ inserted: InsertionSuccess) {
|
||||
id = inserted.rowID
|
||||
}
|
||||
|
||||
public init(
|
||||
id: Int64? = nil,
|
||||
sourceDomain: String,
|
||||
targetDomain: String,
|
||||
isEnabled: Bool = true,
|
||||
createdAt: Double = Date().timeIntervalSince1970
|
||||
) {
|
||||
self.id = id
|
||||
self.sourceDomain = sourceDomain
|
||||
self.targetDomain = targetDomain
|
||||
self.isEnabled = isEnabled
|
||||
self.createdAt = createdAt
|
||||
}
|
||||
}
|
||||
13
ProxyCore/Sources/DataLayer/Models/DomainGroup.swift
Normal file
13
ProxyCore/Sources/DataLayer/Models/DomainGroup.swift
Normal file
@@ -0,0 +1,13 @@
|
||||
import Foundation
|
||||
import GRDB
|
||||
|
||||
public struct DomainGroup: Decodable, FetchableRecord, Identifiable, Hashable, Sendable {
|
||||
public var id: String { domain }
|
||||
public var domain: String
|
||||
public var requestCount: Int
|
||||
|
||||
public init(domain: String, requestCount: Int) {
|
||||
self.domain = domain
|
||||
self.requestCount = requestCount
|
||||
}
|
||||
}
|
||||
45
ProxyCore/Sources/DataLayer/Models/MapLocalRule.swift
Normal file
45
ProxyCore/Sources/DataLayer/Models/MapLocalRule.swift
Normal file
@@ -0,0 +1,45 @@
|
||||
import Foundation
|
||||
import GRDB
|
||||
|
||||
public struct MapLocalRule: Codable, FetchableRecord, MutablePersistableRecord, Identifiable, Sendable {
|
||||
public var id: Int64?
|
||||
public var name: String?
|
||||
public var urlPattern: String
|
||||
public var method: String
|
||||
public var responseStatus: Int
|
||||
public var responseHeaders: String?
|
||||
public var responseBody: Data?
|
||||
public var responseContentType: String?
|
||||
public var isEnabled: Bool
|
||||
public var createdAt: Double
|
||||
|
||||
public static let databaseTableName = "map_local_rules"
|
||||
|
||||
public mutating func didInsert(_ inserted: InsertionSuccess) {
|
||||
id = inserted.rowID
|
||||
}
|
||||
|
||||
public init(
|
||||
id: Int64? = nil,
|
||||
name: String? = nil,
|
||||
urlPattern: String,
|
||||
method: String = "ANY",
|
||||
responseStatus: Int = 200,
|
||||
responseHeaders: String? = nil,
|
||||
responseBody: Data? = nil,
|
||||
responseContentType: String? = nil,
|
||||
isEnabled: Bool = true,
|
||||
createdAt: Double = Date().timeIntervalSince1970
|
||||
) {
|
||||
self.id = id
|
||||
self.name = name
|
||||
self.urlPattern = urlPattern
|
||||
self.method = method
|
||||
self.responseStatus = responseStatus
|
||||
self.responseHeaders = responseHeaders
|
||||
self.responseBody = responseBody
|
||||
self.responseContentType = responseContentType
|
||||
self.isEnabled = isEnabled
|
||||
self.createdAt = createdAt
|
||||
}
|
||||
}
|
||||
22
ProxyCore/Sources/DataLayer/Models/SSLProxyingEntry.swift
Normal file
22
ProxyCore/Sources/DataLayer/Models/SSLProxyingEntry.swift
Normal file
@@ -0,0 +1,22 @@
|
||||
import Foundation
|
||||
import GRDB
|
||||
|
||||
public struct SSLProxyingEntry: Codable, FetchableRecord, MutablePersistableRecord, Identifiable, Sendable {
|
||||
public var id: Int64?
|
||||
public var domainPattern: String
|
||||
public var isInclude: Bool
|
||||
public var createdAt: Double
|
||||
|
||||
public static let databaseTableName = "ssl_proxying_entries"
|
||||
|
||||
public mutating func didInsert(_ inserted: InsertionSuccess) {
|
||||
id = inserted.rowID
|
||||
}
|
||||
|
||||
public init(id: Int64? = nil, domainPattern: String, isInclude: Bool, createdAt: Double = Date().timeIntervalSince1970) {
|
||||
self.id = id
|
||||
self.domainPattern = domainPattern
|
||||
self.isInclude = isInclude
|
||||
self.createdAt = createdAt
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import Foundation
|
||||
import GRDB
|
||||
|
||||
public final class ComposeRepository: Sendable {
|
||||
private let db: DatabaseManager
|
||||
|
||||
public init(db: DatabaseManager = .shared) {
|
||||
self.db = db
|
||||
}
|
||||
|
||||
public func observeRequests() -> ValueObservation<ValueReducers.Fetch<[ComposeRequest]>> {
|
||||
ValueObservation.tracking { db in
|
||||
try ComposeRequest.order(Column("createdAt").desc).fetchAll(db)
|
||||
}
|
||||
}
|
||||
|
||||
public func insert(_ request: inout ComposeRequest) throws {
|
||||
try db.dbPool.write { db in try request.insert(db) }
|
||||
}
|
||||
|
||||
public func update(_ request: ComposeRequest) throws {
|
||||
try db.dbPool.write { db in try request.update(db) }
|
||||
}
|
||||
|
||||
public func delete(id: Int64) throws {
|
||||
try db.dbPool.write { db in _ = try ComposeRequest.deleteOne(db, id: id) }
|
||||
}
|
||||
|
||||
public func deleteAll() throws {
|
||||
try db.dbPool.write { db in _ = try ComposeRequest.deleteAll(db) }
|
||||
}
|
||||
}
|
||||
128
ProxyCore/Sources/DataLayer/Repositories/RulesRepository.swift
Normal file
128
ProxyCore/Sources/DataLayer/Repositories/RulesRepository.swift
Normal file
@@ -0,0 +1,128 @@
|
||||
import Foundation
|
||||
import GRDB
|
||||
|
||||
public final class RulesRepository: Sendable {
|
||||
private let db: DatabaseManager
|
||||
|
||||
public init(db: DatabaseManager = .shared) {
|
||||
self.db = db
|
||||
}
|
||||
|
||||
// MARK: - SSL Proxying
|
||||
|
||||
public func observeSSLEntries() -> ValueObservation<ValueReducers.Fetch<[SSLProxyingEntry]>> {
|
||||
ValueObservation.tracking { db in
|
||||
try SSLProxyingEntry.order(Column("createdAt").desc).fetchAll(db)
|
||||
}
|
||||
}
|
||||
|
||||
public func fetchAllSSLEntries() throws -> [SSLProxyingEntry] {
|
||||
try db.dbPool.read { db in
|
||||
try SSLProxyingEntry.fetchAll(db)
|
||||
}
|
||||
}
|
||||
|
||||
public func insertSSLEntry(_ entry: inout SSLProxyingEntry) throws {
|
||||
try db.dbPool.write { db in try entry.insert(db) }
|
||||
}
|
||||
|
||||
public func deleteSSLEntry(id: Int64) throws {
|
||||
try db.dbPool.write { db in _ = try SSLProxyingEntry.deleteOne(db, id: id) }
|
||||
}
|
||||
|
||||
public func deleteAllSSLEntries() throws {
|
||||
try db.dbPool.write { db in _ = try SSLProxyingEntry.deleteAll(db) }
|
||||
}
|
||||
|
||||
// MARK: - Block List
|
||||
|
||||
public func observeBlockListEntries() -> ValueObservation<ValueReducers.Fetch<[BlockListEntry]>> {
|
||||
ValueObservation.tracking { db in
|
||||
try BlockListEntry.order(Column("createdAt").desc).fetchAll(db)
|
||||
}
|
||||
}
|
||||
|
||||
public func insertBlockEntry(_ entry: inout BlockListEntry) throws {
|
||||
try db.dbPool.write { db in try entry.insert(db) }
|
||||
}
|
||||
|
||||
public func updateBlockEntry(_ entry: BlockListEntry) throws {
|
||||
try db.dbPool.write { db in try entry.update(db) }
|
||||
}
|
||||
|
||||
public func deleteBlockEntry(id: Int64) throws {
|
||||
try db.dbPool.write { db in _ = try BlockListEntry.deleteOne(db, id: id) }
|
||||
}
|
||||
|
||||
public func deleteAllBlockEntries() throws {
|
||||
try db.dbPool.write { db in _ = try BlockListEntry.deleteAll(db) }
|
||||
}
|
||||
|
||||
// MARK: - Breakpoint Rules
|
||||
|
||||
public func observeBreakpointRules() -> ValueObservation<ValueReducers.Fetch<[BreakpointRule]>> {
|
||||
ValueObservation.tracking { db in
|
||||
try BreakpointRule.order(Column("createdAt").desc).fetchAll(db)
|
||||
}
|
||||
}
|
||||
|
||||
public func insertBreakpointRule(_ rule: inout BreakpointRule) throws {
|
||||
try db.dbPool.write { db in try rule.insert(db) }
|
||||
}
|
||||
|
||||
public func updateBreakpointRule(_ rule: BreakpointRule) throws {
|
||||
try db.dbPool.write { db in try rule.update(db) }
|
||||
}
|
||||
|
||||
public func deleteBreakpointRule(id: Int64) throws {
|
||||
try db.dbPool.write { db in _ = try BreakpointRule.deleteOne(db, id: id) }
|
||||
}
|
||||
|
||||
public func deleteAllBreakpointRules() throws {
|
||||
try db.dbPool.write { db in _ = try BreakpointRule.deleteAll(db) }
|
||||
}
|
||||
|
||||
// MARK: - Map Local Rules
|
||||
|
||||
public func observeMapLocalRules() -> ValueObservation<ValueReducers.Fetch<[MapLocalRule]>> {
|
||||
ValueObservation.tracking { db in
|
||||
try MapLocalRule.order(Column("createdAt").desc).fetchAll(db)
|
||||
}
|
||||
}
|
||||
|
||||
public func insertMapLocalRule(_ rule: inout MapLocalRule) throws {
|
||||
try db.dbPool.write { db in try rule.insert(db) }
|
||||
}
|
||||
|
||||
public func updateMapLocalRule(_ rule: MapLocalRule) throws {
|
||||
try db.dbPool.write { db in try rule.update(db) }
|
||||
}
|
||||
|
||||
public func deleteMapLocalRule(id: Int64) throws {
|
||||
try db.dbPool.write { db in _ = try MapLocalRule.deleteOne(db, id: id) }
|
||||
}
|
||||
|
||||
public func deleteAllMapLocalRules() throws {
|
||||
try db.dbPool.write { db in _ = try MapLocalRule.deleteAll(db) }
|
||||
}
|
||||
|
||||
// MARK: - DNS Spoof Rules
|
||||
|
||||
public func observeDNSSpoofRules() -> ValueObservation<ValueReducers.Fetch<[DNSSpoofRule]>> {
|
||||
ValueObservation.tracking { db in
|
||||
try DNSSpoofRule.order(Column("createdAt").desc).fetchAll(db)
|
||||
}
|
||||
}
|
||||
|
||||
public func insertDNSSpoofRule(_ rule: inout DNSSpoofRule) throws {
|
||||
try db.dbPool.write { db in try rule.insert(db) }
|
||||
}
|
||||
|
||||
public func deleteDNSSpoofRule(id: Int64) throws {
|
||||
try db.dbPool.write { db in _ = try DNSSpoofRule.deleteOne(db, id: id) }
|
||||
}
|
||||
|
||||
public func deleteAllDNSSpoofRules() throws {
|
||||
try db.dbPool.write { db in _ = try DNSSpoofRule.deleteAll(db) }
|
||||
}
|
||||
}
|
||||
110
ProxyCore/Sources/DataLayer/Repositories/TrafficRepository.swift
Normal file
110
ProxyCore/Sources/DataLayer/Repositories/TrafficRepository.swift
Normal file
@@ -0,0 +1,110 @@
|
||||
import Foundation
|
||||
import GRDB
|
||||
|
||||
public final class TrafficRepository: Sendable {
|
||||
private let db: DatabaseManager
|
||||
|
||||
public init(db: DatabaseManager = .shared) {
|
||||
self.db = db
|
||||
}
|
||||
|
||||
// MARK: - Domain Groups
|
||||
|
||||
public func observeDomainGroups() -> ValueObservation<ValueReducers.Fetch<[DomainGroup]>> {
|
||||
ValueObservation.tracking { db in
|
||||
try DomainGroup.fetchAll(db, sql: """
|
||||
SELECT domain, COUNT(*) as requestCount
|
||||
FROM captured_traffic
|
||||
WHERE isHidden = 0
|
||||
GROUP BY domain
|
||||
ORDER BY MAX(startedAt) DESC
|
||||
""")
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Traffic for Domain
|
||||
|
||||
public func observeTraffic(forDomain domain: String) -> ValueObservation<ValueReducers.Fetch<[CapturedTraffic]>> {
|
||||
ValueObservation.tracking { db in
|
||||
try CapturedTraffic
|
||||
.filter(Column("domain") == domain)
|
||||
.filter(Column("isHidden") == false)
|
||||
.order(Column("startedAt").desc)
|
||||
.fetchAll(db)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Pinned
|
||||
|
||||
public func observePinnedTraffic() -> ValueObservation<ValueReducers.Fetch<[CapturedTraffic]>> {
|
||||
ValueObservation.tracking { db in
|
||||
try CapturedTraffic
|
||||
.filter(Column("isPinned") == true)
|
||||
.order(Column("startedAt").desc)
|
||||
.fetchAll(db)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Single Request
|
||||
|
||||
public func traffic(byId id: Int64) throws -> CapturedTraffic? {
|
||||
try db.dbPool.read { db in
|
||||
try CapturedTraffic.fetchOne(db, id: id)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Write Operations
|
||||
|
||||
public func insert(_ traffic: inout CapturedTraffic) throws {
|
||||
try db.dbPool.write { db in
|
||||
try traffic.insert(db)
|
||||
}
|
||||
}
|
||||
|
||||
public func updateResponse(
|
||||
requestId: String,
|
||||
statusCode: Int,
|
||||
statusText: String,
|
||||
responseHeaders: String?,
|
||||
responseBody: Data?,
|
||||
responseBodySize: Int,
|
||||
responseContentType: String?,
|
||||
completedAt: Double,
|
||||
durationMs: Int
|
||||
) throws {
|
||||
try db.dbPool.write { db in
|
||||
try db.execute(sql: """
|
||||
UPDATE captured_traffic SET
|
||||
statusCode = ?, statusText = ?,
|
||||
responseHeaders = ?, responseBody = ?,
|
||||
responseBodySize = ?, responseContentType = ?,
|
||||
completedAt = ?, durationMs = ?
|
||||
WHERE requestId = ?
|
||||
""", arguments: [
|
||||
statusCode, statusText,
|
||||
responseHeaders, responseBody,
|
||||
responseBodySize, responseContentType,
|
||||
completedAt, durationMs,
|
||||
requestId
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
public func togglePin(id: Int64, isPinned: Bool) throws {
|
||||
try db.dbPool.write { db in
|
||||
try db.execute(sql: "UPDATE captured_traffic SET isPinned = ? WHERE id = ?", arguments: [isPinned, id])
|
||||
}
|
||||
}
|
||||
|
||||
public func deleteAll() throws {
|
||||
try db.dbPool.write { db in
|
||||
_ = try CapturedTraffic.deleteAll(db)
|
||||
}
|
||||
}
|
||||
|
||||
public func deleteForDomain(_ domain: String) throws {
|
||||
try db.dbPool.write { db in
|
||||
_ = try CapturedTraffic.filter(Column("domain") == domain).deleteAll(db)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user