import Foundation struct Server: Codable, Identifiable, Hashable { var id = UUID() var name: String var host: String var user: String = "mag" var port: Int = 22 var identityFile: String = "" } struct Finding: Codable, Identifiable { var id = UUID() var severity: String var title: String var detail: String } struct AuditResult: Codable, Identifiable { var id = UUID() var serverID: UUID var serverName: String var scannedAt = Date() var hostname = "" var os = "" var phpVersions: [String] = [] var fpmServices: [String] = [] var nginxUpstreams: [String] = [] var packageVersions: [String] = [] var availableUpdates: [String] = [] var packageOrigins: [String] = [] var dockerPHP: [String] = [] var dockerCoverage: [String] = [] var securityTriage: [String] = [] var packageAuditStatus = "Not checked" var dockerAuditStatus = "Not checked" var findings: [Finding] = [] var recentPHPFiles: [String] = [] var webErrorSignals: [String] = [] var investigationStatus = "Not checked" var rawOutput = "" var error: String? = nil enum CodingKeys: String, CodingKey { case serverID, serverName, scannedAt, hostname, os, phpVersions, fpmServices, nginxUpstreams, packageVersions, availableUpdates, packageOrigins, dockerPHP, dockerCoverage, securityTriage, packageAuditStatus, dockerAuditStatus, recentPHPFiles, webErrorSignals, investigationStatus, findings, rawOutput, error, id } init(serverID: UUID, serverName: String) { self.serverID = serverID; self.serverName = serverName } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) serverID = try container.decode(UUID.self, forKey: .serverID) serverName = try container.decode(String.self, forKey: .serverName) scannedAt = try container.decodeIfPresent(Date.self, forKey: .scannedAt) ?? Date() hostname = try container.decodeIfPresent(String.self, forKey: .hostname) ?? "" os = try container.decodeIfPresent(String.self, forKey: .os) ?? "" phpVersions = try container.decodeIfPresent([String].self, forKey: .phpVersions) ?? [] fpmServices = try container.decodeIfPresent([String].self, forKey: .fpmServices) ?? [] nginxUpstreams = try container.decodeIfPresent([String].self, forKey: .nginxUpstreams) ?? [] packageVersions = try container.decodeIfPresent([String].self, forKey: .packageVersions) ?? [] availableUpdates = try container.decodeIfPresent([String].self, forKey: .availableUpdates) ?? [] packageOrigins = try container.decodeIfPresent([String].self, forKey: .packageOrigins) ?? [] dockerPHP = try container.decodeIfPresent([String].self, forKey: .dockerPHP) ?? [] dockerCoverage = try container.decodeIfPresent([String].self, forKey: .dockerCoverage) ?? [] securityTriage = try container.decodeIfPresent([String].self, forKey: .securityTriage) ?? [] packageAuditStatus = try container.decodeIfPresent(String.self, forKey: .packageAuditStatus) ?? "Not checked" dockerAuditStatus = try container.decodeIfPresent(String.self, forKey: .dockerAuditStatus) ?? "Not checked" findings = try container.decodeIfPresent([Finding].self, forKey: .findings) ?? [] recentPHPFiles = try container.decodeIfPresent([String].self, forKey: .recentPHPFiles) ?? [] webErrorSignals = try container.decodeIfPresent([String].self, forKey: .webErrorSignals) ?? [] investigationStatus = try container.decodeIfPresent(String.self, forKey: .investigationStatus) ?? "Not checked" rawOutput = try container.decodeIfPresent(String.self, forKey: .rawOutput) ?? "" error = try container.decodeIfPresent(String.self, forKey: .error) id = try container.decodeIfPresent(UUID.self, forKey: .id) ?? UUID() } func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(serverID, forKey: .serverID) try container.encode(serverName, forKey: .serverName) try container.encode(scannedAt, forKey: .scannedAt) try container.encode(hostname, forKey: .hostname) try container.encode(os, forKey: .os) try container.encode(phpVersions, forKey: .phpVersions) try container.encode(fpmServices, forKey: .fpmServices) try container.encode(nginxUpstreams, forKey: .nginxUpstreams) try container.encode(packageVersions, forKey: .packageVersions) try container.encode(availableUpdates, forKey: .availableUpdates) try container.encode(packageOrigins, forKey: .packageOrigins) try container.encode(dockerPHP, forKey: .dockerPHP) try container.encode(dockerCoverage, forKey: .dockerCoverage) try container.encode(securityTriage, forKey: .securityTriage) try container.encode(packageAuditStatus, forKey: .packageAuditStatus) try container.encode(dockerAuditStatus, forKey: .dockerAuditStatus) try container.encode(findings, forKey: .findings) try container.encode(recentPHPFiles, forKey: .recentPHPFiles) try container.encode(webErrorSignals, forKey: .webErrorSignals) try container.encode(investigationStatus, forKey: .investigationStatus) try container.encode(rawOutput, forKey: .rawOutput) try container.encodeIfPresent(error, forKey: .error) try container.encode(id, forKey: .id) } } struct CVERecord: Codable, Identifiable { var id: String var description: String var published: String var severity: String var score: Double? var referenceURL: String { "https://nvd.nist.gov/vuln/detail/" + id } }