From 811888ba7db3ddea0b97262a8214521721fde588 Mon Sep 17 00:00:00 2001 From: jie Date: Fri, 26 Nov 2021 20:29:10 +0800 Subject: [PATCH] add example for db --- bserv/include/bserv/utils.hpp | 9 ++ bserv/utils.cpp | 9 +- examples/DB.vcxproj | 148 ++++++++++++++++++++++++++++ examples/DB.vcxproj.filters | 22 +++++ examples/DB.vcxproj.user | 4 + examples/Examples.sln | 13 +++ examples/db.cpp | 178 ++++++++++++++++++++++++++++++++++ examples/db.sql | 11 +++ 8 files changed, 393 insertions(+), 1 deletion(-) create mode 100644 examples/DB.vcxproj create mode 100644 examples/DB.vcxproj.filters create mode 100644 examples/DB.vcxproj.user create mode 100644 examples/db.cpp create mode 100644 examples/db.sql diff --git a/bserv/include/bserv/utils.hpp b/bserv/include/bserv/utils.hpp index b21929c..9bdf257 100644 --- a/bserv/include/bserv/utils.hpp +++ b/bserv/include/bserv/utils.hpp @@ -62,6 +62,15 @@ namespace bserv::utils { namespace file { + class file_not_found : public std::exception { + private: + std::string msg_; + public: + file_not_found(const std::string& filename) + : msg_{ std::string{ "'" } + filename + "' does not exist" } {} + const char* what() const { return msg_.c_str(); } + }; + std::string read_bin(const std::string& filename); std::nullopt_t serve( diff --git a/bserv/utils.cpp b/bserv/utils.cpp index 2a04c16..c14d3ce 100644 --- a/bserv/utils.cpp +++ b/bserv/utils.cpp @@ -1,5 +1,6 @@ #include "pch.h" #include "bserv/utils.hpp" +#include "bserv/router.hpp" #include #include @@ -238,6 +239,7 @@ namespace bserv::utils { std::string read_bin(const std::string& filename) { std::ifstream fin(filename, std::ios_base::in | std::ios_base::binary); + if (!fin.is_open()) throw file_not_found{ filename }; std::string res; while (true) { char c = (char)fin.get(); @@ -285,7 +287,12 @@ namespace bserv::utils { response_type& response, const std::string& filename) { response.set(bserv::http::field::content_type, mime_type(filename)); - response.body() = read_bin(filename); + try { + response.body() = read_bin(filename); + } + catch (const file_not_found&) { + throw url_not_found_exception{}; + } response.prepare_payload(); return std::nullopt; } diff --git a/examples/DB.vcxproj b/examples/DB.vcxproj new file mode 100644 index 0000000..ab99116 --- /dev/null +++ b/examples/DB.vcxproj @@ -0,0 +1,148 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 16.0 + Win32Proj + {9c6ee570-c51b-46b0-aa25-8aa1752ca68a} + DB + 10.0 + + + + Application + true + v142 + Unicode + + + Application + false + v142 + true + Unicode + + + Application + true + v142 + Unicode + + + Application + false + v142 + true + Unicode + + + + + + + + + + + + + + + + + + + + + + true + + + false + + + true + + + false + + + + Level3 + true + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + + + + + Level3 + true + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + true + true + + + + + Level3 + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + + + + + Level3 + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + true + true + + + + + + + + + \ No newline at end of file diff --git a/examples/DB.vcxproj.filters b/examples/DB.vcxproj.filters new file mode 100644 index 0000000..25324c9 --- /dev/null +++ b/examples/DB.vcxproj.filters @@ -0,0 +1,22 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + 源文件 + + + \ No newline at end of file diff --git a/examples/DB.vcxproj.user b/examples/DB.vcxproj.user new file mode 100644 index 0000000..88a5509 --- /dev/null +++ b/examples/DB.vcxproj.user @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/examples/Examples.sln b/examples/Examples.sln index 1803b11..393e53e 100644 --- a/examples/Examples.sln +++ b/examples/Examples.sln @@ -15,6 +15,11 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Routing", "Routing.vcxproj" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "bserv", "..\bserv\bserv.vcxproj", "{F5C0CF6D-7BF9-40A5-AF2E-8FC36A1D7296}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "DB", "DB.vcxproj", "{9C6EE570-C51B-46B0-AA25-8AA1752CA68A}" + ProjectSection(ProjectDependencies) = postProject + {F5C0CF6D-7BF9-40A5-AF2E-8FC36A1D7296} = {F5C0CF6D-7BF9-40A5-AF2E-8FC36A1D7296} + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 @@ -47,6 +52,14 @@ Global {F5C0CF6D-7BF9-40A5-AF2E-8FC36A1D7296}.Release|x64.Build.0 = Release|x64 {F5C0CF6D-7BF9-40A5-AF2E-8FC36A1D7296}.Release|x86.ActiveCfg = Release|Win32 {F5C0CF6D-7BF9-40A5-AF2E-8FC36A1D7296}.Release|x86.Build.0 = Release|Win32 + {9C6EE570-C51B-46B0-AA25-8AA1752CA68A}.Debug|x64.ActiveCfg = Debug|x64 + {9C6EE570-C51B-46B0-AA25-8AA1752CA68A}.Debug|x64.Build.0 = Debug|x64 + {9C6EE570-C51B-46B0-AA25-8AA1752CA68A}.Debug|x86.ActiveCfg = Debug|Win32 + {9C6EE570-C51B-46B0-AA25-8AA1752CA68A}.Debug|x86.Build.0 = Debug|Win32 + {9C6EE570-C51B-46B0-AA25-8AA1752CA68A}.Release|x64.ActiveCfg = Release|x64 + {9C6EE570-C51B-46B0-AA25-8AA1752CA68A}.Release|x64.Build.0 = Release|x64 + {9C6EE570-C51B-46B0-AA25-8AA1752CA68A}.Release|x86.ActiveCfg = Release|Win32 + {9C6EE570-C51B-46B0-AA25-8AA1752CA68A}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/examples/db.cpp b/examples/db.cpp new file mode 100644 index 0000000..5a3d7c8 --- /dev/null +++ b/examples/db.cpp @@ -0,0 +1,178 @@ +#include +#include +#include +#include +bserv::db_relation_to_object orm_user{ + bserv::make_db_field("id"), + bserv::make_db_field("username"), + bserv::make_db_field("password"), + bserv::make_db_field("is_active"), + bserv::make_db_field("is_superuser"), + bserv::make_db_field>("first_name"), + bserv::make_db_field>("last_name"), + bserv::make_db_field>("email") +}; +std::optional get_user( + bserv::db_transaction& tx, + const boost::json::value& username) { + bserv::db_result r = tx.exec( + "select * from ex_auth_user where username = ?;", username); + lginfo << r.query(); + return orm_user.convert_to_optional(r); +} +std::optional get_or_empty( + boost::json::object& obj, + const std::string& key) { + if (!obj.contains(key)) return std::nullopt; + return obj[key]; +} +boost::json::object greet( + std::shared_ptr session_ptr) { + bserv::session_type& session = *session_ptr; + if (session.count("user")) { + boost::json::object& user = session["user"].as_object(); + boost::json::object obj; + // the first way to check non-null (!= nullptr) + if (user["first_name"] != nullptr && user["last_name"] != nullptr) { + obj["welcome"] = std::string{ user["first_name"].as_string() } + + ' ' + std::string{ user["last_name"].as_string() }; + } + else obj["welcome"] = user["username"]; + // the second way (!is_null()) + if (!user["email"].is_null()) { + obj["email"] = user["email"]; + } + return obj; + } + else return { {"hello", "world"} }; +} +boost::json::object user_register( + bserv::request_type& request, + boost::json::object&& params, + std::shared_ptr conn) { + if (request.method() != boost::beast::http::verb::post) + throw bserv::url_not_found_exception{}; + if (!params.contains("username")) { + return { + {"success", false}, + {"message", "`username` is required"} + }; + } + if (!params.contains("password")) { + return { + {"success", false}, + {"message", "`password` is required"} + }; + } + auto username = params["username"]; + bserv::db_transaction tx{ conn }; + auto opt_user = get_user(tx, username); + if (opt_user) { + return { + {"success", false}, + {"message", "`username` existed"} + }; + } + auto password = params["password"].as_string(); + bserv::db_result r = tx.exec( + "insert into ex_auth_user " + "(username, password, is_active, is_superuser, " + "first_name, last_name, email) values " + "(?, ?, ?, ?, ?, ?, ?);", + username, + bserv::utils::security::encode_password( + password.c_str()), + params.contains("is_active") ? params["is_active"] : true, + params.contains("is_superuser") ? params["is_superuser"] : false, + get_or_empty(params, "first_name"), + get_or_empty(params, "last_name"), + get_or_empty(params, "email")); + lginfo << r.query(); + tx.commit(); // commit must be done explicitly + return { + {"success", true}, + {"message", "user registered"} + }; +} +boost::json::object user_login( + bserv::request_type& request, + boost::json::object&& params, + std::shared_ptr conn, + std::shared_ptr session_ptr) { + if (request.method() != boost::beast::http::verb::post) + throw bserv::url_not_found_exception{}; + if (!params.contains("username")) { + return { + {"success", false}, + {"message", "`username` is required"} + }; + } + if (!params.contains("password")) { + return { + {"success", false}, + {"message", "`password` is required"} + }; + } + auto username = params["username"]; + bserv::db_transaction tx{ conn }; + auto opt_user = get_user(tx, username); + if (!opt_user) { + return { + {"success", false}, + {"message", "invalid username/password"} + }; + } + auto& user = opt_user.value(); + if (!user["is_active"].as_bool()) { + return { + {"success", false}, + {"message", "invalid username/password"} + }; + } + auto password = params["password"].as_string(); + auto encoded_password = user["password"].as_string(); + if (!bserv::utils::security::check_password( + password.c_str(), encoded_password.c_str())) { + return { + {"success", false}, + {"message", "invalid username/password"} + }; + } + bserv::session_type& session = *session_ptr; + session["user"] = user; + return { + {"success", true}, + {"message", "login successfully"} + }; +} +boost::json::object user_logout( + std::shared_ptr session_ptr) { + bserv::session_type& session = *session_ptr; + if (session.count("user")) + session.erase("user"); + return { + {"success", true}, + {"message", "logout successfully"} + }; +} +int main() { + std::string config_content = bserv::utils::file::read_bin("../config.json"); + boost::json::object config_obj = boost::json::parse(config_content).as_object(); + bserv::server_config config; + config.set_db_conn_str(config_obj["conn-str"].as_string().c_str()); + bserv::server{ config, { + bserv::make_path("/greet", &greet, + bserv::placeholders::session), + bserv::make_path("/register", &user_register, + bserv::placeholders::request, + bserv::placeholders::json_params, + bserv::placeholders::db_connection_ptr), + bserv::make_path("/login", &user_login, + bserv::placeholders::request, + bserv::placeholders::json_params, + bserv::placeholders::db_connection_ptr, + bserv::placeholders::session), + bserv::make_path("/logout", &user_logout, + bserv::placeholders::session) + } }; +} \ No newline at end of file diff --git a/examples/db.sql b/examples/db.sql new file mode 100644 index 0000000..ce08b9f --- /dev/null +++ b/examples/db.sql @@ -0,0 +1,11 @@ +DROP TABLE IF EXISTS ex_auth_user; +CREATE TABLE ex_auth_user ( + id serial PRIMARY KEY, + username character varying(255) NOT NULL UNIQUE, + password character varying(255) NOT NULL, + is_active boolean NOT NULL, + is_superuser boolean NOT NULL, + first_name character varying(255), + last_name character varying(255), + email character varying(255) +); \ No newline at end of file