95 lines
2.8 KiB
Swift
95 lines
2.8 KiB
Swift
import Foundation
|
|
import Observation
|
|
|
|
struct ProjectRow: Decodable {
|
|
let id: String
|
|
let name: String
|
|
}
|
|
|
|
struct TaskRow: Decodable, Hashable {
|
|
let id: String
|
|
let title: String
|
|
let status: String
|
|
}
|
|
|
|
struct BlobRow: Decodable {
|
|
let id: String
|
|
}
|
|
|
|
struct SpikeError: Error, CustomStringConvertible {
|
|
let description: String
|
|
}
|
|
|
|
@MainActor
|
|
@Observable
|
|
final class Server {
|
|
var origin = UserDefaults.standard.string(forKey: "byre.server") ?? ""
|
|
var tasks: [TaskRow] = []
|
|
var taskId = ""
|
|
var status = ""
|
|
var lines: [String] = []
|
|
|
|
func log(_ line: String) {
|
|
lines.append(line)
|
|
}
|
|
|
|
private var base: URLComponents?
|
|
|
|
func url(_ path: String, _ query: [URLQueryItem] = []) throws -> URL {
|
|
guard var c = base else {
|
|
throw SpikeError(description: "not connected")
|
|
}
|
|
c.path = path
|
|
if !query.isEmpty {
|
|
c.queryItems = query
|
|
}
|
|
guard let u = c.url else {
|
|
throw SpikeError(description: "bad URL for \(path)")
|
|
}
|
|
return u
|
|
}
|
|
|
|
func data(_ request: URLRequest) async throws -> Data {
|
|
let (data, response) = try await URLSession.shared.data(for: request)
|
|
let code = (response as! HTTPURLResponse).statusCode
|
|
if !(200..<300).contains(code) {
|
|
throw SpikeError(description: "\(code) \(String(decoding: data, as: UTF8.self))")
|
|
}
|
|
return data
|
|
}
|
|
|
|
func get<T: Decodable>(_ type: T.Type, _ path: String, _ query: [URLQueryItem] = []) async throws -> T {
|
|
try JSONDecoder().decode(T.self, from: try await data(URLRequest(url: try url(path, query))))
|
|
}
|
|
|
|
func connect() async {
|
|
guard let c = URLComponents(string: origin), c.scheme == "https", let host = c.host, !host.isEmpty, c.path.isEmpty || c.path == "/", c.url != nil else {
|
|
base = nil
|
|
status = "Server URL must be an https origin with no path, such as https://host.ts.net:8270"
|
|
return
|
|
}
|
|
base = c
|
|
UserDefaults.standard.set(origin, forKey: "byre.server")
|
|
do {
|
|
var all: [TaskRow] = []
|
|
for p in try await get([ProjectRow].self, "/api/projects") {
|
|
all += try await get([TaskRow].self, "/api/projects/\(p.id)/tasks")
|
|
}
|
|
tasks = all.filter { $0.status == "active" } + all.filter { $0.status != "active" }
|
|
if !tasks.contains(where: { $0.id == taskId }) {
|
|
taskId = tasks.first?.id ?? ""
|
|
}
|
|
status = "\(tasks.count) tasks"
|
|
} catch {
|
|
status = "\(error)"
|
|
}
|
|
}
|
|
|
|
func upload(_ image: Data) async throws -> String {
|
|
var req = URLRequest(url: try url("/api/blobs"))
|
|
req.httpMethod = "POST"
|
|
req.httpBody = image
|
|
return try JSONDecoder().decode(BlobRow.self, from: try await data(req)).id
|
|
}
|
|
}
|