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

This commit is contained in:
Андреев Григорий 2024-08-21 14:29:53 +03:00
parent 6be78c1510
commit 925c072039
8 changed files with 232 additions and 90 deletions

View File

@ -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;

View File

@ -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());
}

View File

@ -6,66 +6,7 @@
#include "find_db.h"
#include <unistd.h>
#include <assert.h>
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<int> 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
}
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");
}

View File

@ -10,6 +10,9 @@
#include <new_york_transit_line/templater.h>
#include <sqlite3.h>
#include <engine_engine_number_9/socket_address.h>
#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<nytl::Templater> templater;
std::unique_ptr<nytl::Templater> templater;
std::unique_ptr<SqliteConnection> db;
};
std::vector<WorkerGuestData> worker_guest_data(slave_number);
for (int i = 0; i < slave_number; i++) {
worker_guest_data[i].templater = std::make_unique<nytl::Templater>(
nytl::TemplaterSettings{nytl::TemplaterDetourRules{assets_dir + "/HypertextPages"}});
worker_guest_data[i].templater->update();
worker_guest_data[i].db = std::make_unique<SqliteConnection>(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", "<h1> Not found! </h1>");
};
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();
}
};

View File

@ -0,0 +1,92 @@
#include "sqlite3_wrapper.h"
#include "str_fields_check.h"
#include <engine_engine_number_9/baza_throw.h>
#include <assert.h>
#include <limits.h>
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<std::pair<int, int64_t>>& int64_binds,
const std::vector<std::pair<int, std::string>>& 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<int, int64_t>& 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<int, std::string>& 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<int> 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");
}

View File

@ -0,0 +1,21 @@
#ifndef IU9_CA_WEB_CHAT_SERVICE_SQLITE_WRAP_H
#define IU9_CA_WEB_CHAT_SERVICE_SQLITE_WRAP_H
#include <sqlite3.h>
#include <vector>
#include <string>
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<std::pair<int, int64_t>>& int64_binds= {},
const std::vector<std::pair<int, std::string>>& text8_binds = {});
#endif

View File

@ -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) {

View File

@ -3,6 +3,13 @@
#include <string>
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);