|
|
@@ -0,0 +1,84 @@
|
|
|
+#include "file_controller.hpp"
|
|
|
+#include "logging/logger.hpp"
|
|
|
+
|
|
|
+namespace smartbotic::webserver::api {
|
|
|
+
|
|
|
+FileController::FileController(storage::StorageClient& storage,
|
|
|
+ auth::AuthMiddleware& middleware)
|
|
|
+ : storage_(storage), middleware_(middleware) {}
|
|
|
+
|
|
|
+void FileController::registerRoutes(httplib::Server& server) {
|
|
|
+ // Raw bytes, for rendering a stored file directly
|
|
|
+ server.Get(R"(/api/v1/files/([^/]+))", [this](const httplib::Request& req, httplib::Response& res) {
|
|
|
+ middleware_.requireAuth(req, res, [this](auto& req, auto& res, auto& ctx) {
|
|
|
+ getFile(req, res, ctx);
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ // Metadata only, for deciding whether something is worth rendering
|
|
|
+ server.Get(R"(/api/v1/files/([^/]+)/info)", [this](const httplib::Request& req, httplib::Response& res) {
|
|
|
+ middleware_.requireAuth(req, res, [this](auto& req, auto& res, auto& ctx) {
|
|
|
+ getFileInfo(req, res, ctx);
|
|
|
+ });
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
+void FileController::getFile(const httplib::Request& req, httplib::Response& res,
|
|
|
+ const auth::AuthContext& ctx) {
|
|
|
+ (void)ctx;
|
|
|
+ const std::string id = req.matches[1];
|
|
|
+
|
|
|
+ // The mime type comes from the record rather than being guessed, so the
|
|
|
+ // browser renders the file as whatever it was stored as.
|
|
|
+ std::string mime_type = "application/octet-stream";
|
|
|
+ auto info = storage_.getFileInfo(id);
|
|
|
+ if (info.ok() && !info.value().mime_type.empty()) {
|
|
|
+ mime_type = info.value().mime_type;
|
|
|
+ }
|
|
|
+
|
|
|
+ auto content = storage_.downloadFile(id);
|
|
|
+ if (content.failed()) {
|
|
|
+ sendError(res, "File not found", 404);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const auto& bytes = content.value();
|
|
|
+ res.set_content(reinterpret_cast<const char*>(bytes.data()), bytes.size(), mime_type);
|
|
|
+
|
|
|
+ // Stored files are addressed by an immutable id, so they can be cached hard.
|
|
|
+ res.set_header("Cache-Control", "private, max-age=86400");
|
|
|
+}
|
|
|
+
|
|
|
+void FileController::getFileInfo(const httplib::Request& req, httplib::Response& res,
|
|
|
+ const auth::AuthContext& ctx) {
|
|
|
+ (void)ctx;
|
|
|
+ const std::string id = req.matches[1];
|
|
|
+
|
|
|
+ auto info = storage_.getFileInfo(id);
|
|
|
+ if (info.failed()) {
|
|
|
+ sendError(res, "File not found", 404);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const auto& f = info.value();
|
|
|
+ nlohmann::json body = {
|
|
|
+ {"id", f.id},
|
|
|
+ {"name", f.name},
|
|
|
+ {"mimeType", f.mime_type},
|
|
|
+ {"size", f.size},
|
|
|
+ {"fileType", f.file_type},
|
|
|
+ {"relatedId", f.related_id},
|
|
|
+ {"checksum", f.checksum},
|
|
|
+ {"refCount", f.ref_count},
|
|
|
+ {"createdAt", f.created_at}
|
|
|
+ };
|
|
|
+ res.set_content(body.dump(), "application/json");
|
|
|
+}
|
|
|
+
|
|
|
+void FileController::sendError(httplib::Response& res, const std::string& message, int status) {
|
|
|
+ nlohmann::json body = {{"error", message}};
|
|
|
+ res.status = status;
|
|
|
+ res.set_content(body.dump(), "application/json");
|
|
|
+}
|
|
|
+
|
|
|
+} // namespace smartbotic::webserver::api
|