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

This commit is contained in:
Андреев Григорий 2024-08-25 13:06:26 +03:00
parent a6f4bd6c88
commit 799e156f88
21 changed files with 693 additions and 279 deletions

View File

@ -1,6 +1,6 @@
{% ELDEF main JSON pres JSON userinfo %} {% ELDEF main JSON pres JSON userinfo %}
<!DOCTYPE html> <!DOCTYPE html>
<html lang="ru"> <html lang="{% WRITE pres.lang %}">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
@ -8,7 +8,7 @@
<link rel="stylesheet" href="/assets/css/list-rooms.css"> <link rel="stylesheet" href="/assets/css/list-rooms.css">
</head> </head>
<body> <body>
{% PUT pass-userinfo userinfo %} {% PUT pass-pres-userinfo pres userinfo %}
<div class="container"> <div class="container">
<h1 style="color: white;">Выберите Чат-Комнату</h1> <h1 style="color: white;">Выберите Чат-Комнату</h1>
<ul class="room-list"> <ul class="room-list">
@ -18,51 +18,51 @@
</div> </div>
<!-- Модальное окно для создания комнаты --> <!-- Модальное окно для создания комнаты -->
<div id="createRoomModal" class="modal"> <!--<div id="createRoomModal" class="modal">-->
<div class="modal-content"> <!-- <div class="modal-content">-->
<div class="modal-header"> <!-- <div class="modal-header">-->
<span class="close" onclick="closeCreateRoomModal()">&times;</span> <!-- <span class="close" onclick="closeCreateRoomModal()">&times;</span>-->
<h2>Создать Комнату</h2> <!-- <h2>Создать Комнату</h2>-->
</div> <!-- </div>-->
<div class="modal-body"> <!-- <div class="modal-body">-->
<input type="text" id="newRoomName" placeholder="Название комнаты"> <!-- <input type="text" id="newRoomName" placeholder="Название комнаты">-->
<input type="password" id="newRoomNickname" placeholder="Никнейм комнаты"> <!-- <input type="password" id="newRoomNickname" placeholder="Никнейм комнаты">-->
</div> <!-- </div>-->
<div id="error"></div> <!-- <div id="error"></div>-->
<div class="modal-footer"> <!-- <div class="modal-footer">-->
<button class="join-button" onclick="createRoom()">Создать</button> <!-- <button class="join-button" onclick="createRoom()">Создать</button>-->
</div> <!-- </div>-->
</div> <!-- </div>-->
</div> <!--</div>-->
<!-- Модальное окно для добавления участников --> <!--&lt;!&ndash; Модальное окно для добавления участников &ndash;&gt;-->
<div class="overlay" id="add_members"> <!--<div class="overlay" id="add_members">-->
<div class="add-members"> <!-- <div class="add-members">-->
<div class="add-members-header"> <!-- <div class="add-members-header">-->
<span class="close" onclick="closeAdd()">&times;</span> <!-- <span class="close" onclick="closeAdd()">&times;</span>-->
<h2>Добавить участников</h2> <!-- <h2>Добавить участников</h2>-->
</div> <!-- </div>-->
<div class="add-members-body"> <!-- <div class="add-members-body">-->
<input type="text" id="newMemberLogin" placeholder="Логин пользователя"> <!-- <input type="text" id="newMemberLogin" placeholder="Логин пользователя">-->
</div> <!-- </div>-->
<div class="add-members-footer"> <!-- <div class="add-members-footer">-->
<button class="add-member-button" onclick="addMember()">Добавить</button> <!-- <button class="add-member-button" onclick="addMember()">Добавить</button>-->
</div> <!-- </div>-->
</div> <!-- </div>-->
</div> <!--</div>-->
<div class="overlay" id="delete-chat"> <!--<div class="overlay" id="delete-chat">-->
<div class="delete-chat"> <!-- <div class="delete-chat">-->
<div class="delete-chat-header"> <!-- <div class="delete-chat-header">-->
<span class="delete-close" onclick="closeConfirm()">&times;</span> <!-- <span class="delete-close" onclick="closeConfirm()">&times;</span>-->
<h2>Вы уверены, что хотите удалить чат?</h2> <!-- <h2>Вы уверены, что хотите удалить чат?</h2>-->
</div> <!-- </div>-->
<div class="delete-chat-body"> <!-- <div class="delete-chat-body">-->
<button class="confirm" onclick="deleteChat()">Да</button> <!-- <button class="confirm" onclick="deleteChat()">Да</button>-->
<button class="cancel" onclick="closeConfirm()">Нет</button> <!-- <button class="cancel" onclick="closeConfirm()">Нет</button>-->
</div> <!-- </div>-->
</div> <!-- </div>-->
</div> <!--</div>-->
<!--<script src="/assets/js/list-rooms.js"></script>--> <script src="/assets/js/list-rooms.js"></script>
</body> </body>
</html> </html>
{% ENDELDEF %} {% ENDELDEF %}

