master #6
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -16,3 +16,4 @@ iu9-ca-web-chat.db | ||||
| log/ | ||||
| core | ||||
| 
 | ||||
| config/example.json | ||||
							
								
								
									
										29
									
								
								assets/HypertextPages/chat-members.nytl.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								assets/HypertextPages/chat-members.nytl.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,29 @@ | ||||
| {% ELDEF main JSON pres JSON userinfo JSON openedchat JSON initial_chatUpdResp %} | ||||
| <!DOCTYPE html> | ||||
| <html lang="ru"> | ||||
| <head> | ||||
|     <meta charset="UTF-8"> | ||||
|     <meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||||
|     <title>Веб-Чат Members</title> | ||||
|     <link rel="stylesheet" href="/assets/css/chat.css"> | ||||
| </head> | ||||
| <body> | ||||
| {% PUT chat.pass pres userinfo openedchat initial_chatUpdResp %} | ||||
| <!--TODO: ADD SOMETHING WRITE SOMETHING AAAAAA --> | ||||
| <div class="overlay" id="overlay"> | ||||
|     <div class="members-list" id="members-list"> | ||||
|         <div class="members-list-header"> | ||||
|             <span class="close" onclick="closeMembersList()">×</span> | ||||
|             <h2 class="all-members">Все участники</h2> | ||||
|         </div> | ||||
|         <div class="members-list-body"> | ||||
|             <ul id="members-list-body"> | ||||
|                 <!-- Список участников будет добавлен динамически --> | ||||
|             </ul> | ||||
|         </div> | ||||
|     </div> | ||||
| </div> | ||||
| <script src="/assets/js/chat-members.js"></script> | ||||
| </body> | ||||
| </html> | ||||
| {% ENDELDEF %} | ||||
| @ -1,4 +1,13 @@ | ||||
| {% ELDEF main JSON pres JSON userinfo %} | ||||
| {% ELDEF pass JSON pres JSON userinfo JSON openedchat JSON initial_chatUpdResp %} | ||||
| <script> | ||||
|     let pres = {% PUT jsinsert pres %}; | ||||
|     let userinfo = {% PUT jsinsert userinfo %}; | ||||
|     let openedchat = {% PUT jsinsert openedchat %}; | ||||
|     let initial_chatUpdResp = {% PUT jsinsert initial_chatUpdResp %}; | ||||
| </script> | ||||
| {% ENDELDEF %} | ||||
| 
 | ||||
| {% ELDEF main JSON pres JSON userinfo JSON openedchat JSON initial_chatUpdResp %} | ||||
| <!DOCTYPE html> | ||||
| <html lang="ru"> | ||||
| <head> | ||||
| @ -8,6 +17,8 @@ | ||||
|     <link rel="stylesheet" href="/assets/css/chat.css"> | ||||
| </head> | ||||
| <body> | ||||
| {% PUT chat.pass pres userinfo openedchat initial_chatUpdResp %} | ||||
| <!-- TODO AAAAA--> | ||||
| <div class="chat-container"> | ||||
|     <div class="chat-header"> | ||||
|         <span class="room-name">Веб чат</span> | ||||
| @ -21,19 +32,6 @@ | ||||
|         <button class="chat-send-button" onclick="sendMessage()">Отправить</button> | ||||
|     </div> | ||||
| </div> | ||||
| <div class="overlay" id="overlay"> | ||||
|     <div class="members-list" id="members-list"> | ||||
|         <div class="members-list-header"> | ||||
|             <span class="close" onclick="closeMembersList()">×</span> | ||||
|             <h2 class="all-members">Все участники</h2> | ||||
|         </div> | ||||
|         <div class="members-list-body"> | ||||
|             <ul id="members-list-body"> | ||||
|                 <!-- Список участников будет добавлен динамически --> | ||||
|             </ul> | ||||
|         </div> | ||||
|     </div> | ||||
| </div> | ||||
| <script src="/assets/js/chat.js"></script> | ||||
| </body> | ||||
| </html> | ||||
|  | ||||
							
								
								
									
										42
									
								
								assets/HypertextPages/edit-profile.nytl.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								assets/HypertextPages/edit-profile.nytl.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,42 @@ | ||||
| {% ELDEF main JSON pres JSON userprofile JSON errors %} | ||||
| <!DOCTYPE html> | ||||
| <html lang="en"> | ||||
| <head> | ||||
|     <meta charset="UTF-8"> | ||||
|     <link rel="stylesheet" href="/assets/css/profile.css"> | ||||
|     <title>Профиль</title> | ||||
| </head> | ||||
| <body> | ||||
|     <div class="main-container"> | ||||
|         {% FOR error IN errors %} | ||||
|             <div> | ||||
|                 <p>{% WRITE error.text %}</p> | ||||
|             </div> | ||||
|         {% ENDFOR %} | ||||
|         <div class="profile-header"> | ||||
|             <h1>Редактирование профиля</h1> | ||||
|         </div> | ||||
|         <form method="post" action="/user/{% WRITE userprofile.nickname %}"> | ||||
|             <div class="columns"> | ||||
|                 <div class="column"> | ||||
|                     <p>{% WRITE userprofile.name %} ( {% WRITE userprofile.nickname %} )</p> | ||||
|                     <label for="name"> Изменить имя </label> | ||||
|                     <input type="text" name="name" id="name"> | ||||
|                     <br> | ||||
|                     <label for="password"> Изменить пароль </label> | ||||
|                     <input type="password" name="password" id="password"> | ||||
|                     <br> | ||||
|                 </div> | ||||
|             </div> | ||||
|             <div class="additional-info"> | ||||
|                 <p>О себе</p> | ||||
|                 <p>{% WRITE userprofile.bio %}</p><br> | ||||
|                 <label for="bio">Изменить</label> | ||||
|                 <textarea name="bio" id="bio"></textarea> | ||||
|             </div> | ||||
|             <button class="save" type="submit">Сохранить изменения</button> | ||||
|         </form> | ||||
|     </div> | ||||
| </body> | ||||
| </html> | ||||
| {% ENDELDEF%} | ||||
							
								
								
									
										10
									
								
								assets/HypertextPages/err-404.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								assets/HypertextPages/err-404.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,10 @@ | ||||
| <!DOCTYPE html> | ||||
| <html lang="en"> | ||||
| <head> | ||||
|     <meta charset="UTF-8"> | ||||
|     <title>Not found</title> | ||||
| </head> | ||||
| <body> | ||||
|     <h1>Page not found</h1> | ||||
| </body> | ||||
| </html> | ||||
| @ -1,4 +1,4 @@ | ||||
| {% ELDEF main JSON pres JSON userinfo %} | ||||
| {% ELDEF main JSON pres JSON userinfo JSON initial_chatListUpdResp %} | ||||
| <!DOCTYPE html> | ||||
| <html lang="{% WRITE pres.lang %}"> | ||||
| <head> | ||||
| @ -8,7 +8,11 @@ | ||||
|     <link rel="stylesheet" href="/assets/css/list-rooms.css"> | ||||
| </head> | ||||
| <body> | ||||
| {% PUT pass-pres-userinfo pres userinfo %} | ||||
| <script> | ||||
|     let pres = {% PUT jsinsert pres %}; | ||||
|     let userinfo = {% PUT jsinsert userinfo %}; | ||||
|     let initial_chatListUpdResp = {% PUT jsinsert initial_chatListUpdResp %}; | ||||
| </script> | ||||
| <div class="container"> | ||||
|     <h1 style="color: white;">Выберите Чат-Комнату</h1> | ||||
|     <ul class="room-list"> | ||||
|  | ||||
| @ -9,10 +9,9 @@ | ||||
| </head> | ||||
| 
 | ||||
| <body> | ||||
| {% PUT pass-pres-userinfo pres userinfo %} | ||||
| {% FOR msg IN errors %} | ||||
|     <div class="error-msg"> | ||||
|         {% WRITE msg %} | ||||
|         {% WRITE msg.text %} | ||||
|     </div> | ||||
| {% ENDFOR %} | ||||
| <div class="form-container"> | ||||
|  | ||||
| @ -1,6 +0,0 @@ | ||||
| {% ELDEF main JSON pres JSON userinfo %} | ||||
| <script> | ||||
|     let pres = {% PUT jsinsert pres %}; | ||||
|     let userinfo = {% PUT jsinsert userinfo %}; | ||||
| </script> | ||||
| {% ENDELDEF %} | ||||
| @ -1,39 +0,0 @@ | ||||
| {% ELDEF main JSON pres JSON userinfo %} | ||||
| <!DOCTYPE html> | ||||
| <html lang="en"> | ||||
| <head> | ||||
|     <meta charset="UTF-8"> | ||||
|     <link rel="stylesheet" href="/assets/css/profile.css"> | ||||
|     <title>Профиль</title> | ||||
| </head> | ||||
| <body> | ||||
| <div class="main-container"> | ||||
|     <div class="profile-header"> | ||||
|         <h1 style="color: white; text-align: center;">Профиль пользователя</h1> | ||||
|         <a class="return" href="chat.nytl.html">Назад</a> | ||||
|     </div> | ||||
|     <form> | ||||
|         <div class="columns"> | ||||
|             <div class="column"> | ||||
|                 <img class="avatar" src="/assets/img/empty_avatar.png" id="avatar" height="200" width="200"><br> | ||||
|                 <input type="file" id="fileInput" style="display:none"> | ||||
|                 <button class="add" type="button" onclick="document.getElementById('fileInput').click();">Изменить фото</button><br> | ||||
|             </div> | ||||
|             <div class="column"> | ||||
|                 <input type="text" name="username" placeholder = "Имя пользователя" value="Some Name" id="username"><br> | ||||
|                 <input type="text" name="login" placeholder="Логин" value="some_login123" id="login" readonly><br> | ||||
|             </div> | ||||
|         </div> | ||||
|         <h3 style="color:#007bb5;">О себе</h3> | ||||
|         <div class="additional-info"> | ||||
|             <textarea name="bio" placeholder="Напишите о себе..." id="bio"></textarea> | ||||
|         </div> | ||||
|         <button class="save" type="submit">Сохранить изменения</button> | ||||
|     </form> | ||||
| </div> | ||||
| 
 | ||||
| <script src="/assets/js/list-rooms.js"> </script> | ||||
| 
 | ||||
| </body> | ||||
| </html> | ||||
| {% ENDELDEF%} | ||||
| @ -1,27 +0,0 @@ | ||||
| {% ELDEF main JSON pres JSON userinfo %} | ||||
| <!DOCTYPE html> | ||||
| <html lang="ru"> | ||||
| <head> | ||||
|     <meta charset="UTF-8"> | ||||
|     <meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||||
|     <title>Страница Регистрации</title> | ||||
|     <link rel="stylesheet" href="/assets/css/registration.css"> | ||||
| 
 | ||||
| </head> | ||||
| 
 | ||||
| <body> | ||||
| <div class="form-container"> | ||||
|     <h1 class="hide-cursor no-select">Вход</h1> | ||||
|     <form action="assets/html/list-rooms.html" method="post"> | ||||
|         <input type="text" name="username" placeholder="Имя пользователя" id="username"><br> | ||||
|         <input type="text" name="login" placeholder="Логин" id="login"><br> | ||||
|         <input type="password" name="password" placeholder="Пароль" id="password"><br> | ||||
|         <button type="submit" class="hide-cursor no-select">Зарегистрироваться</button> | ||||
|         <div id="error"></div> | ||||
|     </form> | ||||
| </div> | ||||
| 
 | ||||
| <script src="assets/js/registration.js"></script> | ||||
| </body> | ||||
| </html> | ||||
| {% ENDELDEF %} | ||||
							
								
								
									
										26
									
								
								assets/HypertextPages/view-profile.nytl.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								assets/HypertextPages/view-profile.nytl.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,26 @@ | ||||
| {% ELDEF main JSON pres JSON userprofile %} | ||||
| <!DOCTYPE html> | ||||
| <html lang="en"> | ||||
| <head> | ||||
|     <meta charset="UTF-8"> | ||||
|     <link rel="stylesheet" href="/assets/css/profile.css"> | ||||
|     <title>Профиль</title> | ||||
| </head> | ||||
| <body> | ||||
|     <div class="main-container"> | ||||
|         <div class="profile-header"> | ||||
|             <h1>Профиль пользователя</h1> | ||||
|         </div> | ||||
|         <div class="columns"> | ||||
|             <div class="column"> | ||||
|                 <p>{% WRITE userprofile.name %} ( {% WRITE userprofile.nickname %} )</p> | ||||
|             </div> | ||||
|         </div> | ||||
|         <div class="additional-info"> | ||||
|             <p>О себе</p> | ||||
|             <p>{% WRITE userprofile.bio %}</p><br> | ||||
|         </div> | ||||
|     </div> | ||||
| </body> | ||||
| </html> | ||||
| {% ENDELDEF%} | ||||
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 81 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/img/logo0.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								assets/img/logo0.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 9.7 KiB | 
							
								
								
									
										0
									
								
								assets/js/chat-members.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								assets/js/chat-members.js
									
									
									
									
									
										Normal file
									
								
							| @ -1,10 +0,0 @@ | ||||
| document.getElementById('fileInput').addEventListener('change', function(event) { | ||||
|     const file = event.target.files[0]; | ||||
|     if (file) { | ||||
|         const reader = new FileReader(); | ||||
|         reader.onload = function(e) { | ||||
|             document.getElementById('avatar').src = e.target.result; | ||||
|         }; | ||||
|         reader.readAsDataURL(file); | ||||
|     } | ||||
| }); | ||||
| @ -149,23 +149,8 @@ struct CAWebChat { | ||||
|                 "login_cookie.cpp", | ||||
|                 "backend_logic/server_data_interact.cpp", | ||||
|                 "backend_logic/client_server_interact.cpp", | ||||
|                 "backend_logic/when_list_rooms.cpp", | ||||
|                 "backend_logic/when_login.cpp", | ||||
|                 "backend_logic/when_chat.cpp", | ||||
|                 "backend_logic/when_user.cpp", | ||||
|                 "backend_logic/api_pollevents.cpp", | ||||
|                 "backend_logic/api_getchatlist.cpp", | ||||
|                 "backend_logic/api_getchatinfo.cpp", | ||||
|                 "backend_logic/api_getchatmemberlist.cpp", | ||||
|                 "backend_logic/api_getuserinfo.cpp", | ||||
|                 "backend_logic/api_getmessageinfo.cpp", | ||||
|                 "backend_logic/api_getmessageneighbours.cpp", | ||||
|                 "backend_logic/api_sendmessage.cpp", | ||||
|                 "backend_logic/api_deletemessage.cpp", | ||||
|                 "backend_logic/api_addmembertochat.cpp", | ||||
|                 "backend_logic/api_removememberfromchat.cpp", | ||||
|                 "backend_logic/api_createchat.cpp", | ||||
|                 "backend_logic/api_removechat.cpp", | ||||
| 
 | ||||
|                 "backend_logic/polling.cpp", | ||||
|             }; | ||||
|             for (std::string& u: T.units) | ||||
|                 u = "web_chat/iu9_ca_web_chat_lib/" + u; | ||||
|  | ||||
							
								
								
									
										0
									
								
								config/default.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								config/default.json
									
									
									
									
									
										Normal file
									
								
							| @ -14,7 +14,9 @@ | ||||
