From 799e156f884dbf4d3a63a582917cc372bc0411ae Mon Sep 17 00:00:00 2001 From: Andreev Gregory Date: Sun, 25 Aug 2024 13:06:26 +0300 Subject: [PATCH] Fiexs bugs caused by misuse if sqlite3, fixed very old memory leak, added separate .cpp files for each request type, added raw login page, did some refactoring, added functions for http redirection (307), /internalapi/pollEvents is done, /internalapi/getChatList is done --- assets/HypertextPages/list-rooms.nytl.html | 92 ++++----- assets/HypertextPages/login.nytl.html | 44 ++--- .../pass-pres-userinfo.nytl.html | 6 + assets/HypertextPages/pass-userinfo.nytl.html | 5 - building/main.cpp | 4 + example/config.json | 2 +- .../http_structures/response_gen.cpp | 6 +- .../running_mainloop.cpp | 10 +- .../new_york_transit_line/rendering.cpp | 14 +- .../new_york_transit_line/templater.cpp | 1 + .../backend_logic/server_data_interact.cpp | 114 +++++++++++ .../backend_logic/server_data_interact.h | 79 ++++++++ .../when_internalapi_getchatlist.cpp | 42 ++++ .../when_internalapi_pollevents.cpp | 142 ++++++++++++++ .../backend_logic/when_login.cpp | 38 ++++ .../iu9_ca_web_chat_lib/initialize.cpp | 83 ++++---- src/web_chat/iu9_ca_web_chat_lib/run.cpp | 182 +++++++----------- .../iu9_ca_web_chat_lib/sqlite3_wrapper.cpp | 55 ++---- .../iu9_ca_web_chat_lib/sqlite3_wrapper.h | 8 +- src/web_chat/misc_tests/api_test0.cpp | 31 +++ .../misc_tests/find_credentials_test.cpp | 14 ++ 21 files changed, 693 insertions(+), 279 deletions(-) create mode 100644 assets/HypertextPages/pass-pres-userinfo.nytl.html delete mode 100644 assets/HypertextPages/pass-userinfo.nytl.html create mode 100644 src/web_chat/iu9_ca_web_chat_lib/backend_logic/server_data_interact.cpp create mode 100644 src/web_chat/iu9_ca_web_chat_lib/backend_logic/server_data_interact.h create mode 100644 src/web_chat/iu9_ca_web_chat_lib/backend_logic/when_internalapi_getchatlist.cpp create mode 100644 src/web_chat/iu9_ca_web_chat_lib/backend_logic/when_internalapi_pollevents.cpp create mode 100644 src/web_chat/iu9_ca_web_chat_lib/backend_logic/when_login.cpp create mode 100644 src/web_chat/misc_tests/api_test0.cpp create mode 100644 src/web_chat/misc_tests/find_credentials_test.cpp diff --git a/assets/HypertextPages/list-rooms.nytl.html b/assets/HypertextPages/list-rooms.nytl.html index 4a638ce..ddc4923 100644 --- a/assets/HypertextPages/list-rooms.nytl.html +++ b/assets/HypertextPages/list-rooms.nytl.html @@ -1,6 +1,6 @@ {% ELDEF main JSON pres JSON userinfo %} - + @@ -8,7 +8,7 @@ -{% PUT pass-userinfo userinfo %} +{% PUT pass-pres-userinfo pres userinfo %}

Выберите Чат-Комнату

- + + + + + + + + + + + + + + + + - -
-
-
- × -

Добавить участников

-
-
- -
- -
-
-
-
-
- × -

Вы уверены, что хотите удалить чат?

-
-
- - -
-
-
- + + + + + + + + + + + + + + + + + + + + + + + + + + + + {% ENDELDEF %} \ No newline at end of file diff --git a/assets/HypertextPages/login.nytl.html b/assets/HypertextPages/login.nytl.html index 700e8b8..674f42b 100644 --- a/assets/HypertextPages/login.nytl.html +++ b/assets/HypertextPages/login.nytl.html @@ -1,27 +1,27 @@ {% ELDEF main JSON pres JSON userinfo %} - - - - - - {% WRITE pres.phr.decl.page-login %} - + + + + + + {% WRITE pres.phr.decl.page-login %} + - + - - {% PUT pass-userinfo userinfo %} -
-

{% WRITE pres.phr.decl.enter %}

-
- -
- -
- -
-
+ +{% PUT pass-pres-userinfo pres userinfo %} +
+

{% WRITE pres.phr.decl.enter %}