View File

@ -1,27 +1,27 @@
{% ELDEF main JSON pres JSON userinfo %} {% ELDEF main JSON pres JSON userinfo %}
<!DOCTYPE html> <!DOCTYPE html>
<html lang="{% WRITE pres.lang %}"> <html lang="{% WRITE pres.lang %}">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% WRITE pres.phr.decl.page-login %}</title> <title>{% WRITE pres.phr.decl.page-login %}</title>
<link rel="stylesheet" href="/assets/css/login.css"> <link rel="stylesheet" href="/assets/css/login.css">
</head> </head>
<body> <body>
{% PUT pass-userinfo userinfo %} {% PUT pass-pres-userinfo pres userinfo %}
<div class="form-container"> <div class="form-container">
<h1 class="hide-cursor no-select">{% WRITE pres.phr.decl.enter %}</h1> <h1 class="hide-cursor no-select">{% WRITE pres.phr.decl.enter %}</h1>
<form action="/login" method="post" enctype="application/x-www-form-urlencoded"> <form action="/login" method="post" enctype="application/x-www-form-urlencoded">
<label for="nickname">{% WRITE pres.phr.decl.nickname %}</label> <label for="nickname">{% WRITE pres.phr.decl.nickname %}</label>
<input type="text" name="nickname" id="nickname"><br> <input type="text" name="nickname" id="nickname"><br>
<label for="password">{% WRITE pres.phr.decl.password %}</label> <label for="password">{% WRITE pres.phr.decl.password %}</label>
<input type="password" name="password" id="password"><br> <input type="password" name="password" id="password"><br>
<button type="submit" class="hide-cursor no-select">{% WRITE pres.phr.act.enter %}</button> <button type="submit" class="hide-cursor no-select">{% WRITE pres.phr.act.enter %}</button>
</form> </form>
</div> </div>
</body> </body>
</html> </html>
{% ENDELDEF %} {% ENDELDEF %}

View File

@ -0,0 +1,6 @@
{% ELDEF main JSON pres JSON userinfo %}
<script>
let pres = {% PUT jsinsert pres %};
let userinfo = {% PUT jsinsert userinfo %};
</script>
{% ENDELDEF %}

View File

@ -1,5 +0,0 @@
{% ELDEF main JSON userinfo %}
<input type="hidden" id="hidden_field_uid" name="uid" value="{% WRITE userinfo.uid %}">
<input type="hidden" id="hidden_field_nickname" name="nickname" value="{% WRITE userinfo.nickname %}">
<input type="hidden" id="hidden_field_name" name="name" value="{% WRITE userinfo.name %}">
{% ENDELDEF %}

View File

@ -147,6 +147,10 @@ struct CAWebChat {
"find_db.cpp", "find_db.cpp",
"sqlite3_wrapper.cpp", "sqlite3_wrapper.cpp",
"login_cookie.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) for (std::string& u: T.units)
u = "web_chat/iu9_ca_web_chat_lib/" + u; u = "web_chat/iu9_ca_web_chat_lib/" + u;

View File

@ -39,6 +39,6 @@
"server": { "server": {
"workers": 8, "workers": 8,
"http-listen": ["127.0.0.1:1025"], "http-listen": ["127.0.0.1:1025"],
"admin-command-listen": ["unix:/run/iu9/iu9cawebchat-ac.sock"] "admin-command-listen": ["[::1]:1026"]
} }
} }

