Older/Server/Application.cpp
2024-05-05 22:00:15 +08:00

203 lines
8.6 KiB
C++

#include "Application.h"
#include "Database.h"
#include "DateTime.h"
#include "HttpSession.h"
#include "IoContext.h"
#include "ServiceLogic.h"
#include "ServiceManager.h"
#include "SystemUsage.h"
#include "WeChatContext/CorporationContext.h"
#include <boost/stacktrace.hpp>
Application::Application(const std::string &path)
: ApplicationSettings(path), m_router{std::make_shared<boost::urls::router<RequestHandler>>()} {
// clang-format off
m_router->insert("/{path*}",[this](HttpSession &session, const Request &request, const boost::urls::matches &matches) {
using namespace boost::beast;
boost::urls::url_view view(request.target());
auto target = view.path();
LOG(info) << target;
if (target.find("..") != boost::beast::string_view::npos) {
session.reply(ServiceLogic::badRequest(request, "Illegal request-target"));
return;
}
std::string path = ResponseUtility::pathCat(getDocumentRoot(), target);
if (target.back() == '/') path.append("index.html");
if (std::filesystem::is_directory(path)) path.append("/index.html");
boost::beast::error_code ec;
http::file_body::value_type body;
body.open(path.c_str(), boost::beast::file_mode::scan, ec);
if (ec == boost::beast::errc::no_such_file_or_directory) {
std::ostringstream oss;
oss << "The resource '" << target << "' was not found.";
LOG(error) << oss.str();
session.errorReply(request, http::status::not_found, oss.str());
return;
} else if (ec) {
session.reply(ServiceLogic::serverError(request, ec.message()));
return;
}
auto const size = body.size();
http::response<http::file_body> res{std::piecewise_construct, std::make_tuple(std::move(body)),
std::make_tuple(http::status::ok, request.version())};
res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
res.set(http::field::content_type, ResponseUtility::mimeType(path));
res.content_length(size);
res.keep_alive(request.keep_alive());
session.reply(std::move(res));
});
m_router->insert("/wechat/{session*}",[this](HttpSession &session, const Request &request, const boost::urls::matches &matches) {
ServiceLogic::onWechat(shared_from_this(), request,
[&session](auto &&response) { session.reply(std::move(response)); });
});
m_router->insert("/api/v1/tasklist", [this](HttpSession &session, const Request &request, const boost::urls::matches &matches) {
using namespace boost::beast;
auto database = Amass::Singleton<Database>::instance();
auto tasks = database->tasks();
http::response<boost::beast::http::string_body> s{boost::beast::http::status::ok, request.version()};
s.set(http::field::server, BOOST_BEAST_VERSION_STRING);
s.set(http::field::content_type, "application/json;charset=UTF-8");
s.keep_alive(request.keep_alive());
s.body() = boost::json::serialize(tasks);
s.prepare_payload();
session.reply(std::move(s));
});
m_router->insert("/api/v1/task/add", [this](HttpSession &session, const Request &request, const boost::urls::matches &matches) {
using namespace boost::beast;
LOG(info) << "add task: " << request.body();
auto database = Amass::Singleton<Database>::instance();
auto rootJson = boost::json::parse(request.body());
auto &root = rootJson.as_object();
std::string content;
if (root.contains("content")) {
content = root.at("content").as_string();
}
bool ret = database->addTask(root.at("createTime").as_int64(), content,
std::string(root.at("comment").as_string()), root.at("parentId").as_int64());
boost::json::object reply;
reply["status"] = ret ? 0 : -1;
http::response<boost::beast::http::string_body> s{boost::beast::http::status::ok, request.version()};
s.set(http::field::server, BOOST_BEAST_VERSION_STRING);
s.set(http::field::content_type, "application/json;charset=UTF-8");
s.keep_alive(request.keep_alive());
s.body() = boost::json::serialize(reply);
s.prepare_payload();
session.reply(std::move(s));
});
m_router->insert("/api/v1/task/delete/{id}", [this](HttpSession &session, const Request &request,const boost::urls::matches &matches) {
using namespace boost::beast;
LOG(info) << "delete task: " << matches.at("id");
auto database = Amass::Singleton<Database>::instance();
auto status = database->removeTask(std::stoi(matches.at("id")));
boost::json::object reply;
reply["status"] = status ? 0 : -1;
http::response<boost::beast::http::string_body> s{boost::beast::http::status::ok, request.version()};
s.set(http::field::server, BOOST_BEAST_VERSION_STRING);
s.set(http::field::content_type, "application/json;charset=UTF-8");
s.keep_alive(request.keep_alive());
s.body() = boost::json::serialize(reply);
s.prepare_payload();
session.reply(std::move(s));
});
m_router->insert("/trigger-ci.hook", [this](HttpSession &session, const Request &request, const boost::urls::matches &matches) {
LOG(info) << "webhook: " << request;
session.reply(ServiceLogic::make_200<boost::beast::http::string_body>(request, "Main page\n", "text/html"));
});
m_router->insert("/notify", [this](HttpSession &session, const Request &request, const boost::urls::matches &matches) {
auto corp = Amass::Singleton<CorporationContext>::instance();
corp->notify(request);
session.reply(
ServiceLogic::make_200<boost::beast::http::string_body>(request, "notify successed.\n", "text/html"));
});
// clang-format on
m_ioContext = Amass::Singleton<IoContext>::instance<Amass::Construct>(getThreads());
m_timer = std::make_shared<boost::asio::system_timer>(*m_ioContext->ioContext());
m_systemUsage = std::make_shared<SystemUsage>(*m_ioContext->ioContext(), getHomeAssistantAccessToken());
m_systemUsage->start();
alarmTask();
}
boost::asio::io_context &Application::ioContext() {
return *m_ioContext->ioContext();
}
const Application::RequestHandler *Application::find(boost::urls::segments_encoded_view path,
boost::urls::matches_base &matches) const noexcept {
const Application::RequestHandler *ret = nullptr;
try {
ret = m_router->find(path, matches);
} catch (const std::exception &e) {
boost::stacktrace::stacktrace trace = boost::stacktrace::stacktrace::from_current_exception();
LOG(error) << e.what() << ", trace:\n" << trace;
}
return ret;
}
int Application::exec() {
LOG(info) << "application start successful ...";
startCheckInterval(*m_ioContext->ioContext(), 2);
m_ioContext->run<IoContext::Mode::Synchronous>();
LOG(info) << "application exit successful ...";
return m_status;
}
void Application::alarmTask() {
int hour = 10;
int minute = 30;
auto alarmTime = DateTime::currentDateTime();
alarmTime.setHour(hour);
alarmTime.setMinute(minute);
if (std::chrono::system_clock::now() > alarmTime()) {
alarmTime = alarmTime.tomorrow();
}
m_timer->expires_at(alarmTime());
m_timer->async_wait([this](const boost::system::error_code &error) mutable {
if (error) {
LOG(error) << error.message();
return;
}
auto database = Amass::Singleton<Database>::instance();
auto tasks = database->tasks();
bool founded = false;
std::string content;
for (auto &task : tasks) {
if (founded) break;
for (auto &child : task.children) {
if (!child.finished) {
content = child.content;
founded = true;
break;
}
}
if (!founded && !task.finished) {
content = task.content;
founded = true;
}
}
if (founded) {
std::ostringstream oss;
oss << "待完成事项:" << std::endl;
oss << "==========" << std::endl;
oss << content << std::endl;
oss << "==========" << std::endl;
oss << "每天都要过得充实开心哦~";
auto manager = Amass::Singleton<ServiceManager>::instance();
if (manager) manager->sendMessage(NotifyServerChan, oss.str());
}
alarmTask();
});
}