From 925c072039fb7e95e0d65b5f2916f82de6b4eb8a Mon Sep 17 00:00:00 2001 From: Andreev Gregory Date: Wed, 21 Aug 2024 14:29:53 +0300 Subject: [PATCH] Initialization of chat service (all tables), admin command to update root password without erasing the whole database. Each een9 worker now has it's own sqlite3 connectin handler --- building/main.cpp | 5 +- .../iu9_ca_web_chat_admin_cli/cli.cpp | 12 +- .../iu9_ca_web_chat_service/initialize.cpp | 125 ++++++++---------- src/web_chat/iu9_ca_web_chat_service/run.cpp | 49 +++++-- .../sqlite3_wrapper.cpp | 92 +++++++++++++ .../iu9_ca_web_chat_service/sqlite3_wrapper.h | 21 +++ .../str_fields_check.cpp | 11 +- .../str_fields_check.h | 7 + 8 files changed, 232 insertions(+), 90 deletions(-) create mode 100644 src/web_chat/iu9_ca_web_chat_service/sqlite3_wrapper.cpp create mode 100644 src/web_chat/iu9_ca_web_chat_service/sqlite3_wrapper.h diff --git a/building/main.cpp b/building/main.cpp index 23ac7ff..787db41 100644 --- a/building/main.cpp +++ b/building/main.cpp @@ -100,8 +100,7 @@ struct CAWebChat { }; for (std::string& u: T.exported_headers) u = "engine_engine_number_9/" + u; - - T.installation_dir = ""; + T.installation_dir = "een9"; my_targets.push_back(T); } { CTarget T{"new_york_transit_line", "shared_library"}; @@ -125,6 +124,7 @@ struct CAWebChat { }; for (std::string& u: T.exported_headers) u = "new_york_transit_line/" + u; + T.installation_dir = "nytl"; my_targets.push_back(T); } { CTarget T{"iu9-ca-web-chat", "executable"}; @@ -142,6 +142,7 @@ struct CAWebChat { "run.cpp", "str_fields_check.cpp", "find_db.cpp", + "sqlite3_wrapper.cpp", }; for (std::string& u: T.units) u = "web_chat/iu9_ca_web_chat_service/" + u; diff --git a/src/web_chat/iu9_ca_web_chat_admin_cli/cli.cpp b/src/web_chat/iu9_ca_web_chat_admin_cli/cli.cpp index fb520f8..a3f5a48 100644 --- a/src/web_chat/iu9_ca_web_chat_admin_cli/cli.cpp +++ b/src/web_chat/iu9_ca_web_chat_admin_cli/cli.cpp @@ -46,6 +46,10 @@ void usage(char* za) { exit(1); } +void funny_log_print(const char* str) { + printf("===\\\\\n -=| %s\n===//\n", str); +} + int main(int argc, char** argv) { if (argc < 1) return 134; @@ -69,12 +73,12 @@ int main(int argc, char** argv) { een9_ASSERT_on_iret(sock, "creating socket to target server"); een9::UniqueFdWrapper sockGuard(sock); een9::connect_to_socket_address(sock, addr); - printf("Ready to send request\n"); + funny_log_print("Ready to send request"); send_request_msg(sock, msg); - printf("Admin-cmd request has been sent\n"); + funny_log_print("Admin-cmd request has been sent"); std::string answer = receive_response_msg(sock); - printf("Admin-cmd response has been read\n"); - printf("Successfull command\n%s", answer.c_str()); + funny_log_print("Admin-cmd response has been read"); + printf("Output:\n%s", answer.c_str()); } catch (const std::exception& e) { printf("%s\n", e.what()); } diff --git a/src/web_chat/iu9_ca_web_chat_service/initialize.cpp b/src/web_chat/iu9_ca_web_chat_service/initialize.cpp index c224c15..da05cae 100644 --- a/src/web_chat/iu9_ca_web_chat_service/initialize.cpp +++ b/src/web_chat/iu9_ca_web_chat_service/initialize.cpp @@ -6,66 +6,7 @@ #include "find_db.h" #include #include - -void sqlite_single_statement(sqlite3* db_hand, const std::string& req_statement) { - sqlite3_stmt* stmt_obj = NULL; - int ret = sqlite3_prepare_v2(db_hand, req_statement.c_str(), -1, &stmt_obj, NULL); - een9_ASSERT(ret == 0, "Can't compile request expression"); - assert(sqlite3_errcode(db_hand) == SQLITE_OK); - struct Guard1{sqlite3_stmt*& r; ~Guard1(){if (sqlite3_finalize(r) != 0) {abort();}}} guard1{stmt_obj}; - while (true) { - ret = sqlite3_step(stmt_obj); - if (ret == SQLITE_DONE) - break; - if (ret != SQLITE_ROW) { - printf("sqlite_row error!!!\n"); - printf("%s\n", sqlite3_errmsg(db_hand)); - break; - } - int cc = sqlite3_column_count(stmt_obj); - std::vector types(cc); - for (int i = 0; i < cc; i++) { - types[i] = sqlite3_column_type(stmt_obj, i); - } - printf("Column: |"); - for (int i = 0; i < cc; i++) { - switch (types[i]) { -#define ccase(tname) case SQLITE_ ## tname: printf(" " #tname " |"); break; - ccase(INTEGER) - ccase(FLOAT) - ccase(BLOB) - ccase(NULL) - case SQLITE3_TEXT: - printf(" TEXT |"); break; - default: - een9_THROW("AAAAAA"); - } - } - printf("\n"); - printf("Values: | "); - for (int i = 0; i < cc; i++) { - if (types[i] == SQLITE_INTEGER) { - printf("%lld | ", sqlite3_column_int64(stmt_obj, i)); - } else if (types[i] == SQLITE_FLOAT) { - printf("%lf | ", sqlite3_column_double(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); - 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); - een9_ASSERT(text, "oom in sqlite3_column_text"); - printf("%s | ", (const char*)text); - // todo: THIS F. B.S. IS NOT SAFE - } - } - printf("\n"); - } - printf("Request steps are done\n"); -} +#include "sqlite3_wrapper.h" void initialize_website(const json::JSON& config, const std::string& root_pw) { printf("Initialization...\n"); @@ -81,15 +22,55 @@ void initialize_website(const json::JSON& config, const std::string& root_pw) { } een9_ASSERT(!een9::isRegularFile(db_path), "Database file exists prior to initialization. " "Can't preceed withut harming existing data"); - sqlite3* db_hand = NULL; - ret = sqlite3_open(db_path.c_str(), &db_hand); - een9_ASSERT(ret == 0, "Can't open database"); - assert(sqlite3_errcode(db_hand) == SQLITE_OK); - struct Guard1{sqlite3*& dhp; ~Guard1(){if (sqlite3_close(dhp) != 0) {abort();}}} guard1{db_hand}; - sqlite_single_statement(db_hand, "CREATE TABLE tb(a INT, b INT)"); - sqlite_single_statement(db_hand, "INSERT INTO tb(a) VALUES (111)"); - sqlite_single_statement(db_hand, "INSERT INTO tb(b) VALUES ('yaeyahiyagdohzghz5echp')"); - sqlite_single_statement(db_hand, "INSERT INTO tb(a, b) VALUES (1123123, 'string')"); - sqlite_single_statement(db_hand, "SELECT * FROM tb"); - // todo: actually write something -} \ No newline at end of file + SqliteConnection conn(db_path.c_str()); + assert(sqlite3_errcode(conn.hand) == SQLITE_OK); + /* Role of memeber of chat: + * 1 - admin + * 2 - regular + * 3 - read-only member + * 4 - deleted (no longer a member) + * + * If user.id is 0, it is a root user + * If chat.lastMsgId is NULL, chat is empty + * If message.previous is NULL, this message is first in it's chat + */ + sqlite_single_statement(conn.hand, "PRAGMA foreign_keys = true"); + sqlite_single_statement(conn.hand, "BEGIN"); + sqlite_single_statement(conn.hand, "CREATE TABLE `nickname` (`it` TEXT PRIMARY KEY NOT NULL) WITHOUT ROWID"); + sqlite_single_statement(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_single_statement(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_single_statement(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_single_statement(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_single_statement(conn.hand, "INSERT INTO `nickname` VALUES (?1)", {}, {{1, "root"}}); + sqlite_single_statement(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_single_statement(conn.hand, "END"); +} diff --git a/src/web_chat/iu9_ca_web_chat_service/run.cpp b/src/web_chat/iu9_ca_web_chat_service/run.cpp index f132e5b..dbf6599 100644 --- a/src/web_chat/iu9_ca_web_chat_service/run.cpp +++ b/src/web_chat/iu9_ca_web_chat_service/run.cpp @@ -10,6 +10,9 @@ #include #include #include +#include "sqlite3_wrapper.h" +#include "str_fields_check.h" +#include "find_db.h" bool termination = false; @@ -35,22 +38,29 @@ void run_website(const json::JSON& config) { int64_t slave_number = config["server"]["workers"].g().asInteger().get_int(); een9_ASSERT(slave_number > 0 && slave_number <= 200, "E"); + std::string sqlite_db_path; + 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 */ - een9::uptr templater; + 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( nytl::TemplaterSettings{nytl::TemplaterDetourRules{assets_dir + "/HypertextPages"}}); worker_guest_data[i].templater->update(); + worker_guest_data[i].db = std::make_unique(sqlite_db_path); } een9::MainloopParameters params; params.guest_core = [&samI, &worker_guest_data, config_presentation] (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()); - nytl::Templater& templater = *worker_guest_data[worker_id].templater; + WorkerGuestData& wgd = worker_guest_data[worker_id]; + nytl::Templater& templater = *wgd.templater; een9::StaticAsset sa; int ret; auto rteee = [&](const std::string& el_name, bool pass_phr) -> std::string { @@ -78,15 +88,34 @@ void run_website(const json::JSON& config) { return een9::form_http_server_response_404("text/html", "