View File

@ -14,7 +14,7 @@ namespace een9 {
return result; 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<std::pair<std::string, std::string>>& headers) { const std::vector<std::pair<std::string, std::string>>& headers) {
return form_http_server_response_header(code, headers) + "\r\n"; 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) { 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, std::string form_http_server_response_307_spec_head(const std::string &Location,
const std::vector<std::pair<std::string, std::string>>& headers) { const std::vector<std::pair<std::string, std::string>>& headers) {
std::vector<std::pair<std::string, std::string>> cp = headers; std::vector<std::pair<std::string, std::string>> cp = headers;
cp.emplace_back("Location", Location); cp.emplace_back("Location", Location);
return form_http_server_reponse_header_only("307", cp); return form_http_server_response_header_only("307", cp);
} }
} }

View File

@ -41,7 +41,6 @@ namespace een9 {
} }
void push_back(SlaveTask task) { 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!! */ /* CLion says. Allocated memory is leaking. YOUR MOTHER IS LEAKING YOU FOOL!! MY CODE IS FINE!! */
QElementHttpConnections* el = new QElementHttpConnections(std::move(task)); QElementHttpConnections* el = new QElementHttpConnections(std::move(task));
/* Exception does not leave queue in incorrect state */ /* Exception does not leave queue in incorrect state */
@ -66,6 +65,15 @@ namespace een9 {
sz--; sz--;
} }
} }
~WorkersTaskQueue() {
QElementHttpConnections* cur = first;
while (cur) {
QElementHttpConnections* nxt = cur->nxt;
delete cur;
cur = nxt;
}
}
}; };
struct WorkersEnvCommon { struct WorkersEnvCommon {

View File

@ -34,7 +34,7 @@ namespace nytl {
} else if (P.isDictionary() && what.isString()) { } else if (P.isDictionary() && what.isString()) {
const std::map<std::string, json::JSON>& dict_p = P.asDictionary(); const std::map<std::string, json::JSON>& dict_p = P.asDictionary();
const std::string& key_w = what.asString(); 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)}; result = LocalVarValue{true, "", &dict_p.at(key_w)};
} else } else
THROW("Incorrect type of \"json[json]\" expression. Unallowed signature of [] operator"); 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++) { for (size_t i = 0; i < m; i++) {
result += text[i]; result += text[i];
if (text[i] == '\n') { if (text[i] == '\n') {
// newlined_somewhere = true;
result.resize(result.size() + wsp_before_newlines, ' '); result.resize(result.size() + wsp_before_newlines, ' ');
cur_line_width = wsp_before_newlines; cur_line_width = wsp_before_newlines;
} else { } else {
// if (cur_line_width == 0 && newlined_somewhere) {
// result.resize(result.size() + wsp_before_newlines, ' ');
// cur_line_width = wsp_before_newlines;
// }
cur_line_width++; cur_line_width++;
} }
} }
@ -246,7 +240,11 @@ namespace nytl {
assert(passed_args.size() == 1); assert(passed_args.size() == 1);
const json::JSON* X = passed_args[0].JSON_subval; const json::JSON* X = passed_args[0].JSON_subval;
assert(X); 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)); std::string escaped_json = escape(json::generate_str(*X, json::print_pretty));
rstrip(escaped_json); rstrip(escaped_json);
append(escaped_json, result); append(escaped_json, result);

View File

