Files
localTube/Modules/Server/src/api.cpp
2021-05-30 02:28:50 +02:00

214 lines
5.8 KiB
C++

//
// Created by s-Kaonnull on 25.05.2021.
//
#include "api.hpp"
#include "tables.hpp"
#include <filesystem>
#include <fstream>
#include <functional>
#include <ctre.hpp>
#include <fmt/chrono.h>
#include <fmt/ostream.h>
#include <nlohmann/json.hpp>
#include <openssl/md5.h>
#include <spdlog/spdlog.h>
using json = nlohmann::json;
Api::Api(soci::session& sql, httplib::Server& server) : sql(sql)
{
RegisterServerHandles(server);
}
void Api::RegisterServerHandles(httplib::Server& server)
{
spdlog::info("Register api endpoints...");
server.Post("/api/add", std::bind_front(&Api::add, this));
server.Get("/api/files", std::bind_front(&Api::files, this));
server.Get("/api/file/([A-E0-9]+)", std::bind_front(&Api::file, this));
server.Options("/(.*)", [this](httplib::Request const& rq, httplib::Response& rp) {});
server.set_pre_routing_handler([this](httplib::Request const& rq, httplib::Response& rp) {
this->set_cross_headers(rp);
spdlog::info("Request {} {}", rq.method, rq.path);
return httplib::Server::HandlerResponse::Unhandled;
});
}
/* /api/add
*
* body: json
* - url
* - audio_only - default false
* - format - optional default source
*
* return: json
* - status: bool
* - file_id: hash
* - error: string
*
*/
void Api::add(const httplib::Request& rq, httplib::Response& rs)
{
try
{
auto data = json::parse(rq.body);
// Parse Request
Task task;
task.url = data["url"];
task.audio_only = data.contains("audio_only") && data["audio_only"].get<bool>();
if (data.contains("format"))
task.format = data["format"].get<std::string>();
task.timestamp = std::time(nullptr);
task.hash = fmt::format(
"{:X}",
std::hash<std::string>()(
task.url + fmt::format("{:%Y-%m-%d %H:%M:%S}", *std::localtime(&task.timestamp))));
task.status = FileStatus::PENDING;
// Insert Sql
sql << "INSERT INTO Tasks (hash, timestamp, url, audio_only, status, format) "
"values(:hash, :timestamp, :url, :audio_only, :status, :format) ",
soci::use(task);
/*long long int tmp;
sql.get_last_insert_id("Files", tmp);
f.id = tmp;*/
json jr;
jr["status"] = "Ok";
jr["file_id"] = task.hash;
spdlog::info("New Task: {}", task);
rs.set_content(jr.dump(), "application/json");
}
catch (json::exception const& e)
{
spdlog::error("Api Error: {}", e.what());
json jr;
jr["status"] = "Err";
jr["error"] = e.what();
rs.status = 400;
rs.set_content(jr.dump(), "application/json");
}
}
void Api::files(httplib::Request const& rq, httplib::Response& rs)
{
try
{
json res;
// Query incomplete Tasks
soci::rowset<Task> data = (sql.prepare << "SELECT * "
"FROM Tasks "
"WHERE status!=2 "
"ORDER BY id DESC ");
for (auto const& el : data)
{
json current;
current["hash"] = el.hash;
current["status"] = magic_enum::enum_name(el.status);
current["status_id"] = static_cast<int>(el.status);
current["source"] = el.url;
current["name"] = fmt::format("File {}", el.id);
current["audio_only"] = el.audio_only;
current["format"] = el.format.value_or("Source");
res["queue"].push_back(current);
}
// Downloaded
soci::rowset<soci::row> files =
(sql.prepare
<< R"(SELECT Tasks.id, Files.timestamp, Files.hash, audio_only, filename, Files.format
FROM Files,
Tasks
WHERE Tasks.hash == Files.hash
ORDER BY Files.timestamp DESC )");
for (auto const& el : files)
{
json current;
std::time_t time = el.get<int>("timestamp");
current["timestamp"] = fmt::format("{:%Y-%m-%d %H:%M:%S}", *std::localtime(&time));
current["hash"] = el.get<std::string>("hash");
current["audio_only"] = el.get<int>("audio_only");
current["filename"] = el.get<std::string>("filename");
current["format"] = el.get<std::string>("format");
res["files"].push_back(current);
}
rs.set_content(res.dump(), "application/json");
}
catch (std::exception const& e)
{
spdlog::error(e.what());
throw e;
}
}
void Api::file(const httplib::Request& rq, httplib::Response& rs)
{
if (!rq.matches[1].matched)
{
rs.status = 404;
rs.set_content("Parameter error", "text/plain");
return;
}
auto file = rq.matches[1];
spdlog::info("File Request: {}", file);
try
{
File dw;
sql << fmt::format("SELECT * FROM Files WHERE hash='{}';", file), soci::into(dw);
std::ifstream fs(dw.local_path, std::ios_base::binary);
fs.seekg(0, std::ios_base::end);
auto size = fs.tellg();
fs.seekg(0);
rs.body.resize(static_cast<size_t>(size));
fs.read(&rs.body[0], static_cast<std::streamsize>(size));
rs.set_header("Content-Disposition", fmt::format("attachment; filename={};", dw.filename));
rs.set_header("content-type", fmt::format("application/{}", dw.format));
}
catch (std::exception const& e)
{
spdlog::error(e.what());
rs.status = 404;
rs.set_content("File Not Found", "text/plain");
}
}
void Api::set_cross_headers(httplib::Response& rs)
{
rs.set_header("Access-Control-Allow-Origin", "*");
rs.set_header("Access-Control-Allow-Methods", "GET, POST, PUT");
rs.set_header("Access-Control-Allow-Headers", "Content-Type");
}