diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index e69c5ac..3142c8e 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -26,7 +26,6 @@ build_backend:
artifacts:
paths:
- server
- expire_in: 1h
test_and_build_frontend:
image: node:latest
@@ -44,7 +43,6 @@ test_and_build_frontend:
artifacts:
paths:
- frontend/dist/
- expire_in: 1h
package_server:
stage: package
diff --git a/backend/.idea/cmake.xml b/backend/.idea/cmake.xml
new file mode 100644
index 0000000..590a81f
--- /dev/null
+++ b/backend/.idea/cmake.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/backend/.idea/dataSources.xml b/backend/.idea/dataSources.xml
index 61eb0a1..78eab0c 100644
--- a/backend/.idea/dataSources.xml
+++ b/backend/.idea/dataSources.xml
@@ -1,18 +1,11 @@
-
- sqlite.xerial
- true
- org.sqlite.JDBC
- jdbc:sqlite:$PROJECT_DIR$/old_backend/sqlite.db
- $ProjectFileDir$
-
sqlite.xerial
true
org.sqlite.JDBC
- jdbc:sqlite:$PROJECT_DIR$/run/sqlite.db
+ jdbc:sqlite:$PROJECT_DIR$/../run/sqlite.db
$ProjectFileDir$
diff --git a/backend/CMakeLists.txt b/backend/CMakeLists.txt
index 643a33b..2ed6e20 100644
--- a/backend/CMakeLists.txt
+++ b/backend/CMakeLists.txt
@@ -1,4 +1,10 @@
-cmake_minimum_required(VERSION 3.20)
+cmake_minimum_required(VERSION 3.21)
+
+if (WIN32 AND (NOT VCPKG_TARGET_TRIPLET))
+ set(VCPKG_TARGET_TRIPLET x64-windows-static)
+endif (WIN32 AND (NOT VCPKG_TARGET_TRIPLET))
+#set(VCPKG_LIBRARY_LINKAGE static)
+
project(backend)
set(CMAKE_CXX_STANDARD 20)
@@ -13,13 +19,6 @@ add_executable(backend
src/db/db.h
src/db/db.cpp
- src/db/model/Inode.cc
- src/db/model/Inode.h
- src/db/model/Tokens.cc
- src/db/model/Tokens.h
- src/db/model/User.cc
- src/db/model/User.h
-
src/controllers/controllers.h
src/controllers/admin.cpp
src/controllers/fs.cpp
@@ -32,14 +31,23 @@ add_executable(backend
src/filters/filters.h
src/filters/filters.cpp
+
+ model/Inode.cc
+ model/Inode.h
+ model/Tokens.cc
+ model/Tokens.h
+ model/User.cc
+ model/User.h
+
+ SMTPMail-drogon-master/SMTPMail.cc
)
find_package(Drogon CONFIG REQUIRED)
-find_package(mailio CONFIG REQUIRED)
-find_package(lodepng CONFIG REQUIRED)
find_package(OpenSSL REQUIRED)
+find_package(OpenCV CONFIG REQUIRED)
+find_package(kubazip CONFIG REQUIRED)
find_path(JWT_CPP_INCLUDE_DIRS "jwt-cpp/base.h")
find_path(BOTAN_INCLUDE_DIRS "botan/botan.h")
find_path(QR_INCLUDE_DIRS "qrcodegen.hpp")
@@ -48,6 +56,10 @@ find_library(QR_LIBRARY nayuki-qr-code-generator)
target_include_directories(backend PRIVATE
src
+ model
+ shl
+ SMTPMail-drogon-master
+ ${OpenCV_INCLUDE_DIRS}
${JWT_CPP_INCLUDE_DIRS}
${BOTAN_INCLUDE_DIRS}
${QR_INCLUDE_DIRS}
@@ -55,14 +67,17 @@ target_include_directories(backend PRIVATE
target_link_libraries(backend
Drogon::Drogon
- mailio
- lodepng
OpenSSL::SSL
+ kubazip::kubazip
+ ${OpenCV_LIBS}
${BOTAN_LIBRARY}
${QR_LIBRARY}
)
-install(TARGETS backend)
+set_property(TARGET backend PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>")
+
+install(TARGETS backend RUNTIME_DEPENDENCY_SET backend_deps DESTINATION .)
+install(RUNTIME_DEPENDENCY_SET backend_deps)
if(NOT MSVC)
target_compile_options(backend PRIVATE
@@ -74,5 +89,6 @@ else()
endif(NOT MSVC)
if(WIN32)
+ target_link_libraries(backend iphlpapi)
target_compile_definitions(backend PRIVATE NOMINMAX _WIN32_WINNT=0x0A00)
endif()
diff --git a/backend/SMTPMail-drogon-master/LICENSE b/backend/SMTPMail-drogon-master/LICENSE
new file mode 100644
index 0000000..bd8c790
--- /dev/null
+++ b/backend/SMTPMail-drogon-master/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2020 ihmc3jn09hk
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/backend/SMTPMail-drogon-master/README.md b/backend/SMTPMail-drogon-master/README.md
new file mode 100644
index 0000000..5463867
--- /dev/null
+++ b/backend/SMTPMail-drogon-master/README.md
@@ -0,0 +1,79 @@
+# SMTPMail-drogon
+Simple Mail for the Drogon framework.
+
+It is made as a plugin for the [drogon](https://github.com/an-tao/drogon) framework.
+It can be included into the drogon build with little
+modification of the class declaration.
+## Updates
+- **[ 13-06-2022 ] Fixed vulnerability issues reported by [Sam](https://snoopysecurity.github.io/about).**
+- [ 13-09-2021 ] Added [HTML content support](https://github.com/ihmc3jn09hk/SMTPMail-drogon/pull/1).
+- [ 23-12-2020 ] Added DNS support.
+
+## Acknowledgement
+* The implementation takes SMTPClient for Qt from [kelvins](https://github.com/kelvins/SMTPClient) as reference.
+* There requires a delay SSL encryption from the Tcp-socket (named TcpClient in trantor/drogon) and the major
+author of drogon [reponsed](https://github.com/an-tao/drogon/issues/346) quickly.
+
+## Usage
+Download to the plugin directory of the target drogon app, E.g. ~/drogon-app/plugins
+```bash
+$ git clone https://github.com/ihmc3jn09hk/SMTPMail-drogon.git
+$ cp SMTPMail-drogon/SMTPMail.* ~/drogon-app/plugins
+```
+
+* _Be aware of add the plugin into the config.json. Set the "name" field to "SMTPMail"_
+
+Add the reference header and get the plugin from the app(), E.g.
+
+```c++
+...
+#include "../plugins/SMTPMail.h"
+...
+
+//Inside some function, E.g. A controller function.
+...
+//Send an email
+auto *smtpmailPtr = app().getPlugin();
+auto id = smtpmailPtr->sendEmail(
+ "127.0.0.1", //The server IP/DNS
+ 587, //The port
+ "mailer@something.com", //Who send the email
+ "receiver@otherthing.com", //Send to whom
+ "Testing SMTPMail Function", //Email Subject/Title
+ "Hello from drogon plugin", //Content
+ "mailer@something.com", //Login user
+ "123456", //User password
+ false //Is HTML content
+ );
+...
+//Or get noted when email is sent
+...
+void callback(const std::string &msg)
+{
+ LOG_INFO << msg; /*Output e.g. "EMail sent. ID : 96ESERVDDFH17588ECF0C7B00326E3"*/
+ /*Do whatever you like*/
+}
+...
+auto *smtpmailPtr = app().getPlugin();
+auto id = smtpmailPtr->sendEmail(
+ "127.0.0.1", //The server IP/DNS
+ 587, //The port
+ "mailer@something.com", //Who send the email
+ "receiver@otherthing.com", //Send to whom
+ "Testing SMTPMail Function", //Email Subject/Title
+ "Hello from drogon plugin", //Content
+ "mailer@something.com", //Login user
+ "123456", //User password
+ false, //Is HTML content
+ callback //Callback
+ );
+```
+
+```bash
+$ cd ~/drogon-app/build
+$ make
+```
+
+## Licence
+* Feel free to use, thanks to open-source.
+* For the sake of concern on commercial usage, a simple licence is included in each of the files.
diff --git a/backend/SMTPMail-drogon-master/SMTPMail.cc b/backend/SMTPMail-drogon-master/SMTPMail.cc
new file mode 100644
index 0000000..8b105c2
--- /dev/null
+++ b/backend/SMTPMail-drogon-master/SMTPMail.cc
@@ -0,0 +1,400 @@
+/**
+*
+* SMTPMail.cc *
+*
+* This plugin is for SMTP mail delivery for the Drogon web-framework.
+Implementation
+* reference from the project "SMTPClient" with Qt5 by kelvins. Please check out
+* https://github.com/kelvins/SMTPClient.
+
+Feel free to use the code. For the sake of any concern, the following licence is
+attached.
+
+ Copyright 2020 ihmc3jn09hk
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+ */
+
+#include "SMTPMail.h"
+#include
+#include
+#include
+#include
+
+using namespace drogon;
+using namespace trantor;
+
+struct EMail {
+ enum states {
+ Init,
+ HandShake,
+ Tls,
+ Auth,
+ User,
+ Pass,
+ Mail,
+ Rcpt,
+ Data,
+ Body,
+ Quit,
+ Close
+ };
+ std::string m_from;
+ std::string m_to;
+ std::string m_subject;
+ std::string m_content;
+ std::string m_user;
+ std::string m_passwd;
+ states m_status;
+ std::string m_uuid;
+ bool m_isHTML{false};
+ std::shared_ptr m_socket;
+
+ EMail(std::string from, std::string to, std::string subject,
+ std::string content, std::string user, std::string passwd, bool isHTML,
+ std::shared_ptr socket)
+ : m_from(std::move(from)), m_to(std::move(to)),
+ m_subject(std::move(subject)), m_content(std::move(content)),
+ m_user(std::move(user)), m_passwd(std::move(passwd)),
+ m_socket(std::move(socket)), m_isHTML(isHTML),
+ m_uuid(drogon::utils::getUuid()) {
+ m_status = Init;
+ }
+
+ ~EMail() = default;
+
+ static std::unordered_map>
+ m_emails; // Container for processing emails
+};
+
+std::unordered_map> EMail::m_emails;
+
+void SMTPMail::initAndStart(const Json::Value &config) {
+ /// Initialize and start the plugin
+ LOG_INFO << "SMTPMail initialized and started";
+}
+
+void SMTPMail::shutdown() {
+ /// Shutdown the plugin
+ LOG_INFO << "STMPMail shutdown";
+}
+
+void messagesHandle(const trantor::TcpConnectionPtr &connPtr,
+ trantor::MsgBuffer *msg,
+ const std::shared_ptr &email,
+ const std::function &cb) {
+ std::string receivedMsg;
+ while (msg->readableBytes() > 0) {
+ std::string buf(msg->peek(), msg->readableBytes());
+ receivedMsg.append(buf);
+ // LOG_INFO << buf;
+ msg->retrieveAll();
+ }
+ LOG_TRACE << "receive: " << receivedMsg;
+ std::string responseCode(receivedMsg.begin(), receivedMsg.begin() + 3);
+ // std::string responseMsg(receivedMsg.begin() + 4, receivedMsg.end());
+
+ if (email->m_status == EMail::Init && responseCode == "220") {
+ std::string outMsg;
+ trantor::MsgBuffer out;
+
+ outMsg.append("EHLO smtpclient.qw");
+ outMsg.append("\r\n");
+
+ out.append(outMsg.data(), outMsg.size());
+
+ connPtr->send(std::move(out));
+
+ email->m_status = EMail::HandShake;
+ } else if (email->m_status == EMail::HandShake && responseCode == "220") {
+ std::string outMsg;
+ trantor::MsgBuffer out;
+
+ outMsg.append("EHLO smtpclient.qw");
+ outMsg.append("\r\n");
+
+ out.append(outMsg.data(), outMsg.size());
+
+ connPtr->startClientEncryption(
+ [connPtr, out]() {
+ // LOG_TRACE << "SSL established";
+ connPtr->send(out);
+ },
+ false, false);
+
+ email->m_status = EMail::Auth;
+ } else if (email->m_status == EMail::HandShake && responseCode == "250") {
+ std::string outMsg;
+ trantor::MsgBuffer out;
+
+ outMsg.append("STARTTLS");
+ outMsg.append("\r\n");
+
+ out.append(outMsg.data(), outMsg.size());
+
+ connPtr->send(std::move(out));
+
+ email->m_status = EMail::HandShake;
+ } else if (email->m_status == EMail::Auth && responseCode == "250") {
+ trantor::MsgBuffer out;
+ std::string outMsg;
+
+ outMsg.append("AUTH LOGIN");
+ outMsg.append("\r\n");
+
+ out.append(outMsg.data(), outMsg.size());
+
+ connPtr->send(std::move(out));
+
+ email->m_status = EMail::User;
+ } else if (email->m_status == EMail::User && responseCode == "334") {
+ trantor::MsgBuffer out;
+ std::string outMsg;
+
+ std::string secret(email->m_user);
+
+ // outMsg.append(base64_encode(reinterpret_cast(secret.c_str()), secret.length()));
+ outMsg.append(drogon::utils::base64Encode(
+ reinterpret_cast(secret.c_str()),
+ secret.length()));
+
+ outMsg.append("\r\n");
+
+ out.append(outMsg.data(), outMsg.size());
+
+ connPtr->send(std::move(out));
+
+ email->m_status = EMail::Pass;
+ } else if (email->m_status == EMail::Pass && responseCode == "334") {
+ trantor::MsgBuffer out;
+ std::string outMsg;
+
+ std::string secret(email->m_passwd);
+
+ outMsg.append(drogon::utils::base64Encode(
+ reinterpret_cast(secret.c_str()),
+ secret.length()));
+ outMsg.append("\r\n");
+
+ out.append(outMsg.data(), outMsg.size());
+
+ connPtr->send(std::move(out));
+
+ email->m_status = EMail::Mail;
+ } else if (email->m_status == EMail::Mail && responseCode == "235") {
+ trantor::MsgBuffer out;
+ std::string outMsg;
+
+ outMsg.append("MAIL FROM:<");
+ outMsg.append(email->m_from);
+ outMsg.append(">\r\n");
+
+ out.append(outMsg.data(), outMsg.size());
+
+ connPtr->send(std::move(out));
+
+ email->m_status = EMail::Rcpt;
+ } else if (email->m_status == EMail::Rcpt && responseCode == "250") {
+ trantor::MsgBuffer out;
+ std::string outMsg;
+
+ outMsg.append("RCPT TO:<");
+ outMsg.append(email->m_to);
+ outMsg.append(">\r\n");
+
+ out.append(outMsg.data(), outMsg.size());
+
+ connPtr->send(std::move(out));
+
+ email->m_status = EMail::Data;
+ } else if (email->m_status == EMail::Data && responseCode == "250") {
+ trantor::MsgBuffer out;
+ std::string outMsg;
+
+ outMsg.append("DATA");
+ outMsg.append("\r\n");
+
+ out.append(outMsg.data(), outMsg.size());
+
+ connPtr->send(std::move(out));
+
+ email->m_status = EMail::Body;
+ } else if (email->m_status == EMail::Body && responseCode == "354") {
+ trantor::MsgBuffer out;
+ std::string outMsg;
+ std::time_t t = std::time(nullptr);
+ char buf[100];
+ std::strftime(buf, 100, "%a, %d %b %Y %T %z", std::localtime(&t));
+
+ outMsg.append("To: " + email->m_to + "\r\n");
+ outMsg.append("From: " + email->m_from + "\r\n");
+ outMsg.append("Date: " + std::string(buf) + "\r\n");
+ if (email->m_isHTML) {
+ outMsg.append("Content-Type: text/html;\r\n");
+ }
+ outMsg.append("Subject: " + email->m_subject + "\r\n\r\n");
+
+ outMsg.append(email->m_content);
+ outMsg.append("\r\n.\r\n");
+
+ out.append(outMsg.data(), outMsg.size());
+
+ connPtr->send(std::move(out));
+
+ email->m_status = EMail::Quit;
+ } else if (email->m_status == EMail::Quit && responseCode == "250") {
+ trantor::MsgBuffer out;
+ std::string outMsg;
+
+ outMsg.append("QUIT");
+ outMsg.append("\r\n");
+
+ out.append(outMsg.data(), outMsg.size());
+
+ connPtr->send(std::move(out));
+
+ email->m_status = EMail::Close;
+ } else if (email->m_status == EMail::Close) {
+ /*Callback here for succeed delivery is probable*/
+ cb("EMail sent. ID : " + email->m_uuid);
+ return;
+ } else {
+ email->m_status = EMail::Close;
+ /*Callback here for notification is probable*/
+ cb(receivedMsg);
+ }
+}
+
+std::string
+SMTPMail::sendEmail(const std::string &mailServer, const uint16_t &port,
+ const std::string &from, const std::string &to,
+ const std::string &subject, const std::string &content,
+ const std::string &user, const std::string &passwd,
+ bool isHTML,
+ const std::function &cb) {
+ if (mailServer.empty() || from.empty() || to.empty() || subject.empty() ||
+ user.empty() || passwd.empty()) {
+ LOG_WARN << "Invalid input(s) - "
+ << "\nServer : " << mailServer << "\nPort : " << port
+ << "\nfrom : " << from << "\nto : " << to
+ << "\nsubject : " << subject << "\nuser : " << user
+ << "\npasswd : " << passwd;
+ return {};
+ }
+
+ static auto hasLineBreak = [](const std::string &msg) {
+ if (std::string::npos != msg.find_first_of("\n") ||
+ std::string::npos != msg.find_first_of("\r")) {
+ return true;
+ }
+ return false;
+ };
+
+ if (hasLineBreak(from)) {
+ LOG_WARN << "Invalid \"FROM\" data : " << from;
+ return {};
+ }
+ if (hasLineBreak(to)) {
+ LOG_WARN << "Invalid \"TO\" data : " << to;
+ return {};
+ }
+ if (hasLineBreak(subject)) {
+ LOG_WARN << "Invalid \"SUBJECT\" data : " << subject.data();
+ return {};
+ }
+
+ LOG_TRACE << "New TcpClient : " << mailServer << ":" << port;
+
+ // Create the email
+ auto email = std::make_shared(from, to, subject, content, user, passwd,
+ isHTML, nullptr);
+
+ auto resolver = app().getResolver();
+ resolver->resolve(
+ mailServer, [email, port, cb](const trantor::InetAddress &addr) {
+ constexpr size_t defaultLoopIdA = 10;
+ constexpr size_t defaultLoopIdB = 9;
+ auto loopA = app().getIOLoop(defaultLoopIdA);
+ auto loopB = app().getIOLoop(defaultLoopIdB);
+
+ if ( loopA == loopB ) {
+ LOG_WARN << "Please provide at least 2 threads for this plugin";
+ return;
+ }
+
+ auto loop = loopA->isInLoopThread() ? loopB : loopA;
+
+ assert(loop); // Should never be null
+ trantor::InetAddress addr_(addr.toIp(), port, false);
+ auto tcpSocket =
+ std::make_shared(loop, addr_, "SMTPMail");
+
+ email->m_socket = tcpSocket;
+
+ std::weak_ptr email_wptr = email;
+
+ EMail::m_emails.emplace(email->m_uuid,
+ email); // Assuming there is no uuid collision
+ tcpSocket->setConnectionCallback(
+ [email_wptr](const trantor::TcpConnectionPtr &connPtr) {
+ auto email_ptr = email_wptr.lock();
+ if (!email_ptr) {
+ LOG_WARN << "EMail pointer gone";
+ return;
+ }
+ if (connPtr->connected()) {
+ // send request;
+ LOG_TRACE << "Connection established!";
+ } else {
+ LOG_TRACE << "Connection disconnect";
+ EMail::m_emails.erase(
+ email_ptr->m_uuid); // Remove the email in list
+ // thisPtr->onError(std::string("ReqResult::NetworkFailure"));
+ }
+ });
+ tcpSocket->setConnectionErrorCallback([email_wptr]() {
+ auto email_ptr = email_wptr.lock();
+ if (!email_ptr) {
+ LOG_ERROR << "EMail pointer gone";
+ return;
+ }
+ // can't connect to server
+ LOG_ERROR << "Bad Server address";
+ EMail::m_emails.erase(email_ptr->m_uuid); // Remove the email in list
+ // thisPtr->onError(std::string("ReqResult::BadServerAddress"));
+ });
+ auto cb_(cb ? cb : [](const std::string &msg) {
+ LOG_INFO << "Default email callback : " << msg;
+ });
+ tcpSocket->setMessageCallback(
+ [email_wptr, cb_](const trantor::TcpConnectionPtr &connPtr,
+ trantor::MsgBuffer *msg) {
+ auto email_ptr = email_wptr.lock();
+ if (!email_ptr) {
+ LOG_ERROR << "EMail pointer gone";
+ return;
+ }
+ // email->m_socket->disconnect();
+ messagesHandle(connPtr, msg, email_ptr, cb_);
+ });
+ tcpSocket->connect(); // Start trying to send the email
+ });
+ return email->m_uuid;
+}
diff --git a/backend/SMTPMail-drogon-master/SMTPMail.h b/backend/SMTPMail-drogon-master/SMTPMail.h
new file mode 100644
index 0000000..2170bb4
--- /dev/null
+++ b/backend/SMTPMail-drogon-master/SMTPMail.h
@@ -0,0 +1,63 @@
+/**
+ *
+ * SMTPMail.h
+ *
+ * This plugin is for SMTP mail delievery for the Drogon web-framework.
+Implementation
+ * reference from the project "SMTPClient" with Qt5 by kelvins. Please check out
+ * https://github.com/kelvins/SMTPClient.
+
+Copyright 2020 ihmc3jn09hk
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+ */
+
+#pragma once
+
+#include
+
+class SMTPMail : public drogon::Plugin {
+public:
+ SMTPMail() = default;
+ /// This method must be called by drogon to initialize and start the plugin.
+ /// It must be implemented by the user.
+ void initAndStart(const Json::Value &config) override;
+
+ /// This method must be called by drogon to shutdown the plugin.
+ /// It must be implemented by the user.
+ void shutdown() override;
+
+ /** Send an email
+ * return : An ID of the email.
+ */
+ std::string sendEmail(
+ const std::string
+ &mailServer, // Mail server address/dns E.g. 127.0.0.1/smtp.mail.com
+ const uint16_t &port, // Port E.g. 587
+ const std::string &from, // Send from whom E.g. drogon@gmail.com
+ const std::string &to, // Reciever E.g. drogon@yahoo.com
+ const std::string &subject, // The email title/subject
+ const std::string &content, // The email content.
+ const std::string &user, // User (Usually same as "from")
+ const std::string &passwd, // Password
+ bool isHTML, // content type
+ const std::function &cb = {}
+ // The callback for email sent notification
+ );
+};
diff --git a/backend/src/db/model/Inode.cc b/backend/model/Inode.cc
similarity index 85%
rename from backend/src/db/model/Inode.cc
rename to backend/model/Inode.cc
index 01d839e..5819a51 100644
--- a/backend/src/db/model/Inode.cc
+++ b/backend/model/Inode.cc
@@ -6,7 +6,7 @@
*/
#include "Inode.h"
-#include
+#include "drogon/utils/Utilities.h"
#include
using namespace drogon;
@@ -19,6 +19,7 @@ const std::string Inode::Cols::_name = "name";
const std::string Inode::Cols::_parent_id = "parent_id";
const std::string Inode::Cols::_owner_id = "owner_id";
const std::string Inode::Cols::_size = "size";
+const std::string Inode::Cols::_has_preview = "has_preview";
const std::string Inode::primaryKeyName = "id";
const bool Inode::hasPrimaryKey = true;
const std::string Inode::tableName = "inode";
@@ -29,7 +30,8 @@ const std::vector Inode::metaData_={
{"name","std::string","text",0,0,0,0},
{"parent_id","uint64_t","integer",8,0,0,0},
{"owner_id","uint64_t","integer",8,0,0,1},
-{"size","uint64_t","integer",8,0,0,0}
+{"size","uint64_t","integer",8,0,0,0},
+{"has_preview","uint64_t","integer",8,0,0,1}
};
const std::string &Inode::getColumnName(size_t index) noexcept(false)
{
@@ -64,11 +66,15 @@ Inode::Inode(const Row &r, const ssize_t indexOffset) noexcept
{
size_=std::make_shared(r["size"].as());
}
+ if(!r["has_preview"].isNull())
+ {
+ hasPreview_=std::make_shared(r["has_preview"].as());
+ }
}
else
{
size_t offset = (size_t)indexOffset;
- if(offset + 6 > r.size())
+ if(offset + 7 > r.size())
{
LOG_FATAL << "Invalid SQL result for this model";
return;
@@ -104,13 +110,18 @@ Inode::Inode(const Row &r, const ssize_t indexOffset) noexcept
{
size_=std::make_shared(r[index].as());
}
+ index = offset + 6;
+ if(!r[index].isNull())
+ {
+ hasPreview_=std::make_shared(r[index].as());
+ }
}
}
Inode::Inode(const Json::Value &pJson, const std::vector &pMasqueradingVector) noexcept(false)
{
- if(pMasqueradingVector.size() != 6)
+ if(pMasqueradingVector.size() != 7)
{
LOG_ERROR << "Bad masquerading vector";
return;
@@ -163,6 +174,14 @@ Inode::Inode(const Json::Value &pJson, const std::vector &pMasquera
size_=std::make_shared((uint64_t)pJson[pMasqueradingVector[5]].asUInt64());
}
}
+ if(!pMasqueradingVector[6].empty() && pJson.isMember(pMasqueradingVector[6]))
+ {
+ dirtyFlag_[6] = true;
+ if(!pJson[pMasqueradingVector[6]].isNull())
+ {
+ hasPreview_=std::make_shared((uint64_t)pJson[pMasqueradingVector[6]].asUInt64());
+ }
+ }
}
Inode::Inode(const Json::Value &pJson) noexcept(false)
@@ -215,12 +234,20 @@ Inode::Inode(const Json::Value &pJson) noexcept(false)
size_=std::make_shared((uint64_t)pJson["size"].asUInt64());
}
}
+ if(pJson.isMember("has_preview"))
+ {
+ dirtyFlag_[6]=true;
+ if(!pJson["has_preview"].isNull())
+ {
+ hasPreview_=std::make_shared((uint64_t)pJson["has_preview"].asUInt64());
+ }
+ }
}
void Inode::updateByMasqueradedJson(const Json::Value &pJson,
const std::vector &pMasqueradingVector) noexcept(false)
{
- if(pMasqueradingVector.size() != 6)
+ if(pMasqueradingVector.size() != 7)
{
LOG_ERROR << "Bad masquerading vector";
return;
@@ -272,6 +299,14 @@ void Inode::updateByMasqueradedJson(const Json::Value &pJson,
size_=std::make_shared((uint64_t)pJson[pMasqueradingVector[5]].asUInt64());
}
}
+ if(!pMasqueradingVector[6].empty() && pJson.isMember(pMasqueradingVector[6]))
+ {
+ dirtyFlag_[6] = true;
+ if(!pJson[pMasqueradingVector[6]].isNull())
+ {
+ hasPreview_=std::make_shared((uint64_t)pJson[pMasqueradingVector[6]].asUInt64());
+ }
+ }
}
void Inode::updateByJson(const Json::Value &pJson) noexcept(false)
@@ -323,6 +358,14 @@ void Inode::updateByJson(const Json::Value &pJson) noexcept(false)
size_=std::make_shared((uint64_t)pJson["size"].asUInt64());
}
}
+ if(pJson.isMember("has_preview"))
+ {
+ dirtyFlag_[6] = true;
+ if(!pJson["has_preview"].isNull())
+ {
+ hasPreview_=std::make_shared((uint64_t)pJson["has_preview"].asUInt64());
+ }
+ }
}
const uint64_t &Inode::getValueOfId() const noexcept
@@ -452,6 +495,23 @@ void Inode::setSizeToNull() noexcept
dirtyFlag_[5] = true;
}
+const uint64_t &Inode::getValueOfHasPreview() const noexcept
+{
+ const static uint64_t defaultValue = uint64_t();
+ if(hasPreview_)
+ return *hasPreview_;
+ return defaultValue;
+}
+const std::shared_ptr &Inode::getHasPreview() const noexcept
+{
+ return hasPreview_;
+}
+void Inode::setHasPreview(const uint64_t &pHasPreview) noexcept
+{
+ hasPreview_ = std::make_shared(pHasPreview);
+ dirtyFlag_[6] = true;
+}
+
void Inode::updateId(const uint64_t id)
{
id_ = std::make_shared(id);
@@ -464,7 +524,8 @@ const std::vector &Inode::insertColumns() noexcept
"name",
"parent_id",
"owner_id",
- "size"
+ "size",
+ "has_preview"
};
return inCols;
}
@@ -526,6 +587,17 @@ void Inode::outputArgs(drogon::orm::internal::SqlBinder &binder) const
binder << nullptr;
}
}
+ if(dirtyFlag_[6])
+ {
+ if(getHasPreview())
+ {
+ binder << getValueOfHasPreview();
+ }
+ else
+ {
+ binder << nullptr;
+ }
+ }
}
const std::vector Inode::updateColumns() const
@@ -551,6 +623,10 @@ const std::vector Inode::updateColumns() const
{
ret.push_back(getColumnName(5));
}
+ if(dirtyFlag_[6])
+ {
+ ret.push_back(getColumnName(6));
+ }
return ret;
}
@@ -611,6 +687,17 @@ void Inode::updateArgs(drogon::orm::internal::SqlBinder &binder) const
binder << nullptr;
}
}
+ if(dirtyFlag_[6])
+ {
+ if(getHasPreview())
+ {
+ binder << getValueOfHasPreview();
+ }
+ else
+ {
+ binder << nullptr;
+ }
+ }
}
Json::Value Inode::toJson() const
{
@@ -663,6 +750,14 @@ Json::Value Inode::toJson() const
{
ret["size"]=Json::Value();
}
+ if(getHasPreview())
+ {
+ ret["has_preview"]=(Json::UInt64)getValueOfHasPreview();
+ }
+ else
+ {
+ ret["has_preview"]=Json::Value();
+ }
return ret;
}
@@ -670,7 +765,7 @@ Json::Value Inode::toMasqueradedJson(
const std::vector &pMasqueradingVector) const
{
Json::Value ret;
- if(pMasqueradingVector.size() == 6)
+ if(pMasqueradingVector.size() == 7)
{
if(!pMasqueradingVector[0].empty())
{
@@ -738,6 +833,17 @@ Json::Value Inode::toMasqueradedJson(
ret[pMasqueradingVector[5]]=Json::Value();
}
}
+ if(!pMasqueradingVector[6].empty())
+ {
+ if(getHasPreview())
+ {
+ ret[pMasqueradingVector[6]]=(Json::UInt64)getValueOfHasPreview();
+ }
+ else
+ {
+ ret[pMasqueradingVector[6]]=Json::Value();
+ }
+ }
return ret;
}
LOG_ERROR << "Masquerade failed";
@@ -789,6 +895,14 @@ Json::Value Inode::toMasqueradedJson(
{
ret["size"]=Json::Value();
}
+ if(getHasPreview())
+ {
+ ret["has_preview"]=(Json::UInt64)getValueOfHasPreview();
+ }
+ else
+ {
+ ret["has_preview"]=Json::Value();
+ }
return ret;
}
@@ -834,13 +948,23 @@ bool Inode::validateJsonForCreation(const Json::Value &pJson, std::string &err)
if(!validJsonOfField(5, "size", pJson["size"], err, true))
return false;
}
+ if(pJson.isMember("has_preview"))
+ {
+ if(!validJsonOfField(6, "has_preview", pJson["has_preview"], err, true))
+ return false;
+ }
+ else
+ {
+ err="The has_preview column cannot be null";
+ return false;
+ }
return true;
}
bool Inode::validateMasqueradedJsonForCreation(const Json::Value &pJson,
const std::vector &pMasqueradingVector,
std::string &err)
{
- if(pMasqueradingVector.size() != 6)
+ if(pMasqueradingVector.size() != 7)
{
err = "Bad masquerading vector";
return false;
@@ -904,6 +1028,19 @@ bool Inode::validateMasqueradedJsonForCreation(const Json::Value &pJson,
return false;
}
}
+ if(!pMasqueradingVector[6].empty())
+ {
+ if(pJson.isMember(pMasqueradingVector[6]))
+ {
+ if(!validJsonOfField(6, pMasqueradingVector[6], pJson[pMasqueradingVector[6]], err, true))
+ return false;
+ }
+ else
+ {
+ err="The " + pMasqueradingVector[6] + " column cannot be null";
+ return false;
+ }
+ }
}
catch(const Json::LogicError &e)
{
@@ -949,13 +1086,18 @@ bool Inode::validateJsonForUpdate(const Json::Value &pJson, std::string &err)
if(!validJsonOfField(5, "size", pJson["size"], err, false))
return false;
}
+ if(pJson.isMember("has_preview"))
+ {
+ if(!validJsonOfField(6, "has_preview", pJson["has_preview"], err, false))
+ return false;
+ }
return true;
}
bool Inode::validateMasqueradedJsonForUpdate(const Json::Value &pJson,
const std::vector &pMasqueradingVector,
std::string &err)
{
- if(pMasqueradingVector.size() != 6)
+ if(pMasqueradingVector.size() != 7)
{
err = "Bad masquerading vector";
return false;
@@ -996,6 +1138,11 @@ bool Inode::validateMasqueradedJsonForUpdate(const Json::Value &pJson,
if(!validJsonOfField(5, pMasqueradingVector[5], pJson[pMasqueradingVector[5]], err, false))
return false;
}
+ if(!pMasqueradingVector[6].empty() && pJson.isMember(pMasqueradingVector[6]))
+ {
+ if(!validJsonOfField(6, pMasqueradingVector[6], pJson[pMasqueradingVector[6]], err, false))
+ return false;
+ }
}
catch(const Json::LogicError &e)
{
@@ -1086,6 +1233,18 @@ bool Inode::validJsonOfField(size_t index,
return false;
}
break;
+ case 6:
+ if(pJson.isNull())
+ {
+ err="The " + fieldName + " column cannot be null";
+ return false;
+ }
+ if(!pJson.isUInt64())
+ {
+ err="Type error in the "+fieldName+" field";
+ return false;
+ }
+ break;
default:
err="Internal error in the server";
return false;
diff --git a/backend/src/db/model/Inode.h b/backend/model/Inode.h
similarity index 89%
rename from backend/src/db/model/Inode.h
rename to backend/model/Inode.h
index 2f59e33..684f179 100644
--- a/backend/src/db/model/Inode.h
+++ b/backend/model/Inode.h
@@ -6,17 +6,17 @@
*/
#pragma once
-#include
-#include
-#include
-#include
-#include
+#include "drogon/orm/Result.h"
+#include "drogon/orm/Row.h"
+#include "drogon/orm/Field.h"
+#include "drogon/orm/SqlBinder.h"
+#include "drogon/orm/Mapper.h"
#ifdef __cpp_impl_coroutine
#include
#endif
-#include
-#include
-#include
+#include "trantor/utils/Date.h"
+#include "trantor/utils/Logger.h"
+#include "json/json.h"
#include
#include
#include
@@ -48,6 +48,7 @@ class Inode
static const std::string _parent_id;
static const std::string _owner_id;
static const std::string _size;
+ static const std::string _has_preview;
};
const static int primaryKeyNumber;
@@ -151,8 +152,16 @@ class Inode
void setSize(const uint64_t &pSize) noexcept;
void setSizeToNull() noexcept;
+ /** For column has_preview */
+ ///Get the value of the column has_preview, returns the default value if the column is null
+ const uint64_t &getValueOfHasPreview() const noexcept;
+ ///Return a shared_ptr object pointing to the column const value, or an empty shared_ptr object if the column is null
+ const std::shared_ptr &getHasPreview() const noexcept;
+ ///Set the value of the column has_preview
+ void setHasPreview(const uint64_t &pHasPreview) noexcept;
- static size_t getColumnNumber() noexcept { return 6; }
+
+ static size_t getColumnNumber() noexcept { return 7; }
static const std::string &getColumnName(size_t index) noexcept(false);
Json::Value toJson() const;
@@ -175,6 +184,7 @@ class Inode
std::shared_ptr parentId_;
std::shared_ptr ownerId_;
std::shared_ptr size_;
+ std::shared_ptr hasPreview_;
struct MetaData
{
const std::string colName_;
@@ -186,7 +196,7 @@ class Inode
const bool notNull_;
};
static const std::vector metaData_;
- bool dirtyFlag_[6]={ false };
+ bool dirtyFlag_[7]={ false };
public:
static const std::string &sqlForFindingByPrimaryKey()
{
@@ -229,6 +239,11 @@ class Inode
sql += "size,";
++parametersCount;
}
+ if(dirtyFlag_[6])
+ {
+ sql += "has_preview,";
+ ++parametersCount;
+ }
if(parametersCount > 0)
{
sql[sql.length()-1]=')';
@@ -261,6 +276,11 @@ class Inode
{
sql.append("?,");
+ }
+ if(dirtyFlag_[6])
+ {
+ sql.append("?,");
+
}
if(parametersCount > 0)
{
diff --git a/backend/src/db/model/Tokens.cc b/backend/model/Tokens.cc
similarity index 99%
rename from backend/src/db/model/Tokens.cc
rename to backend/model/Tokens.cc
index d02df5e..7b8311a 100644
--- a/backend/src/db/model/Tokens.cc
+++ b/backend/model/Tokens.cc
@@ -6,7 +6,7 @@
*/
#include "Tokens.h"
-#include
+#include "drogon/utils/Utilities.h"
#include
using namespace drogon;
diff --git a/backend/src/db/model/Tokens.h b/backend/model/Tokens.h
similarity index 96%
rename from backend/src/db/model/Tokens.h
rename to backend/model/Tokens.h
index b2fe9e2..a6c0da4 100644
--- a/backend/src/db/model/Tokens.h
+++ b/backend/model/Tokens.h
@@ -6,17 +6,17 @@
*/
#pragma once
-#include
-#include
-#include
-#include
-#include
+#include "drogon/orm/Result.h"
+#include "drogon/orm/Row.h"
+#include "drogon/orm/Field.h"
+#include "drogon/orm/SqlBinder.h"
+#include "drogon/orm/Mapper.h"
#ifdef __cpp_impl_coroutine
#include
#endif
-#include
-#include
-#include
+#include "trantor/utils/Date.h"
+#include "trantor/utils/Logger.h"
+#include "json/json.h"
#include
#include
#include
diff --git a/backend/src/db/model/User.cc b/backend/model/User.cc
similarity index 99%
rename from backend/src/db/model/User.cc
rename to backend/model/User.cc
index 45e3f9f..205b6fa 100644
--- a/backend/src/db/model/User.cc
+++ b/backend/model/User.cc
@@ -6,7 +6,7 @@
*/
#include "User.h"
-#include
+#include "drogon/utils/Utilities.h"
#include
using namespace drogon;
diff --git a/backend/src/db/model/User.h b/backend/model/User.h
similarity index 98%
rename from backend/src/db/model/User.h
rename to backend/model/User.h
index 8f13e8a..e5af610 100644
--- a/backend/src/db/model/User.h
+++ b/backend/model/User.h
@@ -6,17 +6,17 @@
*/
#pragma once
-#include
-#include
-#include
-#include
-#include
+#include "drogon/orm/Result.h"
+#include "drogon/orm/Row.h"
+#include "drogon/orm/Field.h"
+#include "drogon/orm/SqlBinder.h"
+#include "drogon/orm/Mapper.h"
#ifdef __cpp_impl_coroutine
#include
#endif
-#include
-#include
-#include
+#include "trantor/utils/Date.h"
+#include "trantor/utils/Logger.h"
+#include "json/json.h"
#include
#include
#include
diff --git a/backend/src/db/model/model.json b/backend/model/model.json
similarity index 100%
rename from backend/src/db/model/model.json
rename to backend/model/model.json
diff --git a/backend/shl/msd/blocking_iterator.hpp b/backend/shl/msd/blocking_iterator.hpp
new file mode 100644
index 0000000..bec3c05
--- /dev/null
+++ b/backend/shl/msd/blocking_iterator.hpp
@@ -0,0 +1,68 @@
+// Copyright (C) 2022 Andrei Avram
+
+#ifndef MSD_CHANNEL_BLOCKING_ITERATOR_HPP_
+#define MSD_CHANNEL_BLOCKING_ITERATOR_HPP_
+
+#include
+#include
+
+namespace msd {
+
+/**
+ * @brief An iterator that block the current thread,
+ * waiting to fetch elements from the channel.
+ *
+ * Used to implement channel range-based for loop.
+ *
+ * @tparam Channel Instance of channel.
+ */
+template
+class blocking_iterator {
+ public:
+ using value_type = typename channel::value_type;
+
+ explicit blocking_iterator(channel& ch) : ch_{ch} {}
+
+ /**
+ * Advances to next element in the channel.
+ */
+ blocking_iterator operator++() const noexcept { return *this; }
+
+ /**
+ * Returns an element from the channel.
+ */
+ value_type operator*() const
+ {
+ value_type value;
+ value << ch_;
+
+ return value;
+ }
+
+ /**
+ * Makes iteration continue until the channel is closed and empty.
+ */
+ bool operator!=(blocking_iterator) const
+ {
+ std::unique_lock lock{ch_.mtx_};
+ ch_.waitBeforeRead(lock);
+
+ return !(ch_.closed() && ch_.empty());
+ }
+
+ private:
+ channel& ch_;
+};
+
+} // namespace msd
+
+/**
+ * @brief Output iterator specialization
+ */
+template
+struct std::iterator_traits> {
+ using value_type = typename msd::blocking_iterator::value_type;
+ using iterator_category = std::output_iterator_tag;
+};
+
+#endif // MSD_CHANNEL_BLOCKING_ITERATOR_HPP_
diff --git a/backend/shl/msd/channel.hpp b/backend/shl/msd/channel.hpp
new file mode 100644
index 0000000..199004a
--- /dev/null
+++ b/backend/shl/msd/channel.hpp
@@ -0,0 +1,130 @@
+// Copyright (C) 2022 Andrei Avram
+
+#ifndef MSD_CHANNEL_HPP_
+#define MSD_CHANNEL_HPP_
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "blocking_iterator.hpp"
+
+namespace msd {
+
+#if (__cplusplus >= 201703L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L))
+#define NODISCARD [[nodiscard]]
+#else
+#define NODISCARD
+#endif
+
+namespace detail {
+template
+struct remove_cvref {
+ using type = typename std::remove_cv::type>::type;
+};
+
+template
+using remove_cvref_t = typename remove_cvref::type;
+} // namespace detail
+
+/**
+ * @brief Exception thrown if trying to write on closed channel.
+ */
+class closed_channel : public std::runtime_error {
+ public:
+ explicit closed_channel(const char* msg) : std::runtime_error{msg} {}
+};
+
+/**
+ * @brief Thread-safe container for sharing data between threads.
+ *
+ * Implements a blocking input iterator.
+ *
+ * @tparam T The type of the elements.
+ */
+template
+class channel {
+ public:
+ using value_type = T;
+ using iterator = blocking_iterator>;
+ using size_type = std::size_t;
+
+ /**
+ * Creates a new channel.
+ *
+ * @param capacity Number of elements the channel can store before blocking.
+ */
+ explicit constexpr channel(size_type capacity = 0);
+
+ /**
+ * Pushes an element into the channel.
+ *
+ * @throws closed_channel if channel is closed.
+ */
+ template
+ friend void operator>>(Type&&, channel>&);
+
+ /**
+ * Pops an element from the channel.
+ *
+ * @tparam Type The type of the elements
+ */
+ template
+ friend void operator<<(Type&, channel&);
+
+ /**
+ * Returns the number of elements in the channel.
+ */
+ NODISCARD inline size_type constexpr size() const noexcept;
+
+ /**
+ * Returns true if there are no elements in channel.
+ */
+ NODISCARD inline bool constexpr empty() const noexcept;
+
+ /**
+ * Closes the channel.
+ */
+ inline void close() noexcept;
+
+ /**
+ * Returns true if the channel is closed.
+ */
+ NODISCARD inline bool closed() const noexcept;
+
+ /**
+ * Iterator
+ */
+ iterator begin() noexcept;
+ iterator end() noexcept;
+
+ /**
+ * Channel cannot be copied or moved.
+ */
+ channel(const channel&) = delete;
+ channel& operator=(const channel&) = delete;
+ channel(channel&&) = delete;
+ channel& operator=(channel&&) = delete;
+ virtual ~channel() = default;
+
+ private:
+ const size_type cap_;
+ std::queue queue_;
+ std::mutex mtx_;
+ std::condition_variable cnd_;
+ std::atomic is_closed_{false};
+
+ inline void waitBeforeRead(std::unique_lock&);
+ friend class blocking_iterator;
+};
+
+#include "channel_impl.hpp"
+
+} // namespace msd
+
+#endif // MSD_CHANNEL_HPP_
diff --git a/backend/shl/msd/channel_impl.hpp b/backend/shl/msd/channel_impl.hpp
new file mode 100644
index 0000000..1e4f4c3
--- /dev/null
+++ b/backend/shl/msd/channel_impl.hpp
@@ -0,0 +1,87 @@
+// Copyright (C) 2022 Andrei Avram
+
+template
+constexpr channel::channel(const size_type capacity) : cap_{capacity}
+{
+}
+
+template
+void operator>>(T&& in, channel>& ch)
+{
+ if (ch.closed()) {
+ throw closed_channel{"cannot write on closed channel"};
+ }
+
+ std::unique_lock lock{ch.mtx_};
+
+ if (ch.cap_ > 0 && ch.queue_.size() == ch.cap_) {
+ ch.cnd_.wait(lock, [&ch]() { return ch.queue_.size() < ch.cap_; });
+ }
+
+ ch.queue_.push(std::forward(in));
+
+ ch.cnd_.notify_one();
+}
+
+template
+void operator<<(T& out, channel& ch)
+{
+ if (ch.closed() && ch.empty()) {
+ return;
+ }
+
+ {
+ std::unique_lock lock{ch.mtx_};
+ ch.waitBeforeRead(lock);
+
+ if (ch.queue_.size() > 0) {
+ out = std::move(ch.queue_.front());
+ ch.queue_.pop();
+ }
+ }
+
+ ch.cnd_.notify_one();
+}
+
+template
+constexpr typename channel::size_type channel::size() const noexcept
+{
+ return queue_.size();
+}
+
+template
+constexpr bool channel::empty() const noexcept
+{
+ return queue_.empty();
+}
+
+template
+void channel::close() noexcept
+{
+ is_closed_.store(true);
+ cnd_.notify_all();
+}
+
+template
+bool channel::closed() const noexcept
+{
+ return is_closed_.load();
+}
+
+template
+blocking_iterator> channel::begin() noexcept
+{
+ return blocking_iterator>{*this};
+}
+
+template
+blocking_iterator> channel::end() noexcept
+{
+ return blocking_iterator>{*this};
+}
+
+template
+void channel::waitBeforeRead(std::unique_lock& lock)
+{
+ cnd_.wait(lock, [this] { return queue_.size() > 0 || closed(); });
+}
diff --git a/backend/src/controllers/admin.cpp b/backend/src/controllers/admin.cpp
index ae777a1..87dbcbf 100644
--- a/backend/src/controllers/admin.cpp
+++ b/backend/src/controllers/admin.cpp
@@ -55,14 +55,15 @@ namespace api {
void admin::delete_user(req_type req, cbk_type cbk) {
Json::Value& json = *req->jsonObject();
+ msd::channel chan;
try {
uint64_t user_id = dto::json_get(json, "user").value();
db::MapperUser user_mapper(drogon::app().getDbClient());
auto user = user_mapper.findByPrimaryKey(user_id);
auth::revoke_all(user);
- fs::delete_node(fs::get_node(user.getValueOfRootId()).value(), true);
- user_mapper.deleteOne(user);
+ fs::delete_node(fs::get_node(user.getValueOfRootId()).value(), chan, true);
+ user_mapper.deleteOne(user);
cbk(dto::Responses::get_success_res());
} catch (const std::exception&) {
cbk(dto::Responses::get_badreq_res("Validation error"));
diff --git a/backend/src/controllers/auth/auth_2fa.cpp b/backend/src/controllers/auth/auth_2fa.cpp
index a8c067b..02123c8 100644
--- a/backend/src/controllers/auth/auth_2fa.cpp
+++ b/backend/src/controllers/auth/auth_2fa.cpp
@@ -5,7 +5,7 @@
#include
#include
#include
-#include
+#include
#include "controllers/controllers.h"
#include "db/db.h"
@@ -24,21 +24,14 @@ std::string create_totp_qrcode(const db::User& user, const std::string& b32_secr
const int mod_count = code.getSize();
const int row_size = qrcode_pixel_size * mod_count;
- std::vector secret, image, row;
- row.reserve(row_size);
- image.reserve(row_size * row_size);
+ cv::Mat image(mod_count, mod_count, CV_8UC1), scaled_image;
+ std::vector image_encoded;
+ for (int y = 0; y < mod_count; y++) for (int x = 0; x < mod_count; x++)
+ image.at(x, y) = code.getModule(x, y) ? 0 : 0xff;
+ cv::resize(image, scaled_image, cv::Size(), qrcode_pixel_size, qrcode_pixel_size, cv::INTER_NEAREST);
+ cv::imencode(".png", scaled_image, image_encoded);
- for (int y = 0; y < mod_count; y++) {
- row.clear();
- for (int x = 0; x < mod_count; x++)
- row.insert(row.end(), qrcode_pixel_size, code.getModule(x, y) ? 0 : 0xff);
- for (int i = 0; i < qrcode_pixel_size; i++)
- image.insert(image.end(), row.begin(), row.end());
- }
-
- lodepng::encode(secret, image, row_size, row_size, LCT_GREY, 8);
-
- return "data:image/png;base64," + Botan::base64_encode(secret);
+ return "data:image/png;base64," + Botan::base64_encode(image_encoded);
}
namespace api {
@@ -64,7 +57,7 @@ namespace api {
std::string code = create_totp_qrcode(user, b32_secret);
cbk(dto::Responses::get_tfa_setup_res(b32_secret, code));
}
- } catch (const std::exception&) {
+ } catch (const std::exception& e) {
cbk(dto::Responses::get_badreq_res("Validation error"));
}
}
diff --git a/backend/src/controllers/auth/auth_common.cpp b/backend/src/controllers/auth/auth_common.cpp
index 120c3fc..c580cac 100644
--- a/backend/src/controllers/auth/auth_common.cpp
+++ b/backend/src/controllers/auth/auth_common.cpp
@@ -17,7 +17,7 @@
#include
#include
-#include
+#include
#include "controllers/controllers.h"
#include "db/db.h"
@@ -43,15 +43,17 @@ namespace api {
char totp[16];
std::snprintf(totp, 16, "%06d", Botan::TOTP(Botan::OctetString(totp_secret)).generate_totp(t));
- mailio::message msg;
- msg.from(mailio::mail_address("Fileserver", "fileserver@mattv.de"));
- msg.add_recipient(mailio::mail_address(user.getValueOfName(), user.getValueOfName()));
- msg.subject("Subject: Fileserver - Email 2fa code");
- msg.content("Your code is: " + std::string(totp) +"\r\nIt is valid for 5 Minutes");
-
- mailio::smtps conn("mail.mattv.de", 587);
- conn.authenticate("no-reply@mattv.de", "noreplyLONGPASS123", mailio::smtps::auth_method_t::START_TLS);
- conn.submit(msg);
+ drogon::app().getPlugin()->sendEmail(
+ "mail.mattv.de",
+ 587,
+ "fileserver@mattv.de",
+ user.getValueOfName(),
+ "MFileserver - Email 2fa code",
+ "Your code is: " + std::string(totp) +"\r\nIt is valid for 5 Minutes",
+ "no-reply@mattv.de",
+ "noreplyLONGPASS123",
+ false
+ );
}
std::string auth::get_token(const db::User& user) {
diff --git a/backend/src/controllers/controllers.h b/backend/src/controllers/controllers.h
index c9e1ba7..890b06d 100644
--- a/backend/src/controllers/controllers.h
+++ b/backend/src/controllers/controllers.h
@@ -1,11 +1,11 @@
#ifndef BACKEND_CONTROLLERS_H
#define BACKEND_CONTROLLERS_H
-#include
-#include
-#include
-#include
#include
+#include
+#include
+#include
+
#include "db/db.h"
using req_type = const drogon::HttpRequestPtr&;
@@ -86,14 +86,32 @@ public:
METHOD_ADD(fs::create_node_req, "/createFile", drogon::Post, "Login");
METHOD_ADD(fs::delete_node_req, "/delete/{}", drogon::Post, "Login");
METHOD_ADD(fs::upload, "/upload/{}", drogon::Post, "Login");
+ METHOD_ADD(fs::create_zip, "/create_zip", drogon::Post, "Login");
METHOD_ADD(fs::download, "/download", drogon::Post, "Login");
+ METHOD_ADD(fs::download_multi, "/download_multi", drogon::Post, "Login");
+ METHOD_ADD(fs::download_preview, "/download_preview/{}", drogon::Get, "Login");
+ METHOD_ADD(fs::download_base64, "/download_base64/{}", drogon::Get, "Login");
+ METHOD_ADD(fs::get_type, "/get_type/{}", drogon::Get, "Login");
METHOD_LIST_END
+ enum class create_node_error {
+ INVALID_NAME,
+ INVALID_PARENT,
+ FILE_PARENT
+ };
+
+ struct mutex_stream {
+ std::stringstream ss;
+ std::mutex mutex;
+ bool done = false;
+ };
+
static std::optional get_node(uint64_t node);
static std::optional get_node_and_validate(const db::User& user, uint64_t node);
static std::vector get_children(const db::INode& parent);
- static std::variant create_node(std::string name, const db::User& owner, bool file, const std::optional &parent, bool force = false);
- static void delete_node(db::INode node, bool allow_root = false);
+ static std::variant>
+ create_node(std::string name, const db::User& owner, bool file, const std::optional &parent, bool force = false);
+ static void delete_node(db::INode node, msd::channel& chan, bool allow_root = false);
void root(req_type, cbk_type);
@@ -102,7 +120,12 @@ public:
template void create_node_req(req_type req, cbk_type cbk);
void delete_node_req(req_type, cbk_type, uint64_t node);
void upload(req_type, cbk_type, uint64_t node);
+ void create_zip(req_type, cbk_type);
void download(req_type, cbk_type);
+ void download_multi(req_type, cbk_type);
+ void download_preview(req_type, cbk_type, uint64_t node);
+ void download_base64(req_type, cbk_type, uint64_t node);
+ void get_type(req_type, cbk_type, uint64_t node);
};
class user : public drogon::HttpController {
diff --git a/backend/src/controllers/fs.cpp b/backend/src/controllers/fs.cpp
index d6f5fb2..f16bb5b 100644
--- a/backend/src/controllers/fs.cpp
+++ b/backend/src/controllers/fs.cpp
@@ -3,12 +3,100 @@
#pragma ide diagnostic ignored "readability-convert-member-functions-to-static"
#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+
#include "controllers.h"
#include "dto/dto.h"
char windows_invalid_chars[] = "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F<>:\"/\\|";
-std::string generate_path(db::INode node) {
+// https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Image_types#common_image_file_types
+const std::unordered_map mime_type_map = {
+ { ".apng" , "image/apng" },
+ { ".avif" , "image/avif" },
+ { ".bmp" , "image/bmp" },
+ { ".gif" , "image/gif" },
+ { ".jpg" , "image/jpeg" },
+ { ".jpeg" , "image/jpeg" },
+ { ".jfif" , "image/jpeg" },
+ { ".pjpeg", "image/jpeg" },
+ { ".pjp" , "image/jpeg" },
+ { ".png" , "image/png" },
+ { ".svg" , "image/svg" },
+ { ".webp" , "image/webp" },
+
+ { ".aac" , "audio/aac" },
+ { ".flac" , "audio/flac" },
+ { ".mp3" , "audio/mp3" },
+ { ".m4a" , "audio/mp4" },
+ { ".oga" , "audio/ogg" },
+ { ".ogg" , "audio/ogg" },
+ { ".wav" , "audio/wav" },
+
+ { ".3gp" , "video/3gpp" },
+ { ".mpg" , "video/mpeg" },
+ { ".mpeg" , "video/mpeg" },
+ { ".mp4" , "video/mp4" },
+ { ".m4v" , "video/mp4" },
+ { ".m4p" , "video/mp4" },
+ { ".ogv" , "video/ogg" },
+ { ".mov" , "video/quicktime" },
+ { ".webm" , "video/webm" },
+ { ".mkv" , "video/x-matroska" },
+ { ".mk3d" , "video/x-matroska" },
+ { ".mks" , "video/x-matroska" },
+};
+
+uint64_t next_temp_id = 0;
+std::unordered_map zip_to_temp_map;
+std::unordered_map> in_progress_zips;
+
+trantor::EventLoop* get_zip_loop() {
+ static bool init_done = false;
+ static trantor::EventLoopThread loop("ZipEventLoop");
+ if (!init_done) {
+ init_done = true;
+ loop.run();
+ loop.getLoop()->runEvery(30*60, []{
+ for (const auto& entry : std::filesystem::directory_iterator("./temp")) {
+ if (!entry.is_regular_file()) continue;
+ const std::string file_name = "./temp/" + entry.path().filename().string();
+ const auto& progress_pos = std::find_if(in_progress_zips.begin(), in_progress_zips.end(),
+ [&file_name](const std::pair>& entry) {
+ return std::get<0>(entry.second) == file_name;
+ }
+ );
+ if (progress_pos != in_progress_zips.end()) return;
+ const auto& zip_map_pos = std::find_if(zip_to_temp_map.begin(), zip_to_temp_map.end(),
+ [&file_name](const std::pair& entry){
+ return entry.second == file_name;
+ }
+ );
+ if (zip_map_pos != zip_to_temp_map.end()) return;
+ std::filesystem::remove(entry.path());
+ }
+ });
+ }
+ return loop.getLoop();
+}
+
+trantor::EventLoop* get_delete_loop() {
+ static bool init_done = false;
+ static trantor::EventLoopThread loop("DeleteEventLoop");
+ if (!init_done) {
+ init_done = true;
+ loop.run();
+ }
+ return loop.getLoop();
+}
+
+void generate_path(db::INode node, std::string& str) {
db::MapperInode inode_mapper(drogon::app().getDbClient());
std::stack path;
path.push(node);
@@ -16,14 +104,101 @@ std::string generate_path(db::INode node) {
node = inode_mapper.findByPrimaryKey(node.getValueOfParentId());
path.push(node);
}
- std::stringstream ss;
while (!path.empty()) {
const db::INode& seg = path.top();
- ss << seg.getValueOfName();
- if (seg.getValueOfIsFile() == 0) ss << '/';
+ str += seg.getValueOfName();
+ if (seg.getValueOfIsFile() == 0) str += "/";
path.pop();
}
- return ss.str();
+}
+
+Json::Value generate_path(db::INode node) {
+ Json::Value segments = Json::Value(Json::ValueType::arrayValue);
+ db::MapperInode inode_mapper(drogon::app().getDbClient());
+ std::stack path;
+ path.push(node);
+ while (node.getParentId() != nullptr) {
+ node = inode_mapper.findByPrimaryKey(node.getValueOfParentId());
+ path.push(node);
+ }
+ while (!path.empty()) {
+ const db::INode& seg = path.top();
+ if (seg.getParentId() == nullptr) {
+ Json::Value json_seg;
+ json_seg["path"] = "/";
+ json_seg["node"] = seg.getValueOfId();
+ segments.append(json_seg);
+ } else {
+ Json::Value json_seg;
+ json_seg["path"] = seg.getValueOfName();
+ json_seg["node"] = seg.getValueOfId();
+ segments.append(json_seg);
+ if (seg.getValueOfIsFile() == 0) {
+ json_seg.removeMember("node");
+ json_seg["path"] = "/";
+ segments.append(json_seg);
+ }
+ }
+ path.pop();
+ }
+ Json::Value resp;
+ resp["segments"] = segments;
+ return resp;
+}
+
+uint64_t calc_total_size(const db::INode& base) {
+ uint64_t size = 0;
+ std::stack queue;
+ queue.push(base);
+ while (!queue.empty()) {
+ const db::INode& node = queue.top();
+ if (node.getValueOfIsFile() == 0) {
+ auto children = api::fs::get_children(node);
+ queue.pop();
+ for (const auto& child : children) {
+ if (child.getValueOfIsFile() == 0) queue.push(child);
+ else if (child.getSize()) size += child.getValueOfSize();
+ }
+ } else {
+ size += node.getValueOfSize();
+ queue.pop();
+ }
+ }
+ return size;
+}
+
+void add_to_zip(struct zip_t* zip, const std::string& key, const db::INode& node, const std::string& path) {
+ if (node.getValueOfIsFile() == 0) {
+ std::string new_path = path + node.getValueOfName() + "/";
+ zip_entry_opencasesensitive(zip, new_path.c_str());
+ zip_entry_close(zip);
+ auto children = api::fs::get_children(node);
+ for (const auto& child : children)
+ add_to_zip(zip, key, child, new_path);
+ } else {
+ zip_entry_opencasesensitive(zip, (path + node.getValueOfName()).c_str());
+ std::ifstream file("./files/" + std::to_string(node.getValueOfId()), std::ifstream::binary);
+ std::vector buffer(64*1024);
+ while (!file.eof()) {
+ file.read(buffer.data(), (std::streamsize)buffer.size());
+ auto read = file.gcount();
+ zip_entry_write(zip, buffer.data(), read);
+ std::get<1>(in_progress_zips[key]) += read;
+ }
+ zip_entry_close(zip);
+ }
+}
+
+template
+std::string join_string(InputIt first, InputIt last, const std::string& separator = ",") {
+ std::ostringstream result;
+ if (first != last) {
+ result << *first;
+ while (++first != last) {
+ result << separator << *first;
+ }
+ }
+ return result.str();
}
namespace api {
@@ -48,26 +223,31 @@ namespace api {
return inode_mapper.findBy(db::Criteria(db::INode::Cols::_parent_id, db::CompareOps::EQ, parent.getValueOfId()));
}
- std::variant fs::create_node(std::string name, const db::User& owner, bool file, const std::optional &parent, bool force) {
+ std::variant>
+ fs::create_node(std::string name, const db::User& owner, bool file, const std::optional &parent, bool force) {
// Stolen from https://github.com/boostorg/filesystem/blob/develop/src/portability.cpp
if (!force)
if (name.empty() || name[0] == ' ' || name.find_first_of(windows_invalid_chars, 0, sizeof(windows_invalid_chars)) != std::string::npos || *(name.end() - 1) == ' ' || *(name.end() - 1) == '.' || name == "." || name == "..")
- return {"Invalid name"};
+ return {create_node_error::INVALID_NAME};
db::INode node;
node.setIsFile(file ? 1 : 0);
node.setName(name);
node.setOwnerId(owner.getValueOfId());
+ node.setHasPreview(0);
if (parent.has_value()) {
auto parent_node = get_node_and_validate(owner, *parent);
if (!parent_node.has_value())
- return {"Invalid parent"};
+ return {create_node_error::INVALID_PARENT};
if (parent_node->getValueOfIsFile() != 0)
- return {"Can't use file as parent"};
+ return {create_node_error::FILE_PARENT};
auto children = get_children(*parent_node);
for (const auto& child : children)
if (child.getValueOfName() == name)
- return {"File/Folder already exists"};
+ return {std::make_tuple(
+ child.getValueOfIsFile() != 0,
+ child.getValueOfId()
+ )};
node.setParentId(*parent);
}
db::MapperInode inode_mapper(drogon::app().getDbClient());
@@ -75,18 +255,56 @@ namespace api {
return {node};
}
- void fs::delete_node(db::INode node, bool allow_root) {
+ void fs::delete_node(db::INode node, msd::channel& chan, bool allow_root) {
if (node.getValueOfParentId() == 0 && (!allow_root)) return;
- if (node.getValueOfIsFile() == 0) {
- auto children = get_children(node);
- for (const auto& child : children) delete_node(child, false);
- } else {
+
+ db::MapperInode inode_mapper(drogon::app().getDbClient());
+
+ const auto delete_file = [&chan, &inode_mapper](const db::INode& node) {
+ std::string entry = "Deleting ";
+ generate_path(node, entry);
+ entry >> chan;
std::filesystem::path p("./files");
p /= std::to_string(node.getValueOfId());
std::filesystem::remove(p);
+ if (node.getValueOfHasPreview() != 0)
+ std::filesystem::remove(p.string() + "_preview.png");
+ inode_mapper.deleteOne(node);
+ std::string(" Done\n") >> chan;
+ };
+
+ std::stack queue, files, folders;
+
+ if (node.getValueOfIsFile() == 0) queue.push(node);
+ else files.push(node);
+
+ while (!queue.empty()) {
+ while (!files.empty()) {
+ delete_file(files.top());
+ files.pop();
+ }
+ std::string entry = "Deleting ";
+ generate_path(queue.top(), entry);
+ entry += "\n";
+ entry >> chan;
+ auto children = get_children(queue.top());
+ folders.push(queue.top());
+ queue.pop();
+ for (const auto& child : children) {
+ if (child.getValueOfIsFile() == 0) queue.push(child);
+ else files.push(child);
+ }
+ }
+
+ while (!files.empty()) {
+ delete_file(files.top());
+ files.pop();
+ }
+
+ while (!folders.empty()) {
+ inode_mapper.deleteOne(folders.top());
+ folders.pop();
}
- db::MapperInode inode_mapper(drogon::app().getDbClient());
- inode_mapper.deleteOne(node);
}
void fs::root(req_type req, cbk_type cbk) {
@@ -98,23 +316,11 @@ namespace api {
db::User user = dto::get_user(req);
auto inode = get_node_and_validate(user, node);
if (!inode.has_value())
- cbk(dto::Responses::get_badreq_res("Unknown node"));
- else if (inode->getValueOfIsFile() == 0) {
- std::vector children;
- for (const db::INode& child : get_children(*inode)) children.push_back(child.getValueOfId());
- cbk(dto::Responses::get_node_folder_res(
- inode->getValueOfId(),
- inode->getValueOfName(),
- inode->getParentId(),
- children
- ));
- } else
- cbk(dto::Responses::get_node_file_res(
- inode->getValueOfId(),
- inode->getValueOfName(),
- inode->getParentId(),
- inode->getValueOfSize()
- ));
+ return cbk(dto::Responses::get_badreq_res("Unknown node"));
+ auto dto_node = dto::Responses::GetNodeEntry(*inode);
+ std::vector children;
+ if (!dto_node.is_file) for (const db::INode& child : get_children(*inode)) children.emplace_back(child);
+ cbk(dto::Responses::get_node_res(dto_node, children));
}
void fs::path(req_type req, cbk_type cbk, uint64_t node) {
@@ -122,8 +328,10 @@ namespace api {
auto inode = get_node_and_validate(user, node);
if (!inode.has_value())
cbk(dto::Responses::get_badreq_res("Unknown node"));
- else
- cbk(dto::Responses::get_path_res( generate_path(*inode)));
+ else {
+ auto path = generate_path(*inode);
+ cbk(dto::Responses::get_success_res(path));
+ }
}
template
@@ -135,10 +343,18 @@ namespace api {
std::string name = dto::json_get(json, "name").value();
auto new_node = create_node(name, user, file, std::make_optional(parent));
- if (std::holds_alternative(new_node))
- cbk(dto::Responses::get_badreq_res(std::get(new_node)));
- else
+ if (std::holds_alternative(new_node))
cbk(dto::Responses::get_new_node_res(std::get(new_node).getValueOfId()));
+ else if (std::holds_alternative(new_node))
+ switch (std::get(new_node)) {
+ case create_node_error::INVALID_NAME: return cbk(dto::Responses::get_badreq_res("Invalid name"));
+ case create_node_error::INVALID_PARENT: return cbk(dto::Responses::get_badreq_res("Invalid parent"));
+ case create_node_error::FILE_PARENT: return cbk(dto::Responses::get_badreq_res("Parent is file"));
+ }
+ else {
+ auto tuple = std::get>(new_node);
+ cbk(dto::Responses::get_node_exists_res(std::get<1>(tuple), std::get<0>(tuple)));
+ }
} catch (const std::exception&) {
cbk(dto::Responses::get_badreq_res("Validation error"));
}
@@ -152,12 +368,27 @@ namespace api {
else if (inode->getValueOfParentId() == 0)
cbk(dto::Responses::get_badreq_res("Can't delete root"));
else {
- delete_node(*inode);
- cbk(dto::Responses::get_success_res());
+ auto chan = std::make_shared>();
+ std::string("Waiting in queue...\n") >> (*chan);
+ get_delete_loop()->queueInLoop([chan, inode=*inode]{
+ delete_node(inode, *chan);
+ chan->close();
+ });
+ cbk(drogon::HttpResponse::newStreamResponse([chan](char* buf, std::size_t size) -> std::size_t{
+ if (buf == nullptr) return 0;
+ if (chan->closed() && chan->empty()) return 0;
+ std::string buffer;
+ buffer << *chan;
+ if (buffer.empty()) return 0;
+ std::size_t read = std::min(size, buffer.size());
+ std::memcpy(buf, buffer.data(), read); // NOLINT(bugprone-not-null-terminated-result)
+ return read;
+ }));
}
}
void fs::upload(req_type req, cbk_type cbk, uint64_t node) {
+ constexpr int image_height = 256;
db::User user = dto::get_user(req);
auto inode = get_node_and_validate(user, node);
@@ -178,13 +409,73 @@ namespace api {
p /= std::to_string(inode->getValueOfId());
file.saveAs(p.string());
+ try {
+ if (file.fileLength() > 100 * 1024 * 1024) throw std::exception();
+ std::filesystem::path filename(inode->getValueOfName());
+ const std::string& mime = mime_type_map.at(filename.extension().string());
+ if (!mime.starts_with("image")) throw std::exception();
+ cv::_InputArray image_arr(file.fileData(), (int) file.fileLength());
+ cv::Mat image = cv::imdecode(image_arr, cv::IMREAD_COLOR);
+ if (!image.empty()) {
+ float h_ration = ((float) image_height) / ((float) image.rows);
+ cv::Mat preview;
+ cv::resize(image, preview, cv::Size((int) (((float) image.cols) * h_ration), image_height), 0, 0, cv::INTER_AREA);
+ cv::imwrite(p.string() + "_preview.png", preview);
+ inode->setHasPreview(1);
+ }
+ } catch (const std::exception&) {}
inode->setSize(file.fileLength());
+
db::MapperInode inode_mapper(drogon::app().getDbClient());
inode_mapper.update(*inode);
+
cbk(dto::Responses::get_success_res());
}
+ void fs::create_zip(req_type req, cbk_type cbk) {
+ db::User user = dto::get_user(req);
+ Json::Value& json = *req->jsonObject();
+ try {
+ if (!json.isMember("nodes")) throw std::exception();
+ Json::Value node_arr = json["nodes"];
+ if (!node_arr.isArray()) throw std::exception();
+ std::vector node_ids;
+ for (const auto& node : node_arr)
+ node_ids.push_back(node.asUInt64());
+
+ std::vector nodes;
+ std::transform(node_ids.begin(), node_ids.end(), std::back_inserter(nodes), [&user](uint64_t node) {
+ return api::fs::get_node_and_validate(user, node).value();
+ });
+
+ std::string key = join_string(node_ids.begin(), node_ids.end());
+
+ if (zip_to_temp_map.contains(key)) return cbk(dto::Responses::get_create_zip_done_res());
+ if (in_progress_zips.contains(key)) {
+ auto progress = in_progress_zips.at(key);
+ return cbk(dto::Responses::get_create_zip_done_res(std::get<1>(progress), std::get<2>(progress)));
+ }
+ uint64_t size = 0;
+ for (const auto& node : nodes) size += calc_total_size(node);
+ std::string file_name = "./temp/fs_" + std::to_string(next_temp_id++) + ".zip";
+ in_progress_zips.emplace(key, std::make_tuple(file_name, 0, size));
+ get_zip_loop()->queueInLoop([key = std::move(key), nodes = std::move(nodes), file_name = std::move(file_name)]{
+ {
+ struct zip_t* zip = zip_open(file_name.c_str(), ZIP_DEFAULT_COMPRESSION_LEVEL, 'w');
+ for (const db::INode& node : nodes)
+ add_to_zip(zip, key, node, "");
+ zip_close(zip);
+ }
+ zip_to_temp_map.emplace(key, file_name);
+ in_progress_zips.erase(key);
+ });
+ return cbk(dto::Responses::get_create_zip_done_res(0, size));
+ } catch (const std::exception&) {
+ cbk(dto::Responses::get_badreq_res("Validation error"));
+ }
+ }
+
void fs::download(req_type req, cbk_type cbk) {
db::User user = dto::get_user(req);
@@ -193,19 +484,114 @@ namespace api {
cbk(dto::Responses::get_badreq_res("Invalid node"));
return;
}
- auto inode = get_node_and_validate(user, *node_id);
+ auto inode = get_node_and_validate(user, *node_id);
if (!inode.has_value()) {
cbk(dto::Responses::get_badreq_res("Invalid node"));
return;
}
+ if (inode->getValueOfIsFile() != 0) {
+ std::filesystem::path p("./files");
+ p /= std::to_string(inode->getValueOfId());
+
+ cbk(drogon::HttpResponse::newFileResponse(
+ p.string(),
+ inode->getValueOfName()
+ ));
+ } else {
+ try {
+ std::string key = std::to_string(inode->getValueOfId());
+ std::string file = zip_to_temp_map.at(key);
+ zip_to_temp_map.erase(key);
+ cbk(drogon::HttpResponse::newFileResponse(
+ file,
+ inode->getValueOfName() + ".zip"
+ ));
+ } catch (const std::exception&) {
+ cbk(dto::Responses::get_badreq_res("Invalid node"));
+ }
+ }
+ }
+
+ void fs::download_multi(req_type req, cbk_type cbk) {
+ db::User user = dto::get_user(req);
+
+ auto node_ids_str = req->getOptionalParameter("id");
+ if (!node_ids_str.has_value())
+ return cbk(dto::Responses::get_badreq_res("No nodes"));
+
+ std::stringstream node_ids_ss(*node_ids_str);
+ std::string temp;
+ try {
+ while (std::getline(node_ids_ss, temp, ','))
+ if (!get_node_and_validate(user, std::stoull(temp)).has_value()) throw std::exception();
+
+ std::string file = zip_to_temp_map.at(*node_ids_str);
+ zip_to_temp_map.erase(*node_ids_str);
+ cbk(drogon::HttpResponse::newFileResponse(
+ file,
+ "files.zip"
+ ));
+ } catch (const std::exception&) {
+ cbk(dto::Responses::get_badreq_res("Invalid nodes"));
+ }
+ }
+
+ void fs::download_preview(req_type req, cbk_type cbk, uint64_t node) {
+ db::User user = dto::get_user(req);
+
+ auto inode = get_node_and_validate(user, node);
+ if (!inode.has_value())
+ return cbk(dto::Responses::get_badreq_res("Unknown node"));
+ if (inode->getValueOfHasPreview() == 0)
+ return cbk(dto::Responses::get_badreq_res("No preview"));
+
std::filesystem::path p("./files");
+ p /= std::to_string(inode->getValueOfId()) + "_preview.png";
+ std::ifstream file(p, std::ios::in | std::ios::binary);
+ std::vector image((std::istreambuf_iterator(file)), std::istreambuf_iterator());
+
+ cbk(dto::Responses::get_download_base64_res("data:image/png;base64," + Botan::base64_encode(image)));
+ }
+
+ void fs::download_base64(req_type req, cbk_type cbk, uint64_t node) {
+ db::User user = dto::get_user(req);
+
+ auto inode = get_node_and_validate(user, node);
+ if (!inode.has_value())
+ return cbk(dto::Responses::get_badreq_res("Unknown node"));
+
+
+ std::filesystem::path p("./files"), name(inode->getValueOfName());
p /= std::to_string(inode->getValueOfId());
- cbk(drogon::HttpResponse::newFileResponse(
- p.string(),
- inode->getValueOfName()
- ));
+ try {
+ std::string mime = mime_type_map.at(name.extension().string());
+ std::ifstream file(p, std::ios::in | std::ios::binary);
+ std::vector content((std::istreambuf_iterator(file)), std::istreambuf_iterator());
+
+ cbk(dto::Responses::get_download_base64_res("data:" + mime + ";base64," + Botan::base64_encode(content)));
+ } catch (const std::exception&) {
+ cbk(dto::Responses::get_badreq_res("Invalid file type"));
+ }
+ }
+
+ void fs::get_type(req_type req, cbk_type cbk, uint64_t node){
+ db::User user = dto::get_user(req);
+
+ auto inode = get_node_and_validate(user, node);
+ if (!inode.has_value())
+ return cbk(dto::Responses::get_badreq_res("Unknown node"));
+
+
+ std::filesystem::path p("./files"), name(inode->getValueOfName());
+ p /= std::to_string(inode->getValueOfId());
+
+ try {
+ cbk(dto::Responses::get_type_res(mime_type_map.at(name.extension().string())));
+ } catch (const std::exception&) {
+ cbk(dto::Responses::get_badreq_res("Invalid file type"));
+ }
}
}
#pragma clang diagnostic pop
\ No newline at end of file
diff --git a/backend/src/controllers/user.cpp b/backend/src/controllers/user.cpp
index aab0fb7..d650726 100644
--- a/backend/src/controllers/user.cpp
+++ b/backend/src/controllers/user.cpp
@@ -18,9 +18,10 @@ namespace api {
void user::delete_user(req_type req, cbk_type cbk) {
db::MapperUser user_mapper(drogon::app().getDbClient());
+ msd::channel chan;
db::User user = dto::get_user(req);
auth::revoke_all(user);
- fs::delete_node((fs::get_node(user.getValueOfRootId())).value(), true);
+ fs::delete_node((fs::get_node(user.getValueOfRootId())).value(), chan, true);
user_mapper.deleteOne(user);
cbk(dto::Responses::get_success_res());
diff --git a/backend/src/db/db.h b/backend/src/db/db.h
index f72e684..06e1165 100644
--- a/backend/src/db/db.h
+++ b/backend/src/db/db.h
@@ -6,9 +6,9 @@
#include
#include
-#include "model/Inode.h"
-#include "model/Tokens.h"
-#include "model/User.h"
+#include "Inode.h"
+#include "Tokens.h"
+#include "User.h"
const std::string jwt_secret = "CUM";
diff --git a/backend/src/dto/dto.h b/backend/src/dto/dto.h
index 46ea9d9..d605993 100644
--- a/backend/src/dto/dto.h
+++ b/backend/src/dto/dto.h
@@ -30,6 +30,16 @@ namespace dto {
db::UserRole role;
};
+ struct GetNodeEntry {
+ explicit GetNodeEntry(const db::INode& node) : id(node.getValueOfId()), name(node.getValueOfName()), is_file(node.getValueOfIsFile() != 0), has_preview(node.getValueOfHasPreview() != 0), parent(node.getParentId()) {
+ if (node.getValueOfIsFile() != 0) size = node.getValueOfSize();
+ }
+ uint64_t id, size;
+ std::string name;
+ bool is_file, has_preview;
+ std::shared_ptr parent;
+ };
+
drogon::HttpResponsePtr get_error_res(drogon::HttpStatusCode, const std::string &msg);
drogon::HttpResponsePtr get_success_res();
drogon::HttpResponsePtr get_success_res(Json::Value &);
@@ -46,10 +56,13 @@ namespace dto {
drogon::HttpResponsePtr get_admin_users_res(const std::vector& users);
drogon::HttpResponsePtr get_root_res(uint64_t root);
- drogon::HttpResponsePtr get_node_folder_res(uint64_t id, const std::string& name, const std::shared_ptr& parent, const std::vector& children);
- drogon::HttpResponsePtr get_node_file_res(uint64_t id, const std::string& name, const std::shared_ptr& parent, uint64_t size);
- drogon::HttpResponsePtr get_path_res(const std::string& path);
+ drogon::HttpResponsePtr get_node_res(const GetNodeEntry& node, const std::vector& children);
drogon::HttpResponsePtr get_new_node_res(uint64_t id);
+ drogon::HttpResponsePtr get_node_exists_res(uint64_t id, bool file);
+ drogon::HttpResponsePtr get_download_base64_res(const std::string& data);
+ drogon::HttpResponsePtr get_type_res(const std::string& type);
+ drogon::HttpResponsePtr get_create_zip_done_res();
+ drogon::HttpResponsePtr get_create_zip_done_res(uint64_t progress, uint64_t total);
}
}
diff --git a/backend/src/dto/responses.cpp b/backend/src/dto/responses.cpp
index e0abff3..4a0db1f 100644
--- a/backend/src/dto/responses.cpp
+++ b/backend/src/dto/responses.cpp
@@ -63,30 +63,24 @@ namespace dto::Responses {
return get_success_res(json);
}
- drogon::HttpResponsePtr get_node_folder_res(uint64_t id, const std::string &name, const std::shared_ptr &parent, const std::vector &children) {
+ Json::Value parse_node(const GetNodeEntry& node) {
Json::Value json;
- json["id"] = id;
- json["name"] = name;
- json["isFile"] = false;
- json["parent"] = (parent != nullptr) ? *parent : Json::Value::nullSingleton();
- for (uint64_t child : children)
- json["children"].append(child);
- return get_success_res(json);
+ json["id"] = node.id;
+ json["name"] = node.name;
+ json["isFile"] = node.is_file;
+ json["preview"] = node.has_preview;
+ json["parent"] = (node.parent != nullptr) ? *node.parent : Json::Value::nullSingleton();
+ if (node.is_file) json["size"] = node.size;
+ return json;
}
- drogon::HttpResponsePtr get_node_file_res(uint64_t id, const std::string &name, const std::shared_ptr &parent, uint64_t size) {
- Json::Value json;
- json["id"] = id;
- json["name"] = name;
- json["isFile"] = true;
- json["parent"] = (parent != nullptr) ? *parent : Json::Value::nullSingleton();
- json["size"] = size;
- return get_success_res(json);
- }
-
- drogon::HttpResponsePtr get_path_res(const std::string& path) {
- Json::Value json;
- json["path"] = path;
+ drogon::HttpResponsePtr get_node_res(const GetNodeEntry& node, const std::vector& children) {
+ Json::Value json = parse_node(node);
+ if (!node.is_file) {
+ json["children"] = Json::Value(Json::arrayValue);
+ for (const GetNodeEntry& child : children)
+ json["children"].append(parse_node(child));
+ }
return get_success_res(json);
}
@@ -95,4 +89,38 @@ namespace dto::Responses {
json["id"] = id;
return get_success_res(json);
}
+
+ drogon::HttpResponsePtr get_node_exists_res(uint64_t id, bool file) {
+ Json::Value json;
+ json["id"] = id;
+ json["exists"] = true;
+ json["isFile"] = file;
+ return get_success_res(json);
+ }
+
+ drogon::HttpResponsePtr get_download_base64_res(const std::string &data) {
+ Json::Value json;
+ json["data"] = data;
+ return get_success_res(json);
+ }
+
+ drogon::HttpResponsePtr get_type_res(const std::string &type) {
+ Json::Value json;
+ json["type"] = type;
+ return get_success_res(json);
+ }
+
+ drogon::HttpResponsePtr get_create_zip_done_res() {
+ Json::Value json;
+ json["done"] = true;
+ return get_success_res(json);
+ }
+
+ drogon::HttpResponsePtr get_create_zip_done_res(uint64_t progress, uint64_t total) {
+ Json::Value json;
+ json["done"] = false;
+ json["progress"] = progress;
+ json["total"] = total;
+ return get_success_res(json);
+ }
}
diff --git a/backend/src/filters/filters.cpp b/backend/src/filters/filters.cpp
index 471a36f..c6547a7 100644
--- a/backend/src/filters/filters.cpp
+++ b/backend/src/filters/filters.cpp
@@ -16,7 +16,7 @@ void cleanup_tokens(db::MapperToken& mapper) {
void Login::doFilter(const drogon::HttpRequestPtr& req, drogon::FilterCallback&& cb, drogon::FilterChainCallback&& ccb) {
std::string token_str;
- if (req->path() == "/api/fs/download") {
+ if (req->path() == "/api/fs/download" || req->path() == "/api/fs/download_multi") {
token_str = req->getParameter("jwtToken");
} else {
std::string auth_header = req->getHeader("Authorization");
diff --git a/backend/src/main.cpp b/backend/src/main.cpp
index 802b680..2f55a20 100644
--- a/backend/src/main.cpp
+++ b/backend/src/main.cpp
@@ -13,6 +13,9 @@ void cleanup() {
std::cout << "Cleanup up uploads...";
std::filesystem::remove_all("uploads");
std::cout << " [Done]" << std::endl;
+ std::cout << "Removing temp folder..." << std::flush;
+ std::filesystem::remove_all("temp");
+ std::cout << " [Done]" << std::endl;
std::cout << "Goodbye!" << std::endl;
}
@@ -47,6 +50,15 @@ int main(int argc, char* argv[]) {
std::filesystem::create_directory("logs");
std::cout << " [Done]" << std::endl;
}
+ if (std::filesystem::exists("temp")) {
+ std::cout << "Removing existing temp folder..." << std::flush;
+ std::filesystem::remove_all("temp");
+ std::cout << " [Done]" << std::endl;
+ }
+ std::cout << "Creating temp folder..." << std::flush;
+ std::filesystem::create_directory("temp");
+ std::cout << " [Done]" << std::endl;
+
auto* loop = drogon::app().getLoop();
loop->queueInLoop([]{
@@ -76,7 +88,8 @@ int main(int argc, char* argv[]) {
" 'name' TEXT,\n"
" 'parent_id' INTEGER,\n"
" 'owner_id' INTEGER NOT NULL,\n"
- " 'size' INTEGER\n"
+ " 'size' INTEGER,\n"
+ " 'has_preview' INTEGER NOT NULL\n"
")");
std::cout << " [Done]" << std::endl;
std::cout << "Started!" << std::endl;
@@ -101,8 +114,12 @@ int main(int argc, char* argv[]) {
Json::Value access_logger;
access_logger["name"] = "drogon::plugin::AccessLogger";
+ Json::Value smtp_mail;
+ smtp_mail["name"] = "SMTPMail";
+
Json::Value config;
config["plugins"].append(access_logger);
+ config["plugins"].append(smtp_mail);
drogon::app()
.setClientMaxBodySize(std::numeric_limits::max())
@@ -123,8 +140,10 @@ int main(int argc, char* argv[]) {
.setIntSignalHandler(cleanup)
.setTermSignalHandler(cleanup)
- .addListener("0.0.0.0", 5678)
- .setThreadNum(2);
+ .enableRelaunchOnError()
+
+ .addListener("0.0.0.0", 2345)
+ .setThreadNum(8);
std::cout << "Setup done!" << std::endl;
drogon::app().run();
diff --git a/backend/vcpkg-configuration.json b/backend/vcpkg-configuration.json
new file mode 100644
index 0000000..1d9709f
--- /dev/null
+++ b/backend/vcpkg-configuration.json
@@ -0,0 +1,15 @@
+{
+ "default-registry": {
+ "kind": "git",
+ "repository": "https://github.com/microsoft/vcpkg.git",
+ "baseline": "927006b24c3a28dfd8aa0ec5f8ce43098480a7f1"
+ },
+ "registries": [
+ {
+ "kind": "filesystem",
+ "baseline": "default",
+ "path": "./vcpkg_reg",
+ "packages": [ "drogon" ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/backend/vcpkg.json b/backend/vcpkg.json
index 310b235..d7e7dae 100644
--- a/backend/vcpkg.json
+++ b/backend/vcpkg.json
@@ -7,11 +7,15 @@
"name": "drogon",
"features": ["orm", "sqlite3"]
},
+ {
+ "name": "opencv4",
+ "default-features": false,
+ "features": ["tiff", "png", "jpeg", "webp", "openexr"]
+ },
"jwt-cpp",
"botan",
- "mailio",
"nayuki-qr-code-generator",
- "lodepng",
- "openssl"
+ "openssl",
+ "kubazip"
]
}
\ No newline at end of file
diff --git a/backend/vcpkg_reg/ports/drogon/drogon_config.patch b/backend/vcpkg_reg/ports/drogon/drogon_config.patch
new file mode 100644
index 0000000..61b7c96
--- /dev/null
+++ b/backend/vcpkg_reg/ports/drogon/drogon_config.patch
@@ -0,0 +1,13 @@
+diff --git a/cmake/templates/DrogonConfig.cmake.in b/cmake/templates/DrogonConfig.cmake.in
+index a21122a..6367259 100644
+--- a/cmake/templates/DrogonConfig.cmake.in
++++ b/cmake/templates/DrogonConfig.cmake.in
+@@ -19,7 +19,7 @@ find_dependency(UUID REQUIRED)
+ endif(NOT ${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD" AND NOT ${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD" AND NOT WIN32)
+ find_dependency(ZLIB REQUIRED)
+ if(@pg_FOUND@)
+-find_dependency(pg)
++find_dependency(PostgreSQL)
+ endif()
+ if(@SQLite3_FOUND@)
+ find_dependency(SQLite3)
diff --git a/backend/vcpkg_reg/ports/drogon/portfile.cmake b/backend/vcpkg_reg/ports/drogon/portfile.cmake
new file mode 100644
index 0000000..e639187
--- /dev/null
+++ b/backend/vcpkg_reg/ports/drogon/portfile.cmake
@@ -0,0 +1,61 @@
+vcpkg_from_github(
+ OUT_SOURCE_PATH SOURCE_PATH
+ REPO an-tao/drogon
+ REF v1.8.0
+ SHA512 a834d937e3719059223d9bf19d777dbc92eaf09c5c9c44b5a742bfefcbcd95a146a6568cef8c058050fb87e330f221434ffe784dfa29a49de12b031f86ab1a33
+ HEAD_REF master
+ PATCHES
+ vcpkg.patch
+ drogon_config.patch
+)
+
+vcpkg_check_features(
+ OUT_FEATURE_OPTIONS FEATURE_OPTIONS
+ FEATURES
+ ctl BUILD_CTL
+ mysql BUILD_MYSQL
+ orm BUILD_ORM
+ postgres BUILD_POSTGRESQL
+ postgres LIBPQ_BATCH_MODE
+ redis BUILD_REDIS
+ sqlite3 BUILD_SQLITE
+)
+
+string(COMPARE EQUAL "${VCPKG_LIBRARY_LINKAGE}" "dynamic" BUILD_DROGON_SHARED)
+
+vcpkg_cmake_configure(
+ SOURCE_PATH "${SOURCE_PATH}"
+ DISABLE_PARALLEL_CONFIGURE
+ OPTIONS
+ -DBUILD_SHARED_LIBS=${BUILD_DROGON_SHARED}
+ -DBUILD_EXAMPLES=OFF
+ -DCMAKE_DISABLE_FIND_PACKAGE_Boost=ON
+ ${FEATURE_OPTIONS}
+ MAYBE_UNUSED_VARIABLES
+ CMAKE_DISABLE_FIND_PACKAGE_Boost
+)
+
+vcpkg_cmake_install(ADD_BIN_TO_PATH)
+
+# Fix CMake files
+vcpkg_cmake_config_fixup(CONFIG_PATH lib/cmake/Drogon)
+
+vcpkg_fixup_pkgconfig()
+
+# Copy drogon_ctl
+if("ctl" IN_LIST FEATURES)
+ vcpkg_copy_tools(TOOL_NAMES drogon_ctl AUTO_CLEAN)
+endif()
+
+# Remove includes in debug
+file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/debug/include")
+file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/debug/share")
+if(VCPKG_LIBRARY_LINKAGE STREQUAL "static")
+ file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/bin" "${CURRENT_PACKAGES_DIR}/debug/bin")
+endif()
+
+file(INSTALL "${CMAKE_CURRENT_LIST_DIR}/usage" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}")
+file(INSTALL "${SOURCE_PATH}/LICENSE" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}" RENAME copyright)
+
+# Copy pdb files
+vcpkg_copy_pdbs()
diff --git a/backend/vcpkg_reg/ports/drogon/usage b/backend/vcpkg_reg/ports/drogon/usage
new file mode 100644
index 0000000..7887e34
--- /dev/null
+++ b/backend/vcpkg_reg/ports/drogon/usage
@@ -0,0 +1,4 @@
+The package drogon provides CMake targets:
+
+ find_package(Drogon CONFIG REQUIRED)
+ target_link_libraries(main PRIVATE Drogon::Drogon)
diff --git a/backend/vcpkg_reg/ports/drogon/vcpkg.json b/backend/vcpkg_reg/ports/drogon/vcpkg.json
new file mode 100644
index 0000000..a5fd728
--- /dev/null
+++ b/backend/vcpkg_reg/ports/drogon/vcpkg.json
@@ -0,0 +1,92 @@
+{
+ "name": "drogon",
+ "version-semver": "1.8.0",
+ "description": "A C++14/17 based HTTP web application framework running on Linux/macOS/Unix/Windows",
+ "homepage": "https://github.com/an-tao/drogon",
+ "documentation": "https://drogon.docsforge.com/master/overview/",
+ "license": "MIT",
+ "dependencies": [
+ "brotli",
+ "jsoncpp",
+ {
+ "name": "libuuid",
+ "platform": "!windows & !osx"
+ },
+ "trantor",
+ {
+ "name": "vcpkg-cmake",
+ "host": true
+ },
+ {
+ "name": "vcpkg-cmake-config",
+ "host": true
+ },
+ "zlib"
+ ],
+ "features": {
+ "ctl": {
+ "description": "Build drogon_ctl tool."
+ },
+ "mysql": {
+ "description": "Support reading and writing from/to MySQL databases.",
+ "dependencies": [
+ {
+ "name": "drogon",
+ "features": [
+ "orm"
+ ]
+ },
+ {
+ "name": "libmariadb",
+ "features": [
+ "iconv"
+ ],
+ "platform": "osx"
+ },
+ {
+ "name": "libmariadb",
+ "platform": "!osx"
+ }
+ ]
+ },
+ "orm": {
+ "description": "Build with object-relational mapping support."
+ },
+ "postgres": {
+ "description": "Support reading and writing from/to Postgres databases.",
+ "dependencies": [
+ {
+ "name": "drogon",
+ "features": [
+ "orm"
+ ]
+ },
+ "libpq"
+ ]
+ },
+ "redis": {
+ "description": "Support reading and writing from/to Redis databases.",
+ "dependencies": [
+ {
+ "name": "drogon",
+ "features": [
+ "orm"
+ ]
+ },
+ "hiredis"
+ ]
+ },
+ "sqlite3": {
+ "description": "Support reading and writing from/to SQLite databases.",
+ "dependencies": [
+ {
+ "name": "drogon",
+ "features": [
+ "orm"
+ ]
+ },
+ "sqlite3"
+ ]
+ }
+ }
+}
diff --git a/backend/vcpkg_reg/ports/drogon/vcpkg.patch b/backend/vcpkg_reg/ports/drogon/vcpkg.patch
new file mode 100644
index 0000000..326fcaa
--- /dev/null
+++ b/backend/vcpkg_reg/ports/drogon/vcpkg.patch
@@ -0,0 +1,53 @@
+diff --git a/CMakeLists.txt b/CMakeLists.txt
+--- a/CMakeLists.txt
++++ b/CMakeLists.txt
+@@ -120,9 +120,9 @@ if (WIN32)
+ PRIVATE $)
+ endif (WIN32)
+
+-add_subdirectory(trantor)
++find_package(Trantor CONFIG REQUIRED)
+
+-target_link_libraries(${PROJECT_NAME} PUBLIC trantor)
++target_link_libraries(${PROJECT_NAME} PUBLIC Trantor::Trantor)
+
+ if(${CMAKE_SYSTEM_NAME} STREQUAL "Haiku")
+ target_link_libraries(${PROJECT_NAME} PRIVATE network)
+@@ -316,11 +316,10 @@ endif (NOT WIN32)
+
+ if (BUILD_POSTGRESQL)
+ # find postgres
+- find_package(pg)
+- if (pg_FOUND)
+- message(STATUS "libpq inc path:" ${PG_INCLUDE_DIRS})
+- message(STATUS "libpq lib:" ${PG_LIBRARIES})
+- target_link_libraries(${PROJECT_NAME} PRIVATE pg_lib)
++ find_package(PostgreSQL REQUIRED)
++ if(PostgreSQL_FOUND)
++ set(pg_FOUND true)
++ target_link_libraries(${PROJECT_NAME} PRIVATE PostgreSQL::PostgreSQL)
+ set(DROGON_SOURCES
+ ${DROGON_SOURCES}
+ orm_lib/src/postgresql_impl/PostgreSQLResultImpl.cc)
+@@ -348,7 +348,7 @@ if (BUILD_POSTGRESQL)
+ ${private_headers}
+ orm_lib/src/postgresql_impl/PgConnection.h)
+ endif (libpq_supports_batch)
+- endif (pg_FOUND)
++ endif (PostgreSQL_FOUND)
+ endif (BUILD_POSTGRESQL)
+
+ if (BUILD_MYSQL)
+diff --git a/drogon_ctl/CMakeLists.txt b/drogon_ctl/CMakeLists.txt
+index 9f2f1e7..09871f8 100755
+--- a/drogon_ctl/CMakeLists.txt
++++ b/drogon_ctl/CMakeLists.txt
+@@ -19,7 +19,7 @@ add_executable(_drogon_ctl
+ target_link_libraries(_drogon_ctl ${PROJECT_NAME})
+ if (WIN32 AND BUILD_SHARED_LIBS)
+ set(DROGON_FILE $