@ -111,6 +111,7 @@ namespace nytl {
void Templater::update() { void Templater::update() {
elements = { elements = {
{"jsinsert", Element{{json::JSON(true)}, true}},
{"jesc", Element{{json::JSON(true)}, true}}, {"jesc", Element{{json::JSON(true)}, true}},
{"jesccomp", Element{{json::JSON(true)}, true}}, {"jesccomp", Element{{json::JSON(true)}, true}},
/* str2text base element has a dedicated operator - WRITE */ /* str2text base element has a dedicated operator - WRITE */

View File

@ -0,0 +1,114 @@
#include "server_data_interact.h"
#include <engine_engine_number_9/baza_throw.h>
#include <engine_engine_number_9/http_structures/cookies.h>
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<std::pair<std::string, std::string>> &ret_cookies,
std::vector<LoginCookie> &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);
}
}

View File

@ -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 <memory>
#include <new_york_transit_line/templater.h>
#include "../sqlite3_wrapper.h"
#include "../login_cookie.h"
#include <engine_engine_number_9/http_structures/client_request.h>
#include <engine_engine_number_9/http_structures/response_gen.h>
#include <jsonincpp/string_representation.h>
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<nytl::Templater> templater;
std::unique_ptr<SqliteConnection> 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<std::pair<std::string, std::string>>& ret_cookies,
std::vector<LoginCookie>& 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<LoginCookie>& 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

View File

@ -0,0 +1,42 @@
#include "server_data_interact.h"
#include <engine_engine_number_9/form_data_structure/urlencoded_query.h>
#include <engine_engine_number_9/baza_throw.h>
#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<json::JSON>& 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);
}
}

View File

@ -0,0 +1,142 @@
#include "server_data_interact.h"
#include <engine_engine_number_9/form_data_structure/urlencoded_query.h>
#include <engine_engine_number_9/baza_throw.h>
#include "../str_fields.h"
#include <engine_engine_number_9/http_structures/response_gen.h>
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<json::JSON>& 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<json::JSON>& 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<json::JSON>& 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<json::JSON>& req_scope = Sent["scope"].g().asArray();
std::vector<json::JSON>& 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<json::JSON>& 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);
}
}

View File

@ -0,0 +1,38 @@
#include "server_data_interact.h"
#include <engine_engine_number_9/form_data_structure/urlencoded_query.h>
#include <engine_engine_number_9/baza_throw.h>
#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<LoginCookie>& login_cookies, const json::JSON& userinfo) {
if (req.method == "POST") {
std::vector<std::pair<std::string, std::string>> query = een9::split_html_query(req.body);
int64_t uid = -1;
std::string nickname;
std::string password;
try {
for (const std::pair<std::string, std::string>& 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<std::pair<std::string, std::string>> 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);
}
}

View File

