Skip to content

Commit 30bdd67

Browse files
kochj23claude
andcommitted
fix(nova): correct NovaAPIServer API surface to match actual class definitions
Fixed field/method names after verifying against actual source. All three focus apps now build and archive successfully. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
1 parent dcca147 commit 30bdd67

1 file changed

Lines changed: 43 additions & 48 deletions

File tree

RsyncGUI/NovaAPIServer.swift

Lines changed: 43 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,13 @@
55
// Nova/Claude API — port 37424
66
//
77
// Endpoints:
8-
// GET /api/status → app status, running jobs
8+
// GET /api/status → app status, job count
99
// GET /api/jobs → list all sync jobs
1010
// GET /api/jobs/:id → single job detail
11-
// POST /api/jobs/:id/run → run a specific job
12-
// POST /api/jobs/:id/stop → stop a running job
13-
// POST /api/jobs → create new job
14-
// GET /api/jobs/:id/history → execution history for job
15-
// GET /api/history → all recent execution history
16-
// POST /api/jobs/:id/test → dry-run test
11+
// POST /api/jobs/:id/run → execute a job
12+
// POST /api/jobs/:id/dryrun → dry-run test
13+
// GET /api/history → recent execution history
14+
// GET /api/jobs/:id/history → history for specific job
1715
//
1816
// Created by Jordan Koch on 2026.
1917
// Copyright © 2026 Jordan Koch. All rights reserved.
@@ -59,29 +57,33 @@ class NovaAPIServer {
5957
private func route(_ req: NovaRequest) async -> String {
6058
if req.method == "OPTIONS" { return http(200, "") }
6159
let jm = JobManager.shared
60+
let hm = ExecutionHistoryManager.shared
6261

6362
switch (req.method, req.path) {
6463

6564
case ("GET", "/api/status"):
66-
let running = jm.jobs.filter { $0.currentStatus == .running }
6765
return json(200, [
6866
"status": "running", "app": "RsyncGUI", "version": "1.0", "port": "\(port)",
6967
"jobCount": jm.jobs.count,
70-
"runningJobs": running.count,
68+
"enabledJobs": jm.jobs.filter { $0.isEnabled }.count,
7169
"uptimeSeconds": Int(Date().timeIntervalSince(startTime))
7270
])
7371

72+
case ("GET", "/api/ping"):
73+
return json(200, ["pong": true])
74+
7475
case ("GET", "/api/jobs"):
7576
let jobs = jm.jobs.map { j -> [String: Any] in [
76-
"id": j.id.uuidString, "name": j.name,
77-
"source": j.source, "destination": j.destination,
78-
"status": j.currentStatus.rawValue,
77+
"id": j.id.uuidString,
78+
"name": j.name,
79+
"source": j.source,
80+
"destination": j.destination,
7981
"isEnabled": j.isEnabled,
80-
"lastRun": j.lastExecutionDate.map { ISO8601DateFormatter().string(from: $0) } ?? ""
82+
"syncMode": j.syncMode.rawValue
8183
]}
8284
return jsonArray(200, jobs)
8385

84-
case ("GET", _) where req.path.hasPrefix("/api/jobs/") && !req.path.contains("/history") && !req.path.hasSuffix("/run") && !req.path.hasSuffix("/stop") && !req.path.hasSuffix("/test"):
86+
case ("GET", _) where req.path.hasPrefix("/api/jobs/") && !req.path.hasSuffix("/run") && !req.path.hasSuffix("/dryrun") && !req.path.hasSuffix("/history"):
8587
let idStr = req.path.replacingOccurrences(of: "/api/jobs/", with: "")
8688
guard let uuid = UUID(uuidString: idStr),
8789
let job = jm.jobs.first(where: { $0.id == uuid }) else {
@@ -90,9 +92,7 @@ class NovaAPIServer {
9092
return json(200, [
9193
"id": job.id.uuidString, "name": job.name,
9294
"sources": job.sources, "destination": job.destination,
93-
"status": job.currentStatus.rawValue,
94-
"isEnabled": job.isEnabled,
95-
"mode": job.syncMode.rawValue
95+
"isEnabled": job.isEnabled, "syncMode": job.syncMode.rawValue
9696
] as [String: Any])
9797

9898
case ("POST", _) where req.path.hasSuffix("/run"):
@@ -101,49 +101,44 @@ class NovaAPIServer {
101101
let job = jm.jobs.first(where: { $0.id == uuid }) else {
102102
return json(404, ["error": "Job not found"])
103103
}
104-
await jm.runJob(job)
104+
Task {
105+
_ = try? await jm.executeJob(job, dryRun: false)
106+
}
105107
return json(200, ["status": "started", "job": job.name])
106108

107-
case ("POST", _) where req.path.hasSuffix("/stop"):
109+
case ("POST", _) where req.path.hasSuffix("/dryrun"):
108110
let idStr = req.path.components(separatedBy: "/").dropLast().last ?? ""
109111
guard let uuid = UUID(uuidString: idStr),
110112
let job = jm.jobs.first(where: { $0.id == uuid }) else {
111113
return json(404, ["error": "Job not found"])
112114
}
113-
jm.stopJob(job)
114-
return json(200, ["status": "stopped", "job": job.name])
115-
116-
case ("GET", _) where req.path.hasSuffix("/history"):
117-
let idStr = req.path.components(separatedBy: "/").dropLast().last ?? ""
118-
guard let uuid = UUID(uuidString: idStr) else { return json(400, ["error": "Invalid UUID"]) }
119-
let history = ExecutionHistoryManager.shared.history(for: uuid)
120-
let entries = history.map { h -> [String: Any] in [
121-
"id": h.id.uuidString,
122-
"startedAt": ISO8601DateFormatter().string(from: h.startedAt),
123-
"duration": h.duration,
124-
"status": h.status.rawValue,
125-
"filesSynced": h.filesSynced,
126-
"bytesTransferred": h.bytesTransferred
127-
]}
128-
return jsonArray(200, entries)
115+
Task {
116+
_ = try? await jm.executeJob(job, dryRun: true)
117+
}
118+
return json(200, ["status": "dryrun_started", "job": job.name])
129119

130120
case ("GET", "/api/history"):
131-
let history = ExecutionHistoryManager.shared.recentHistory(limit: 50)
132-
let entries = history.map { h -> [String: Any] in [
133-
"id": h.id.uuidString, "jobId": h.jobId.uuidString,
134-
"startedAt": ISO8601DateFormatter().string(from: h.startedAt),
135-
"status": h.status.rawValue, "filesSynced": h.filesSynced
121+
let entries = hm.getAllHistory(limit: 50).map { e -> [String: Any] in [
122+
"id": e.id.uuidString,
123+
"jobName": e.jobName,
124+
"startTime": ISO8601DateFormatter().string(from: e.timestamp),
125+
"status": e.status.rawValue,
126+
"filesTransferred": e.filesTransferred,
127+
"bytesTransferred": e.bytesTransferred
136128
]}
137129
return jsonArray(200, entries)
138130

139-
case ("POST", _) where req.path.hasSuffix("/test"):
131+
case ("GET", _) where req.path.hasSuffix("/history"):
140132
let idStr = req.path.components(separatedBy: "/").dropLast().last ?? ""
141-
guard let uuid = UUID(uuidString: idStr),
142-
let job = jm.jobs.first(where: { $0.id == uuid }) else {
143-
return json(404, ["error": "Job not found"])
144-
}
145-
await jm.dryRunJob(job)
146-
return json(200, ["status": "test_complete", "job": job.name])
133+
guard let uuid = UUID(uuidString: idStr) else { return json(400, ["error": "Invalid UUID"]) }
134+
let entries = hm.getHistory(for: uuid).map { e -> [String: Any] in [
135+
"id": e.id.uuidString,
136+
"startTime": ISO8601DateFormatter().string(from: e.timestamp),
137+
"status": e.status.rawValue,
138+
"filesTransferred": e.filesTransferred,
139+
"bytesTransferred": e.bytesTransferred
140+
]}
141+
return jsonArray(200, entries)
147142

148143
default:
149144
return json(404, ["error": "Not found: \(req.method) \(req.path)"])
@@ -157,7 +152,7 @@ class NovaAPIServer {
157152
guard let raw = String(data: data, encoding: .utf8), raw.contains("\r\n\r\n") else { return nil }
158153
let parts = raw.components(separatedBy: "\r\n\r\n"); let lines = parts[0].components(separatedBy: "\r\n")
159154
guard let rl = lines.first else { return nil }; let tokens = rl.components(separatedBy: " "); guard tokens.count >= 2 else { return nil }
160-
var hdrs: [String: String] = []; for l in lines.dropFirst() { let kv = l.components(separatedBy: ": "); if kv.count >= 2 { hdrs[kv[0].lowercased()] = kv.dropFirst().joined(separator: ": ") } }
155+
var hdrs: [String: String] = [:]; for l in lines.dropFirst() { let kv = l.components(separatedBy: ": "); if kv.count >= 2 { hdrs[kv[0].lowercased()] = kv.dropFirst().joined(separator: ": ") } }
161156
let rawBody = parts.dropFirst().joined(separator: "\r\n\r\n")
162157
if let cl = hdrs["content-length"], let n = Int(cl), rawBody.utf8.count < n { return nil }
163158
method = tokens[0]; path = tokens[1].components(separatedBy: "?").first ?? tokens[1]; body = rawBody

0 commit comments

Comments
 (0)