import Vapor import Foundation /// The decoder to extract caps from JSON payloads given to the `cap` route. private let decoder = JSONDecoder() /// The date formatter to decode dates in requests private let dateFormatter: DateFormatter = { let df = DateFormatter() df.dateFormat = "yy-MM-dd-HH-mm-ss" return df }() extension CapServer { private func ensureOperability() throws { guard isOperational else { throw Abort(.noContent) } } func registerRoutes(with app: Application, authenticator: Authenticator) { app.get("version") { _ in try self.ensureOperability() return "\(self.classifierVersion)" } // Add or change a cap app.postCatching("cap") { request in try self.ensureOperability() try authenticator.authorize(request) let data = try request.getBodyData(request: "/cap") let cap = try decoder.decode(Cap.self, from: data) try self.addOrUpdate(cap) } // Upload an image app.postCatching("image", .parameter("cap")) { request in try self.ensureOperability() try authenticator.authorize(request) guard let cap = request.parameters.get("cap", as: Int.self) else { log("/images/:cap: Invalid 'cap' parameter") throw Abort(.badRequest) } let data = try request.getBodyData(request: "/images/:cap") try self.save(image: data, for: cap) } // Update the classifier app.on(.POST, "classifier", ":version", body: .collect(maxSize: "50mb")) { request -> HTTPStatus in guard let version = request.parameters.get("version", as: Int.self) else { log("/classifier/:version: Invalid parameter for version") throw Abort(.badRequest) } guard version > self.classifierVersion else { throw Abort(.alreadyReported) // 208 } try self.ensureOperability() try authenticator.authorize(request) let data = try request.getBodyData(request: "/classifier/:version") try self.save(classifier: data, version: version) return .ok } // Update the trained classes app.postCatching("classes", ":date") { request in guard let dateString = request.parameters.get("date") else { log("/classes/:date: Invalid 'date' parameter") throw Abort(.badRequest) } guard let date = dateFormatter.date(from: dateString) else { log("/classes/:date: Invalid 'date' specification") throw Abort(.badRequest) } try self.ensureOperability() try authenticator.authorize(request) let body = try request.getStringBody(request: "/classes/:date") self.updateTrainedClasses(content: body) self.removeAllEntriesInImageChangeList(before: date) } // Get the list of missing thumbnails app.get("thumbnails", "missing") { request in try self.ensureOperability() let missingThumbnails = self.getListOfMissingThumbnails() return missingThumbnails.map(String.init).joined(separator: ",") } // Upload the thumbnail of a cap app.postCatching("thumbnails", ":cap") { request in guard let cap = request.parameters.get("cap", as: Int.self) else { log("/thumbnails/:cap: Invalid cap parameter for thumbnail upload") throw Abort(.badRequest) } try self.ensureOperability() try authenticator.authorize(request) let data = try request.getBodyData(request: "/thumbnails/:cap") self.saveThumbnail(data, for: cap) } // Delete the image of a cap app.postCatching("delete", ":cap", ":version") { request in guard let cap = request.parameters.get("cap", as: Int.self) else { log("/delete/:cap/:version: Invalid 'cap' parameter for image deletion") throw Abort(.badRequest) } guard let version = request.parameters.get("version", as: Int.self) else { log("/delete/:cap/:version: Invalid 'version' parameter for image deletion") throw Abort(.badRequest) } try self.ensureOperability() try authenticator.authorize(request) guard self.deleteImage(version: version, for: cap) else { throw Abort(.gone) } } } } private extension Request { func getBodyData(request: String) throws -> Data { guard let buffer = body.data else { log("\(request): Missing body data") throw CapError.invalidBody } return Data(buffer: buffer) } func getStringBody(request: String) throws -> String { let data = try getBodyData(request: request) guard let content = String(data: data, encoding: .utf8) else { log("\(request): Invalid string body") throw CapError.invalidBody } return content } }