@ -35,44 +35,49 @@ namespace iu9cawebchat {
* If chat.lastMsgId is NULL, chat is empty * If chat.lastMsgId is NULL, chat is empty
* If message.previous is NULL, this message is first in it's chat * If message.previous is NULL, this message is first in it's chat
*/ */
sqlite_nooutput(conn.hand, "PRAGMA foreign_keys = true"); try {
sqlite_nooutput(conn.hand, "BEGIN"); sqlite_nooutput(conn, "PRAGMA foreign_keys = true");
sqlite_nooutput(conn.hand, "CREATE TABLE `nickname` (`it` TEXT PRIMARY KEY NOT NULL) WITHOUT ROWID"); sqlite_nooutput(conn, "BEGIN");
sqlite_nooutput(conn.hand, "CREATE TABLE `user` (" sqlite_nooutput(conn, "CREATE TABLE `nickname` (`it` TEXT PRIMARY KEY NOT NULL) WITHOUT ROWID");
"`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL," sqlite_nooutput(conn, "CREATE TABLE `user` ("
"`nickname` TEXT UNIQUE REFERENCES `nickname` NOT NULL," "`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,"
"`name` TEXT NOT NULL," "`nickname` TEXT UNIQUE REFERENCES `nickname` NOT NULL,"
"`chatList_HistoryId` INTEGER NOT NULL," "`name` TEXT NOT NULL,"
"`password` 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," sqlite_nooutput(conn, "CREATE TABLE `chat` ("
"`nickname` TEXT UNIQUE REFERENCES `nickname` NOT NULL," "`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,"
"`name` TEXT NOT NULL," "`nickname` TEXT UNIQUE REFERENCES `nickname` NOT NULL,"
"`it_HistoryId` INTEGER NOT NULL," "`name` TEXT NOT NULL,"
"`lastMsgId` INTEGER REFERENCES `message`" "`it_HistoryId` INTEGER NOT NULL,"
")"); "`lastMsgId` INTEGER REFERENCES `message`"
sqlite_nooutput(conn.hand, "CREATE TABLE `user_chat_membership` (" ")");
"`userId` INTEGER REFERENCES `user` NOT NULL," sqlite_nooutput(conn, "CREATE TABLE `user_chat_membership` ("
"`chatId` INTEGER REFERENCES `chat` NOT NULL," "`userId` INTEGER REFERENCES `user` NOT NULL,"
"`user_chatList_IncHistoryId` INTEGER NOT NULL," "`chatId` INTEGER REFERENCES `chat` NOT NULL,"
"`chat_IncHistoryId` INTEGER NOT NULL," "`user_chatList_IncHistoryId` INTEGER NOT NULL,"
"`role` 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," sqlite_nooutput(conn, "CREATE TABLE `message` ("
"`chatId` INTEGER REFERENCES `chat` NOT NULL," "`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,"
"`previous` INTEGER REFERENCES `message`," "`chatId` INTEGER REFERENCES `chat` NOT NULL,"
"`senderUserId` INTEGER REFERENCES `user` NOT NULL," "`previous` INTEGER REFERENCES `message`,"
"`exists` BOOLEAN NOT NULL," "`senderUserId` INTEGER REFERENCES `user` NOT NULL,"
"`isSystem` BOOLEAN NOT NULL," "`exists` BOOLEAN NOT NULL,"
"`text` TEXT NOT NULL," "`isSystem` BOOLEAN NOT NULL,"
"`chat_IncHistoryId` INTEGER 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 " sqlite_nooutput(conn, "INSERT INTO `nickname` VALUES (?1)", {}, {{1, "root"}});
"(0, ?1, ?2, 0, ?3)", {}, sqlite_nooutput(conn, "INSERT INTO `user` (`id`, `nickname`, `name`, `chatList_HistoryId`, `password`) VALUES "
{{1, "root"}, {2, "Rootov Root Rootovich"}, {3, root_pw}}); "(0, ?1, ?2, 0, ?3)", {},
sqlite_nooutput(conn.hand, "END"); {{1, "root"}, {2, "Rootov Root Rootovich"}, {3, root_pw}});
sqlite_nooutput(conn, "END");
} catch (const std::exception& e) {
sqlite_nooutput(conn, "ROLLBACK", {}, {});
throw;
}
} }
} }

View File

