#include "handlers.h" #include #include "rendering.h" // register an orm mapping (to convert the db query results into // json objects). // the db query results contain several rows, each has a number of // fields. the order of `make_db_field(name[i])` in the // initializer list corresponds to these fields (`Type[0]` and // `name[0]` correspond to field[0], `Type[1]` and `name[1]` // correspond to field[1], ...). `Type[i]` is the type you want // to convert the field value to, and `name[i]` is the identifier // with which you want to store the field in the json object, so // if the returned json object is `obj`, `obj[name[i]]` will have // the type of `Type[i]` and store the value of field[i]. 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_superuser"), bserv::make_db_field("first_name"), bserv::make_db_field("last_name"), bserv::make_db_field("email"), bserv::make_db_field("is_active") }; std::optional get_user( bserv::db_transaction& tx, const boost::json::string& username) { bserv::db_result r = tx.exec( "select * from auth_user where username = ?", username); lginfo << r.query(); // this is how you log info return orm_user.convert_to_optional(r); } std::string get_or_empty( boost::json::object& obj, const std::string& key) { return obj.count(key) ? obj[key].as_string().c_str() : ""; } // if you want to manually modify the response, // the return type should be `std::nullopt_t`, // and the return value should be `std::nullopt`. std::nullopt_t hello( bserv::response_type& response, std::shared_ptr session_ptr) { bserv::session_type& session = *session_ptr; boost::json::object obj; if (session.count("user")) { // NOTE: modifications to sessions must be performed // BEFORE referencing objects in them. this is because // modifications might invalidate referenced objects. // in this example, "count" might be added to `session`, // which should be performed first. // then `user` can be referenced safely. if (!session.count("count")) { session["count"] = 0; } auto& user = session["user"].as_object(); session["count"] = session["count"].as_int64() + 1; obj = { {"welcome", user["username"]}, {"count", session["count"]} }; } else { obj = { {"msg", "hello, world!"} }; } // the response body is a string, // so the `obj` should be serialized response.body() = boost::json::serialize(obj); response.prepare_payload(); // this line is important! return std::nullopt; } // if you return a json object, the serialization // is performed automatically. boost::json::object user_register( bserv::request_type& request, // the json object is obtained from the request body, // as well as the url parameters boost::json::object&& params, std::shared_ptr conn) { if (request.method() != boost::beast::http::verb::post) { throw bserv::url_not_found_exception{}; } if (params.count("username") == 0) { return { {"success", false}, {"message", "`username` is required"} }; } if (params.count("password") == 0) { return { {"success", false}, {"message", "`password` is required"} }; } auto username = params["username"].as_string(); bserv::db_transaction tx{ conn }; auto opt_user = get_user(tx, username); if (opt_user.has_value()) { return { {"success", false}, {"message", "`username` existed"} }; } auto password = params["password"].as_string(); bserv::db_result r = tx.exec( "insert into ? " "(?, password, is_superuser, " "first_name, last_name, email, is_active) values " "(?, ?, ?, ?, ?, ?, ?)", bserv::db_name("auth_user"), bserv::db_name("username"), username, bserv::utils::security::encode_password( password.c_str()), false, get_or_empty(params, "first_name"), get_or_empty(params, "last_name"), get_or_empty(params, "email"), true); lginfo << r.query(); tx.commit(); // you must manually commit changes 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.count("username") == 0) { return { {"success", false}, {"message", "`username` is required"} }; } if (params.count("password") == 0) { return { {"success", false}, {"message", "`password` is required"} }; } auto username = params["username"].as_string(); bserv::db_transaction tx{ conn }; auto opt_user = get_user(tx, username); if (!opt_user.has_value()) { 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 find_user( std::shared_ptr conn, const std::string& username) { bserv::db_transaction tx{ conn }; auto user = get_user(tx, username.c_str()); if (!user.has_value()) { return { {"success", false}, {"message", "requested user does not exist"} }; } user.value().erase("id"); user.value().erase("password"); return { {"success", true}, {"user", user.value()} }; } 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"} }; } boost::json::object send_request( std::shared_ptr session, std::shared_ptr client_ptr, boost::json::object&& params) { // post for response: // auto res = client_ptr->post( // "localhost", "8080", "/echo", {{"msg", "request"}} // ); // return {{"response", boost::json::parse(res.body())}}; // ------------------------------------------------------- // - if it takes longer than 30 seconds (by default) to // - get the response, this will raise a read timeout // ------------------------------------------------------- // post for json response (json value, rather than json // object, is returned): auto obj = client_ptr->post_for_value( "localhost", "8080", "/echo", { {"request", params} } ); if (session->count("cnt") == 0) { (*session)["cnt"] = 0; } (*session)["cnt"] = (*session)["cnt"].as_int64() + 1; return { {"response", obj}, {"cnt", (*session)["cnt"]} }; } boost::json::object echo( boost::json::object&& params) { return { {"echo", params} }; } // websocket std::nullopt_t ws_echo( std::shared_ptr session, std::shared_ptr ws_server) { ws_server->write_json((*session)["cnt"]); while (true) { try { std::string data = ws_server->read(); ws_server->write(data); } catch (bserv::websocket_closed&) { break; } } return std::nullopt; } std::nullopt_t serve_static_files( bserv::response_type& response, const std::string& path) { return serve(response, path); } std::nullopt_t index( const std::string& template_path, std::shared_ptr session_ptr, bserv::response_type& response, boost::json::object& context) { bserv::session_type& session = *session_ptr; if (session.contains("user")) { context["user"] = session["user"]; } return render(response, template_path, context); } std::nullopt_t index_page( std::shared_ptr session_ptr, bserv::response_type& response) { boost::json::object context; return index("index.html", session_ptr, response, context); } std::nullopt_t form_login( bserv::request_type& request, bserv::response_type& response, boost::json::object&& params, std::shared_ptr conn, std::shared_ptr session_ptr) { lgdebug << params << std::endl; auto context = user_login(request, std::move(params), conn, session_ptr); lginfo << "login: " << context << std::endl; return index("index.html", session_ptr, response, context); } std::nullopt_t form_logout( std::shared_ptr session_ptr, bserv::response_type& response) { auto context = user_logout(session_ptr); lginfo << "logout: " << context << std::endl; return index("index.html", session_ptr, response, context); } std::nullopt_t redirect_to_users( std::shared_ptr conn, std::shared_ptr session_ptr, bserv::response_type& response, int page_id, boost::json::object&& context) { lgdebug << "view users: " << page_id << std::endl; bserv::db_transaction tx{ conn }; bserv::db_result db_res = tx.exec("select count(*) from auth_user;"); lginfo << db_res.query(); std::size_t total_users = (*db_res.begin())[0].as(); lgdebug << "total users: " << total_users << std::endl; int total_pages = (int)total_users / 10; if (total_users % 10 != 0) ++total_pages; lgdebug << "total pages: " << total_pages << std::endl; db_res = tx.exec("select * from auth_user limit 10 offset ?;", (page_id - 1) * 10); lginfo << db_res.query(); auto users = orm_user.convert_to_vector(db_res); boost::json::array json_users; for (auto& user : users) { json_users.push_back(user); } boost::json::object pagination; if (total_pages != 0) { pagination["total"] = total_pages; if (page_id > 1) { pagination["previous"] = page_id - 1; } if (page_id < total_pages) { pagination["next"] = page_id + 1; } int lower = page_id - 3; int upper = page_id + 3; if (page_id - 3 > 2) { pagination["left_ellipsis"] = true; } else { lower = 1; } if (page_id + 3 < total_pages - 1) { pagination["right_ellipsis"] = true; } else { upper = total_pages; } pagination["current"] = page_id; boost::json::array pages_left; for (int i = lower; i < page_id; ++i) { pages_left.push_back(i); } pagination["pages_left"] = pages_left; boost::json::array pages_right; for (int i = page_id + 1; i <= upper; ++i) { pages_right.push_back(i); } pagination["pages_right"] = pages_right; context["pagination"] = pagination; } context["users"] = json_users; return index("users.html", session_ptr, response, context); } std::nullopt_t view_users( std::shared_ptr conn, std::shared_ptr session_ptr, bserv::response_type& response, const std::string& page_num) { int page_id = std::stoi(page_num); boost::json::object context; return redirect_to_users(conn, session_ptr, response, page_id, std::move(context)); } std::nullopt_t form_add_user( bserv::request_type& request, bserv::response_type& response, boost::json::object&& params, std::shared_ptr conn, std::shared_ptr session_ptr) { boost::json::object context = user_register(request, std::move(params), conn); return redirect_to_users(conn, session_ptr, response, 1, std::move(context)); }