|         "name-of-room": "Название комнаты", | ||||
|         "create-room": "Создать комнату", | ||||
| 
 | ||||
|         "incorrect-nickname-or-password": "Неправильный никнейм или пароль" | ||||
|         "incorrect-nickname-or-password": "Неправильный никнейм или пароль", | ||||
| 
 | ||||
|         "incorrect-profile-data": "Неправильно заполнены поля профиля" | ||||
|       }, | ||||
|       "ask" : { | ||||
|         "select-chat-room": "Выберете чат комнату" | ||||
| @ -29,7 +31,7 @@ | ||||
|   }, | ||||
|   "assets": "./assets", | ||||
|   "database": { | ||||
|     "type": "sqlite", | ||||
|     "type": "sqlite3", | ||||
|     "file": "./iu9-ca-web-chat.db" | ||||
|   }, | ||||
|   "limits": { | ||||
|  | ||||
| @ -258,8 +258,8 @@ namespace een9 { | ||||
|                 } | ||||
|                 errno = 0; | ||||
|                 ret = poll(pollfds.data(), Nip, params.mainloop_recheck_interval_us); | ||||
|                 if (ret != 0) { | ||||
|                     printf("poll() error :> %d\n", errno); | ||||
|                 if (ret != 0 && errno != 0) { | ||||
|                     printf("poll() error :> %s\n", een9::prettyprint_errno("").c_str()); | ||||
|                 } | ||||
|                 ASSERT_on_iret(ret, "poll()"); | ||||
|                 for (size_t i = 0; i < Nip; i++) { | ||||
|  | ||||
| @ -18,7 +18,14 @@ int main(int argc, char** argv) { | ||||
|     een9::readFile(config_file, config_text); | ||||
|     const json::JSON config = json::parse_str_flawless(config_text); | ||||
| 
 | ||||
|     std::string answer2 = templater.render("login", {&config["presentation"].g()}); | ||||
|     json::JSON userprofile; | ||||
|     userprofile["uid"].asInteger() = json::Integer(0l); | ||||
|     userprofile["name"].asString() = "radasdasdasdadsdasd"; | ||||
|     userprofile["nickname"].asString() = "root"; | ||||
|     userprofile["bio"].asString() = "Your mother"; | ||||
|     json::JSON errors; | ||||
|     errors = json::JSON(json::array); | ||||
|     std::string answer2 = templater.render("edit-profile", {&config["presentation"], &userprofile, &errors}); | ||||
|     printf("%s\n<a><f><t><e><r><><l><f>\n", answer2.c_str()); | ||||
| 
 | ||||
|     return 0; | ||||
|  | ||||
| @ -178,7 +178,7 @@ namespace nytl { | ||||
|                 } | ||||
|                 ASSERT(nm == "EL", "Type of argument variable is either JSON or EL(...signature)") | ||||
|                 skip(ctx, '('); | ||||
|                 result = json::JSON(json::array); | ||||
|                 result.asArray(); | ||||
|                 assert(result.isArray()); | ||||
|             } | ||||
|             skipWhitespace(ctx); | ||||
| @ -220,16 +220,16 @@ namespace nytl { | ||||
|                 ASSERT(!first.empty(), "Expression should start with 'root' name of global package or local variable"); | ||||
|                 ASSERT(first != "_", "_ ??? ARE YOU KIDDING???"); | ||||
|                 if (local_var_names.count(first) == 1) { | ||||
|                     result["V"] = json::JSON(json::Integer((int64_t)local_var_names.at(first))); | ||||
|                     result["V"].asInteger() = json::Integer((int64_t)local_var_names.at(first)); | ||||
|                 } else { | ||||
|                     result["V"] = json::JSON(first); | ||||
|                     result["V"].asString() = first; | ||||
|                 } | ||||
|                 result["C"] = json::JSON(json::array); | ||||
|                 result["C"].asArray(); | ||||
|             } else { | ||||
|                 skipWhitespace(ctx); | ||||
|                 skip(ctx, ']'); | ||||
|             } | ||||
|             std::vector<json::JSON>& chain = result["C"].g().asArray(); | ||||
|             std::vector<json::JSON>& chain = result["C"].asArray(); | ||||
|             while (true) { | ||||
|                 if (peep(ctx) == '.') { | ||||
|                     skip(ctx, '.'); | ||||
|  | ||||
| @ -56,18 +56,18 @@ namespace nytl { | ||||
|                 descend(*(temp_ret.JSON_subval)); | ||||
|             } else { | ||||
|                 assert(expr.isDictionary()); | ||||
|                 const json::JSON& val = expr["V"].g(); | ||||
|                 const json::JSON& val = expr["V"]; | ||||
|                 if (val.isInteger()) { | ||||
|                     size_t lv_ind = val.asInteger().get_int(); | ||||
|                     assert(lv_ind < local_vars.size()); | ||||
|                     result = local_vars[lv_ind]; | ||||
|                 } else if (val.isString()) { | ||||
|                     std::string cur_el_name_str = expr["V"].g().asString(); | ||||
|                     std::string cur_el_name_str = expr["V"].asString(); | ||||
|                     result = LocalVarValue{false, cur_el_name_str, NULL}; | ||||
|                 } else | ||||
|                     assert(false); | ||||
|             } | ||||
|             const std::vector<json::JSON>& chain = expr["C"].g().asArray(); | ||||
|             const std::vector<json::JSON>& chain = expr["C"].asArray(); | ||||
|             while (true) { | ||||
|                 if (chain_el >= chain.size()) | ||||
|                     return NULL; | ||||
| @ -211,7 +211,8 @@ namespace nytl { | ||||
|     uptr<RFrame> RFrame_OverParts::toMe(bool returned, const global_elem_set_t &elem_ns, Ditch &result, | ||||
|         const std::function<std::string(std::string)> &escape) { | ||||
|         if (!returned) | ||||
|             ASSERT(elem_ns.count(name) == 1, "No such element"); | ||||
|             if (elem_ns.count(name) != 1) | ||||
|                 THROW("No such element"); | ||||
|         const Element& el = elem_ns.at(name); | ||||
|         if (!returned) { | ||||
|             /* Continue to do checks */ | ||||
|  | ||||
| @ -0,0 +1,30 @@ | ||||
| #include "server_data_interact.h" | ||||
| #include <engine_engine_number_9/baza_throw.h> | ||||
| #include <engine_engine_number_9/baza.h> | ||||
| #include "../str_fields.h" | ||||
| 
 | ||||
| namespace iu9cawebchat { | ||||
|     std::string admin_control_procedure(SqliteConnection& conn, const std::string& req, bool& termination) { | ||||
|         if (req == "hello") { | ||||
|             return ":0 omg! hiii!! Hewwou :3 !!!!\n"; | ||||
|         } | ||||
|         if (req == "8") { | ||||
|             termination = true; | ||||
|             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\n"; | ||||
|             std::string new_password = req.substr(nid + 1); | ||||
|             if (!check_password(new_password)) | ||||
|                 een9_THROW("Bad password"); | ||||
|             sqlite_nooutput(conn, | ||||
|                 "UPDATE `user` SET `password` = ?1 WHERE `id` = 0 ", | ||||
|                 {}, {{1, new_password}}); | ||||
|             return "Successul update\n"; | ||||
|         } | ||||
|         return "Incorrect command\n"; | ||||
|     } | ||||
| } | ||||
| @ -1,15 +1,48 @@ | ||||
| #include "server_data_interact.h" | ||||
| #include <engine_engine_number_9/baza_throw.h> | ||||
| #include <assert.h> | ||||
| 
 | ||||
| namespace iu9cawebchat { | ||||
|     void make_her_a_member_of_the_midnight_crew(SqliteConnection& conn, int64_t chatId, int64_t alienUserId, int64_t role) { | ||||
|         assert(role != user_chat_role_deleted); | ||||
|         int64_t chat_HistoryId_BEFORE_EV = get_current_history_id_of_chat(conn, chatId); | ||||
|         int64_t alien_chatlist_HistoryId_BEFORE_EV = get_current_history_id_of_user_chatList(conn, alienUserId); | ||||
|         sqlite_nooutput(conn, | ||||
|             "INSERT INTO `user_chat_membership` (`userId`, `chatId`, `user_chatList_IncHistoryId`," | ||||
|             "`chat_IncHistoryId`, `role`) VALUES (?1, ?2, ?3, ?4, ?5)", | ||||
|             {{1, alienUserId}, {2, chatId}, {3, alien_chatlist_HistoryId_BEFORE_EV + 1}, | ||||
|             {4, chat_HistoryId_BEFORE_EV + 1}, {5, role}}, {}); | ||||
| 
 | ||||
|         sqlite_nooutput(conn, | ||||
|             "UPDATE `chat` SET `it_HistoryId` = ?1 WHERE `id` = ?2", {{1, chat_HistoryId_BEFORE_EV + 1}, | ||||
|             {2, chatId}}, {}); | ||||
| 
 | ||||
|         sqlite_nooutput(conn, | ||||
|             "UPDATE `user` SET `chatList_HistoryId` = ?1 WHERE `id` = ?2", | ||||
|             {{1, alien_chatlist_HistoryId_BEFORE_EV + 1}, {2, alienUserId}}, {}); | ||||
|     } | ||||
| 
 | ||||
|     json::JSON internalapi_addMemberToChat(SqliteConnection& conn, int64_t uid, const json::JSON& Sent) { | ||||
|         int64_t chatId = Sent["chatId"].g().asInteger().get_int(); | ||||
|         int64_t chatId = Sent["chatUpdReq"]["chatId"].asInteger().get_int(); | ||||
|         int64_t my_role_here = get_role_of_user_in_chat(conn, uid, chatId); | ||||
|         if (my_role_here == user_chat_role_deleted) | ||||
|             een9_THROW("Unauthorized user tries to access internalapi_getChatInfo"); | ||||
|         if (my_role_here != user_chat_role_admin) | ||||
|             een9_THROW("Non-admin user tries to access internalapi_getChatInfo"); | ||||
| 
 | ||||
|         std::string alien_nickname = Sent["nickname"].asString(); | ||||
|         RowUser_Content alien; | ||||
|         try { | ||||
|             alien = lookup_user_content_by_nickname(conn, alien_nickname); | ||||
|         } catch (std::exception& e) { | ||||
|             return json::JSON(json::jdict{{"status", json::JSON(-1l)}}); | ||||
|         } | ||||
| 
 | ||||
|         int64_t aliens_old_role = get_role_of_user_in_chat(conn, alien.id, chatId); | ||||
|         if (aliens_old_role == user_chat_role_deleted) { | ||||
|             make_her_a_member_of_the_midnight_crew(conn, chatId, alien.id, user_chat_role_regular); | ||||
|         } | ||||
| 
 | ||||
|         json::JSON Recv; | ||||
|         Recv["status"] = json::JSON(0l); | ||||
|         // todo: WRITE THIS MORBID THING
 | ||||
|         poll_update_chat(conn, Sent, Recv); | ||||
|         return Recv; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -1,11 +1,27 @@ | ||||
| #include "server_data_interact.h" | ||||
| #include <engine_engine_number_9/baza_throw.h> | ||||
| #include "../str_fields.h" | ||||
| 
 | ||||
| namespace iu9cawebchat { | ||||
|     json::JSON internalapi_createChat(SqliteConnection& conn, int64_t uid, const json::JSON& Sent) { | ||||
|         std::string new_chat_name = Sent["content"]["name"].asString(); | ||||
|         std::string new_chat_nickname = Sent["content"]["nickname"].asString(); | ||||
|         if (!check_nickname(new_chat_nickname) || !check_name(new_chat_name)) | ||||
|             return json::JSON(json::jdict{{"status", json::JSON(-1l)}}); | ||||
|         if (is_nickname_taken(conn, new_chat_nickname)) | ||||
|             return json::JSON(json::jdict{{"status", json::JSON(-2l)}}); | ||||
|         reserve_nickname(conn, new_chat_nickname); | ||||
| 
 | ||||
|         sqlite_nooutput(conn, | ||||
|             "INSERT INTO `chat` (`nickname`, `name`, `it_HistoryId`, `lastMsgId`) VALUES (?1, ?2, 0, -1)"); | ||||
| 
 | ||||
|         int64_t CHAT_ID = sqlite_trsess_last_insert_rowid(conn); | ||||
| 
 | ||||
|         make_her_a_member_of_the_midnight_crew(conn, CHAT_ID, uid, user_chat_role_admin); | ||||
| 
 | ||||
|         // todo: send a message into chat
 | ||||
|         json::JSON Recv; | ||||
|         Recv["status"] = json::JSON(0l); | ||||
|         // todo: WRITE THIS MORBID THING
 | ||||
|         poll_update_chat_list(conn, uid, Sent, Recv); | ||||
|         return Recv; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -3,13 +3,30 @@ | ||||
| 
 | ||||
| namespace iu9cawebchat { | ||||
|     json::JSON internalapi_deleteMessage(SqliteConnection& conn, int64_t uid, const json::JSON& Sent) { | ||||
|         int64_t chatId = Sent["chatId"].g().asInteger().get_int(); | ||||
|         int64_t chatId = Sent["chatUpdReq"].asInteger().get_int(); | ||||
|         int64_t my_role_here = get_role_of_user_in_chat(conn, uid, chatId); | ||||
|         if (my_role_here == user_chat_role_deleted) | ||||
|             een9_THROW("Unauthorized user tries to access internalapi_getChatInfo"); | ||||
|         if (my_role_here == user_chat_role_read_only) | ||||
|             een9_THROW("read-only user can't send messages"); | ||||
| 
 | ||||
|         int64_t LocalHistoryId = Sent["chatUpdReq"]["LocalHistoryId"].asInteger().get_int(); | ||||
|         int64_t msgId = Sent["id"].asInteger().get_int(); | ||||
|         RowMessage_Content msgInQuestion = lookup_message_content(conn, chatId, msgId); | ||||
|         if (!(!msgInQuestion.isSystem && (msgInQuestion.senderUserId == uid || my_role_here == user_chat_role_admin) )) | ||||
|             een9_THROW("Can't delete: permission denied"); | ||||
| 
 | ||||
|         int64_t chat_HistoryId_BEFORE_EV = get_current_history_id_of_chat(conn, chatId); | ||||
| 
 | ||||
|         sqlite_nooutput(conn, | ||||
|             "UPDATE `message` SET `exists` = 0, `text` = NULL, `chat_IncHistoryId` = ?1 WHERE `id` = ?2", | ||||
|             {{1, chat_HistoryId_BEFORE_EV + 1}, {2, msgId}}); | ||||
| 
 | ||||
|         sqlite_nooutput(conn, "UPDATE `chat` SET `it_HistoryId` = ?1 WHERE `id` = ?2", | ||||
|             {{1, chat_HistoryId_BEFORE_EV + 1}, {2, chatId}}, {}); | ||||
| 
 | ||||
|         json::JSON Recv; | ||||
|         Recv["status"] = json::JSON(0l); | ||||
|         // todo: WRITE THIS MORBID THING
 | ||||
|         poll_update_chat(conn, Sent, Recv); | ||||
|         return Recv; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -1,19 +0,0 @@ | ||||
| #include "server_data_interact.h" | ||||
| #include <engine_engine_number_9/baza_throw.h> | ||||
| 
 | ||||
| namespace iu9cawebchat { | ||||
|     json::JSON internalapi_getChatInfo(SqliteConnection& conn, int64_t uid, const json::JSON& Sent) { | ||||
|         int64_t chatId = Sent["id"].g().asInteger().get_int(); | ||||
|         int64_t my_role_here = get_role_of_user_in_chat(conn, uid, chatId); | ||||
|         if (my_role_here == user_chat_role_deleted) | ||||
|             een9_THROW("Unauthorized user tries to access internalapi_getChatInfo"); | ||||
|         json::JSON Recv; | ||||
|         Recv["status"] = json::JSON(0l); | ||||
|         RowChat_Content content = lookup_chat_content(conn, chatId); | ||||
|         Recv["name"] = json::JSON(content.name); | ||||
|         Recv["nickname"] = json::JSON(content.nickname); | ||||
|         Recv["lastMsgId"] = json::JSON(content.lastMsgId); | ||||
|         Recv["roleHere"] = json::JSON(stringify_user_chat_role(my_role_here)); | ||||
|         return Recv; | ||||
|     } | ||||
| } | ||||
| @ -1,33 +0,0 @@ | ||||
| #include "server_data_interact.h" | ||||
| #include <engine_engine_number_9/baza_throw.h> | ||||
| 
 | ||||
| namespace iu9cawebchat { | ||||
|     json::JSON internalapi_getChatList(SqliteConnection& conn, int64_t uid, const json::JSON& Sent) { | ||||
|         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; | ||||
|     } | ||||
| } | ||||
| @ -1,36 +0,0 @@ | ||||
| #include "server_data_interact.h" | ||||
| #include <engine_engine_number_9/baza_throw.h> | ||||
| 
 | ||||
| namespace iu9cawebchat { | ||||
|     json::JSON internalapi_getChatMemberList(SqliteConnection& conn, int64_t uid, const json::JSON& Sent) { | ||||
|         int64_t chatId = Sent["id"].g().asInteger().get_int(); | ||||
|         int64_t my_role_here = get_role_of_user_in_chat(conn, uid, chatId); | ||||
|         if (my_role_here == user_chat_role_deleted) | ||||
|             een9_THROW("Unauthorized user tries to access internalapi_getChatInfo"); | ||||
|         json::JSON Recv; | ||||
|         Recv["status"] = json::JSON(0l); | ||||
|         Recv["members"] = json::JSON(json::array); | ||||
|         std::vector<json::JSON>& members = Recv["members"].g().asArray(); | ||||
|         SqliteStatement req(conn, | ||||
|             "SELECT `user`.`id`, `user`.`nickname`, `user`.`name`, `user_chat_membership`.`role` FROM " | ||||
|             "`user` RIGHT JOIN `user_chat_membership` ON `user`.`id` = `user_chat_membership`.`userId` " | ||||
|             "WHERE `user_chat_membership`.`chatId` = ?1", | ||||
|             {{1, chatId}}, {}); | ||||
|         while (true) { | ||||
|             fsql_integer_or_null this_user_id; | ||||
|             fsql_text8_or_null this_user_nickname, this_user_name; | ||||
|             fsql_integer_or_null this_users_role; | ||||
|             int status = sqlite_stmt_step(req, {{0, &this_user_id}, {3, &this_users_role}}, | ||||
|                 {{1, &this_user_nickname}, {2, &this_user_name}}); | ||||
|             if (status != SQLITE_ROW) | ||||
|                 break; | ||||
|             members.emplace_back(); | ||||
|             json::JSON& member = members.back(); | ||||
|             member["id"] = json::JSON(this_user_id.value); | ||||
|             member["content"]["nickname"] = json::JSON(this_user_nickname.value); | ||||
|             member["content"]["name"] = json::JSON(this_user_name.value); | ||||
|             member["content"]["role"] = json::JSON(this_users_role.value); | ||||
|         } | ||||
|         return Recv; | ||||
|     } | ||||
| } | ||||
| @ -1,20 +0,0 @@ | ||||
| #include "server_data_interact.h" | ||||
| #include <engine_engine_number_9/baza_throw.h> | ||||
| 
 | ||||
| namespace iu9cawebchat { | ||||
|     json::JSON internalapi_getMessageInfo(SqliteConnection& conn, int64_t uid, const json::JSON& Sent) { | ||||
|         int64_t chatId = Sent["chatId"].g().asInteger().get_int(); | ||||
|         int64_t msgId = Sent["id"].g().asInteger().get_int(); | ||||
|         if (get_role_of_user_in_chat(conn, uid, chatId) == user_chat_role_deleted) | ||||
|             een9_THROW("Authentication failure"); | ||||
|         json::JSON Recv; | ||||
|         Recv["status"] = json::JSON(0l); | ||||
|         RowMessage_Content content = lookup_message_content(conn, chatId, msgId); | ||||
|         Recv["text"] = json::JSON(content.text); | ||||
|         Recv["isSystem"] = json::JSON(content.isSystem); | ||||
|         Recv["sender"] = json::JSON(content.senderUserId); | ||||
|         // todo: sync that addition with api documentation
 | ||||
|         Recv["previous"] = json::JSON(content.previous); | ||||
|         return Recv; | ||||
|     } | ||||
| } | ||||
| @ -1,52 +0,0 @@ | ||||
| #include "server_data_interact.h" | ||||
| #include <engine_engine_number_9/baza_throw.h> | ||||
| 
 | ||||
| namespace iu9cawebchat { | ||||
|     /* This is literally the most dumb and useless query */ | ||||
|     json::JSON internalapi_getMessageNeighbours(SqliteConnection& conn, int64_t uid, const json::JSON& Sent) { | ||||
|         int64_t chatId = Sent["chatId"].g().asInteger().get_int(); | ||||
|         if (get_role_of_user_in_chat(conn, uid, chatId) == user_chat_role_deleted) | ||||
|             een9_THROW("Authentication failure"); | ||||
|         bool dir_forward = Sent["direction"].g().asString() == "forward"; | ||||
|         int64_t amount = Sent["amount"].g().asInteger().get_int(); | ||||
|         if (amount < 0) | ||||
|             een9_THROW("Incorrect amount"); | ||||
|         json::JSON Recv; | ||||
|         Recv["status"] = json::JSON(0l); | ||||
|         Recv["messages"] = json::JSON(json::array); | ||||
|         std::vector<json::JSON>& messages = Recv["messages"].g().asArray(); | ||||
|         if (dir_forward) { | ||||
|             int64_t curMsg = Sent["id"].g().asInteger().get_int(); | ||||
|             if (curMsg < 0) | ||||
|                 een9_THROW("forward message lookup from the beginning of chat is not supported yet"); | ||||
|             while (true) { | ||||
|                 /* At this point, curMsg is non-negative */ | ||||
|                 std::pair<int64_t, RowMessage_Content> nxt = lookup_message_content_rev_side(conn, chatId, curMsg); | ||||
|                 if (nxt.first < 0) | ||||
|                     break; | ||||
|                 messages.emplace_back(); | ||||
|                 json::JSON& message = messages.back(); | ||||
|                 message["id"] = json::JSON(nxt.first); | ||||
|                 message["previous"] = json::JSON(curMsg); | ||||
|                 message["content"]["text"] = json::JSON(nxt.second.text); | ||||
|                 message["content"]["isSystem"] = json::JSON(nxt.second.isSystem); | ||||
|                 message["content"]["sender"] = json::JSON(nxt.second.senderUserId); | ||||
|                 curMsg = nxt.first; | ||||
|             } | ||||
|         } else { | ||||
|             int64_t curMsg = Sent["previousMsgId"].g().asInteger().get_int(); | ||||
|             while (curMsg >= 0) { | ||||
|                 RowMessage_Content curRow = lookup_message_content(conn, chatId, curMsg); | ||||
|                 messages.emplace_back(); | ||||
|                 json::JSON& message = messages.back(); | ||||
|                 message["id"] = json::JSON(curMsg); | ||||
|                 message["previous"] = json::JSON(curRow.previous); | ||||
|                 message["content"]["text"] = json::JSON(curRow.text); | ||||
|                 message["content"]["isSystem"] = json::JSON(curRow.isSystem); | ||||
|                 message["content"]["sender"] = json::JSON(curRow.senderUserId); | ||||
|                 curMsg = curRow.previous; | ||||
|             } | ||||
|         } | ||||
|         return Recv; | ||||
|     } | ||||
| } | ||||
| @ -1,13 +0,0 @@ | ||||
| #include "server_data_interact.h" | ||||
| 
 | ||||
| namespace iu9cawebchat { | ||||
|     json::JSON internalapi_getUserInfo(SqliteConnection& conn, int64_t uid, const json::JSON& Sent) { | ||||
|         int64_t otherUserId = Sent["id"].g().asInteger().get_int(); | ||||
|         json::JSON Recv; | ||||
|         Recv["status"] = json::JSON(0l); | ||||
|         RowUser_Content content = lookup_user_content(conn, otherUserId); | ||||
|         Recv["name"] = json::JSON(content.name); | ||||
|         Recv["nickname"] = json::JSON(content.nickname); | ||||
|         return Recv; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,14 @@ | ||||
| #include "server_data_interact.h" | ||||
| #include <engine_engine_number_9/baza_throw.h> | ||||
| 
 | ||||
| namespace iu9cawebchat { | ||||
|     json::JSON internalapi_leaveChat(SqliteConnection& conn, int64_t uid, const json::JSON& Sent) { | ||||
|         int64_t chatId = Sent["chatId"].asInteger().get_int(); | ||||
|         if (get_role_of_user_in_chat(conn, uid, chatId) == user_chat_role_deleted) | ||||
|             een9_THROW("Not a member"); | ||||
|         kick_from_chat(conn, uid, chatId); | ||||
|         json::JSON Recv; | ||||
|         poll_update_chat_list(conn, uid, Sent, Recv); | ||||
|         return Recv; | ||||
|     } | ||||
| } | ||||
| @ -1,151 +0,0 @@ | ||||
| #include "server_data_interact.h" | ||||
| #include <engine_engine_number_9/baza_throw.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)); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     int64_t event_polling_fill_chat_hist_entity_response(SqliteConnection& conn, json::JSON& hist_entity_response, | ||||
|             int64_t uid, int64_t chatId, int64_t LocalHistoryId) { | ||||
|         hist_entity_response["type"] = json::JSON("chat"); | ||||
|         if (get_role_of_user_in_chat(conn, uid, chatId) == user_chat_role_deleted) | ||||
|             een9_THROW("internalapi/pollEvents: trying to access chat that user does not belong to"); | ||||
|         hist_entity_response["chatId"] = json::JSON(chatId); | ||||
|         int64_t NewHistoryId = get_current_history_id_of_chat(conn, chatId); | ||||
|         hist_entity_response["HistoryId"] = json::JSON(NewHistoryId); | ||||
| 
 | ||||
|         hist_entity_response["events"] = json::JSON(json::array); | ||||
|         std::vector<json::JSON>& events = hist_entity_response["events"].g().asArray(); | ||||
|         /* 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); | ||||
|         return NewHistoryId; | ||||
|     } | ||||
| 
 | ||||
|     int64_t event_polling_fill_chatlist_hist_entity_response(SqliteConnection& conn, json::JSON& hist_entity_response, | ||||
|             int64_t uid, int64_t LocalHistoryId) { | ||||
|         hist_entity_response["type"] = json::JSON("chatlist"); | ||||
|         int64_t NewHistoryId = get_current_history_id_of_user_chatList(conn, uid); | ||||
|         hist_entity_response["HistotyId"] = json::JSON(NewHistoryId); | ||||
| 
 | ||||
|         hist_entity_response["events"] = json::JSON(json::array); | ||||
|         std::vector<json::JSON>& events = hist_entity_response["events"].g().asArray(); | ||||
|         internalapi_pollEvents_in_user_chatList_collect_events(conn, events, uid, LocalHistoryId); | ||||
|         return NewHistoryId; | ||||
|     } | ||||
| 
 | ||||
|     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(); | ||||
|             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(); | ||||
|                 event_polling_fill_chat_hist_entity_response(conn, hist_entity_response, uid, chatId, LocalHistoryId); | ||||
|             } else if (hist_entity_request["type"].g().asString() == "chatlist") { | ||||
|                 event_polling_fill_chatlist_hist_entity_response(conn, hist_entity_response, uid, LocalHistoryId); | ||||
|             } else | ||||
|                 een9_THROW("Bad request"); | ||||
|         } | ||||
|         return Recv; | ||||
|     } | ||||
| } | ||||
| @ -1,11 +0,0 @@ | ||||
| #include "server_data_interact.h" | ||||
| #include <engine_engine_number_9/baza_throw.h> | ||||
| 
 | ||||
| namespace iu9cawebchat { | ||||
|     json::JSON internalapi_removeChat(SqliteConnection& conn, int64_t uid, const json::JSON& Sent) { | ||||
|         json::JSON Recv; | ||||
|         Recv["status"] = json::JSON(0l); | ||||
|         // todo: WRITE THIS MORBID THING
 | ||||
|         return Recv; | ||||
|     } | ||||
| } | ||||
| @ -2,14 +2,36 @@ | ||||
| #include <engine_engine_number_9/baza_throw.h> | ||||
| 
 | ||||
| namespace iu9cawebchat { | ||||
|     void kick_from_chat(SqliteConnection& conn, int64_t alienUserId, int64_t chatId) { | ||||
|         if (get_role_of_user_in_chat(conn, alienUserId, chatId) == user_chat_role_deleted) | ||||
|             een9_THROW("Can't delete a deleted member"); | ||||
|         int64_t chat_HistoryId_BEFORE_EV = get_current_history_id_of_chat(conn, chatId); | ||||
|         int64_t alien_chatlist_HistoryId_BEFORE_EV = get_current_history_id_of_user_chatList(conn, alienUserId); | ||||
| 
 | ||||
|         sqlite_nooutput(conn, | ||||
|         "UPDATE `user_chat_membership` SET `user_chatList_IncHistoryId` = ?3," | ||||
|         "`chat_IncHistoryId` = ?4, `role` = ?5 WHERE `userId` = ?1 AND `chatId` = ?2", | ||||
|             {{1, alienUserId}, {2, chatId}, {3, alien_chatlist_HistoryId_BEFORE_EV + 1}, | ||||
|             {4, chat_HistoryId_BEFORE_EV + 1}, {5, user_chat_role_deleted}}, {}); | ||||
| 
 | ||||
|         sqlite_nooutput(conn, | ||||
|             "UPDATE `chat` SET `it_HistoryId` = ?1 WHERE `id` = ?2", {{1, chat_HistoryId_BEFORE_EV + 1}, | ||||
|             {2, chatId}}, {}); | ||||
| 
 | ||||
|         sqlite_nooutput(conn, | ||||
|             "UPDATE `user` SET `chatList_HistoryId` = ?1 WHERE `id` = ?2", | ||||
|             {{1, alien_chatlist_HistoryId_BEFORE_EV + 1}, {2, alienUserId}}, {}); | ||||
|     } | ||||
| 
 | ||||
|     json::JSON internalapi_removeMemberFromChat(SqliteConnection& conn, int64_t uid, const json::JSON& Sent) { | ||||
|         int64_t chatId = Sent["chatId"].g().asInteger().get_int(); | ||||
|         int64_t chatId = Sent["chatUpdReq"]["chatId"].asInteger().get_int(); | ||||
|         int64_t my_role_here = get_role_of_user_in_chat(conn, uid, chatId); | ||||
|         if (my_role_here == user_chat_role_deleted) | ||||
|             een9_THROW("Unauthorized user tries to access internalapi_getChatInfo"); | ||||
|         if (my_role_here != user_chat_role_deleted) | ||||
|             een9_THROW("Only admin can delete members of chat"); | ||||
|         int64_t badAlienId = Sent["chatUpdReq"]["userId"].asInteger().get_int(); | ||||
|         kick_from_chat(conn, badAlienId, chatId); | ||||
|         json::JSON Recv; | ||||
|         Recv["status"] = json::JSON(0l); | ||||
|         // todo: WRITE THIS MORBID THING
 | ||||
|         poll_update_chat(conn, Sent, Recv); | ||||
|         return Recv; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -3,46 +3,39 @@ | ||||
| #include "../str_fields.h" | ||||
| 
 | ||||
| namespace iu9cawebchat { | ||||
|     /* Throws excetion on failure
 | ||||
|      * Chat's HistoryId will increment after this operation, incremented | ||||
|     /* No authorization check is performed
 | ||||
|      * Chat's HistoryId will increment after this operation | ||||
|      * if adding system message, uid is ignored | ||||
|      */ | ||||
|     int64_t insert_new_message(SqliteConnection& conn, int64_t uid, int64_t chatId, | ||||
|     void insert_new_message(SqliteConnection& conn, int64_t uid, int64_t chatId, | ||||
|             const std::string& text, bool isSystem) { | ||||
|         int64_t chat_HistoryId_BEFORE_MSG = get_current_history_id_of_chat(conn, chatId); | ||||
|         if (chat_HistoryId_BEFORE_MSG > INT64_MAX - 100) | ||||
|             een9_THROW("please no"); | ||||
|         int64_t chat_lastMsgId = get_lastMsgId_of_chat(conn, chatId); | ||||
|         SqliteStatement req(conn, | ||||
|             "INSERT INTO `message` (`chatId`, `previous`, `senderUserId`, `exists`, `isSystem`, `text`," | ||||
|             "`chat_IncHistoryId`) VALUES (?1, ?2, ?3, 1, ?4, ?5, ?6)", | ||||
|             {{1, chatId}, {4, (int64_t)isSystem}, {6, chat_HistoryId_BEFORE_MSG + 1}}, {{5, text}}); | ||||
|         int64_t chat_cur_last_msg_id = get_lastMsgId_of_chat(conn, chatId); | ||||
|         if (chat_cur_last_msg_id >= 0) | ||||
|             sqlite_stmt_bind_int64(req, 2, chat_cur_last_msg_id); | ||||
|             "INSERT INTO `message` (`chatId`, `id`, `senderUserId`, `exists`, `isSystem`, `chat_IncHistoryId`, " | ||||
|             "`text`) VALUES (?1, ?2, ?3 1, ?4, ?5, ?6)", | ||||
|             {{1, chatId}, {2, chat_lastMsgId + 1}, {4, (int64_t)isSystem}, {5, chat_HistoryId_BEFORE_MSG + 1}}, {{6, text}}); | ||||
|         if (!isSystem) | ||||
|             sqlite_stmt_bind_int64(req, 3, uid); | ||||
|         int64_t MSG_ID = sqlite_trsess_last_insert_rowid(conn); | ||||
|         sqlite_nooutput(conn, "UPDATE `chat` SET `lastMsgId` = ?1, `it_HistoryId` = ?2 WHERE `id` = ?3", | ||||
|             {{1, MSG_ID}, {2, chat_HistoryId_BEFORE_MSG + 1}, {3, chatId}}, {}); | ||||
|         return MSG_ID; | ||||
|             {{1, chat_lastMsgId + 1}, {2, chat_HistoryId_BEFORE_MSG + 1}, {3, chatId}}, {}); | ||||
|     } | ||||
| 
 | ||||
|     json::JSON internalapi_sendMessage(SqliteConnection& conn, int64_t uid, const json::JSON& Sent) { | ||||
|         int64_t chatId = Sent["chatId"].g().asInteger().get_int(); | ||||
|         int64_t chatId = Sent["chatUpdReq"].asInteger().get_int(); | ||||
|         int64_t my_role_here = get_role_of_user_in_chat(conn, uid, chatId); | ||||
|         if (my_role_here == user_chat_role_deleted) | ||||
|             een9_THROW("Unauthorized user tries to access internalapi_getChatInfo"); | ||||
|         if (my_role_here == user_chat_role_read_only) | ||||
|             een9_THROW("read-only user can't send messages"); | ||||
|         const int64_t LocalHistoryId = Sent["LocalHistoryId"].g().asInteger().get_int(); | ||||
|         std::string text = Sent["content"]["text"].g().asString(); | ||||
| 
 | ||||
|         std::string text = Sent["content"]["text"].asString(); | ||||
|         if (!is_orthodox_string(text) || text.empty()) | ||||
|             een9_THROW("Bad input text"); | ||||
|         int64_t MSG_ID = insert_new_message(conn, uid, chatId, text, false); | ||||
|         insert_new_message(conn, uid, chatId, text, false); | ||||
| 
 | ||||
|         json::JSON Recv; | ||||
|         Recv["status"] = json::JSON(0l); | ||||
|         json::JSON hist_ent_response = Recv["update"][0].g(); | ||||
|         event_polling_fill_chat_hist_entity_response(conn, hist_ent_response, uid, chatId, LocalHistoryId); | ||||
|         poll_update_chat(conn, Sent, Recv); | ||||
|         return Recv; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -20,64 +20,45 @@ namespace iu9cawebchat { | ||||
|             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)); | ||||
|                 ret_userinfo["name"] = json::JSON(get_user_name(conn, ret_logged_in_user)); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     std::string R200_pres_uinfo(const std::string& el_name, WorkerGuestData& wgd, | ||||
|                      const json::JSON& config_presentation, | ||||
|                      const json::JSON& userinfo) { | ||||
| 
 | ||||
|         std::string page = wgd.templater->render(el_name, {&config_presentation, &userinfo}); | ||||
|     std::string http_R200(const std::string &el_name, WorkerGuestData &wgd, | ||||
|         const std::vector<const json::JSON *> &args) { | ||||
|         std::string page = wgd.templater->render(el_name, args); | ||||
|         return een9::form_http_server_response_200("text/html", page); | ||||
|     } | ||||
| 
 | ||||
|     std::string R200_pres_uinfo_msges(const std::string& el_name, WorkerGuestData& wgd, | ||||
|         const json::JSON& config_presentation, | ||||
|         const json::JSON& userinfo, const std::vector<HtmlMsgBox>& messages) { | ||||
| 
 | ||||
|         json::JSON jmessages; | ||||
|         // todo: optimize
 | ||||
|     json::JSON jsonify_html_message_list(const std::vector<HtmlMsgBox>& messages) { | ||||
|         json::JSON jmessages(json::array); | ||||
|         for (size_t i = 0; i < messages.size(); i++) { | ||||
|             jmessages[i]["class"] = json::JSON(messages[i].class_); | ||||
|             jmessages[i]["text"] = json::JSON(messages[i].text); | ||||
|             jmessages[i]["class"].asString() = messages[i].class_; | ||||
|             jmessages[i]["text"].asString() = messages[i].text; | ||||
|         } | ||||
|         std::string page = wgd.templater->render(el_name, {&config_presentation, &userinfo, &jmessages}); | ||||
|         return een9::form_http_server_response_200("text/html", page); | ||||
|         return jmessages; | ||||
|     } | ||||
| 
 | ||||
|     std::string page_E404(WorkerGuestData &wgd) { | ||||
|         return een9::form_http_server_response_404("text/html", | ||||
|             wgd.templater->render("err-404", {})); | ||||
|     } | ||||
| 
 | ||||
|     /* ========================= API =========================*/ | ||||
| 
 | ||||
|     std::string when_internalapi(WorkerGuestData& wgd, const een9::ClientRequest& req, int64_t uid, | ||||
|         const std::function<json::JSON(SqliteConnection&, int64_t, const json::JSON&)>& F) { | ||||
|                                  const std::function<json::JSON(SqliteConnection&, int64_t, const json::JSON&)>& F) { | ||||
|         const json::JSON& Sent = json::parse_str_flawless(req.body); | ||||
|         std::string result = json::generate_str(F(*wgd.db, uid, Sent), json::print_pretty); | ||||
|         return een9::form_http_server_response_200("text/json", result); | ||||
|     } | ||||
| 
 | ||||
|     std::string when_internalapi_pollevents(WorkerGuestData& wgd, const een9::ClientRequest& req, int64_t uid) { | ||||
|         return when_internalapi(wgd, req, uid, internalapi_pollEvents); | ||||
|     std::string when_internalapi_chatpollevents(WorkerGuestData& wgd, const een9::ClientRequest& req, int64_t uid) { | ||||
|         return when_internalapi(wgd, req, uid, internalapi_chatPollEvents); | ||||
|     } | ||||
| 
 | ||||
|     std::string when_internalapi_getchatlist(WorkerGuestData& wgd, const een9::ClientRequest& req, int64_t uid) { | ||||
|         return when_internalapi(wgd, req, uid, internalapi_getChatList); | ||||
|     } | ||||
| 
 | ||||
|     std::string when_internalapi_getchatinfo(WorkerGuestData& wgd, const een9::ClientRequest& req, int64_t uid) { | ||||
|         return when_internalapi(wgd, req, uid, internalapi_getChatInfo); | ||||
|     } | ||||
| 
 | ||||
|     std::string when_internalapi_getchatmemberlist(WorkerGuestData& wgd, const een9::ClientRequest& req, int64_t uid) { | ||||
|         return when_internalapi(wgd, req, uid, internalapi_getChatMemberList); | ||||
|     } | ||||
| 
 | ||||
|     std::string when_internalapi_getuserinfo(WorkerGuestData& wgd, const een9::ClientRequest& req, int64_t uid) { | ||||
|         return when_internalapi(wgd, req, uid, internalapi_getUserInfo); | ||||
|     } | ||||
| 
 | ||||
|     std::string when_internalapi_getmessageinfo(WorkerGuestData& wgd, const een9::ClientRequest& req, int64_t uid) { | ||||
|         return when_internalapi(wgd, req, uid, internalapi_getMessageInfo); | ||||
|     std::string when_internalapi_chatlistpollevents(WorkerGuestData &wgd, const een9::ClientRequest &req, int64_t uid) { | ||||
|         return when_internalapi(wgd, req, uid, internalapi_chatListPollEvents); | ||||
|     } | ||||
| 
 | ||||
|     std::string when_internalapi_getmessageneighbours(WorkerGuestData &wgd, const een9::ClientRequest &req, int64_t uid) { | ||||
| @ -104,7 +85,7 @@ namespace iu9cawebchat { | ||||
|         return when_internalapi(wgd, req, uid, internalapi_createChat); | ||||
|     } | ||||
| 
 | ||||
|     std::string when_internalapi_removechat(WorkerGuestData &wgd, const een9::ClientRequest &req, int64_t uid) { | ||||
|         return when_internalapi(wgd, req, uid, internalapi_removeChat); | ||||
|     std::string when_internalapi_leavechat(WorkerGuestData &wgd, const een9::ClientRequest &req, int64_t uid) { | ||||
|         return when_internalapi(wgd, req, uid, internalapi_leaveChat); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -27,18 +27,17 @@ namespace iu9cawebchat { | ||||
|             int64_t& ret_logged_in_user | ||||
|         ); | ||||
| 
 | ||||
|     std::string R200_pres_uinfo(const std::string& el_name, WorkerGuestData& wgd, | ||||
|         const json::JSON& config_presentation, | ||||
|         const json::JSON& userinfo); | ||||
|     std::string http_R200(const std::string& el_name, WorkerGuestData& wgd, | ||||
|         const std::vector<const json::JSON*>& args); | ||||
| 
 | ||||
|     struct HtmlMsgBox { | ||||
|         std::string class_; | ||||
|         std::string text; | ||||
|     }; | ||||
| 
 | ||||
|     std::string R200_pres_uinfo_msges(const std::string& el_name, WorkerGuestData& wgd, | ||||
|         const json::JSON& config_presentation, | ||||
|         const json::JSON& userinfo, const std::vector<HtmlMsgBox>& messages); | ||||
|     json::JSON jsonify_html_message_list(const std::vector<HtmlMsgBox>& messages); | ||||
| 
 | ||||
|     std::string page_E404(WorkerGuestData& wgd); | ||||
| 
 | ||||
|     /*  ========================== PAGES ================================== */ | ||||
| 
 | ||||
| @ -52,22 +51,13 @@ namespace iu9cawebchat { | ||||
|                                const een9::ClientRequest& req, const json::JSON& userinfo); | ||||
| 
 | ||||
|     std::string when_page_user(WorkerGuestData& wgd, const json::JSON& config_presentation, | ||||
|             const een9::ClientRequest& req, const json::JSON& userinfo); | ||||
|             const een9::ClientRequest& req, const std::vector<LoginCookie>& login_cookies, const json::JSON& userinfo); | ||||
| 
 | ||||
| 
 | ||||
|     /* ========================  API  ============================== */ | ||||
|     std::string when_internalapi_chatpollevents(WorkerGuestData& wgd, const een9::ClientRequest& req, int64_t uid); | ||||
| 
 | ||||
|     std::string when_internalapi_pollevents(WorkerGuestData& wgd, const een9::ClientRequest& req, int64_t uid); | ||||
| 
 | ||||
|     std::string when_internalapi_getchatlist(WorkerGuestData& wgd, const een9::ClientRequest& req, int64_t uid); | ||||
| 
 | ||||
|     std::string when_internalapi_getchatinfo(WorkerGuestData& wgd, const een9::ClientRequest& req, int64_t uid); | ||||
| 
 | ||||
|     std::string when_internalapi_getchatmemberlist(WorkerGuestData& wgd, const een9::ClientRequest& req, int64_t uid); | ||||
| 
 | ||||
|     std::string when_internalapi_getuserinfo(WorkerGuestData& wgd, const een9::ClientRequest& req, int64_t uid); | ||||
| 
 | ||||
|     std::string when_internalapi_getmessageinfo(WorkerGuestData& wgd, const een9::ClientRequest& req, int64_t uid); | ||||
|     std::string when_internalapi_chatlistpollevents(WorkerGuestData& wgd, const een9::ClientRequest& req, int64_t uid); | ||||
| 
 | ||||
|     std::string when_internalapi_getmessageneighbours(WorkerGuestData& wgd, const een9::ClientRequest& req, int64_t uid); | ||||
| 
 | ||||
| @ -81,7 +71,7 @@ namespace iu9cawebchat { | ||||
| 
 | ||||
|     std::string when_internalapi_createchat(WorkerGuestData &wgd, const een9::ClientRequest &req, int64_t uid); | ||||
| 
 | ||||
|     std::string when_internalapi_removechat(WorkerGuestData &wgd, const een9::ClientRequest &req, int64_t uid); | ||||
|     std::string when_internalapi_leavechat(WorkerGuestData &wgd, const een9::ClientRequest &req, int64_t uid); | ||||
| } | ||||
| 
 | ||||
| #endif | ||||
|  | ||||
							
								
								
									
										196
									
								
								src/web_chat/iu9_ca_web_chat_lib/backend_logic/polling.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										196
									
								
								src/web_chat/iu9_ca_web_chat_lib/backend_logic/polling.cpp
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,196 @@ | ||||
| #include "server_data_interact.h" | ||||
| #include <assert.h> | ||||
| #include <engine_engine_number_9/baza_throw.h> | ||||
| 
 | ||||
| namespace iu9cawebchat { | ||||
|     json::JSON poll_update_chat_list_resp(SqliteConnection& conn, int64_t userId, int64_t LocalHistoryId) { | ||||
|         json::JSON chatListUpdResp; | ||||
|         SqliteStatement my_membership_changes(conn, | ||||
|            "SELECT `chatId`, `role` FROM `user_chat_membership` WHERE `userId` = ?1 " | ||||
|            "AND `user_chatList_IncHistoryId` > ?2", {{1, userId}, {2, LocalHistoryId}}); | ||||
|         json::jarr myChats = chatListUpdResp["myChats"].asArray(); | ||||
|         while (true) { | ||||
|             fsql_integer_or_null ev_chatId, usersRoleHere; | ||||
|             int status = sqlite_stmt_step(my_membership_changes, {{0, &ev_chatId}, {1, &usersRoleHere}}, {}); | ||||
|             if (status != SQLITE_ROW) | ||||
|                 break; | ||||
|             myChats.emplace_back(); | ||||
|             json::JSON& myMembershipSt = myChats.back(); | ||||
|             myMembershipSt["chatId"].asInteger() = json::Integer(ev_chatId.value); | ||||
|             myMembershipSt["myRoleHere"].asString() = stringify_user_chat_role(usersRoleHere.value); | ||||
|             if (usersRoleHere.value != user_chat_role_deleted) { | ||||
|                 RowChat_Content CHAT = lookup_chat_content(conn, ev_chatId.value); | ||||
|                 myMembershipSt["chatName"].asString() = CHAT.name; | ||||
|                 myMembershipSt["chatNickname"].asString() = CHAT.nickname; | ||||
|             } | ||||
|         } | ||||
|         int64_t HistoryId = get_current_history_id_of_user_chatList(conn, userId); | ||||
|         chatListUpdResp["HistoryId"].asInteger() = json::Integer(HistoryId); | ||||
|         return chatListUpdResp; | ||||
|     } | ||||
| 
 | ||||
|     void poll_update_chat_list(SqliteConnection& conn, int64_t userId, const json::JSON& Sent, json::JSON& Recv) { | ||||
|         Recv["status"].asInteger() = json::Integer(0l); | ||||
|         // todo: in libjsonincpp: get rid of Integer
 | ||||
|         poll_update_chat_list_resp(conn, userId, Sent["chatListUpdReq"]["LocalHistoryId"].asInteger().get_int()); | ||||
|     } | ||||
| 
 | ||||
|     json::JSON make_messageSt_obj(int64_t id, int64_t senderUserId, bool exists, bool isSystem, const std::string& text) { | ||||
|         json::JSON messageSt; | ||||
|         messageSt["id"].asInteger() = json::Integer(id); | ||||
|         if (!isSystem) | ||||
|             messageSt["senderUserId"].asInteger() = json::Integer(senderUserId); | ||||
|         messageSt["exists"] = json::JSON(exists); | ||||
|         messageSt["isSystem"] = json::JSON(isSystem); | ||||
|         if (exists) | ||||
|             messageSt["text"].asString() = text; | ||||
|         return messageSt; | ||||
|     } | ||||
| 
 | ||||
|     json::jarr poll_update_chat_resp_messages(SqliteConnection& conn, int64_t chatId, int64_t LocalHistoryId, | ||||
|         int64_t QSEG_A, int64_t QSEG_B) { | ||||
| 
 | ||||
|         json::jarr messages; | ||||
|         SqliteStatement messages_changes(conn, | ||||
|             "SELECT `id`, `senderUserId`, `exists`, `isSystem`, `text` FROM `messages` " | ||||
|             "WHERE `chatId` = ?1 AND ( `chat_IncHistoryId` > ?2 OR ( ?3 <= `id` AND `id` <= ?4 ) )", | ||||
|             {{1, chatId}, {2, LocalHistoryId}, {3, QSEG_A}, {4, QSEG_B}}, {}); | ||||
|         while (true) { | ||||
|             fsql_integer_or_null msgId, msgSenderUserId, msgExists, msgIsSystem; | ||||
|             fsql_text8_or_null msgText; | ||||
|             int status = sqlite_stmt_step(messages_changes, | ||||
|                 {{0, &msgId}, {1, &msgSenderUserId}, {2, &msgExists}, {3, &msgIsSystem}}, | ||||
|                 {{4, &msgText}}); | ||||
|             if (status != SQLITE_ROW) | ||||
|                 break; | ||||
|             messages.push_back(make_messageSt_obj(msgId.value, msgSenderUserId.value, msgExists.value, | ||||
|                 msgIsSystem.value, msgText.value)); | ||||
|         } | ||||
|         return messages; | ||||
|     } | ||||
| 
 | ||||
|     json::jarr poll_update_chat_resp_members(SqliteConnection& conn, int64_t chatId, int64_t LocalHistoryId) { | ||||
|         json::jarr members; | ||||
| 
 | ||||
|         SqliteStatement membership_changes(conn, | ||||
|                    "SELECT `userId`, `role`, FROM `user_chat_membership` WHERE `chatId` = ?1 " | ||||
|                    "AND `chat_IncHistoryId` > ?2", {{1, chatId}, {2, LocalHistoryId}}, {}); | ||||
|         while (true) { | ||||
|             fsql_integer_or_null alienUserId; | ||||
|             fsql_integer_or_null alienRoleHere; | ||||
|             int status = sqlite_stmt_step(membership_changes, | ||||
|                 {{0, &alienUserId}, {1, &alienRoleHere}}, {}); | ||||
|             if (status != SQLITE_ROW) | ||||
|                 break; | ||||
|             members.emplace_back(); | ||||
|             json::JSON& memberSt = members.back(); | ||||
|             memberSt["userId"].asInteger() = json::Integer(alienUserId.value); | ||||
|             memberSt["roleHere"].asString() = stringify_user_chat_role(alienRoleHere.value); | ||||
|             if (alienRoleHere.value != user_chat_role_deleted) { | ||||
|                 RowUser_Content alien = lookup_user_content(conn, alienUserId.value); | ||||
|                 memberSt["name"].asString() = alien.name; | ||||
|                 memberSt["nickname"].asString() = alien.nickname; | ||||
|             } | ||||
|         } | ||||
|         return members; | ||||
|     } | ||||
| 
 | ||||
|     json::JSON poll_update_chat_ONE_MSG_resp(SqliteConnection& conn, int64_t chatId, int64_t selectedMsg) { | ||||
|         json::JSON chatUpdResp; | ||||
| 
 | ||||
|         chatUpdResp["members"].asArray() = poll_update_chat_resp_members(conn, chatId, 0); | ||||
| 
 | ||||
| 
 | ||||
|         json::jarr messages = chatUpdResp["messages"].asArray(); | ||||
|         if (selectedMsg >= 0) { | ||||
|             RowMessage_Content msg = lookup_message_content(conn, chatId, selectedMsg); | ||||
|             messages.push_back(make_messageSt_obj(msg.id, msg.senderUserId, msg.exists, msg.isSystem, msg.text)); | ||||
|         } | ||||
| 
 | ||||
|         int64_t lastMsgId = get_lastMsgId_of_chat(conn, chatId); | ||||
|         chatUpdResp["lastMsgId"].asInteger() = json::Integer(lastMsgId); | ||||
| 
 | ||||
|         int64_t HistoryId = get_current_history_id_of_chat(conn, chatId); | ||||
|         chatUpdResp["HistoryId"].asInteger() = json::Integer(HistoryId); | ||||
|         return chatUpdResp; | ||||
|     } | ||||
| 
 | ||||
|     json::JSON poll_update_chat_important_segment_resp(SqliteConnection& conn, int64_t chatId, int64_t LocalHistoryId, | ||||
|         int64_t QSEG_A, int64_t QSEG_B) { | ||||
| 
 | ||||
|         json::JSON chatUpdResp; | ||||
| 
 | ||||
|         chatUpdResp["members"].asArray() = poll_update_chat_resp_members(conn, chatId, LocalHistoryId); | ||||
|         chatUpdResp["messages"].asArray() = poll_update_chat_resp_messages(conn, chatId, LocalHistoryId, QSEG_A, QSEG_B); | ||||
| 
 | ||||
|         int64_t lastMsgId = get_lastMsgId_of_chat(conn, chatId); | ||||
|         chatUpdResp["lastMsgId"].asInteger() = json::Integer(lastMsgId); | ||||
| 
 | ||||
|         int64_t HistoryId = get_current_history_id_of_chat(conn, chatId); | ||||
|         chatUpdResp["HistoryId"].asInteger() = json::Integer(HistoryId); | ||||
|         return chatUpdResp; | ||||
|     } | ||||
| 
 | ||||
|     /* chat polling function MUST have one queer feature: it accepts a range of msgId, which are guaranteed to be
 | ||||
|      * lookud up. */ | ||||
|     void poll_update_chat_important_segment(SqliteConnection& conn, const json::JSON& Sent, json::JSON& Recv, | ||||
|         int64_t QSEG_A, int64_t QSEG_B) { | ||||
| 
 | ||||
|         Recv["status"].asInteger() = json::Integer(0l); | ||||
|         Recv["charUpdResp"] = poll_update_chat_important_segment_resp(conn, | ||||
|             Sent["chatUpdReq"]["chatId"].asInteger().get_int(), | ||||
|             Sent["chatUpdReq"]["LocalHistoryId"].asInteger().get_int(), QSEG_A, QSEG_B); | ||||
|     } | ||||
| 
 | ||||
|     void poll_update_chat(SqliteConnection& conn, const json::JSON& Sent, json::JSON& Recv) { | ||||
|         poll_update_chat_important_segment(conn, Sent, Recv, -1, -2); | ||||
|     } | ||||
| 
 | ||||
|     json::JSON internalapi_chatPollEvents(SqliteConnection& conn, int64_t uid, const json::JSON& Sent) { | ||||
|         int64_t chatId = Sent["chatUpdReq"]["chatId"].asInteger().get_int(); | ||||
|         if (get_role_of_user_in_chat(conn, uid, chatId) == user_chat_role_deleted) | ||||
|             een9_THROW("chatPollEvents: trying to access chat that user does not belong to"); | ||||
|         json::JSON Recv; | ||||
|         poll_update_chat(conn, Sent, Recv); | ||||
|         return Recv; | ||||
|     } | ||||
| 
 | ||||
|     json::JSON internalapi_chatListPollEvents(SqliteConnection& conn, int64_t uid, const json::JSON& Sent) { | ||||
|         json::JSON Recv; | ||||
|         poll_update_chat_list(conn, uid, Sent, Recv); | ||||
|         return Recv; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     /* Reznya */ | ||||
|     json::JSON internalapi_getMessageNeighbours(SqliteConnection& conn, int64_t uid, const json::JSON& Sent) { | ||||
|         int64_t chatId = Sent["chatId"].asInteger().get_int(); | ||||
|         if (get_role_of_user_in_chat(conn, uid, chatId) == user_chat_role_deleted) | ||||
|             een9_THROW("Authentication failure"); | ||||
|         int64_t lastMsgId = get_lastMsgId_of_chat(conn, chatId); | ||||
|         bool dir_forward = Sent["direction"].asString() == "forward"; | ||||
|         int64_t amount = Sent["amount"].asInteger().get_int(); | ||||
|         int64_t K = Sent["msgId"].asInteger().get_int(); | ||||
|         if (amount <= 0 || amount > 15) | ||||
|             een9_THROW("Incorrect amount"); | ||||
|         json::JSON Recv; | ||||
|         int64_t qBeg = -1; | ||||
|         int64_t qEnd = -2; | ||||
|         if (lastMsgId >= 0) { | ||||
|             if (K < 0) { | ||||
|                 if (dir_forward) | ||||
|                     een9_THROW("Can't go from the top of chat"); | ||||
|                 qBeg = std::max(0l, lastMsgId - amount + 1); | ||||
|                 qEnd = lastMsgId; | ||||
|             } else if (dir_forward) { | ||||
|                 qBeg = K + 1; | ||||
|                 qEnd = K + amount; | ||||
|             } else { | ||||
|                 qBeg = K - amount; | ||||
|                 qEnd = K - 1; | ||||
|             } | ||||
|         } | ||||
|         poll_update_chat_important_segment(conn, Sent, Recv, qBeg, qEnd); | ||||
|         return Recv; | ||||
|     } | ||||
| } | ||||
| @ -2,6 +2,7 @@ | ||||
| 
 | ||||
| #include <engine_engine_number_9/baza_throw.h> | ||||
| #include <engine_engine_number_9/http_structures/cookies.h> | ||||
| #include "../str_fields.h" | ||||
| 
 | ||||
| namespace iu9cawebchat { | ||||
|     const char* stringify_user_chat_role(int64_t role) { | ||||
| @ -27,7 +28,7 @@ namespace iu9cawebchat { | ||||
|         return -1; | ||||
|     } | ||||
| 
 | ||||
|     std::string find_user_name (SqliteConnection& conn, int64_t uid) { | ||||
|     std::string get_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", | ||||
| @ -50,7 +51,20 @@ namespace iu9cawebchat { | ||||
|         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 {uid, std::move(nickname_col.value), std::move(name_col.value)}; | ||||
|         } | ||||
|         een9_THROW("No such user"); | ||||
|     } | ||||
| 
 | ||||
|     RowUser_Content lookup_user_content_by_nickname(SqliteConnection& conn, const std::string& nickname) { | ||||
|         SqliteStatement sql_req(conn, | ||||
|            "SELECT `id`, `name` FROM `user` WHERE `nickname` = ?1", | ||||
|            {}, {{1, nickname}}); | ||||
|         fsql_integer_or_null id_col; | ||||
|         fsql_text8_or_null name_col; | ||||
|         int status = sqlite_stmt_step(sql_req, {{0, &id_col}}, {{1, &name_col}}); | ||||
|         if (status == SQLITE_ROW) { | ||||
|             return {id_col.value, nickname, std::move(name_col.value)}; | ||||
|         } | ||||
|         een9_THROW("No such user"); | ||||
|     } | ||||
| @ -65,7 +79,22 @@ namespace iu9cawebchat { | ||||
|         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), | ||||
|             return {chatId, std::move(nickname_col.value), std::move(name_col.value), | ||||
|                 last_msg_id_col.exist ? last_msg_id_col.value : -1}; | ||||
|         } | ||||
|         een9_THROW("No such chat"); | ||||
|     } | ||||
| 
 | ||||
|     RowChat_Content lookup_chat_content_by_nickname(SqliteConnection &conn, const std::string& nickname) { | ||||
|         SqliteStatement sql_req(conn, | ||||
|            "SELECT `id`, `name`, `lastMsgId` FROM `chat` WHERE `nickname` = ?1", | ||||
|            {}, {{1, nickname}}); | ||||
|         fsql_integer_or_null id_col; | ||||
|         fsql_text8_or_null name_col; | ||||
|         fsql_integer_or_null last_msg_id_col; | ||||
|         int status = sqlite_stmt_step(sql_req, {{0, &id_col}, {2, &last_msg_id_col}}, {{1, &name_col}}); | ||||
|         if (status == SQLITE_ROW) { | ||||
|             return {id_col.value, nickname, std::move(name_col.value), | ||||
|                 last_msg_id_col.exist ? last_msg_id_col.value : -1}; | ||||
|         } | ||||
|         een9_THROW("No such chat"); | ||||
| @ -73,39 +102,21 @@ namespace iu9cawebchat { | ||||
| 
 | ||||
|     RowMessage_Content lookup_message_content(SqliteConnection& conn, int64_t chatId, int64_t msgId) { | ||||
|         SqliteStatement req(conn, | ||||
|             "SELECT `previousId`, `senderUserId`, `exists`, `isSystem`, `text` FROM `message` WHERE " | ||||
|             "SELECT `senderUserId`, `exists`, `isSystem`, `text` FROM `message` WHERE " | ||||
|             "`chatId` = ?1 AND `id` = ?2", {{1, chatId}, {2, msgId}}, {}); | ||||
|         fsql_integer_or_null previousId, senderUserId, exists, isSystem; | ||||
|         fsql_integer_or_null senderUserId, exists, isSystem; | ||||
|         fsql_text8_or_null msg_text; | ||||
|         int status = sqlite_stmt_step(req, {{0, &previousId}, {1, &senderUserId}, {2, &exists}, {3, &isSystem}}, | ||||
|             {{4, &msg_text}}); | ||||
|         int status = sqlite_stmt_step(req, {{0, &senderUserId}, {1, &exists}, {2, &isSystem}}, | ||||
|             {{3, &msg_text}}); | ||||
|         if (status == SQLITE_ROW) { | ||||
|             if (!(bool)exists.value) | ||||
|                 een9_THROW("Message existed, but now it isn't"); | ||||
|             return {(bool)isSystem.value, msg_text.value, senderUserId.exist ? senderUserId.value : -1, | ||||
|             previousId.exist ? previousId.value : -1}; | ||||
|                 een9_THROW("Message existed, but now it does not"); | ||||
|             return {msgId, senderUserId.exist ? senderUserId.value : -1, (bool)exists.value, | ||||
|                 (bool)isSystem.value, msg_text.value}; | ||||
|         } | ||||
|         een9_THROW("No such message"); | ||||
|     } | ||||
| 
 | ||||
|     std::pair<int64_t, RowMessage_Content> lookup_message_content_rev_side(SqliteConnection& conn, int64_t chatId, int64_t prevMsgId) { | ||||
|         een9_ASSERT(prevMsgId >= 0, "V durku dobro pozhalovat"); | ||||
|         SqliteStatement req(conn, | ||||
|             "SELECT `id`, `senderUserId`, `exists`, `isSystem`, `text` FROM `message` WHERE " | ||||
|             "`chatId` = ?1 AND `previous` = ?2", {{1, chatId}, {2, prevMsgId}}, {}); | ||||
|         fsql_integer_or_null id, senderUserId, exists, isSystem; | ||||
|         fsql_text8_or_null msg_text; | ||||
|         int status = sqlite_stmt_step(req, {{0, &id}, {1, &senderUserId}, {2, &exists}, {3, &isSystem}}, | ||||
|             {{4, &msg_text}}); | ||||
|         if (status == SQLITE_ROW) { | ||||
|             een9_ASSERT_pl(exists.value == 1); | ||||
|             return {id.value, | ||||
|                 {(bool)isSystem.value, msg_text.value, senderUserId.exist ? senderUserId.value : -1, prevMsgId}}; | ||||
|         } | ||||
|         return {-1, {}}; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     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", | ||||
| @ -132,4 +143,103 @@ namespace iu9cawebchat { | ||||
| 
 | ||||
|     /* All the api calls processing is done in dedicated files.
 | ||||
|      * All functions related to polling are defined in api_pollevents.cpp */ | ||||
| 
 | ||||
|     // todo: move everything to dedicated files
 | ||||
|     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; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     // todo: extract useful clues from deprecated code.
 | ||||
|     // todo: deprecated code goes here:
 | ||||
|     /* !!! DEPRECATED FUNCTION */ | ||||
|     json::JSON toremoveinternalapi_getChatList(SqliteConnection& conn, int64_t uid, const json::JSON& Sent) { | ||||
|         json::JSON Recv; | ||||
|         Recv["status"] = json::JSON(0l); | ||||
|         Recv["chats"] = json::JSON(json::array); | ||||
|         std::vector<json::JSON>& chats = Recv["chats"].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; | ||||
|     } | ||||
| 
 | ||||
|     /* !!! DEPRECATED FUNCTION */ | ||||
|     json::JSON toremoveinternalapi_getChatMemberList(SqliteConnection& conn, int64_t uid, const json::JSON& Sent) { | ||||
|         int64_t chatId = Sent["id"].asInteger().get_int(); | ||||
|         int64_t my_role_here = get_role_of_user_in_chat(conn, uid, chatId); | ||||
|         if (my_role_here == user_chat_role_deleted) | ||||
|             een9_THROW("Unauthorized user tries to access internalapi_getChatInfo"); | ||||
|         json::JSON Recv; | ||||
|         Recv["status"] = json::JSON(0l); | ||||
|         Recv["members"] = json::JSON(json::array); | ||||
|         std::vector<json::JSON>& members = Recv["members"].asArray(); | ||||
|         SqliteStatement req(conn, | ||||
|             "SELECT `user`.`id`, `user`.`nickname`, `user`.`name`, `user_chat_membership`.`role` FROM " | ||||
|             "`user` RIGHT JOIN `user_chat_membership` ON `user`.`id` = `user_chat_membership`.`userId` " | ||||
|             "WHERE `user_chat_membership`.`chatId` = ?1", | ||||
|             {{1, chatId}}, {}); | ||||
|         while (true) { | ||||
|             fsql_integer_or_null this_user_id; | ||||
|             fsql_text8_or_null this_user_nickname, this_user_name; | ||||
|             fsql_integer_or_null this_users_role; | ||||
|             int status = sqlite_stmt_step(req, {{0, &this_user_id}, {3, &this_users_role}}, | ||||
|                 {{1, &this_user_nickname}, {2, &this_user_name}}); | ||||
|             if (status != SQLITE_ROW) | ||||
|                 break; | ||||
|             members.emplace_back(); | ||||
|             json::JSON& member = members.back(); | ||||
|             member["id"] = json::JSON(this_user_id.value); | ||||
|             member["content"]["nickname"] = json::JSON(this_user_nickname.value); | ||||
|             member["content"]["name"] = json::JSON(this_user_name.value); | ||||
|             member["content"]["role"] = json::JSON(this_users_role.value); | ||||
|         } | ||||
|         return Recv; | ||||
|     } | ||||
| 
 | ||||
|     bool is_nickname_taken(SqliteConnection& conn, const std::string& nickname) { | ||||
|         if (!check_nickname(nickname)) | ||||
|             return true; | ||||
|         SqliteStatement req(conn, "SELECT EXISTS(SELECT 1 FROM `nickname` WHERE `it` = ?1)", | ||||
|             {}, {{1, nickname}}); | ||||
|         fsql_integer_or_null r{true, 0}; | ||||
|         int status = sqlite_stmt_step(req, {{0, &r}}, {}); | ||||
|         return r.value; | ||||
|     } | ||||
| 
 | ||||
|     void reserve_nickname(SqliteConnection& conn, const std::string& nickname) { | ||||
|         if (!check_nickname(nickname)) | ||||
|             een9_THROW("PRECAUTION! Trying to insert incorrect nickname into nickname table"); | ||||
|         sqlite_nooutput(conn, "INSERT INTO `nickname` (`it`) VALUES (?1)", {}, {{1, nickname}}); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -16,62 +16,70 @@ namespace iu9cawebchat { | ||||
|     const char* stringify_user_chat_role(int64_t role); | ||||
| 
 | ||||
|     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); | ||||
|     std::string get_user_name (SqliteConnection& conn, int64_t uid); | ||||
| 
 | ||||
|     struct RowUser_Content { | ||||
|         int64_t id; | ||||
|         std::string nickname; | ||||
|         std::string name; | ||||
|     }; | ||||
| 
 | ||||
|     struct RowChat_Content { | ||||
|         int64_t id; | ||||
|         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); | ||||
|     RowUser_Content lookup_user_content_by_nickname(SqliteConnection& conn, const std::string& nickname); | ||||
|     /* Does not make authorization check */ | ||||
|     RowChat_Content lookup_chat_content(SqliteConnection& conn, int64_t chatId); | ||||
|     RowChat_Content lookup_chat_content_by_nickname(SqliteConnection &conn, const std::string& nickname); | ||||
| 
 | ||||
|     struct RowMessage_Content { | ||||
|         int64_t id; | ||||
|         int64_t senderUserId; | ||||
|         bool exists; | ||||
|         bool isSystem; | ||||
|         std::string text; | ||||
|         int64_t senderUserId; | ||||
|         int64_t previous = -1; | ||||
|     }; | ||||
| 
 | ||||
|     RowMessage_Content lookup_message_content(SqliteConnection& conn, int64_t chatId, int64_t msgId); | ||||
| 
 | ||||
|     /* If prevMsgId is id of the last message in chat, and there is no message ahead of it, .first = -1 */ | ||||
|     std::pair<int64_t, RowMessage_Content> lookup_message_content_rev_side(SqliteConnection& conn, int64_t chatId, int64_t prevMsgId); | ||||
| 
 | ||||
|     /* Does not make authorization check */ | ||||
|     int64_t get_role_of_user_in_chat(SqliteConnection& conn, int64_t userId, int64_t chatId); | ||||
|     /* Does not make authorization check */ | ||||
|     int64_t get_lastMsgId_of_chat(SqliteConnection& conn, int64_t chatId); | ||||
| 
 | ||||
|     /* ============================= API ====================================*/ | ||||
|     int64_t get_current_history_id_of_chat(SqliteConnection& conn, int64_t chatId); | ||||
|     int64_t get_current_history_id_of_user_chatList(SqliteConnection& conn, int64_t userId); | ||||
|     /* Returns HistoryId of chat (latest) */ | ||||
|     int64_t event_polling_fill_chat_hist_entity_response(SqliteConnection& conn, json::JSON& hist_entity_response, | ||||
|             int64_t uid, int64_t chatId, int64_t LocalHistoryId); | ||||
|     /* Returns HistoryId of chat list (latest) */ | ||||
|     int64_t event_polling_fill_chatlist_hist_entity_response(SqliteConnection& conn, json::JSON& hist_entity_response, | ||||
|             int64_t uid, int64_t LocalHistoryId); | ||||
|     json::JSON internalapi_pollEvents(SqliteConnection& conn, int64_t uid, const json::JSON& Sent); | ||||
|     json::JSON internalapi_getChatList(SqliteConnection& conn, int64_t uid, const json::JSON& Sent); | ||||
|     json::JSON internalapi_getChatInfo(SqliteConnection& conn, int64_t uid, const json::JSON& Sent); | ||||
|     json::JSON internalapi_getChatMemberList(SqliteConnection& conn, int64_t uid, const json::JSON& Sent); | ||||
|     json::JSON internalapi_getUserInfo(SqliteConnection& conn, int64_t uid, const json::JSON& Sent); | ||||
|     json::JSON internalapi_getMessageInfo(SqliteConnection& conn, int64_t uid, const json::JSON& Sent); | ||||
| 
 | ||||
|     json::JSON poll_update_chat_list_resp(SqliteConnection& conn, int64_t userId, int64_t LocalHistoryId); | ||||
|     void poll_update_chat_list(SqliteConnection& conn, int64_t userId, const json::JSON& Sent, json::JSON& Recv); | ||||
| 
 | ||||
|     json::JSON poll_update_chat_ONE_MSG_resp(SqliteConnection& conn, int64_t chatId, int64_t selectedMsg); | ||||
|     void poll_update_chat(SqliteConnection& conn, const json::JSON& Sent, json::JSON& Recv); | ||||
| 
 | ||||
|     void make_her_a_member_of_the_midnight_crew(SqliteConnection& conn, int64_t chatId, int64_t alienUserId, int64_t role); | ||||
|     void kick_from_chat(SqliteConnection& conn, int64_t alienUserId, int64_t chatId); | ||||
| 
 | ||||
|     bool is_nickname_taken(SqliteConnection& conn, const std::string& nickname); | ||||
|     void reserve_nickname(SqliteConnection& conn, const std::string& nickname); | ||||
| 
 | ||||
|     /* ============================= API ==================================== */ | ||||
|     json::JSON internalapi_chatPollEvents(SqliteConnection& conn, int64_t uid, const json::JSON& Sent); | ||||
|     json::JSON internalapi_chatListPollEvents(SqliteConnection& conn, int64_t uid, const json::JSON& Sent); | ||||
|     json::JSON internalapi_getMessageNeighbours(SqliteConnection& conn, int64_t uid, const json::JSON& Sent); | ||||
|     json::JSON internalapi_sendMessage(SqliteConnection& conn, int64_t uid, const json::JSON& Sent); | ||||
|     json::JSON internalapi_deleteMessage(SqliteConnection& conn, int64_t uid, const json::JSON& Sent); | ||||
|     json::JSON internalapi_addMemberToChat(SqliteConnection& conn, int64_t uid, const json::JSON& Sent); | ||||
|     json::JSON internalapi_removeMemberFromChat(SqliteConnection& conn, int64_t uid, const json::JSON& Sent); | ||||
|     json::JSON internalapi_createChat(SqliteConnection& conn, int64_t uid, const json::JSON& Sent); | ||||
|     json::JSON internalapi_removeChat(SqliteConnection& conn, int64_t uid, const json::JSON& Sent); | ||||
|     json::JSON internalapi_leaveChat(SqliteConnection& conn, int64_t uid, const json::JSON& Sent); | ||||
| 
 | ||||
|     /**/ | ||||
|     std::string admin_control_procedure(SqliteConnection& conn, const std::string& req, bool& termination); | ||||
| } | ||||
| 
 | ||||
| #endif | ||||
|  | ||||
| @ -1,8 +1,54 @@ | ||||
| #include "client_server_interact.h" | ||||
| 
 | ||||
| #include <engine_engine_number_9/baza_throw.h> | ||||
| #include "../str_fields.h" | ||||
| 
 | ||||
| namespace iu9cawebchat { | ||||
|     std::string when_page_chat(WorkerGuestData& wgd, const json::JSON& config_presentation, | ||||
|             const een9::ClientRequest& req, const json::JSON& userinfo) { | ||||
|         return R200_pres_uinfo("chat", config_presentation, wgd, userinfo); | ||||
|         const een9::ClientRequest& req, const json::JSON& userinfo) { | ||||
| 
 | ||||
|         std::vector<std::string> path_segs = {""}; | ||||
|         path_segs.reserve(4); | ||||
|         for (char ch: req.uri_path) { | ||||
|             if (ch == '/') | ||||
|                 path_segs.emplace_back(); | ||||
|             else | ||||
|                 path_segs.back() += ch; | ||||
|         } | ||||
|         // Parameter, passed from server to javascript
 | ||||
|         std::string chat_nickname; | ||||
|         int64_t selected_message_id = -1; | ||||
|         if (path_segs.size() >= 2) { | ||||
|             chat_nickname = std::move(path_segs[1]); | ||||
|         } | ||||
|         if (!check_nickname(chat_nickname)) | ||||
|             return page_E404(wgd); | ||||
|         if (path_segs.size() == 2) { | ||||
|         } else if (path_segs.size() == 4) { | ||||
|             if (path_segs[2] != "m") | ||||
|                 return page_E404(wgd); | ||||
|             selected_message_id = std::stoll(path_segs[3]); | ||||
|             return page_E404(wgd); | ||||
|         } else | ||||
|             return page_E404(wgd); | ||||
| 
 | ||||
|         bool chat_members = (path_segs[0] == "chat-members"); | ||||
| 
 | ||||
| 
 | ||||
|         // todo: do not throw id chat not found, catch exception and return 404
 | ||||
|         json::JSON openedchat; | ||||
|         RowChat_Content chatInfo = lookup_chat_content_by_nickname(*wgd.db, chat_nickname); | ||||
|         if (get_role_of_user_in_chat(*wgd.db, userinfo["id"].asInteger().get_int(), chatInfo.id) == user_chat_role_deleted) { | ||||
|             return page_E404(wgd); | ||||
|         } | ||||
| 
 | ||||
|         openedchat["name"] = json::JSON(chatInfo.name); | ||||
|         openedchat["nickname"] = json::JSON(chatInfo.nickname); | ||||
|         openedchat["id"] = json::JSON(chatInfo.id); | ||||
|         openedchat["selectedMessageId"] = json::JSON(selected_message_id);  // -1 means that nothing was selected
 | ||||
|         json::JSON initial_chatUpdResp = poll_update_chat_ONE_MSG_resp(*wgd.db, chatInfo.id, selected_message_id); | ||||
|         if (chat_members) | ||||
|             return http_R200("chat", wgd, {&config_presentation, &userinfo, &openedchat, &initial_chatUpdResp}); | ||||
|         return http_R200("chat", wgd, {&config_presentation, &userinfo, &openedchat, &initial_chatUpdResp}); | ||||
|     } | ||||
| } | ||||
| } | ||||
|  | ||||
| @ -6,6 +6,8 @@ namespace iu9cawebchat { | ||||
|         if (userinfo.isNull()) { | ||||
|             return een9::form_http_server_response_303("/login"); | ||||
|         } | ||||
|         return R200_pres_uinfo("list-rooms", config_presentation, wgd, userinfo); | ||||
|         json::JSON initial_chatListUpdResp = poll_update_chat_list_resp(*wgd.db, | ||||
|             userinfo["uid"].asInteger().get_int(), 0); | ||||
|         return http_R200("list-rooms", wgd, {&config_presentation, &userinfo, &initial_chatListUpdResp}); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -25,15 +25,18 @@ namespace iu9cawebchat { | ||||
|             } catch(const std::exception& e){} | ||||
|             if (uid < 0) { | ||||
|                 printf("Redirecting back to /login because of incorrect credentials\n"); | ||||
|                 // class will be successfully ignored
 | ||||
|                 return R200_pres_uinfo_msges("login", wgd, config_presentation, userinfo, {{"", | ||||
|                     config_presentation["phr"]["decl"]["incorrect-nickname-or-password"].g().asString()}}); | ||||
|                 json::JSON msg_list = jsonify_html_message_list({{"", | ||||
|                     config_presentation["phr"]["decl"]["incorrect-nickname-or-password"].asString()}}); | ||||
|                 return http_R200("login", wgd, {&config_presentation, &userinfo, &msg_list}); | ||||
|             } | ||||
|             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_303_spec_head("/", response_hlines); | ||||
|         } | ||||
|         return R200_pres_uinfo_msges("login", wgd, config_presentation, userinfo, {}); | ||||
|         if (req.method != "GET") | ||||
|             een9_THROW("Bad method"); | ||||
|         json::JSON empty_msg_list = json::JSON(json::array); | ||||
|         return http_R200("login", wgd, {&config_presentation, &userinfo, &empty_msg_list}); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -1,8 +1,111 @@ | ||||
| #include "client_server_interact.h" | ||||
| #include <engine_engine_number_9/form_data_structure/urlencoded_query.h> | ||||
| #include <engine_engine_number_9/baza_throw.h> | ||||
| #include <string.h> | ||||
| #include "../str_fields.h" | ||||
| #include "../login_cookie.h" | ||||
| 
 | ||||
| namespace iu9cawebchat { | ||||
|     std::string get_user_bio(SqliteConnection& conn, int64_t userId) { | ||||
|         een9_ASSERT(userId >= 0, "Are you crazy?"); | ||||
|         SqliteStatement sql_req(conn, "SELECT `bio` FROM `user` WHERE `id` = ?1", {{1, userId}}, {}); | ||||
|         fsql_text8_or_null bio_col; | ||||
|         int status = sqlite_stmt_step(sql_req, {}, {{0, &bio_col}}); | ||||
|         if (status == SQLITE_ROW) | ||||
|             return bio_col.value; | ||||
|         een9_THROW("No such user"); | ||||
|     } | ||||
| 
 | ||||
|     static const char* pr_path = "/user/"; | ||||
| 
 | ||||
|     bool is_page_of_certain_user(const std::string& path) { | ||||
|         if (!een9::beginsWith(path, pr_path)) | ||||
|             return false; | ||||
|         std::string r = path.substr(strlen(pr_path)); | ||||
|         return check_nickname(r); | ||||
|     } | ||||
| 
 | ||||
|     std::string obtain_nickname_in_user_page_path(const std::string& path) { | ||||
|         return path.substr(strlen(pr_path)); | ||||
|     } | ||||
| 
 | ||||
|     json::JSON user_row_to_userprofile_obj(SqliteConnection& conn, const RowUser_Content& alien) { | ||||
|         return json::JSON(json::jdict{ | ||||
|             {"name", json::JSON(alien.name)}, | ||||
|             {"nickname", json::JSON(alien.nickname)}, | ||||
|             {"bio", json::JSON(get_user_bio(conn, alien.id))} | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     std::string when_page_user(WorkerGuestData& wgd, const json::JSON& config_presentation, | ||||
|                 const een9::ClientRequest& req, const json::JSON& userinfo) { | ||||
|         return R200_pres_uinfo("profile", config_presentation, wgd, userinfo); | ||||
|                 const een9::ClientRequest& req, const std::vector<LoginCookie>& login_cookies, const json::JSON& userinfo) { | ||||
|         SqliteConnection& conn = *wgd.db; | ||||
|         if (!is_page_of_certain_user(req.uri_path)) | ||||
|             return page_E404(wgd); | ||||
|         std::string alien_nickname = obtain_nickname_in_user_page_path(req.uri_path); | ||||
|         RowUser_Content alien; | ||||
|         try { | ||||
|             alien = lookup_user_content_by_nickname(conn, alien_nickname); | ||||
|         } catch (const std::exception& e) { | ||||
|             return page_E404(wgd); | ||||
|         } | ||||
|         // todo: in libjsonincpp: fix '999999 problem'
 | ||||
|         int64_t myuid = userinfo["uid"].asInteger().get_int(); | ||||
|         bool can_edit = (alien.id == myuid); | ||||
|         if (req.method == "POST") { | ||||
|             std::vector<std::pair<std::string, std::string>> response_hlines; | ||||
|             try { | ||||
|                 if (!can_edit) | ||||
|                     een9_THROW("Unauthorized access"); | ||||
|                 std::vector<std::pair<std::string, std::string>> query = een9::split_html_query(req.body); | ||||
|                 // Profile update processing
 | ||||
|                 std::string bio; | ||||
|                 std::string name; | ||||
|                 std::string password; | ||||
|                 for (const std::pair<std::string, std::string>& p: query) { | ||||
|                     if (p.first == "bio") | ||||
|                         bio = p.second; | ||||
|                     else if (p.first == "name") | ||||
|                         name = p.second; | ||||
|                     else if (p.first == "password") | ||||
|                         password = p.second; | ||||
|                 } | ||||
|                 if (!bio.empty()) { | ||||
|                     if (!is_orthodox_string(bio) || bio.size() > 100000) | ||||
|                         een9_THROW("Incorrect `bio`"); | ||||
|                     sqlite_nooutput(conn, "UPDATE `user` SET `bio` = ?1", {}, {{1, bio}}); | ||||
|                 } | ||||
|                 if (!name.empty()) { | ||||
|                     if (!check_name(name)) | ||||
|                         een9_THROW("Incorrect `name`"); | ||||
|                     sqlite_nooutput(conn, "UPDATE `user` SET `name` = ?1", {}, {{1, name}}); | ||||
|                 } | ||||
|                 if (!password.empty()) { | ||||
|                     if (!check_strong_password(password)) | ||||
|                         een9_THROW("Incorrect `password`"); | ||||
|                     sqlite_nooutput(conn, "UPDATE `user` SET `password` = ?1", {}, {{1, password}}); | ||||
|                     if (alien.id == myuid) { | ||||
|                         LoginCookie new_login_cookie = create_login_cookie(userinfo["nickname"].asString(), password); | ||||
|                         add_set_cookie_headers_to_login(login_cookies, response_hlines, new_login_cookie); | ||||
|                     } | ||||
|                 } | ||||
|             } catch (const std::exception& e) { | ||||
|                 printf("Redirecting back to /user/... because of incorrect credentials\n"); | ||||
|                 json::JSON msg_list = jsonify_html_message_list({{"", | ||||
|                     config_presentation["phr"]["decl"]["incorrect-profile-data"].asString()}}); | ||||
|                 json::JSON alien_userprofile = user_row_to_userprofile_obj(conn, alien); | ||||
|                 return http_R200("edit-profile", wgd, {&config_presentation, &alien_userprofile, &msg_list}); | ||||
|             } | ||||
|             return een9::form_http_server_response_303_spec_head("/user/" + alien_nickname, response_hlines); | ||||
|         } | ||||
|         if (req.method == "GET") { | ||||
|             json::JSON alien_userprofile = user_row_to_userprofile_obj(conn, alien); | ||||
|             if (can_edit) { | ||||
|                 json::JSON empty_msg_list = jsonify_html_message_list({}); | ||||
|                 return http_R200("edit-profile", wgd, {&config_presentation, &alien_userprofile, &empty_msg_list}); | ||||
|             } | ||||
|             return http_R200("view-profile", wgd, {&config_presentation, &alien_userprofile}); | ||||
|         } | ||||
|         een9_THROW("Bad method"); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -2,15 +2,17 @@ | ||||
| 
 | ||||
| namespace iu9cawebchat{ | ||||
|     int find_db_sqlite_file_path(const json::JSON& config, std::string& res_path) { | ||||
|         const json::JSON& type = config["database"]["type"].g(); | ||||
|         if (!type.isString() && type.asString() == "sqlite3") | ||||
|         try { | ||||
|             const std::string& type = config["database"]["type"].asString(); | ||||
|             if (type != "sqlite3") | ||||
|                 return -1; | ||||
|             const std::string& path = config["database"]["file"].asString(); | ||||
|             if (path.empty() || path[0] == ':') | ||||
|                 return -1; | ||||
|             res_path = path; | ||||
|         } catch (const json::misuse& e) { | ||||
|             return -1; | ||||
|         const json::JSON& path = config["database"]["file"].g(); | ||||
|         if (!path.isString()) | ||||
|             return -1; | ||||
|         if (path.asString().empty() || path.asString()[0] == ':') | ||||
|             return -1; | ||||
|         res_path = path.asString(); | ||||
|         } | ||||
|         return 0; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -44,36 +44,38 @@ namespace iu9cawebchat { | ||||
|                                              "`nickname` TEXT UNIQUE REFERENCES `nickname` NOT NULL," | ||||
|                                              "`name` TEXT NOT NULL," | ||||
|                                              "`chatList_HistoryId` INTEGER NOT NULL," | ||||
|                                              "`password` TEXT NOT NULL" | ||||
|                                              "`password` TEXT NOT NULL," | ||||
|                                              "`bio` 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`" | ||||
|                                              "`lastMsgId` INTEGER NOT NULL" | ||||
|                                              ")"); | ||||
|             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" | ||||
|                                              "`role` INTEGER NOT NULL," | ||||
|                                              "UNIQUE (`userId`, `chatId`)" | ||||
|                                              ")"); | ||||
|             sqlite_nooutput(conn, "CREATE TABLE `message` (" | ||||
|                                              "`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL," | ||||
|                                              "`chatId` INTEGER REFERENCES `chat` NOT NULL," | ||||
|                                              "`previous` INTEGER REFERENCES `message`," | ||||
|                                              "`id` INTEGER NOT NULL," | ||||
|                                              "`senderUserId` INTEGER REFERENCES `user`," | ||||
|                                              "`exists` BOOLEAN NOT NULL," | ||||
|                                              "`isSystem` BOOLEAN NOT NULL," | ||||
|                                              "`text` TEXT NOT NULL," | ||||
|                                              "`chat_IncHistoryId` INTEGER NOT NULL" | ||||
|                                              "`text` TEXT," | ||||
|                                              "`chat_IncHistoryId` INTEGER NOT NULL," | ||||
|                                              "PRIMARY KEY (`chatId`, `id`)" | ||||
|                                              ")"); | ||||
|             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, "INSERT INTO `user` (`id`, `nickname`, `name`, `chatList_HistoryId`, `password`, " | ||||
|                                   "`bio` ) VALUES (0, ?1, ?2, 0, ?3, ?4)", {}, | ||||
|                     {{1, "root"}, {2, "Rootov Root Rootovich"}, {3, root_pw}, {4, "One admin to rule them all"}}); | ||||
|             sqlite_nooutput(conn, "END"); | ||||
|         } catch (const std::exception& e) { | ||||
|             sqlite_nooutput(conn, "ROLLBACK", {}, {}); | ||||
|  | ||||
| @ -23,8 +23,8 @@ namespace iu9cawebchat { | ||||
|         uint64_t nsec = std::stoull(ft.substr(s_ + 1, ft.size() - s_ - 1)); | ||||
|         een9_ASSERT_pl(nsec < 1000000000); | ||||
|         const json::JSON cnt = json::parse_str_flawless(base64_decode(login_cookie_encoded.second)); | ||||
|         std::string nickname = cnt[0].g().asString(); | ||||
|         std::string password = cnt[1].g().asString(); | ||||
|         std::string nickname = cnt[0].asString(); | ||||
|         std::string password = cnt[1].asString(); | ||||
|         return LoginCookie{{(time_t)sec, (time_t)nsec}, nickname, password}; | ||||
|     } | ||||
| 
 | ||||
| @ -37,8 +37,8 @@ namespace iu9cawebchat { | ||||
| 
 | ||||
|     std::pair<std::string, std::string> encode_login_cookie(const LoginCookie& cookie) { | ||||
|             json::JSON cnt; | ||||
|             cnt[1].g() = cookie.password; | ||||
|             cnt[0].g() = cookie.nickname; | ||||
|             cnt[1].asString() = cookie.password; | ||||
|             cnt[0].asString() = cookie.nickname; | ||||
|         return {"login_" + std::to_string(cookie.login_time.tv_sec) + "_" + std::to_string(cookie.login_time.tv_nsec), | ||||
|             base64_encode(json::generate_str(cnt, json::print_compact))}; | ||||
|     } | ||||
|  | ||||
| @ -16,9 +16,25 @@ namespace iu9cawebchat { | ||||
|         termination = true; | ||||
|     } | ||||
| 
 | ||||
|     struct ONE_SQLITE_TRANSACTION_GUARD { | ||||
|         SqliteConnection& conn; | ||||
|         bool rollback = false; | ||||
| 
 | ||||
|         explicit ONE_SQLITE_TRANSACTION_GUARD(SqliteConnection& conn_) : conn(conn_) { | ||||
|             sqlite_nooutput(conn, "BEGIN", {}, {}); | ||||
|         } | ||||
| 
 | ||||
|         ~ONE_SQLITE_TRANSACTION_GUARD() { | ||||
|             if (rollback) | ||||
|                 sqlite_nooutput(conn, "ROLLBACK", {}, {}); | ||||
|             else | ||||
|                 sqlite_nooutput(conn, "END", {}, {}); | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     void run_website(const json::JSON& config) { | ||||
|         een9_ASSERT(config["assets"].g().isString(), "config[\"assets\"] is not string"); | ||||
|         std::string assets_dir = config["assets"].g().asString(); | ||||
|         een9_ASSERT(config["assets"].isString(), "config[\"assets\"] is not string"); | ||||
|         const std::string& assets_dir = config["assets"].asString(); | ||||
|         een9_ASSERT(een9::isDirectory(assets_dir), "\"" + assets_dir + "\" is not a directory"); | ||||
| 
 | ||||
|         een9::StaticAssetManagerSlaveModule samI; | ||||
| @ -30,8 +46,8 @@ namespace iu9cawebchat { | ||||
|             } }, | ||||
|         }); | ||||
| 
 | ||||
|         const json::JSON& config_presentation = config["presentation"].g(); | ||||
|         int64_t slave_number = config["server"]["workers"].g().asInteger().get_int(); | ||||
|         const json::JSON& config_presentation = config["presentation"]; | ||||
|         int64_t slave_number = config["server"]["workers"].asInteger().get_int(); | ||||
|         een9_ASSERT(slave_number > 0 && slave_number <= 200, "E"); | ||||
| 
 | ||||
|         std::string sqlite_db_path; | ||||
| @ -52,13 +68,7 @@ namespace iu9cawebchat { | ||||
|             een9_ASSERT_pl(0 <= worker_id && worker_id < worker_guest_data.size()); | ||||
|             WorkerGuestData& wgd = worker_guest_data[worker_id]; | ||||
|             een9::StaticAsset sa; | ||||
|             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}; | ||||
|             ONE_SQLITE_TRANSACTION_GUARD conn_guard(*wgd.db); | ||||
|             try { | ||||
|                 std::vector<std::pair<std::string, std::string>> cookies; | ||||
|                 std::vector<LoginCookie> login_cookies; | ||||
| @ -72,53 +82,42 @@ namespace iu9cawebchat { | ||||
|                 if (req.uri_path == "/login") { | ||||
|                     return when_page_login(wgd, config_presentation, req, login_cookies, userinfo); | ||||
|                 } | ||||
|                 if (een9::beginsWith(req.uri_path, "/chat")) { | ||||
|                 // todo: split
 | ||||
|                 if (een9::beginsWith(req.uri_path, "/chat/") || een9::beginsWith(req.uri_path, "/chat-members/")) { | ||||
|                     return when_page_chat(wgd, config_presentation, req, userinfo); | ||||
|                 } | ||||
|                 if (req.uri_path == "/user") { | ||||
|                     return when_page_user(wgd, config_presentation, req, userinfo); | ||||
|                 if (een9::beginsWith(req.uri_path, "/user/")) { | ||||
|                     return when_page_user(wgd, config_presentation, req, login_cookies, userinfo); | ||||
|                 } | ||||
|                 if (req.uri_path == "/internalapi/pollEvents") { | ||||
|                     return when_internalapi_pollevents(wgd, req, logged_in_user); | ||||
|                 if (req.uri_path == "/api/chatPollEvents") { | ||||
|                     return when_internalapi_chatpollevents(wgd, req, logged_in_user); | ||||
|                 } | ||||
|                 if (req.uri_path == "/internalapi/getChatList") { | ||||
|                     return  when_internalapi_getchatlist(wgd, req, logged_in_user); | ||||
|                 if (req.uri_path == "/api/chatListPollEvents") { | ||||
|                     return  when_internalapi_chatlistpollevents(wgd, req, logged_in_user); | ||||
|                 } | ||||
|                 if (req.uri_path == "/internalapi/getChatInfo") { | ||||
|                     return when_internalapi_getchatinfo(wgd, req, logged_in_user); | ||||
|                 } | ||||
|                 if (req.uri_path == "/internalapi/getChatMemberList") { | ||||
|                     return when_internalapi_getchatmemberlist(wgd, req, logged_in_user); | ||||
|                 } | ||||
|                 if (req.uri_path == "/internalapi/getUserInfo") { | ||||
|                     return when_internalapi_getuserinfo(wgd, req, logged_in_user); | ||||
|                 } | ||||
|                 if (req.uri_path == "/internalapi/getMessageInfo") { | ||||
|                     return when_internalapi_getmessageinfo(wgd, req, logged_in_user); | ||||
|                 } | ||||
|                 if (req.uri_path == "/internalapi/getMessageNeighbours") { | ||||
|                 if (req.uri_path == "/api/getMessageNeighbours") { | ||||
|                     return when_internalapi_getmessageneighbours(wgd, req, logged_in_user); | ||||
|                 } | ||||
|                 if (req.uri_path == "/internalapi/sendMessage") { | ||||
|                 if (req.uri_path == "/api/sendMessage") { | ||||
|                     return when_internalapi_sendmessage(wgd, req, logged_in_user); | ||||
|                 } | ||||
|                 if (req.uri_path == "/internalapi/deleteMessage") { | ||||
|                 if (req.uri_path == "/api/deleteMessage") { | ||||
|                     return when_internalapi_deletemessage(wgd, req, logged_in_user); | ||||
|                 } | ||||
|                 if (req.uri_path == "/internalapi/addMemberToChat") { | ||||
|                 if (req.uri_path == "/api/addMemberToChat") { | ||||
|                     return when_internalapi_addmembertochat(wgd, req, logged_in_user); | ||||
|                 } | ||||
|                 if (req.uri_path == "/internalapi/removeMemberFromChat") { | ||||
|                 if (req.uri_path == "/api/removeMemberFromChat") { | ||||
|                     return when_internalapi_removememberfromchat(wgd, req, logged_in_user); | ||||
|                 } | ||||
|                 if (req.uri_path == "/internalapi/createChat") { | ||||
|                 if (req.uri_path == "/api/createChat") { | ||||
|                     return when_internalapi_createchat(wgd, req, logged_in_user); | ||||
|                 } | ||||
|                 if (req.uri_path == "/internalapi/removeChat") { | ||||
|                     return when_internalapi_removechat(wgd, req, logged_in_user); | ||||
|                 if (req.uri_path == "/api/leaveChat") { | ||||
|                     return when_internalapi_leavechat(wgd, req, logged_in_user); | ||||
|                 } | ||||
|             } catch (const std::exception& e) { | ||||
|                 guard_.rollback = true; | ||||
|                 conn_guard.rollback = true; | ||||
|                 throw; | ||||
|             } | ||||
|             /* Trying to interpret request as asset lookup */ | ||||
| @ -133,29 +132,11 @@ namespace iu9cawebchat { | ||||
|         (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]; | ||||
|             ONE_SQLITE_TRANSACTION_GUARD conn_guad(*wgd.db); | ||||
|             try { | ||||
|                 if (req == "hello") { | ||||
|                     return ":0 omg! hiii!! Hewwou :3 !!!!\n"; | ||||
|                 } | ||||
|                 if (req == "8") { | ||||
|                     termination = true; | ||||
|                     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\n"; | ||||
|                     std::string new_password = req.substr(nid + 1); | ||||
|                     if (!check_password(new_password)) | ||||
|                         een9_THROW("Bad password"); | ||||
|                     sqlite_nooutput(*wgd.db, | ||||
|                         "UPDATE `user` SET `password` = ?1 WHERE `id` = 0 ", | ||||
|                         {}, {{1, new_password}}); | ||||
|                     return "Successul update\n"; | ||||
|                 } | ||||
|                 return "Incorrect command\n"; | ||||
|                 return admin_control_procedure(*wgd.db, req, termination); | ||||
|             } catch (std::exception& e) { | ||||
|                 conn_guad.rollback = true; | ||||
|                 return std::string("Server error\n") + e.what(); | ||||
|             } | ||||
|         }; | ||||
| @ -171,8 +152,8 @@ namespace iu9cawebchat { | ||||
|                 een9_ASSERT(ret == 0, "Incorrect ear address: " + source[i].asString()); | ||||
|             } | ||||
|         }; | ||||
|         translate_addr_list_conf(params.client_regular_listened, config["server"]["http-listen"].g().asArray()); | ||||
|         translate_addr_list_conf(params.admin_control_listened, config["server"]["admin-command-listen"].g().asArray()); | ||||
|         translate_addr_list_conf(params.client_regular_listened, config["server"]["http-listen"].asArray()); | ||||
|         translate_addr_list_conf(params.admin_control_listened, config["server"]["admin-command-listen"].asArray()); | ||||
| 
 | ||||
|         signal(SIGINT, sigterm_action); | ||||
|         signal(SIGTERM, sigterm_action); | ||||
|  | ||||
| @ -28,11 +28,15 @@ namespace iu9cawebchat { | ||||
|     } | ||||
| 
 | ||||
|     bool check_password(const std::string &pwd) { | ||||
|         return is_orthodox_string(pwd) && pwd.size() >= 8; | ||||
|         return is_orthodox_string(pwd) && pwd.size() <= 150; | ||||
|     } | ||||
| 
 | ||||
|     bool check_strong_password(const std::string& pwd) { | ||||
|         return check_password(pwd) && pwd.size() >= 8; | ||||
|     } | ||||
| 
 | ||||
|     bool check_name(const std::string &name) { | ||||
|         return is_orthodox_string(name); | ||||
|         return is_orthodox_string(name) && name.size() <= 150; | ||||
|     } | ||||
| 
 | ||||
|     bool check_nickname(const std::string &nickname) { | ||||
| @ -42,7 +46,7 @@ namespace iu9cawebchat { | ||||
|             if (!isUNCHAR(ch)) | ||||
|                 return false; | ||||
|         } | ||||
|         return true; | ||||
|         return nickname.size() <= 150; | ||||
|     } | ||||
| 
 | ||||
|     /* Yeah baby, it's base64 time!!! */ | ||||
|  | ||||
| @ -13,6 +13,7 @@ namespace iu9cawebchat { | ||||
|     bool is_orthodox_string(const std::string& str); | ||||
| 
 | ||||
|     bool check_password(const std::string& pwd); | ||||
|     bool check_strong_password(const std::string& pwd); | ||||
|     bool check_name(const std::string& name); | ||||
|     bool check_nickname(const std::string& nickname); | ||||
| 
 | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user