@ -1,20 +1,29 @@
#include "actions.h" #include "actions.h"
#include <engine_engine_number_9/baza_throw.h> #include <engine_engine_number_9/baza_throw.h>
#include <engine_engine_number_9/running_mainloop.h> #include <engine_engine_number_9/os_utils.h>
#include <engine_engine_number_9/http_structures/response_gen.h>
#include <signal.h>
#include <engine_engine_number_9/connecting_assets/static_asset_manager.h> #include <engine_engine_number_9/connecting_assets/static_asset_manager.h>
#include <assert.h>
#include <engine_engine_number_9/form_data_structure/urlencoded_query.h>
#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.h"
#include "find_db.h" #include "find_db.h"
#include "login_cookie.h" #include <engine_engine_number_9/running_mainloop.h>
#include <engine_engine_number_9/http_structures/cookies.h> #include <signal.h>
#include "str_fields.h"
// #include <engine_engine_number_9/baza_throw.h>
// #include <engine_engine_number_9/running_mainloop.h>
// #include <engine_engine_number_9/http_structures/response_gen.h>
// #include <engine_engine_number_9/connecting_assets/static_asset_manager.h>
// #include <assert.h>
// #include <engine_engine_number_9/form_data_structure/urlencoded_query.h>
// #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.h"
// #include "find_db.h"
// #include "login_cookie.h"
// #include <engine_engine_number_9/http_structures/cookies.h>
// #include <jsonincpp/string_representation.h>
#include "backend_logic/server_data_interact.h"
namespace iu9cawebchat { namespace iu9cawebchat {
bool termination = false; bool termination = false;
@ -45,11 +54,6 @@ namespace iu9cawebchat {
int ret = find_db_sqlite_file_path(config, sqlite_db_path); int ret = find_db_sqlite_file_path(config, sqlite_db_path);
een9_ASSERT(ret == 0, "Can't find database file"); 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<nytl::Templater> templater;
std::unique_ptr<SqliteConnection> db;
};
std::vector<WorkerGuestData> worker_guest_data(slave_number); std::vector<WorkerGuestData> worker_guest_data(slave_number);
for (int i = 0; i < slave_number; i++) { for (int i = 0; i < slave_number; i++) {
worker_guest_data[i].templater = std::make_unique<nytl::Templater>( worker_guest_data[i].templater = std::make_unique<nytl::Templater>(
@ -63,101 +67,53 @@ namespace iu9cawebchat {
(const een9::SlaveTask& task, const een9::ClientRequest& req, een9::worker_id_t worker_id) -> std::string { (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()); een9_ASSERT_pl(0 <= worker_id && worker_id < worker_guest_data.size());
WorkerGuestData& wgd = worker_guest_data[worker_id]; WorkerGuestData& wgd = worker_guest_data[worker_id];
nytl::Templater& templater = *wgd.templater;
een9::StaticAsset sa; een9::StaticAsset sa;
int ret; sqlite_nooutput(*wgd.db, "BEGIN", {}, {});
auto find_user_by_credentials = [&](const std::string& nickname, const std::string& password) -> int64_t { struct guard {SqliteConnection& conn; bool rollback = false; ~guard() {
SqliteStatement sql_req(*wgd.db, if (rollback)
"SELECT `id` FROM `user` WHERE `nickname` = ?1 AND `password` = ?2", sqlite_nooutput(conn, "ROLLBACK", {}, {});
{}, {{1, nickname}, {2, password}}); else
fsql_integer_or_null id_col; sqlite_nooutput(conn, "END", {}, {});
int status = sqlite_stmt_step(sql_req, {{0, &id_col}}, {}); }} guard_{*wgd.db};
if (status == SQLITE_ROW) { try {
een9_ASSERT_pl(id_col.exist & id_col.value >= 0); std::vector<std::pair<std::string, std::string>> cookies;
return id_col.value; std::vector<LoginCookie> login_cookies;
} json::JSON userinfo;
return -1; 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);
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 "";
};
std::vector<std::pair<std::string, std::string>> cookies = een9::findAllClientCookies(req.headers); std::string result;
std::vector<LoginCookie> 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));
}
}
auto RTEE = [&](const std::string& el_name) -> std::string { if (req.uri_path == "/" || req.uri_path == "/list-rooms") {
std::string page = templater.render(el_name, {&config_presentation, &userinfo}); if (logged_in_user < 0)
return een9::form_http_server_response_200("text/html", page); result = een9::form_http_server_response_307("/login");
}; return RTEE("list-rooms", config_presentation, wgd, userinfo);
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<std::pair<std::string, std::string>> query = een9::split_html_query(req.body);
std::string nickname;
std::string password;
for (const std::pair<std::string, std::string>& 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<std::pair<std::string, std::string>> 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"); if (req.uri_path == "/login") {
} return when_page_login(wgd, config_presentation, req, login_cookies, userinfo);
if (req.uri_path == "/chat") { }
return RTEE("chat"); if (req.uri_path == "/chat") {
} return RTEE("chat", config_presentation, wgd, userinfo);
if (req.uri_path == "/profile") { }
return RTEE("profile"); if (req.uri_path == "/profile") {
} return RTEE("profile", config_presentation, wgd, userinfo);
if (req.uri_path == "/registration") { }
return RTEE("registration"); // 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 */ /* Trying to interpret request as asset lookup */
ret = samI.get_asset(req.uri_path, sa); int rets = samI.get_asset(req.uri_path, sa);
if (ret >= 0) { if (rets >= 0) {
return een9::form_http_server_response_200(sa.type, sa.content); return een9::form_http_server_response_200(sa.type, sa.content);
} }
return een9::form_http_server_response_404("text/html", "<h1> Not found! </h1>"); return een9::form_http_server_response_404("text/html", "<h1> Not found! </h1>");
@ -169,26 +125,26 @@ namespace iu9cawebchat {
WorkerGuestData& wgd = worker_guest_data[worker_id]; WorkerGuestData& wgd = worker_guest_data[worker_id];
try { try {
if (req == "hello") { if (req == "hello") {
return ":0 omg! hiii!! Hewwou :3 !!!!"; return ":0 omg! hiii!! Hewwou :3 !!!!\n";
} }
if (req == "8") { if (req == "8") {
termination = true; termination = true;
return "Bye"; return "Bye\n";
} }
std::string updaterootpw_pref = "updaterootpw"; std::string updaterootpw_pref = "updaterootpw";
if (een9::beginsWith(req, "updaterootpw")) { if (een9::beginsWith(req, "updaterootpw")) {
size_t nid = updaterootpw_pref.size(); size_t nid = updaterootpw_pref.size();
if (nid >= req.size() || !isSPACE(req[nid])) 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); std::string new_password = req.substr(nid + 1);
if (!check_password(new_password)) if (!check_password(new_password))
een9_THROW("Bad password"); een9_THROW("Bad password");
sqlite_nooutput(wgd.db->hand, sqlite_nooutput(*wgd.db,
"UPDATE `user` SET `password` = ?1 WHERE `id` = 0 ", "UPDATE `user` SET `password` = ?1 WHERE `id` = 0 ",
{}, {{1, new_password}}); {}, {{1, new_password}});
return "Successul update"; return "Successul update\n";
} }
return "Incorrect command"; return "Incorrect command\n";
} catch (std::exception& e) { } catch (std::exception& e) {
return std::string("Server error\n") + e.what(); return std::string("Server error\n") + e.what();
} }

View File

@ -13,42 +13,22 @@ namespace iu9cawebchat {
} }
SqliteConnection::~SqliteConnection() { 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<std::pair<int, int64_t>>& int64_binds, const std::vector<std::pair<int, int64_t>>& int64_binds,
const std::vector<std::pair<int, std::string>>& text8_binds) { const std::vector<std::pair<int, std::string>>& text8_binds) {
sqlite3_stmt* stmt_obj = NULL; SqliteStatement stmt(conn, req_statement, int64_binds, text8_binds);
int ret = sqlite3_prepare_v2(db_hand, req_statement.c_str(), -1, &stmt_obj, NULL); int ret;
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<int, int64_t>& bv: int64_binds) {
ret = sqlite3_bind_int64(stmt_obj, bv.first, bv.second);
een9_ASSERT(ret == 0, "sqlite3_bind_int64");
}
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);
een9_ASSERT(ret == 0, "sqlite3_bind_text");
}
while (true) { while (true) {
ret = sqlite3_step(stmt_obj); ret = sqlite_stmt_step(stmt, {}, {});
if (ret == SQLITE_DONE)
break;
if (ret != SQLITE_ROW) if (ret != SQLITE_ROW)
een9_THROW(std::string("sqlite_row ") + sqlite3_errstr(ret)); break;
int cc = sqlite3_column_count(stmt_obj); int cc = sqlite3_column_count(stmt.stmt_obj);
std::vector<int> types(cc); std::vector<int> types(cc);
for (int i = 0; i < cc; i++) { for (int i = 0; i < cc; i++)
types[i] = sqlite3_column_type(stmt_obj, i); types[i] = sqlite3_column_type(stmt.stmt_obj, i);
}
printf("Column: |"); printf("Column: |");
for (int i = 0; i < cc; i++) { for (int i = 0; i < cc; i++) {
switch (types[i]) { switch (types[i]) {
@ -67,18 +47,18 @@ namespace iu9cawebchat {
printf("Values: | "); printf("Values: | ");
for (int i = 0; i < cc; i++) { for (int i = 0; i < cc; i++) {
if (types[i] == SQLITE_INTEGER) { 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) { } 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) { } else if (types[i] == SQLITE_BLOB) {
const void* blob = sqlite3_column_blob(stmt_obj, i); const void* blob = sqlite3_column_blob(stmt.stmt_obj, i);
een9_ASSERT(sqlite3_errcode(db_hand) == SQLITE_OK, "oom in sqlite3_column_blob"); een9_ASSERT(sqlite3_errcode(conn.hand) == SQLITE_OK, "oom in sqlite3_column_blob");
size_t sz = sqlite3_column_bytes(stmt_obj, i); size_t sz = sqlite3_column_bytes(stmt.stmt_obj, i);
printf("Blob of size %lu | ", sz); printf("Blob of size %lu | ", sz);
} else if (types[i] == SQLITE_NULL) { } else if (types[i] == SQLITE_NULL) {
printf("NULL | "); printf("NULL | ");
} else { } 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"); een9_ASSERT(text, "oom in sqlite3_column_text");
printf("%s | ", (const char*)text); printf("%s | ", (const char*)text);
// todo: print only if string is safe to print // todo: print only if string is safe to print
@ -108,7 +88,7 @@ namespace iu9cawebchat {
for (const std::pair<int, std::string>& bv: text8_binds) { 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(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"); 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"); een9_ASSERT(ret == 0, "sqlite3_bind_text");
} }
} catch (const std::exception& e) { } catch (const std::exception& e) {
@ -127,7 +107,8 @@ namespace iu9cawebchat {
int ret = sqlite3_step(stmt.stmt_obj); int ret = sqlite3_step(stmt.stmt_obj);
if (ret == SQLITE_DONE) if (ret == SQLITE_DONE)
return ret; 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); int cc = sqlite3_column_count(stmt.stmt_obj);
for (auto& resp: ret_of_integer_or_null) { for (auto& resp: ret_of_integer_or_null) {
if (resp.first >= cc) if (resp.first >= cc)

View File

@ -15,9 +15,9 @@ namespace iu9cawebchat {
~SqliteConnection(); ~SqliteConnection();
}; };
void sqlite_nooutput(sqlite3* db_hand, const std::string& req_statement, void sqlite_nooutput(SqliteConnection& conn, const std::string& req_statement,
const std::vector<std::pair<int, int64_t>>& int64_binds= {}, const std::vector<std::pair<int, int64_t>>& int64_binds = {},
const std::vector<std::pair<int, std::string>>& text8_binds = {}); const std::vector<std::pair<int, std::string>>& text8_binds = {});
struct fsql_integer_or_null { struct fsql_integer_or_null {
bool exist; bool exist;
@ -33,7 +33,7 @@ namespace iu9cawebchat {
SqliteConnection& conn; SqliteConnection& conn;
sqlite3_stmt* stmt_obj = NULL; sqlite3_stmt* stmt_obj = NULL;
SqliteStatement(SqliteConnection& connection, const std::string& req_statement, SqliteStatement(SqliteConnection& connection, const std::string& req_statement,
const std::vector<std::pair<int, int64_t>>& int64_binds= {}, const std::vector<std::pair<int, int64_t>>& int64_binds = {},
const std::vector<std::pair<int, std::string>>& text8_binds = {}); const std::vector<std::pair<int, std::string>>& text8_binds = {});
SqliteStatement(SqliteStatement&) = delete; SqliteStatement(SqliteStatement&) = delete;
SqliteStatement& operator=(SqliteStatement&) = delete; SqliteStatement& operator=(SqliteStatement&) = delete;

View File

@ -0,0 +1,31 @@
#include <iu9_ca_web_chat_lib/backend_logic/server_data_interact.h>
#include <iu9_ca_web_chat_lib/sqlite3_wrapper.h>
#include <jsonincpp/string_representation.h>
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;
}

View File

@ -0,0 +1,14 @@
#include <iu9_ca_web_chat_lib/backend_logic/server_data_interact.h>
#include <iu9_ca_web_chat_lib/sqlite3_wrapper.h>
#include <jsonincpp/string_representation.h>
#include <assert.h>
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);
}
}