Not found!

"); }; - params.guest_core_admin_control = [] - (const een9::SlaveTask& task, const std::string& req, een9::worker_id_t) -> std::string { - if (req == "hello") { - return ":0 omg! hiii!! Hewwou :3 !!!!"; - } if (req == "8") { - termination = true; - return "Bye"; - } else { + params.guest_core_admin_control = [&worker_guest_data] + (const een9::SlaveTask& task, const std::string& 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]; + try { + if (req == "hello") { + return ":0 omg! hiii!! Hewwou :3 !!!!"; + } + if (req == "8") { + termination = true; + return "Bye"; + } + 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"; + std::string new_password = req.substr(nid + 1); + if (!check_password(new_password)) + een9_THROW("Bad password"); + sqlite_single_statement(wgd.db->hand, + "UPDATE `user` SET `password` = ?1 WHERE `id` = 0 ", + {}, {{1, new_password}}); + return "Successul update"; + } return "Incorrect command"; + } catch (std::exception& e) { + return std::string("Server error\n") + e.what(); } }; diff --git a/src/web_chat/iu9_ca_web_chat_service/sqlite3_wrapper.cpp b/src/web_chat/iu9_ca_web_chat_service/sqlite3_wrapper.cpp new file mode 100644 index 0000000..4a53bbf --- /dev/null +++ b/src/web_chat/iu9_ca_web_chat_service/sqlite3_wrapper.cpp @@ -0,0 +1,92 @@ +#include "sqlite3_wrapper.h" +#include "str_fields_check.h" +#include +#include +#include + +SqliteConnection::SqliteConnection(const std::string &file_path) { + int ret = sqlite3_open(file_path.c_str(), &hand); + if (ret != 0) { + een9_THROW(std::string("Can't open sqlite3 database ") + sqlite3_errstr(ret)); + } +} + +SqliteConnection::~SqliteConnection() { + if (sqlite3_close(hand) != 0) {abort();} +} + +void sqlite_single_statement(sqlite3* db_hand, 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) : "")); + } + een9_ASSERT(ret == 0, "Can't compile request expression"); + 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, "Can't bind to parameter #" + std::to_string(bv.first)); + } + 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); + } + while (true) { + ret = sqlite3_step(stmt_obj); + if (ret == SQLITE_DONE) + break; + if (ret != SQLITE_ROW) { + printf("sqlite_row error!!!\n"); + printf("%s\n", sqlite3_errmsg(db_hand)); + break; + } + int cc = sqlite3_column_count(stmt_obj); + std::vector types(cc); + for (int i = 0; i < cc; i++) { + types[i] = sqlite3_column_type(stmt_obj, i); + } + printf("Column: |"); + for (int i = 0; i < cc; i++) { + switch (types[i]) { +#define ccase(tname) case SQLITE_ ## tname: printf(" " #tname " |"); break; + ccase(INTEGER) + ccase(FLOAT) + ccase(BLOB) + ccase(NULL) + case SQLITE3_TEXT: + printf(" TEXT |"); break; + default: + een9_THROW("AAAAAA"); + } + } + printf("\n"); + printf("Values: | "); + for (int i = 0; i < cc; i++) { + if (types[i] == SQLITE_INTEGER) { + printf("%lld | ", sqlite3_column_int64(stmt_obj, i)); + } else if (types[i] == SQLITE_FLOAT) { + printf("%lf | ", sqlite3_column_double(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); + 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); + een9_ASSERT(text, "oom in sqlite3_column_text"); + printf("%s | ", (const char*)text); + // todo: THIS F. B.S. IS NOT SAFE + } + } + printf("\n"); + } + printf("Request steps are done\n"); +} diff --git a/src/web_chat/iu9_ca_web_chat_service/sqlite3_wrapper.h b/src/web_chat/iu9_ca_web_chat_service/sqlite3_wrapper.h new file mode 100644 index 0000000..6659088 --- /dev/null +++ b/src/web_chat/iu9_ca_web_chat_service/sqlite3_wrapper.h @@ -0,0 +1,21 @@ +#ifndef IU9_CA_WEB_CHAT_SERVICE_SQLITE_WRAP_H +#define IU9_CA_WEB_CHAT_SERVICE_SQLITE_WRAP_H + +#include +#include +#include + +struct SqliteConnection { + sqlite3* hand = NULL; + SqliteConnection() = default; + explicit SqliteConnection(const std::string& file_path); + SqliteConnection (const SqliteConnection&) = delete; + SqliteConnection& operator= (const SqliteConnection&) = delete; + ~SqliteConnection(); +}; + +void sqlite_single_statement(sqlite3* db_hand, const std::string& req_statement, + const std::vector>& int64_binds= {}, + const std::vector>& text8_binds = {}); + +#endif diff --git a/src/web_chat/iu9_ca_web_chat_service/str_fields_check.cpp b/src/web_chat/iu9_ca_web_chat_service/str_fields_check.cpp index 8da27ed..24c1c38 100644 --- a/src/web_chat/iu9_ca_web_chat_service/str_fields_check.cpp +++ b/src/web_chat/iu9_ca_web_chat_service/str_fields_check.cpp @@ -18,12 +18,19 @@ bool isSPACE(char ch) { } +bool is_orthodox_string(const std::string &str) { + for (char ch: str) + if (ch == 0) + return false; + return json::isUtf8String(str); +} + bool check_password(const std::string &pwd) { - return isUtf8String(pwd) && pwd.size() >= 8; + return is_orthodox_string(pwd) && pwd.size() >= 8; } bool check_name(const std::string &name) { - return isUtf8String(name); + return is_orthodox_string(name); } bool check_nickname(const std::string &nickname) { diff --git a/src/web_chat/iu9_ca_web_chat_service/str_fields_check.h b/src/web_chat/iu9_ca_web_chat_service/str_fields_check.h index 1875142..e7f951a 100644 --- a/src/web_chat/iu9_ca_web_chat_service/str_fields_check.h +++ b/src/web_chat/iu9_ca_web_chat_service/str_fields_check.h @@ -3,6 +3,13 @@ #include +bool isALPHA(char ch); +bool isNUM(char ch); +bool isUNCHAR(char ch); +bool isSPACE(char ch); + +bool is_orthodox_string(const std::string& str); + bool check_password(const std::string& pwd); bool check_name(const std::string& name); bool check_nickname(const std::string& nickname);