+
+ +
+ +
+ +
+
- - + + {% ENDELDEF %} diff --git a/assets/HypertextPages/pass-pres-userinfo.nytl.html b/assets/HypertextPages/pass-pres-userinfo.nytl.html new file mode 100644 index 0000000..8331b75 --- /dev/null +++ b/assets/HypertextPages/pass-pres-userinfo.nytl.html @@ -0,0 +1,6 @@ +{% ELDEF main JSON pres JSON userinfo %} + +{% ENDELDEF %} diff --git a/assets/HypertextPages/pass-userinfo.nytl.html b/assets/HypertextPages/pass-userinfo.nytl.html deleted file mode 100644 index 164366f..0000000 --- a/assets/HypertextPages/pass-userinfo.nytl.html +++ /dev/null @@ -1,5 +0,0 @@ -{% ELDEF main JSON userinfo %} - - - -{% ENDELDEF %} diff --git a/building/main.cpp b/building/main.cpp index 9b03229..7f7cfc5 100644 --- a/building/main.cpp +++ b/building/main.cpp @@ -147,6 +147,10 @@ struct CAWebChat { "find_db.cpp", "sqlite3_wrapper.cpp", "login_cookie.cpp", + "backend_logic/server_data_interact.cpp", + "backend_logic/when_login.cpp", + "backend_logic/when_internalapi_pollevents.cpp", + "backend_logic/when_internalapi_getchatlist.cpp", }; for (std::string& u: T.units) u = "web_chat/iu9_ca_web_chat_lib/" + u; diff --git a/example/config.json b/example/config.json index 2e99947..c88f13f 100644 --- a/example/config.json +++ b/example/config.json @@ -39,6 +39,6 @@ "server": { "workers": 8, "http-listen": ["127.0.0.1:1025"], - "admin-command-listen": ["unix:/run/iu9/iu9cawebchat-ac.sock"] + "admin-command-listen": ["[::1]:1026"] } } diff --git a/src/http_server/engine_engine_number_9/http_structures/response_gen.cpp b/src/http_server/engine_engine_number_9/http_structures/response_gen.cpp index e194e3f..a1be045 100644 --- a/src/http_server/engine_engine_number_9/http_structures/response_gen.cpp +++ b/src/http_server/engine_engine_number_9/http_structures/response_gen.cpp @@ -14,7 +14,7 @@ namespace een9 { return result; } - std::string form_http_server_reponse_header_only(const char* code, + std::string form_http_server_response_header_only(const char* code, const std::vector>& headers) { return form_http_server_response_header(code, headers) + "\r\n"; } @@ -42,13 +42,13 @@ namespace een9 { } std::string form_http_server_response_307(const std::string& Location) { - return form_http_server_reponse_header_only("307", {{"Location", Location}}); + return form_http_server_response_header_only("307", {{"Location", Location}}); } std::string form_http_server_response_307_spec_head(const std::string &Location, const std::vector>& headers) { std::vector> cp = headers; cp.emplace_back("Location", Location); - return form_http_server_reponse_header_only("307", cp); + return form_http_server_response_header_only("307", cp); } } diff --git a/src/http_server/engine_engine_number_9/running_mainloop.cpp b/src/http_server/engine_engine_number_9/running_mainloop.cpp index 1e3911f..0cb1355 100644 --- a/src/http_server/engine_engine_number_9/running_mainloop.cpp +++ b/src/http_server/engine_engine_number_9/running_mainloop.cpp @@ -41,7 +41,6 @@ namespace een9 { } void push_back(SlaveTask task) { - /* Throws a goddamn execption. Because why not. Ofcourse everything has to throw an exception */ /* CLion says. Allocated memory is leaking. YOUR MOTHER IS LEAKING YOU FOOL!! MY CODE IS FINE!! */ QElementHttpConnections* el = new QElementHttpConnections(std::move(task)); /* Exception does not leave queue in incorrect state */ @@ -66,6 +65,15 @@ namespace een9 { sz--; } } + + ~WorkersTaskQueue() { + QElementHttpConnections* cur = first; + while (cur) { + QElementHttpConnections* nxt = cur->nxt; + delete cur; + cur = nxt; + } + } }; struct WorkersEnvCommon { diff --git a/src/http_server/new_york_transit_line/rendering.cpp b/src/http_server/new_york_transit_line/rendering.cpp index 3be9d53..bbdddbc 100644 --- a/src/http_server/new_york_transit_line/rendering.cpp +++ b/src/http_server/new_york_transit_line/rendering.cpp @@ -34,7 +34,7 @@ namespace nytl { } else if (P.isDictionary() && what.isString()) { const std::map& dict_p = P.asDictionary(); const std::string& key_w = what.asString(); - ASSERT(dict_p.count(key_w) == 1, "No such key exception"); + ASSERT(dict_p.count(key_w) == 1, "No such key exception (" + key_w + ")"); result = LocalVarValue{true, "", &dict_p.at(key_w)}; } else THROW("Incorrect type of \"json[json]\" expression. Unallowed signature of [] operator"); @@ -112,15 +112,9 @@ namespace nytl { for (size_t i = 0; i < m; i++) { result += text[i]; if (text[i] == '\n') { - // newlined_somewhere = true; - result.resize(result.size() + wsp_before_newlines, ' '); cur_line_width = wsp_before_newlines; } else { - // if (cur_line_width == 0 && newlined_somewhere) { - // result.resize(result.size() + wsp_before_newlines, ' '); - // cur_line_width = wsp_before_newlines; - // } cur_line_width++; } } @@ -246,7 +240,11 @@ namespace nytl { assert(passed_args.size() == 1); const json::JSON* X = passed_args[0].JSON_subval; assert(X); - if (name == "jesc") { + if (name == "jsinsert") { + std::string pure_json = json::generate_str(*X, json::print_pretty); + rstrip(pure_json); + append(pure_json, result); + } else if (name == "jesc") { std::string escaped_json = escape(json::generate_str(*X, json::print_pretty)); rstrip(escaped_json); append(escaped_json, result); diff --git a/src/http_server/new_york_transit_line/templater.cpp b/src/http_server/new_york_transit_line/templater.cpp index fa613af..b20b365 100644 --- a/src/http_server/new_york_transit_line/templater.cpp +++ b/src/http_server/new_york_transit_line/templater.cpp @@ -111,6 +111,7 @@ namespace nytl { void Templater::update() { elements = { + {"jsinsert", Element{{json::JSON(true)}, true}}, {"jesc", Element{{json::JSON(true)}, true}}, {"jesccomp", Element{{json::JSON(true)}, true}}, /* str2text base element has a dedicated operator - WRITE */ diff --git a/src/web_chat/iu9_ca_web_chat_lib/backend_logic/server_data_interact.cpp b/src/web_chat/iu9_ca_web_chat_lib/backend_logic/server_data_interact.cpp new file mode 100644 index 0000000..f4a604f --- /dev/null +++ b/src/web_chat/iu9_ca_web_chat_lib/backend_logic/server_data_interact.cpp @@ -0,0 +1,114 @@ +#include "server_data_interact.h" + +#include +#include + +namespace iu9cawebchat { + const char* stringify_user_chat_role(int64_t role) { + if (role == user_chat_role_admin) + return "admin"; + if (role == user_chat_role_regular) + return "regular"; + if (role == user_chat_role_read_only) + return "read-only"; + return "not-a-member"; + } + + int64_t find_user_by_credentials (SqliteConnection& conn, const std::string& nickname, const std::string& password) { + SqliteStatement sql_req(conn, + "SELECT `id` FROM `user` WHERE `nickname` = ?1 AND `password` = ?2", + {}, {{1, nickname}, {2, password}}); + fsql_integer_or_null id_col; + int status = sqlite_stmt_step(sql_req, {{0, &id_col}}, {}); + if (status == SQLITE_ROW) { + een9_ASSERT_pl(id_col.exist & id_col.value >= 0); + return id_col.value; + } + return -1; + } + + std::string find_user_name (SqliteConnection& conn, int64_t uid) { + een9_ASSERT(uid >= 0, "Are you crazy?"); + SqliteStatement sql_req(conn, + "SELECT `name` FROM `user` WHERE `id` = ?1", + {{1, uid}}, {}); + fsql_text8_or_null name_col; + int status = sqlite_stmt_step(sql_req, {}, {{0, &name_col}}); + if (status == SQLITE_ROW) { + een9_ASSERT_pl(name_col.exist); + return name_col.value; + } + return ""; + } + + RowUser_Content lookup_user_content(SqliteConnection &conn, int64_t uid) { + een9_ASSERT(uid >= 0, "Are you crazy?"); + SqliteStatement sql_req(conn, + "SELECT `nickname`, `name` FROM `user` WHERE `id` = ?1", + {{1, uid}}, {}); + fsql_text8_or_null nickname_col; + fsql_text8_or_null name_col; + int status = sqlite_stmt_step(sql_req, {}, {{0, &nickname_col}, {1, &name_col}}); + if (status == SQLITE_ROW) { + return {std::move(nickname_col.value), std::move(name_col.value)}; + } + return {}; + } + + RowChat_Content lookup_chat_content(SqliteConnection &conn, int64_t uid) { + een9_ASSERT(uid >= 0, "Are you crazy?"); + SqliteStatement sql_req(conn, + "SELECT `nickname`, `name`, `lastMsgId` FROM `chat` WHERE `id` = ?1", + {{1, uid}}, {}); + fsql_text8_or_null nickname_col; + fsql_text8_or_null name_col; + fsql_integer_or_null last_msg_id_col; + int status = sqlite_stmt_step(sql_req, {{2, &last_msg_id_col}}, {{0, &nickname_col}, {1, &name_col}}); + if (status == SQLITE_ROW) { + return {std::move(nickname_col.value), std::move(name_col.value), + last_msg_id_col.exist ? last_msg_id_col.value : -1}; + } + return {}; + } + + void initial_extraction_of_all_the_useful_info_from_cookies( + SqliteConnection& conn, const een9::ClientRequest& req, + std::vector> &ret_cookies, + std::vector &ret_login_cookies, + json::JSON &ret_userinfo, + int64_t& ret_logged_in_user + ) { + ret_cookies = een9::findAllClientCookies(req.headers); + ret_login_cookies = select_login_cookies(ret_cookies); + ret_logged_in_user = -1; /* Negative means that user is not logged in */ + if (!ret_login_cookies.empty()){ + size_t oldest_ind = select_oldest_login_cookie(ret_login_cookies); + LoginCookie& tried = ret_login_cookies[oldest_ind]; + ret_logged_in_user = find_user_by_credentials(conn, tried.nickname, tried.password); + if (ret_logged_in_user >= 0) { + ret_userinfo["uid"] = json::JSON(ret_logged_in_user); + ret_userinfo["nickname"] = json::JSON(tried.nickname); + ret_userinfo["name"] = json::JSON(find_user_name(conn, ret_logged_in_user)); + } + } + } + + int64_t get_role_of_user_in_chat(SqliteConnection& conn, int64_t userId, int64_t chatId) { + SqliteStatement req(conn, + "SELECT `role` FROM `user_chat_membership` WHERE `userId` = ?1 AND `chatId` = ?2", + {{1, userId}, {2, chatId}}, {}); + fsql_integer_or_null role; + int status = sqlite_stmt_step(req, {{0, &role}}, {}); + if (status == SQLITE_ROW) { + return role.exist ? (int)role.value : user_chat_role_deleted; + } + return user_chat_role_deleted; + } + + std::string RTEE(const std::string& el_name, + const json::JSON& config_presentation, WorkerGuestData& wgd, + const json::JSON& userinfo) { + std::string page = wgd.templater->render(el_name, {&config_presentation, &userinfo}); + return een9::form_http_server_response_200("text/html", page); + } +} diff --git a/src/web_chat/iu9_ca_web_chat_lib/backend_logic/server_data_interact.h b/src/web_chat/iu9_ca_web_chat_lib/backend_logic/server_data_interact.h new file mode 100644 index 0000000..e6361ac --- /dev/null +++ b/src/web_chat/iu9_ca_web_chat_lib/backend_logic/server_data_interact.h @@ -0,0 +1,79 @@ +#ifndef IU9_CA_WEB_CHAT_LIB_BACKEND_LOGIC_SERVER_DATA_INTERACT_H +#define IU9_CA_WEB_CHAT_LIB_BACKEND_LOGIC_SERVER_DATA_INTERACT_H + +/* This folder covers all code that helps to interact with database and templater, +* or dictates the logic of how this web chat functions */ + +#include +#include +#include "../sqlite3_wrapper.h" +#include "../login_cookie.h" +#include +#include +#include + +namespace iu9cawebchat { + constexpr int64_t user_chat_role_admin = 1; + constexpr int64_t user_chat_role_regular = 2; + constexpr int64_t user_chat_role_read_only = 3; + constexpr int64_t user_chat_role_deleted = 4; + + const char* stringify_user_chat_role(int64_t role); + + struct WorkerGuestData { + /* Because templaters use libjsonincpp, they can't be READ by two thread simultaneously */ + std::unique_ptr templater; + std::unique_ptr db; + int debug_trans_op; // todo: delete when debug is over + }; + + int64_t find_user_by_credentials (SqliteConnection& conn, const std::string& nickname, const std::string& password); + std::string find_user_name (SqliteConnection& conn, int64_t uid); + + struct RowUser_Content { + std::string nickname; + std::string name; + }; + + struct RowChat_Content { + std::string nickname; + std::string name; + int64_t lastMsgId; // Negative if it does not exist + }; + + RowUser_Content lookup_user_content(SqliteConnection& conn, int64_t uid); + RowChat_Content lookup_chat_content(SqliteConnection& conn, int64_t uid); + + + void initial_extraction_of_all_the_useful_info_from_cookies( + SqliteConnection& conn, const een9::ClientRequest& req, + std::vector>& ret_cookies, + std::vector& ret_login_cookies, + json::JSON& ret_userinfo, + int64_t& ret_logged_in_user + ); + + int64_t get_role_of_user_in_chat(SqliteConnection& conn, int64_t userId, int64_t chatId); + + std::string RTEE(const std::string& el_name, + const json::JSON& config_presentation, WorkerGuestData& wgd, + const json::JSON& userinfo); + + /* ========================== PAGES ================================== */ + std::string when_page_login(WorkerGuestData& wgd, const json::JSON& config_presentation, + const een9::ClientRequest& req, const std::vector& login_cookies, const json::JSON& userinfo); + + + json::JSON internalapi_pollEvents(SqliteConnection& conn, int64_t uid, const json::JSON& Sent); + + std::string when_internalapi_pollevents(WorkerGuestData& wgd, + const een9::ClientRequest& req, int64_t uid); + + + json::JSON internalapi_getChatList(SqliteConnection& conn, int64_t uid); + + std::string when_internalapi_getchatlist(WorkerGuestData& wgd, + const een9::ClientRequest& req, int64_t uid); +} + +#endif diff --git a/src/web_chat/iu9_ca_web_chat_lib/backend_logic/when_internalapi_getchatlist.cpp b/src/web_chat/iu9_ca_web_chat_lib/backend_logic/when_internalapi_getchatlist.cpp new file mode 100644 index 0000000..ff441fe --- /dev/null +++ b/src/web_chat/iu9_ca_web_chat_lib/backend_logic/when_internalapi_getchatlist.cpp @@ -0,0 +1,42 @@ +#include "server_data_interact.h" +#include +#include +#include "../str_fields.h" + +namespace iu9cawebchat { + json::JSON internalapi_getChatList(SqliteConnection& conn, int64_t uid) { + json::JSON Recv; + Recv["status"] = json::JSON(0l); + Recv["chats"] = json::JSON(json::array); + std::vector& chats = Recv["chats"].g().asArray(); + SqliteStatement req(conn, + "SELECT `chat`.`id`, `chat`.`nickname`, `chat`.`name`, `chat`.`lastMsgId`, " + "`user_chat_membership`.`role` FROM `chat` " + "RIGHT JOIN `user_chat_membership` ON `chat`.`id` = `user_chat_membership`.`chatId` " + "WHERE `user_chat_membership`.`userId` = ?1 ", {{1, uid}}, {}); + while (true) { + fsql_integer_or_null chat_id; + fsql_text8_or_null chat_nickname, chat_name; + fsql_integer_or_null chat_lastMsgId, role_here; + int status = sqlite_stmt_step(req, {{0, &chat_id}, {3, &chat_lastMsgId}, {4, &role_here}}, + {{1, &chat_nickname}, {2, &chat_name}}); + if (status != SQLITE_ROW) + break; + chats.emplace_back(); + json::JSON& chat = chats.back(); + chat["id"] = json::JSON(chat_id.value); + chat["content"]["nickname"] = json::JSON(chat_nickname.value); + chat["content"]["name"] = json::JSON(chat_name.value); + chat["content"]["lastMsgId"] = json::JSON(chat_lastMsgId.exist ? chat_lastMsgId.value : -1); + chat["content"]["roleHere"] = json::JSON(stringify_user_chat_role(role_here.value)); + } + return Recv; + } + + std::string when_internalapi_getchatlist(WorkerGuestData& wgd, + const een9::ClientRequest& req, int64_t uid) { + const json::JSON& Sent = json::parse_str_flawless(req.body); + std::string result = json::generate_str(internalapi_getChatList(*wgd.db, uid), json::print_pretty); + return een9::form_http_server_response_200("text/json", result); + } +} diff --git a/src/web_chat/iu9_ca_web_chat_lib/backend_logic/when_internalapi_pollevents.cpp b/src/web_chat/iu9_ca_web_chat_lib/backend_logic/when_internalapi_pollevents.cpp new file mode 100644 index 0000000..b6af302 --- /dev/null +++ b/src/web_chat/iu9_ca_web_chat_lib/backend_logic/when_internalapi_pollevents.cpp @@ -0,0 +1,142 @@ +#include "server_data_interact.h" +#include +#include +#include "../str_fields.h" +#include + +namespace iu9cawebchat { + int64_t get_current_history_id_of_chat(SqliteConnection& conn, int64_t chatId) { + SqliteStatement req(conn, "SELECT `it_HistoryId` FROM `chat` WHERE `id` = ?1", {{1, chatId}}, {}); + fsql_integer_or_null HistoryId; + int status = sqlite_stmt_step(req, {{0, &HistoryId}}, {}); + een9_ASSERT_pl(status == SQLITE_ROW); + return HistoryId.value; + } + + int64_t get_current_history_id_of_user_chatList(SqliteConnection& conn, int64_t userId) { + SqliteStatement req(conn, "SELECT `chatList_HistoryId` FROM `user` WHERE `id` = ?1", {{1, userId}}, {}); + fsql_integer_or_null HistoryId; + int status = sqlite_stmt_step(req, {{0, &HistoryId}}, {}); + een9_ASSERT_pl(status == SQLITE_ROW); + return HistoryId.value; + } + + void internalapi_pollEvents_in_chat_collect_membership_events(SqliteConnection& conn, + std::vector& events, int64_t chatId, int64_t LocalHistoryId) { + SqliteStatement membership_changes(conn, + "SELECT `userId`, `role`, FROM `user_chat_membership` WHERE `chatId` = ?1 " + "AND `chat_IncHistoryId` > ?2", {{1, chatId}, {2, LocalHistoryId}}, {}); + fsql_integer_or_null ev_userId; + fsql_integer_or_null ev_user_role; + while (true) { + int status = sqlite_stmt_step(membership_changes, + {{0, &ev_userId}, {1, &ev_user_role}}, {}); + if (status != SQLITE_ROW) + break; + events.emplace_back(); + json::JSON& event = events.back(); + event["member"] = json::JSON(ev_userId.value); + if (ev_user_role.value == user_chat_role_deleted) { + event["type"] = json::JSON("removedMember"); + } else { + event["type"] = json::JSON("addedMember"); + RowUser_Content USER = lookup_user_content(conn, ev_userId.value); + event["content"]["name"] = json::JSON(USER.name); + event["content"]["nickname"] = json::JSON(USER.nickname); + event["content"]["role"] = json::JSON(stringify_user_chat_role(ev_user_role.value)); + } + events.push_back(event); + } + } + + void internalapi_pollEvents_in_chat_collect_messages_events(SqliteConnection& conn, + std::vector& events, int64_t chatId, int64_t LocalHistoryId) { + SqliteStatement messages_changes(conn, + "SELECT `id`, `previous`, `senderUserId`, `exists`, `isSystem`, `text` FROM `messages` WHERE " + "WHERE `chatId` = ?1 AND `chat_IncHistoryId` > ?2", {{1, chatId}, {2, LocalHistoryId}}, {}); + fsql_integer_or_null ev_msgId, ev_previousMsgId, msgSenderUserId, msgExists, msgIsSystem; + fsql_text8_or_null msgText; + while (true) { + int status = sqlite_stmt_step(messages_changes, + {{0, &ev_msgId}, {1, &ev_previousMsgId}, {2, &msgSenderUserId}, {3, &msgExists}, {4, &msgIsSystem}}, + {{5, &msgText}}); + if (status != SQLITE_ROW) + break; + events.emplace_back(); + json::JSON& event = events.back(); + event["type"] = json::JSON("newMessage"); + event["id"] = json::JSON(ev_msgId.value); + event["previous"] = json::JSON(ev_previousMsgId.value); + event["content"]["sender"] = json::JSON(msgSenderUserId.value); + event["content"]["isSystem"] = json::JSON((bool)msgIsSystem.value); + event["content"]["text"] = json::JSON(msgText.value); + events.push_back(event); + } + } + + void internalapi_pollEvents_in_user_chatList_collect_events(SqliteConnection& conn, + std::vector& events, int64_t userId, int64_t LocalHistoryId) { + SqliteStatement membership_changes(conn, + "SELECT `chatId`, `role` FROM `user_chat_membership` WHERE `userId` = ?1 " + "AND `user_chatList_IncHistoryId` > ?2", {{1, userId}, {2, LocalHistoryId}}); + fsql_integer_or_null ev_chatId, usersRoleHere; + while (true) { + int status = sqlite_stmt_step(membership_changes, {{0, &ev_chatId}, {1, &usersRoleHere}}, {}); + if (status != SQLITE_ROW) + break; + events.emplace_back(); + json::JSON& event = events.back(); + event["id"] = json::JSON(ev_chatId.value); + if (usersRoleHere.value == user_chat_role_deleted) { + event["type"] = json::JSON("removedChat"); + } else { + event["type"] = json::JSON("addedChat"); + RowChat_Content CHAT = lookup_chat_content(conn, ev_chatId.value); + event["content"]["name"] = json::JSON(CHAT.name); + event["content"]["nickname"] = json::JSON(CHAT.nickname); + event["content"]["lastMsgId"] = json::JSON(CHAT.lastMsgId); + event["content"]["roleHere"] = json::JSON(stringify_user_chat_role(usersRoleHere.value)); + } + } + } + + json::JSON internalapi_pollEvents(SqliteConnection& conn, int64_t uid, const json::JSON& Sent) { + json::JSON Recv; + Recv["status"] = json::JSON(0l); + Recv["update"] = json::JSON(json::array); + const std::vector& req_scope = Sent["scope"].g().asArray(); + std::vector& updated = Recv["update"].g().asArray(); + for (const json::JSON& hist_entity_request: req_scope) { + updated.emplace_back(); + json::JSON& hist_entity_response = updated.back(); + hist_entity_response["type"] = hist_entity_request["type"].g(); + hist_entity_response["events"] = json::JSON(json::array); + std::vector& events = hist_entity_response["events"].g().asArray(); + const int64_t LocalHistoryId = hist_entity_request["LocalHistoryId"].g().asInteger().get_int(); + if (hist_entity_request["type"].g().asString() == "chat") { + int64_t chatId = hist_entity_request["chatId"].g().asInteger().get_int(); + if (get_role_of_user_in_chat(conn, uid, chatId) == user_chat_role_deleted) + een9_THROW("internalapi: trying to access chat that user does not belong to"); + hist_entity_response["chatId"] = json::JSON(chatId); + hist_entity_response["HistoryId"] = json::JSON(get_current_history_id_of_chat(conn, chatId)); + /* Two classes of 'real events' can happen to chat: membership table change, message table change */ + /* Here, I collect membership changes (related to this chat) */ + internalapi_pollEvents_in_chat_collect_membership_events(conn, events, chatId, LocalHistoryId); + /* Here, I collect message changes (related to this chat) */ + internalapi_pollEvents_in_chat_collect_messages_events(conn, events, chatId, LocalHistoryId); + } else if (hist_entity_request["type"].g().asString() == "chatlist") { + hist_entity_response["HistotyId"] = json::JSON(get_current_history_id_of_user_chatList(conn, uid)); + internalapi_pollEvents_in_user_chatList_collect_events(conn, events, uid, LocalHistoryId); + } else + een9_THROW("Bad request"); + } + return Recv; + } + + std::string when_internalapi_pollevents(WorkerGuestData& wgd, + const een9::ClientRequest& req, int64_t uid) { + const json::JSON& Sent = json::parse_str_flawless(req.body); + std::string result = json::generate_str(internalapi_pollEvents(*wgd.db, uid, Sent), json::print_pretty); + return een9::form_http_server_response_200("text/json", result); + } +} diff --git a/src/web_chat/iu9_ca_web_chat_lib/backend_logic/when_login.cpp b/src/web_chat/iu9_ca_web_chat_lib/backend_logic/when_login.cpp new file mode 100644 index 0000000..d69516f --- /dev/null +++ b/src/web_chat/iu9_ca_web_chat_lib/backend_logic/when_login.cpp @@ -0,0 +1,38 @@ +#include "server_data_interact.h" +#include +#include +#include "../str_fields.h" + +namespace iu9cawebchat { + std::string when_page_login(WorkerGuestData& wgd, const json::JSON& config_presentation, + const een9::ClientRequest& req, const std::vector& login_cookies, const json::JSON& userinfo) { + if (req.method == "POST") { + std::vector> query = een9::split_html_query(req.body); + int64_t uid = -1; + std::string nickname; + std::string password; + try { + for (const std::pair& cmp: query) { + if (cmp.first == "nickname") + nickname = cmp.second; + if (cmp.first == "password") + password = cmp.second; + } + een9_ASSERT(check_nickname(nickname), "/login/accpet-data rejected impossible nickname"); + een9_ASSERT(check_password(password), "/login/accpet-data rejected impossible password"); + uid = find_user_by_credentials(*wgd.db, nickname, password); + + } catch(const std::exception& e){} + if (uid < 0) { + printf("Redirecting back to /login because of incorrect credentials\n"); + /* todo: Here I need to tell somehow to user (through fancy red box, maybe), that login was incorrect */ + return RTEE("login", config_presentation, wgd, userinfo); + } + std::vector> response_hlines; + LoginCookie new_login_cookie = create_login_cookie(nickname, password); + add_set_cookie_headers_to_login(login_cookies, response_hlines, new_login_cookie); + return een9::form_http_server_response_307_spec_head("/", response_hlines); + } + return RTEE("login", config_presentation, wgd, userinfo); + } +} diff --git a/src/web_chat/iu9_ca_web_chat_lib/initialize.cpp b/src/web_chat/iu9_ca_web_chat_lib/initialize.cpp index 2c621b1..d5cd081 100644 --- a/src/web_chat/iu9_ca_web_chat_lib/initialize.cpp +++ b/src/web_chat/iu9_ca_web_chat_lib/initialize.cpp @@ -35,44 +35,49 @@ namespace iu9cawebchat { * If chat.lastMsgId is NULL, chat is empty * If message.previous is NULL, this message is first in it's chat */ - sqlite_nooutput(conn.hand, "PRAGMA foreign_keys = true"); - sqlite_nooutput(conn.hand, "BEGIN"); - sqlite_nooutput(conn.hand, "CREATE TABLE `nickname` (`it` TEXT PRIMARY KEY NOT NULL) WITHOUT ROWID"); - sqlite_nooutput(conn.hand, "CREATE TABLE `user` (" - "`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL," - "`nickname` TEXT UNIQUE REFERENCES `nickname` NOT NULL," - "`name` TEXT NOT NULL," - "`chatList_HistoryId` INTEGER NOT NULL," - "`password` TEXT NOT NULL" - ")"); - sqlite_nooutput(conn.hand, "CREATE TABLE `chat` (" - "`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL," - "`nickname` TEXT UNIQUE REFERENCES `nickname` NOT NULL," - "`name` TEXT NOT NULL," - "`it_HistoryId` INTEGER NOT NULL," - "`lastMsgId` INTEGER REFERENCES `message`" - ")"); - sqlite_nooutput(conn.hand, "CREATE TABLE `user_chat_membership` (" - "`userId` INTEGER REFERENCES `user` NOT NULL," - "`chatId` INTEGER REFERENCES `chat` NOT NULL," - "`user_chatList_IncHistoryId` INTEGER NOT NULL," - "`chat_IncHistoryId` INTEGER NOT NULL," - "`role` INTEGER NOT NULL" - ")"); - sqlite_nooutput(conn.hand, "CREATE TABLE `message` (" - "`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL," - "`chatId` INTEGER REFERENCES `chat` NOT NULL," - "`previous` INTEGER REFERENCES `message`," - "`senderUserId` INTEGER REFERENCES `user` NOT NULL," - "`exists` BOOLEAN NOT NULL," - "`isSystem` BOOLEAN NOT NULL," - "`text` TEXT NOT NULL," - "`chat_IncHistoryId` INTEGER NOT NULL" - ")"); - sqlite_nooutput(conn.hand, "INSERT INTO `nickname` VALUES (?1)", {}, {{1, "root"}}); - sqlite_nooutput(conn.hand, "INSERT INTO `user` (`id`, `nickname`, `name`, `chatList_HistoryId`, `password`) VALUES " - "(0, ?1, ?2, 0, ?3)", {}, - {{1, "root"}, {2, "Rootov Root Rootovich"}, {3, root_pw}}); - sqlite_nooutput(conn.hand, "END"); + try { + sqlite_nooutput(conn, "PRAGMA foreign_keys = true"); + sqlite_nooutput(conn, "BEGIN"); + sqlite_nooutput(conn, "CREATE TABLE `nickname` (`it` TEXT PRIMARY KEY NOT NULL) WITHOUT ROWID"); + sqlite_nooutput(conn, "CREATE TABLE `user` (" + "`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL," + "`nickname` TEXT UNIQUE REFERENCES `nickname` NOT NULL," + "`name` TEXT NOT NULL," + "`chatList_HistoryId` INTEGER NOT NULL," + "`password` TEXT NOT NULL" + ")"); + sqlite_nooutput(conn, "CREATE TABLE `chat` (" + "`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL," + "`nickname` TEXT UNIQUE REFERENCES `nickname` NOT NULL," + "`name` TEXT NOT NULL," + "`it_HistoryId` INTEGER NOT NULL," + "`lastMsgId` INTEGER REFERENCES `message`" + ")"); + sqlite_nooutput(conn, "CREATE TABLE `user_chat_membership` (" + "`userId` INTEGER REFERENCES `user` NOT NULL," + "`chatId` INTEGER REFERENCES `chat` NOT NULL," + "`user_chatList_IncHistoryId` INTEGER NOT NULL," + "`chat_IncHistoryId` INTEGER NOT NULL," + "`role` INTEGER NOT NULL" + ")"); + sqlite_nooutput(conn, "CREATE TABLE `message` (" + "`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL," + "`chatId` INTEGER REFERENCES `chat` NOT NULL," + "`previous` INTEGER REFERENCES `message`," + "`senderUserId` INTEGER REFERENCES `user` NOT NULL," + "`exists` BOOLEAN NOT NULL," + "`isSystem` BOOLEAN NOT NULL," + "`text` TEXT NOT NULL," + "`chat_IncHistoryId` INTEGER NOT NULL" + ")"); + sqlite_nooutput(conn, "INSERT INTO `nickname` VALUES (?1)", {}, {{1, "root"}}); + sqlite_nooutput(conn, "INSERT INTO `user` (`id`, `nickname`, `name`, `chatList_HistoryId`, `password`) VALUES " + "(0, ?1, ?2, 0, ?3)", {}, + {{1, "root"}, {2, "Rootov Root Rootovich"}, {3, root_pw}}); + sqlite_nooutput(conn, "END"); + } catch (const std::exception& e) { + sqlite_nooutput(conn, "ROLLBACK", {}, {}); + throw; + } } } diff --git a/src/web_chat/iu9_ca_web_chat_lib/run.cpp b/src/web_chat/iu9_ca_web_chat_lib/run.cpp index 082c7aa..3cff03a 100644 --- a/src/web_chat/iu9_ca_web_chat_lib/run.cpp +++ b/src/web_chat/iu9_ca_web_chat_lib/run.cpp @@ -1,20 +1,29 @@ #include "actions.h" - #include -#include -#include -#include +#include #include -#include -#include -#include -#include -#include -#include "sqlite3_wrapper.h" -#include "str_fields.h" #include "find_db.h" -#include "login_cookie.h" -#include +#include +#include +#include "str_fields.h" + +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include "sqlite3_wrapper.h" +// #include "str_fields.h" +// #include "find_db.h" +// #include "login_cookie.h" +// #include +// #include + +#include "backend_logic/server_data_interact.h" namespace iu9cawebchat { bool termination = false; @@ -45,11 +54,6 @@ namespace iu9cawebchat { int ret = find_db_sqlite_file_path(config, sqlite_db_path); een9_ASSERT(ret == 0, "Can't find database file"); - struct WorkerGuestData { - /* Because templaters use libjsonincpp, they can't be READ by two thread simultaneously */ - std::unique_ptr templater; - std::unique_ptr db; - }; std::vector worker_guest_data(slave_number); for (int i = 0; i < slave_number; i++) { worker_guest_data[i].templater = std::make_unique( @@ -63,101 +67,53 @@ namespace iu9cawebchat { (const een9::SlaveTask& task, const een9::ClientRequest& req, een9::worker_id_t worker_id) -> std::string { een9_ASSERT_pl(0 <= worker_id && worker_id < worker_guest_data.size()); WorkerGuestData& wgd = worker_guest_data[worker_id]; - nytl::Templater& templater = *wgd.templater; een9::StaticAsset sa; - int ret; - auto find_user_by_credentials = [&](const std::string& nickname, const std::string& password) -> int64_t { - SqliteStatement sql_req(*wgd.db, - "SELECT `id` FROM `user` WHERE `nickname` = ?1 AND `password` = ?2", - {}, {{1, nickname}, {2, password}}); - fsql_integer_or_null id_col; - int status = sqlite_stmt_step(sql_req, {{0, &id_col}}, {}); - if (status == SQLITE_ROW) { - een9_ASSERT_pl(id_col.exist & id_col.value >= 0); - return id_col.value; - } - return -1; - }; - auto find_user_name = [&](int64_t uid) -> std::string { - een9_ASSERT(uid >= 0, "Are you crazy?"); - SqliteStatement sql_req(*wgd.db, - "SELECT `name` FROM `user` WHERE `id` = ?1", - {{1, uid}}, {}); - fsql_text8_or_null name_col; - int status = sqlite_stmt_step(sql_req, {}, {{0, &name_col}}); - if (status == SQLITE_ROW) { - een9_ASSERT_pl(name_col.exist); - return name_col.value; - } - return ""; - }; + sqlite_nooutput(*wgd.db, "BEGIN", {}, {}); + struct guard {SqliteConnection& conn; bool rollback = false; ~guard() { + if (rollback) + sqlite_nooutput(conn, "ROLLBACK", {}, {}); + else + sqlite_nooutput(conn, "END", {}, {}); + }} guard_{*wgd.db}; + try { + std::vector> cookies; + std::vector login_cookies; + json::JSON userinfo; + int64_t logged_in_user = -1; + initial_extraction_of_all_the_useful_info_from_cookies(*wgd.db, req, cookies, login_cookies, userinfo, logged_in_user); - std::vector> cookies = een9::findAllClientCookies(req.headers); - std::vector login_cookies = select_login_cookies(cookies); - json::JSON userinfo; - userinfo["uid"] = json::JSON("-1"); - userinfo["nickname"] = json::JSON(""); - userinfo["name"] = json::JSON(""); - int64_t logged_in_user = -1; /* Negative means that user is not logged in */ - if (!login_cookies.empty()){ - size_t oldest_ind = select_oldest_login_cookie(login_cookies); - LoginCookie& tried = login_cookies[oldest_ind]; - logged_in_user = find_user_by_credentials(tried.nickname, tried.password); - if (logged_in_user >= 0) { - userinfo["uid"] = json::JSON(std::to_string(logged_in_user)); - userinfo["nickname"] = json::JSON(tried.nickname); - userinfo["name"] = json::JSON(find_user_name(logged_in_user)); - } - } + std::string result; - auto RTEE = [&](const std::string& el_name) -> std::string { - std::string page = templater.render(el_name, {&config_presentation, &userinfo}); - return een9::form_http_server_response_200("text/html", page); - }; - - if (req.uri_path == "/" || req.uri_path == "/list-rooms") { - printf("DEBUG:::: %d\n", logged_in_user); - if (logged_in_user < 0) - return een9::form_http_server_response_307("/login"); - return RTEE("list-rooms"); - } - if (req.uri_path == "/login") { - if (req.method == "POST") { - std::vector> query = een9::split_html_query(req.body); - std::string nickname; - std::string password; - for (const std::pair& cmp: query) { - if (cmp.first == "nickname") - nickname = cmp.second; - if (cmp.first == "password") - password = cmp.second; - } - een9_ASSERT(check_nickname(nickname), "/login/accpet-data rejected impossible nickname"); - een9_ASSERT(check_password(password), "/login/accpet-data rejected impossible password"); - int64_t uid = find_user_by_credentials(nickname, password); - if (uid < 0) { - /* todo: Here I need to tell somehow to user (through fancy red box, maybe), that login was incorrect */ - return RTEE("login"); - } - std::vector> response_hlines; - LoginCookie new_login_cookie = create_login_cookie(nickname, password); - add_set_cookie_headers_to_login(login_cookies, response_hlines, new_login_cookie); - return een9::form_http_server_response_307_spec_head("/", response_hlines); + if (req.uri_path == "/" || req.uri_path == "/list-rooms") { + if (logged_in_user < 0) + result = een9::form_http_server_response_307("/login"); + return RTEE("list-rooms", config_presentation, wgd, userinfo); } - return RTEE("login"); - } - if (req.uri_path == "/chat") { - return RTEE("chat"); - } - if (req.uri_path == "/profile") { - return RTEE("profile"); - } - if (req.uri_path == "/registration") { - return RTEE("registration"); + if (req.uri_path == "/login") { + return when_page_login(wgd, config_presentation, req, login_cookies, userinfo); + } + if (req.uri_path == "/chat") { + return RTEE("chat", config_presentation, wgd, userinfo); + } + if (req.uri_path == "/profile") { + return RTEE("profile", config_presentation, wgd, userinfo); + } + // if (req.uri_path == "/registration") { + // RTEE("registration", config_presentation, wgd, userinfo); + // } + if (req.uri_path == "/internalapi/pollEvents") { + return when_internalapi_pollevents(wgd, req, logged_in_user); + } + if (req.uri_path == "/internalapi/getChatList") { + return when_internalapi_getchatlist(wgd, req, logged_in_user); + } + } catch (const std::exception& e) { + guard_.rollback = true; + throw; } /* Trying to interpret request as asset lookup */ - ret = samI.get_asset(req.uri_path, sa); - if (ret >= 0) { + int rets = samI.get_asset(req.uri_path, sa); + if (rets >= 0) { return een9::form_http_server_response_200(sa.type, sa.content); } return een9::form_http_server_response_404("text/html", "

Not found!

"); @@ -169,26 +125,26 @@ namespace iu9cawebchat { WorkerGuestData& wgd = worker_guest_data[worker_id]; try { if (req == "hello") { - return ":0 omg! hiii!! Hewwou :3 !!!!"; + return ":0 omg! hiii!! Hewwou :3 !!!!\n"; } if (req == "8") { termination = true; - return "Bye"; + return "Bye\n"; } std::string updaterootpw_pref = "updaterootpw"; if (een9::beginsWith(req, "updaterootpw")) { size_t nid = updaterootpw_pref.size(); if (nid >= req.size() || !isSPACE(req[nid])) - return "Bad command syntax. Missing whitespace"; + return "Bad command syntax. Missing whitespace\n"; std::string new_password = req.substr(nid + 1); if (!check_password(new_password)) een9_THROW("Bad password"); - sqlite_nooutput(wgd.db->hand, + sqlite_nooutput(*wgd.db, "UPDATE `user` SET `password` = ?1 WHERE `id` = 0 ", {}, {{1, new_password}}); - return "Successul update"; + return "Successul update\n"; } - return "Incorrect command"; + return "Incorrect command\n"; } catch (std::exception& e) { return std::string("Server error\n") + e.what(); } diff --git a/src/web_chat/iu9_ca_web_chat_lib/sqlite3_wrapper.cpp b/src/web_chat/iu9_ca_web_chat_lib/sqlite3_wrapper.cpp index f5a6190..55f150c 100644 --- a/src/web_chat/iu9_ca_web_chat_lib/sqlite3_wrapper.cpp +++ b/src/web_chat/iu9_ca_web_chat_lib/sqlite3_wrapper.cpp @@ -13,42 +13,22 @@ namespace iu9cawebchat { } SqliteConnection::~SqliteConnection() { - if (sqlite3_close(hand) != 0) {abort();} + sqlite3_close_v2(hand); } - void sqlite_nooutput(sqlite3* db_hand, const std::string& req_statement, + void sqlite_nooutput(SqliteConnection& conn, const std::string& req_statement, const std::vector>& int64_binds, const std::vector>& text8_binds) { - sqlite3_stmt* stmt_obj = NULL; - int ret = sqlite3_prepare_v2(db_hand, req_statement.c_str(), -1, &stmt_obj, NULL); - if (ret != 0) { - int err_pos = sqlite3_error_offset(db_hand); - een9_THROW("Compilation of request\n" + req_statement + "\nfailed" + - ((err_pos >= 0) ? " with offset " + std::to_string(err_pos) : "")); - } - assert(sqlite3_errcode(db_hand) == SQLITE_OK); - struct Guard1{sqlite3_stmt*& r; ~Guard1(){if (sqlite3_finalize(r) != 0) {abort();}}} guard1{stmt_obj}; - for (const std::pair& bv: int64_binds) { - ret = sqlite3_bind_int64(stmt_obj, bv.first, bv.second); - een9_ASSERT(ret == 0, "sqlite3_bind_int64"); - } - for (const std::pair& bv: text8_binds) { - een9_ASSERT(is_orthodox_string(bv.second), "Can't bind this string to parameter"); - een9_ASSERT(bv.second.size() + 1 < INT_MAX, "Ah, oh, senpai, your string is toooo huge"); - ret = sqlite3_bind_text(stmt_obj, bv.first, bv.second.c_str(), (int)bv.second.size(), SQLITE_STATIC); - een9_ASSERT(ret == 0, "sqlite3_bind_text"); - } + SqliteStatement stmt(conn, req_statement, int64_binds, text8_binds); + int ret; while (true) { - ret = sqlite3_step(stmt_obj); - if (ret == SQLITE_DONE) - break; + ret = sqlite_stmt_step(stmt, {}, {}); if (ret != SQLITE_ROW) - een9_THROW(std::string("sqlite_row ") + sqlite3_errstr(ret)); - int cc = sqlite3_column_count(stmt_obj); + break; + int cc = sqlite3_column_count(stmt.stmt_obj); std::vector types(cc); - for (int i = 0; i < cc; i++) { - types[i] = sqlite3_column_type(stmt_obj, i); - } + for (int i = 0; i < cc; i++) + types[i] = sqlite3_column_type(stmt.stmt_obj, i); printf("Column: |"); for (int i = 0; i < cc; i++) { switch (types[i]) { @@ -67,18 +47,18 @@ namespace iu9cawebchat { printf("Values: | "); for (int i = 0; i < cc; i++) { if (types[i] == SQLITE_INTEGER) { - printf("%lld | ", sqlite3_column_int64(stmt_obj, i)); + printf("%lld | ", sqlite3_column_int64(stmt.stmt_obj, i)); } else if (types[i] == SQLITE_FLOAT) { - printf("%lf | ", sqlite3_column_double(stmt_obj, i)); + printf("%lf | ", sqlite3_column_double(stmt.stmt_obj, i)); } else if (types[i] == SQLITE_BLOB) { - const void* blob = sqlite3_column_blob(stmt_obj, i); - een9_ASSERT(sqlite3_errcode(db_hand) == SQLITE_OK, "oom in sqlite3_column_blob"); - size_t sz = sqlite3_column_bytes(stmt_obj, i); + const void* blob = sqlite3_column_blob(stmt.stmt_obj, i); + een9_ASSERT(sqlite3_errcode(conn.hand) == SQLITE_OK, "oom in sqlite3_column_blob"); + size_t sz = sqlite3_column_bytes(stmt.stmt_obj, i); printf("Blob of size %lu | ", sz); } else if (types[i] == SQLITE_NULL) { printf("NULL | "); } else { - const unsigned char* text = sqlite3_column_text(stmt_obj, i); + const unsigned char* text = sqlite3_column_text(stmt.stmt_obj, i); een9_ASSERT(text, "oom in sqlite3_column_text"); printf("%s | ", (const char*)text); // todo: print only if string is safe to print @@ -108,7 +88,7 @@ namespace iu9cawebchat { for (const std::pair& bv: text8_binds) { een9_ASSERT(is_orthodox_string(bv.second), "Can't bind this string to parameter"); een9_ASSERT(bv.second.size() + 1 < INT_MAX, "Ah, oh, senpai, your string is toooo huge"); - ret = sqlite3_bind_text(stmt_obj, bv.first, bv.second.c_str(), (int)bv.second.size(), SQLITE_STATIC); + ret = sqlite3_bind_text(stmt_obj, bv.first, bv.second.c_str(), (int)bv.second.size(), SQLITE_TRANSIENT); een9_ASSERT(ret == 0, "sqlite3_bind_text"); } } catch (const std::exception& e) { @@ -127,7 +107,8 @@ namespace iu9cawebchat { int ret = sqlite3_step(stmt.stmt_obj); if (ret == SQLITE_DONE) return ret; - een9_ASSERT(ret == SQLITE_ROW, std::string("sqlite3_step ") + sqlite3_errstr(ret)); + if (ret != SQLITE_ROW) + een9_THROW(std::string("sqlite3_step ") + sqlite3_errstr(ret) + " :> " + sqlite3_errmsg(stmt.conn.hand)); int cc = sqlite3_column_count(stmt.stmt_obj); for (auto& resp: ret_of_integer_or_null) { if (resp.first >= cc) diff --git a/src/web_chat/iu9_ca_web_chat_lib/sqlite3_wrapper.h b/src/web_chat/iu9_ca_web_chat_lib/sqlite3_wrapper.h index 620203f..2c880a8 100644 --- a/src/web_chat/iu9_ca_web_chat_lib/sqlite3_wrapper.h +++ b/src/web_chat/iu9_ca_web_chat_lib/sqlite3_wrapper.h @@ -15,9 +15,9 @@ namespace iu9cawebchat { ~SqliteConnection(); }; - void sqlite_nooutput(sqlite3* db_hand, const std::string& req_statement, - const std::vector>& int64_binds= {}, - const std::vector>& text8_binds = {}); + void sqlite_nooutput(SqliteConnection& conn, const std::string& req_statement, + const std::vector>& int64_binds = {}, + const std::vector>& text8_binds = {}); struct fsql_integer_or_null { bool exist; @@ -33,7 +33,7 @@ namespace iu9cawebchat { SqliteConnection& conn; sqlite3_stmt* stmt_obj = NULL; SqliteStatement(SqliteConnection& connection, const std::string& req_statement, - const std::vector>& int64_binds= {}, + const std::vector>& int64_binds = {}, const std::vector>& text8_binds = {}); SqliteStatement(SqliteStatement&) = delete; SqliteStatement& operator=(SqliteStatement&) = delete; diff --git a/src/web_chat/misc_tests/api_test0.cpp b/src/web_chat/misc_tests/api_test0.cpp new file mode 100644 index 0000000..e2d3f29 --- /dev/null +++ b/src/web_chat/misc_tests/api_test0.cpp @@ -0,0 +1,31 @@ +#include +#include +#include + +using namespace iu9cawebchat; + +void test(SqliteConnection& conn, int64_t uid){ + json::JSON Recv = internalapi_getChatList(conn, uid); + printf("%s\n", json::generate_str(Recv, json::print_pretty).c_str()); +} + +void test_polling(SqliteConnection& conn, int64_t uid, int64_t LocalHistoryId) { + json::JSON Sent; + Sent["scope"][0]["type"] = json::JSON("chatlist"); + Sent["scope"][0]["LocalHistoryId"] = json::JSON(LocalHistoryId); + + json::JSON Recv = internalapi_pollEvents(conn, uid, Sent); + printf("%s\n", json::generate_str(Recv, json::print_pretty).c_str()); +} + +int main() { + SqliteConnection conn("./iu9-ca-web-chat.db"); + // test(conn, 0); + // test(conn, 1); + // test(conn, 2); + // printf("\n\n ===== Now testing polling of events ===== \n\n"); + test_polling(conn, 1, 0); + test_polling(conn, 1, 1); + test_polling(conn, 1, 2); + return 0; +} diff --git a/src/web_chat/misc_tests/find_credentials_test.cpp b/src/web_chat/misc_tests/find_credentials_test.cpp new file mode 100644 index 0000000..9bf2239 --- /dev/null +++ b/src/web_chat/misc_tests/find_credentials_test.cpp @@ -0,0 +1,14 @@ +#include +#include +#include +#include + +using namespace iu9cawebchat; + +int main() { + SqliteConnection conn("./iu9-ca-web-chat.db"); + for (size_t i = 0; i < 100; i++) { + int64_t uid = find_user_by_credentials(conn, "root", "12345678"); + assert(uid == 0); + } +} \ No newline at end of file