master #6
							
								
								
									
										40
									
								
								README.md
									
									
									
									
									
								
							
							
						
						| @ -38,12 +38,12 @@ regexis024_build_system.sh | |||||||
| 
 | 
 | ||||||
| Помимо самого бинарника нужен файл с настройками сервиса. Формат настроек: JSON. | Помимо самого бинарника нужен файл с настройками сервиса. Формат настроек: JSON. | ||||||
| Комментарии не поддерживаются. Пример такого файла находится в example/config.json. | Комментарии не поддерживаются. Пример такого файла находится в example/config.json. | ||||||
| Вместе с бинарным фалом так же распространяются ассеты, необъходимые для работы сайта. | Вместе с бинарным фалом так же распространяются ассеты, необходимые для работы сайта. | ||||||
| Их можно найти в папке assets. В настроках (поле `["assets"]`) указывается путь до | Их можно найти в папке assets. В настроках (поле `config.assets`) указывается путь до | ||||||
| папки с ассетами. Путь может быть как абсолютным, так и относительным к рабочей директории. | папки с ассетами. Путь может быть как абсолютным, так и относительным к рабочей директории. | ||||||
| Поле настроек `["database"]` указывает как соединиться с базой данных. | Поле настроек `config.database` указывает как соединиться с базой данных. | ||||||
| Поддерживается только база данных sqlite. Поддерживается только хранение в файле. | Поддерживается только база данных sqlite3. Поддерживается только хранение в файле. | ||||||
| Поле `["database"]["file"]` указывает путь где хранится sqlite база данных. | Поле `config.database.file` указывает путь где хранится sqlite база данных. | ||||||
| 
 | 
 | ||||||
| Перед тем как использовать сервис нужно его проинициализировать (а точнее проинициализировать | Перед тем как использовать сервис нужно его проинициализировать (а точнее проинициализировать | ||||||
| базу данных): | базу данных): | ||||||
| @ -60,6 +60,36 @@ regexis024_build_system.sh | |||||||
| Утилита `iu9-ca-web-chat-admin-cli` позволяет администратору сервиса контролировать его через сокет | Утилита `iu9-ca-web-chat-admin-cli` позволяет администратору сервиса контролировать его через сокет | ||||||
| (адрес указан в `config["server"]["admin-command-listen"]`). | (адрес указан в `config["server"]["admin-command-listen"]`). | ||||||
| 
 | 
 | ||||||
|  | По адресам `config.server.admin-command-listen` идёт прослушивание так называемых "команд администратора". | ||||||
|  | iu9cawebchat определяет свой простой протокол для передачи этих команд. | ||||||
|  | Утилита iu9-ca-web-chat-admin-cli может отправить текст с некой командой на сервер на этот адрес и получить | ||||||
|  | ответ от сервера. | ||||||
|  | 
 | ||||||
|  | ```shell | ||||||
|  | iu9-ca-web-chat-admin-cli <server admin-control address> <command text> [<command text> ...] | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | Дополнительные параметры конкатенируются, разделяясь переводом строки. | ||||||
|  | Команды администратора: | ||||||
|  | 
 | ||||||
|  | `updateroopw <new root password>` - сменить пароль пользователя с номером 0 | ||||||
|  | 
 | ||||||
|  | `adduser <user nickname> <user name> <user password> <user bio>` - зарегистрировать пользователя сайта  | ||||||
|  | 
 | ||||||
|  | `8` - остановить сервис | ||||||
|  | 
 | ||||||
|  | Если нужно ввести пробел или символ `\ ` в любое из этих полей, перед ними нужно поставить `\ `; | ||||||
|  | Если указать меньше полей, чем нужно, незаполненные поля станут пустыми строками. | ||||||
|  | 
 | ||||||
|  | Параметры конфигурации `config.lang.whitelist` и `config.lang.force-order` определяют на | ||||||
|  | какие языки будет локализован сервер, и какие переводы приоритетнее каких. | ||||||
|  | На данный момент поддерживаются | ||||||
|  |  - `ru-RU`  | ||||||
|  |  - `en-US` | ||||||
|  | 
 | ||||||
|  | Все переводы хранятся в папке `assets/lang`. Для добавления своего перевода нужно форкнуть репозиторий и | ||||||
|  | сделать копию файла `assets/lang/ru-RU.lang.json` в `assets/lang/XXXXX.lang.json`. | ||||||
|  | 
 | ||||||
| # Список участников | # Список участников | ||||||
| 
 | 
 | ||||||
| 1. [Китанин Фёдор](https://gitflic.ru/user/fed-kit) | 1. [Китанин Фёдор](https://gitflic.ru/user/fed-kit) | ||||||
|  | |||||||
							
								
								
									
										61
									
								
								assets/HypertextPages/chat-members.nytl.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,61 @@ | |||||||
|  | {% 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"> | ||||||
|  |     <link rel="icon" type="image/png" href="/assets/img/favicon.png"> | ||||||
|  |     <link rel="stylesheet" href="/assets/css/common.css"> | ||||||
|  |     <link rel="stylesheet" href="/assets/css/common-popup.css"> | ||||||
|  |     <link rel="stylesheet" href="/assets/css/chat-members.css"> | ||||||
|  |     <title>{%w pres.chat-members.members-of %} {%w openedchat.name %}</title> | ||||||
|  | </head> | ||||||
|  | <body> | ||||||
|  |     {% PUT chat.pass pres userinfo openedchat initial_chatUpdResp %} | ||||||
|  | 
 | ||||||
|  |     <div id="user-summoning-win" class="popup-window"> | ||||||
|  |         <h1 class="popup-window-msg">{%w pres.chat-members.summon-label-nickname %}</h1> | ||||||
|  |         <input class="one-line-input" id="summoned-user-nickname"> | ||||||
|  |         <input type="checkbox" id="summoned-user-is-read-only"> | ||||||
|  |         <label>{%w pres.chat-members.summon-label-ro %}</label><br> | ||||||
|  |         <button class="popup-window-btn-yes" id="user-summoning-yes">{%w pres.chat-members.yes-summon %}</button> | ||||||
|  |         <button class="popup-window-btn-no" id="user-summoning-no">{%w pres.chat-members.no-summon %}</button> | ||||||
|  |     </div> | ||||||
|  | 
 | ||||||
|  |     <div id="user-deletion-win" class="popup-window"> | ||||||
|  |         <!-- header will actually be rewritten before showing the window to include user nickname --> | ||||||
|  |         <h1 id="user-deletion-win-title" class="popup-window-msg"> ||||||||| </h1> | ||||||
|  |         <button class="popup-window-btn-yes" id="user-deletion-yes">{%w pres.chat-members.yes-kick %}</button> | ||||||
|  |         <button class="popup-window-btn-no" id="user-deletion-no">{%w pres.chat-members.no-kick %}</button> | ||||||
|  |     </div> | ||||||
|  | 
 | ||||||
|  |     <div class="document-container resp-container"> | ||||||
|  |         <div id="navigation-panel" class="panel"> | ||||||
|  |             <a href="/list-rooms" id="go-to-chat-list" class="panel-thing"> | ||||||
|  |                 <img alt="Go to list of chats" src="/assets/img/list-rooms.svg" width="32px"> | ||||||
|  |             </a> | ||||||
|  |             <a href="/user/{% W userinfo.nickname %}" id="go-to-my-profile" class="panel-thing"> | ||||||
|  |                 <img alt="Go to my profile" src="/assets/img/user.svg" width="32px"> | ||||||
|  |             </a> | ||||||
|  |             <p class="panel-thing panel-header-txt"> | ||||||
|  |                 {%w pres.chat-members.members-list-of %} {% W openedchat.name %} ({% W openedchat.nickname %}) | ||||||
|  |             </p> | ||||||
|  |             <a href="/chat/{% W openedchat.nickname %}" id="go-to-chat-settings" class="panel-thing"> | ||||||
|  |                 <img alt="Back to chat" src="/assets/img/return.svg" width="32px"> | ||||||
|  |             </a> | ||||||
|  |         </div> | ||||||
|  | 
 | ||||||
|  |         <div class="dynamic-block-list"> | ||||||
|  |             <img id="CM-btn-add" class="button-add centered-block-el" alt="New chat" src="/assets/img/add.svg"> | ||||||
|  |             <div class="dynamic-block-list-el-container" id="CM-list"> | ||||||
|  | 
 | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |     </div> | ||||||
|  | 
 | ||||||
|  |     <script src="/assets/js/common.js"></script> | ||||||
|  |     <script src="/assets/js/common-popup.js"></script> | ||||||
|  |     <script src="/assets/js/chat-members.js"></script> | ||||||
|  | </body> | ||||||
|  | </html> | ||||||
|  | {% ENDELDEF %} | ||||||
| @ -1,40 +1,68 @@ | |||||||
| {% 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> | <!DOCTYPE html> | ||||||
| <html lang="ru"> | <html lang="en"> | ||||||
| <head> | <head> | ||||||
|     <meta charset="UTF-8"> |     <meta charset="UTF-8"> | ||||||
|     <meta name="viewport" content="width=device-width, initial-scale=1.0"> |     <meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||||||
|     <title>Веб-Чат</title> |     <link rel="icon" type="image/png" href="/assets/img/favicon.png"> | ||||||
|  |     <link rel="stylesheet" href="/assets/css/debug.css"> | ||||||
|  |     <link rel="stylesheet" href="/assets/css/common.css"> | ||||||
|  |     <link rel="stylesheet" href="/assets/css/common-popup.css"> | ||||||
|     <link rel="stylesheet" href="/assets/css/chat.css"> |     <link rel="stylesheet" href="/assets/css/chat.css"> | ||||||
|  |     <title>{%w pres.chat.header-chat %} {%w openedchat.name %}</title> | ||||||
| </head> | </head> | ||||||
| <body> | <body> | ||||||
| <div class="chat-container"> |     {% PUT chat.pass pres userinfo openedchat initial_chatUpdResp %} | ||||||
|     <div class="chat-header"> | 
 | ||||||
|         <span class="room-name">Веб чат</span> |     <div id="msg-deletion-win" class="popup-window"> | ||||||
|         <button class="members" onclick="openMembersList()">Показать участников</button> |         <h1 class="popup-window-msg">{%w pres.chat.reask-delete-message %}</h1> | ||||||
|  |         <!-- mesage preview will be actually rewritten before each window activation--> | ||||||
|  |         <p class="message-in-popup-preview" id="win-deletion-msg-preview">|||||||||</p> | ||||||
|  |         <button class="popup-window-btn-yes" id="msg-deletion-yes">{%w pres.chat.yes-delete %}</button> | ||||||
|  |         <button class="popup-window-btn-no" id="msg-deletion-no">{%w pres.chat.no-delete %}</button> | ||||||
|     </div> |     </div> | ||||||
|     <div class="chat-messages" id="chat-messages"> | 
 | ||||||
|         <!-- Сообщения чата будут здесь --> |     <div class="fullscreen-container resp-container"> | ||||||
|     </div> |         <div class="panel" id="navigation-info-panel"> | ||||||
|     <div class="chat-footer"> |             <a href="/list-rooms" id="go-to-chat-list" class="panel-thing"> | ||||||
|         <input type="text" class="chat-input" id="chat-input" placeholder="Введите сообщение..."> |                 <img alt="Go to list of chats" src="/assets/img/list-rooms.svg" width="32px"> | ||||||
|         <button class="chat-send-button" onclick="sendMessage()">Отправить</button> |             </a> | ||||||
|     </div> |             <a href="/user/{% W userinfo.nickname %}" id="go-to-my-profile" class="panel-thing"> | ||||||
| </div> |                 <img alt="Go to my profile" src="/assets/img/user.svg" width="32px"> | ||||||
| <div class="overlay" id="overlay"> |             </a> | ||||||
|     <div class="members-list" id="members-list"> |             <p class="panel-thing panel-header-txt"> {% W openedchat.name %} ({% W openedchat.nickname %})</p> | ||||||
|         <div class="members-list-header"> |             <a href="/chat-members/{% W openedchat.nickname %}" id="go-to-chat-settings" class="panel-thing"> | ||||||
|             <span class="close" onclick="closeMembersList()">×</span> |                 <img alt="Settings of chat. List of members" src="/assets/img/settings-iron.svg" width="32px"> | ||||||
|             <h2 class="all-members">Все участники</h2> |             </a> | ||||||
|         </div> |         </div> | ||||||
|         <div class="members-list-body"> |         <div id="chat-widget"> | ||||||
|             <ul id="members-list-body"> |             <div class="chat-debug-rect" id="debug-line-highest" style="background-color: #8600d3"></div> | ||||||
|                 <!-- Список участников будет добавлен динамически --> |             <div class="chat-debug-rect" id="debug-line-top-padding" style="background-color: #ff00ae"></div> | ||||||
|             </ul> |             <div class="chat-debug-rect" id="debug-line-bottom-padding" style="background-color: #ff0062"></div> | ||||||
|  |             <div class="chat-debug-rect" id="debug-line-lowest" style="background-color: #ff2f00"></div> | ||||||
|  |             <div id="top-loading" class="message-supercontainer"> | ||||||
|  |                 <img class="loading-spinner" alt="Loading backward..." src="/assets/gif/loading.gif"> | ||||||
|  |             </div> | ||||||
|  |             <div id="bottom-loading" class="message-supercontainer"> | ||||||
|  |                 <img class="loading-spinner" alt="Loading forward..." src="/assets/gif/loading.gif"> | ||||||
|  |             </div> | ||||||
|         </div> |         </div> | ||||||
|  |         <div class="panel" id="input-panel"> | ||||||
|  |             <div contentEditable id="message-input" class="panel-thing"></div> | ||||||
|  |         </div> | ||||||
|  |         <script src="/assets/js/common.js"></script> | ||||||
|  |         <script src="/assets/js/common-popup.js"></script> | ||||||
|  |         <script src="/assets/js/chat.js"></script> | ||||||
|     </div> |     </div> | ||||||
| </div> |  | ||||||
| <script src="/assets/js/chat.js"></script> |  | ||||||
| </body> | </body> | ||||||
| </html> | </html> | ||||||
| {% ENDELDEF %} | {% ENDELDEF %} | ||||||
|  | |||||||
| @ -1,46 +0,0 @@ | |||||||
| {% ELDEF main JSON pres JSON userinfo %} |  | ||||||
| <!DOCTYPE html> |  | ||||||
| <html lang="en"> |  | ||||||
| <head> |  | ||||||
|     <meta charset="UTF-8"> |  | ||||||
|     <meta name="viewport" content="width=device-width, initial-scale=1.0"> |  | ||||||
|     <link rel="stylesheet" href="/assets/css/chatSettings.css"> |  | ||||||
|     <title>Настройки комнаты</title> |  | ||||||
| </head> |  | ||||||
| <body> |  | ||||||
| <div class="chat-settings-container"> |  | ||||||
|     <div class="chat-settings-container-header"> |  | ||||||
|         <input class="room-name" id="room-name" placeholder="Введите название комнаты..." value="Название комнаты"> |  | ||||||
|         <button class="changeName" onclick="handleChangeName()">Изменить название</button> |  | ||||||
|     </div> |  | ||||||
|     <div class="chat-settings-container-body"> |  | ||||||
|         <ul id="chat-settings-container-body"> |  | ||||||
|             <!-- Пример списка участников --> |  | ||||||
|             <li id="member-1">Участник 1<button class="remove-member-button" onclick="handleRemoveMember(1)">Удалить</button></li> |  | ||||||
|             <li id="member-2">Участник 2<button class="remove-member-button" onclick="handleRemoveMember(2)">Удалить</button></li> |  | ||||||
|             <li id="member-3">Участник 3<button class="remove-member-button" onclick="handleRemoveMember(3)">Удалить</button></li> |  | ||||||
|         </ul> |  | ||||||
|     </div> |  | ||||||
|     <div class="chat-settings-container-invite"> |  | ||||||
|         <button class="invite-member" onclick="openInvite()">Добавить участника</button> |  | ||||||
|     </div> |  | ||||||
| </div> |  | ||||||
| 
 |  | ||||||
| <div class="overlay" id="add_members"> |  | ||||||
|     <div class="add-members"> |  | ||||||
|         <div class="add-members-header"> |  | ||||||
|             <span class="close" onclick="closeAdd()">×</span> |  | ||||||
|             <h2>Добавить участников</h2> |  | ||||||
|         </div> |  | ||||||
|         <div class="add-members-body"> |  | ||||||
|             <input type="text" id="newMemberLogin" placeholder="Логин пользователя"> |  | ||||||
|         </div> |  | ||||||
|         <div class="add-members-footer"> |  | ||||||
|             <button class="add-member-button" onclick="handleAddMember()">Добавить</button> |  | ||||||
|         </div> |  | ||||||
|     </div> |  | ||||||
| </div> |  | ||||||
| <script src="/assets/js/chatSettings.js"></script> |  | ||||||
| </body> |  | ||||||
| </html> |  | ||||||
| {% ENDELDEF%} |  | ||||||
							
								
								
									
										71
									
								
								assets/HypertextPages/edit-profile.nytl.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,71 @@ | |||||||
|  | {% ELDEF main JSON pres JSON userinfo JSON alienprofile JSON errors %} | ||||||
|  | <!DOCTYPE html> | ||||||
|  | <html lang="en"> | ||||||
|  | <head> | ||||||
|  |     <meta charset="UTF-8"> | ||||||
|  |     <meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||||||
|  |     <link rel="icon" type="image/png" href="/assets/img/favicon.png"> | ||||||
|  |     <link rel="stylesheet" href="/assets/css/common.css"> | ||||||
|  |     <link rel="stylesheet" href="/assets/css/edit-profile.css"> | ||||||
|  |     <title>{%w pres.edit-profile.header-profile-of %} {%w alienprofile.name %}</title> | ||||||
|  | </head> | ||||||
|  | <body> | ||||||
|  | 
 | ||||||
|  |     <div class="document-container"> | ||||||
|  |         <div id="navigation-panel" class="panel"> | ||||||
|  |             <a href="/list-rooms" id="go-to-chat-list" class="panel-thing"> | ||||||
|  |                 <img alt="Go to list of chats" src="/assets/img/list-rooms.svg" width="32px"> | ||||||
|  |             </a> | ||||||
|  |             <a href="/user/{% W userinfo.nickname %}" id="go-to-my-profile" class="panel-thing"> | ||||||
|  |                 <img alt="Go to my profile" src="/assets/img/user.svg" width="32px"> | ||||||
|  |             </a> | ||||||
|  |         </div> | ||||||
|  | 
 | ||||||
|  |         {% FOR error IN errors %} | ||||||
|  |             <div class="server-notif-error-msg-box"> | ||||||
|  |                 {% W error.text %} | ||||||
|  |             </div> | ||||||
|  |         {% ENDFOR %} | ||||||
|  | 
 | ||||||
|  |         <div class="profile-container"> | ||||||
|  |             <h2 class="profile-name-text">{% W alienprofile.name %}</h2> | ||||||
|  |             <h3 class="profile-nickname-text">{%w pres.edit-profile.directive-nickname %} {% W alienprofile.nickname %}</h3> | ||||||
|  |             <p class="profile-bio-text"> | ||||||
|  |                 {% W alienprofile.bio %} | ||||||
|  |             </p> | ||||||
|  |         </div> | ||||||
|  | 
 | ||||||
|  |         <div class="profile-container"> | ||||||
|  |             <h1 class="wide-centered-header">{%w pres.edit-profile.change-user-attributes %}</h1> | ||||||
|  |             <form action = "/user/{% W alienprofile.nickname %}" method="post" enctype="application/x-www-form-urlencoded"> | ||||||
|  |                 <table class="logins-input-table"> | ||||||
|  |                     <tr> | ||||||
|  |                         <td class="logins-input-td1"> | ||||||
|  |                             <label for="new-name-input">{%w pres.edit-profile.directive-name %}</label> | ||||||
|  |                         </td> | ||||||
|  |                         <td class="logins-input-td2"> | ||||||
|  |                             <input name="name" id="new-name-input" type="text" | ||||||
|  |                                    placeholder="{%w pres.edit-profile.placeholder-name %}" class="one-line-input"> | ||||||
|  |                         </td> | ||||||
|  |                     </tr> | ||||||
|  |                     <tr> | ||||||
|  |                         <td class="logins-input-td1"> | ||||||
|  |                             <label for="new-password-input">{%w pres.edit-profile.directive-password %}</label> | ||||||
|  |                         </td> | ||||||
|  |                         <td class="logins-input-td2"> | ||||||
|  |                             <input name="password" id="new-password-input" type="password" | ||||||
|  |                                    placeholder="{%w pres.edit-profile.placeholder-password %}" class="one-line-input"> | ||||||
|  |                         </td> | ||||||
|  |                     </tr> | ||||||
|  |                 </table> | ||||||
|  |                 <label for="input-change-bio">{%w pres.edit-profile.directive-bio %}</label> | ||||||
|  |                 <br> | ||||||
|  |                 <textarea name="bio" id="input-change-bio" class="multiline-input"></textarea> | ||||||
|  |                 <button class="action-button centered-block-el" type="submit">{%w pres.edit-profile.act-submit %}</button> | ||||||
|  |             </form> | ||||||
|  |         </div> | ||||||
|  |     </div> | ||||||
|  | 
 | ||||||
|  | </body> | ||||||
|  | </html> | ||||||
|  | {% ENDELDEF%} | ||||||
							
								
								
									
										11
									
								
								assets/HypertextPages/err-404.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,11 @@ | |||||||
|  | <!DOCTYPE html> | ||||||
|  | <html lang="en"> | ||||||
|  | <head> | ||||||
|  |     <meta charset="UTF-8"> | ||||||
|  |     <link rel="icon" type="image/png" href="/assets/img/favicon.png"> | ||||||
|  |     <title>Not found</title> | ||||||
|  | </head> | ||||||
|  | <body> | ||||||
|  |     <h1>Page not found</h1> | ||||||
|  | </body> | ||||||
|  | </html> | ||||||
| @ -1,68 +1,75 @@ | |||||||
| {% ELDEF main JSON pres JSON userinfo %} | {% ELDEF main JSON pres JSON userinfo JSON initial_chatListUpdResp %} | ||||||
| <!DOCTYPE html> | <!DOCTYPE html> | ||||||
| <html lang="{% WRITE pres.lang %}"> | <html lang="{% W pres.lang %}"> | ||||||
| <head> | <head> | ||||||
|     <meta charset="UTF-8"> |     <meta charset="UTF-8"> | ||||||
|     <meta name="viewport" content="width=device-width, initial-scale=1.0"> |     <meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||||||
|     <title>Список Чат-Комнат</title> |     <title>{%w pres.list-rooms.header %}</title> | ||||||
|  |     <link rel="icon" type="image/png" href="/assets/img/favicon.png"> | ||||||
|  |     <link rel="stylesheet" href="/assets/css/common.css"> | ||||||
|  |     <link rel="stylesheet" href="/assets/css/common-popup.css"> | ||||||
|     <link rel="stylesheet" href="/assets/css/list-rooms.css"> |     <link rel="stylesheet" href="/assets/css/list-rooms.css"> | ||||||
| </head> | </head> | ||||||
| <body> | <body> | ||||||
| {% PUT pass-pres-userinfo pres userinfo %} |     <script> | ||||||
| <div class="container"> |         let pres = {% PUT jsinsert pres %}; | ||||||
|     <h1 style="color: white;">Выберите Чат-Комнату</h1> |         let userinfo = {% PUT jsinsert userinfo %}; | ||||||
|     <ul class="room-list"> |         let initial_chatListUpdResp = {% PUT jsinsert initial_chatListUpdResp %}; | ||||||
|         <!-- Здесь будет список комнат --> |     </script> | ||||||
|     </ul> |     <div id="chat-creation-win" class="popup-window"> | ||||||
|     <button class="create-room-button" onclick="openCreateRoomModal()">Создать Комнату</button> |         <h1 class="popup-window-msg">{%w pres.list-rooms.new-chat-header %}</h1> | ||||||
| </div> |         <table class="id-str-input-table"> | ||||||
|  |             <tr> | ||||||
|  |                 <td class="id-str-input-td1"> | ||||||
|  |                     <label for="chat-nickname-input">{%w pres.list-rooms.directive-nickname %}</label> | ||||||
|  |                 </td> | ||||||
|  |                 <td class="id-str-input-td2"> | ||||||
|  |                     <input id="chat-nickname-input" type="text" class="one-line-input" | ||||||
|  |                            placeholder="{%w pres.list-rooms.placeholder-nickname %}" required> | ||||||
|  |                 </td> | ||||||
|  |             </tr> | ||||||
|  |             <tr> | ||||||
|  |                 <td class="id-str-input-td1"> | ||||||
|  |                     <label for="chat-name-input">{%w pres.list-rooms.directive-name %}</label> | ||||||
|  |                 </td> | ||||||
|  |                 <td class="id-str-input-td2"> | ||||||
|  |                     <input id="chat-name-input" type="text" class="one-line-input" | ||||||
|  |                            placeholder="{%w pres.list-rooms.placeholder-name %}" required> | ||||||
|  |                 </td> | ||||||
|  |             </tr> | ||||||
|  |         </table> | ||||||
|  |         <h1 class="popup-window-msg">{%w pres.list-rooms.reask-create-new-chat %}</h1> | ||||||
|  |         <button class="popup-window-btn-yes" id="chat-creation-win-yes">{%w pres.list-rooms.yes-create %}</button> | ||||||
|  |         <button class="popup-window-btn-no" id="chat-creation-win-no">{%w pres.list-rooms.no-create %}</button> | ||||||
|  |     </div> | ||||||
| 
 | 
 | ||||||
| <!-- Модальное окно для создания комнаты --> |     <div id="chat-renunciation-win" class="popup-window"> | ||||||
| <!--<div id="createRoomModal" class="modal">--> |         <!-- header will actually be rewritten before showing the window to include chat nickname --> | ||||||
| <!--    <div class="modal-content">--> |         <h1 id="chat-renunciation-win-title" class="popup-window-msg">||||||||||</h1> | ||||||
| <!--        <div class="modal-header">--> |         <button class="popup-window-btn-yes" id="chat-renunciation-win-yes">{%w pres.list-rooms.yes-leave %}</button> | ||||||
| <!--            <span class="close" onclick="closeCreateRoomModal()">×</span>--> |         <button class="popup-window-btn-no" id="chat-renunciation-win-no">{%w pres.list-rooms.no-leave %}</button> | ||||||
| <!--            <h2> Создать Комнату</h2>--> |     </div> | ||||||
| <!--        </div>--> |  | ||||||
| <!--        <div class="modal-body">--> |  | ||||||
| <!--            <input type="text" id="newRoomName" placeholder="Название комнаты">--> |  | ||||||
| <!--            <input type="password" id="newRoomNickname" placeholder="Никнейм комнаты">--> |  | ||||||
| <!--        </div>--> |  | ||||||
| <!--        <div id="error"></div>--> |  | ||||||
| <!--        <div class="modal-footer">--> |  | ||||||
| <!--            <button class="join-button" onclick="createRoom()">Создать</button>--> |  | ||||||
| <!--        </div>--> |  | ||||||
| <!--    </div>--> |  | ||||||
| <!--</div>--> |  | ||||||
| 
 | 
 | ||||||
| <!--<!– Модальное окно для добавления участников –>--> |     <div class="document-container resp-container"> | ||||||
| <!--<div class="overlay" id="add_members">--> |         <div id="navigation-panel" class="panel"> | ||||||
| <!--    <div class="add-members">--> |             <a href="/user/{%w userinfo.nickname %}" id="go-to-my-profile" class="panel-thing"> | ||||||
| <!--        <div class="add-members-header">--> |                 <img alt="Go to my profile" src="/assets/img/user.svg" width="32px"> | ||||||
| <!--            <span class="close" onclick="closeAdd()">×</span>--> |             </a> | ||||||
| <!--            <h2>Добавить участников</h2>--> |             <p class="panel-thing panel-header-txt"> | ||||||
| <!--        </div>--> |                 {%w pres.list-rooms.page-description %} | ||||||
| <!--        <div class="add-members-body">--> |             </p> | ||||||
| <!--            <input type="text" id="newMemberLogin" placeholder="Логин пользователя">--> |         </div> | ||||||
| <!--        </div>--> | 
 | ||||||
| <!--        <div class="add-members-footer">--> |         <div class="dynamic-block-list"> | ||||||
| <!--            <button class="add-member-button" onclick="addMember()">Добавить</button>--> |             <img id="CL-bacbe" class="button-add centered-block-el" alt="New chat" src="/assets/img/add.svg"> | ||||||
| <!--        </div>--> |             <div class="dynamic-block-list-el-container" id="CL-dblec"> | ||||||
| <!--    </div>--> | 
 | ||||||
| <!--</div>--> |             </div> | ||||||
| <!--<div class="overlay" id="delete-chat">--> |         </div> | ||||||
| <!--    <div class="delete-chat">--> |     </div> | ||||||
| <!--        <div class="delete-chat-header">--> |     <script src="/assets/js/common.js"></script> | ||||||
| <!--            <span class="delete-close" onclick="closeConfirm()">×</span>--> |     <script src="/assets/js/common-popup.js"></script> | ||||||
| <!--            <h2>Вы уверены, что хотите удалить чат?</h2>--> |     <script src="/assets/js/list-rooms.js"></script> | ||||||
| <!--        </div>--> |  | ||||||
| <!--        <div class="delete-chat-body">--> |  | ||||||
| <!--            <button class="confirm" onclick="deleteChat()">Да</button>--> |  | ||||||
| <!--            <button class="cancel" onclick="closeConfirm()">Нет</button>--> |  | ||||||
| <!--        </div>--> |  | ||||||
| <!--    </div>--> |  | ||||||
| <!--</div>--> |  | ||||||
| <script src="/assets/js/list-rooms.js"></script> |  | ||||||
| </body> | </body> | ||||||
| </html> | </html> | ||||||
| {% ENDELDEF %} | {% ENDELDEF %} | ||||||
|  | |||||||
| @ -1,25 +1,43 @@ | |||||||
| {% ELDEF main JSON pres JSON userinfo %} | {% ELDEF main JSON pres JSON userinfo JSON errors %} | ||||||
| <!DOCTYPE html> | <!DOCTYPE html> | ||||||
| <html lang="{% WRITE pres.lang %}"> | <html lang="{% W pres.lang %}"> | ||||||
| <head> | <head> | ||||||
|     <meta charset="UTF-8"> |     <meta charset="UTF-8"> | ||||||
|     <meta name="viewport" content="width=device-width, initial-scale=1.0"> |     <meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||||||
|     <title>{% WRITE pres.phr.decl.page-login %}</title> |     <link rel="icon" type="image/png" href="/assets/img/favicon.png"> | ||||||
|  |     <link rel="stylesheet" href="/assets/css/common.css"> | ||||||
|     <link rel="stylesheet" href="/assets/css/login.css"> |     <link rel="stylesheet" href="/assets/css/login.css"> | ||||||
|     <script src="/assets/js/login.js" defer></script> |     <title>{% W pres.login.header %}</title> | ||||||
| </head> | </head> | ||||||
| 
 |  | ||||||
| <body> | <body> | ||||||
| <div class="form-container"> |     {% FOR error IN errors %} | ||||||
|     <h1 class="hide-cursor no-select">{% WRITE pres.phr.decl.enter %}</h1> |         <div class="server-notif-error-msg-box"> | ||||||
|     <form action="/login" method="post" enctype="application/x-www-form-urlencoded"> |             {% W error.text %} | ||||||
|         <label for="nickname">{% WRITE pres.phr.decl.nickname %}</label> |         </div> | ||||||
|         <input type="text" name="nickname" id="nickname"><br> |     {% ENDFOR %} | ||||||
|         <label for="password">{% WRITE pres.phr.decl.password %}</label> | 
 | ||||||
|         <input type="password" name="password" id="password"><br> |     <div class="form-container"> | ||||||
|         <button type="submit" class="hide-cursor no-select">{% WRITE pres.phr.act.enter %}</button> |         <h1 class="wide-centered-header">{% W pres.login.header %}</h1> | ||||||
|     </form> |         <form action="/login" method="post" enctype="application/x-www-form-urlencoded"> | ||||||
| </div> |             <table class="logins-input-table"> | ||||||
|  |                 <tr> | ||||||
|  |                     <td class="logins-input-td1"><label for="input-nickname">{% W pres.login.directive-nickname %}</label></td> | ||||||
|  |                     <td class="logins-input-td2"> | ||||||
|  |                         <input type="text" name="nickname" id="input-nickname" | ||||||
|  |                                placeholder="{% W pres.login.placeholder-nickname %}" class="one-line-input" required> | ||||||
|  |                     </td> | ||||||
|  |                 </tr> | ||||||
|  |                 <tr> | ||||||
|  |                     <td class="logins-input-td1"><label for="input-password">{% W pres.login.directive-password %}</label></td> | ||||||
|  |                     <td class="logins-input-td2"> | ||||||
|  |                         <input name="password" id="input-password" type="password" | ||||||
|  |                                placeholder="{% W pres.login.placeholder-password %}" class="one-line-input" required> | ||||||
|  |                     </td> | ||||||
|  |                 </tr> | ||||||
|  |             </table> | ||||||
|  |             <button class="action-button centered-block-el" type="submit">{% W pres.login.act %}</button> | ||||||
|  |         </form> | ||||||
|  |     </div> | ||||||
| 
 | 
 | ||||||
| </body> | </body> | ||||||
| </html> | </html> | ||||||
|  | |||||||
| @ -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%} |  | ||||||
							
								
								
									
										51
									
								
								assets/HypertextPages/register.nytl.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,51 @@ | |||||||
|  | {% ELDEF main JSON pres JSON userinfo JSON messages %} | ||||||
|  | <!DOCTYPE html> | ||||||
|  | <html lang="en"> | ||||||
|  | <head> | ||||||
|  |     <meta charset="UTF-8"> | ||||||
|  |     <meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||||||
|  |     <link rel="icon" type="image/png" href="/assets/img/favicon.png"> | ||||||
|  |     <link rel="stylesheet" href="/assets/css/common.css"> | ||||||
|  |     <link rel="stylesheet" href="/assets/css/register.css"> | ||||||
|  |     <title>{% W pres.register.header %}</title> | ||||||
|  | </head> | ||||||
|  | <body> | ||||||
|  |     {% FOR error IN messages %} | ||||||
|  |     <div class="server-notif-error-msg-box"> | ||||||
|  |         {% W error.text %} | ||||||
|  |     </div> | ||||||
|  |     {% ENDFOR %} | ||||||
|  | 
 | ||||||
|  |     <div class="form-container"> | ||||||
|  |         <h1 class="wide-centered-header">{% W pres.register.header %}</h1> | ||||||
|  |         <form action="/register" method="post" enctype="application/x-www-form-urlencoded"> | ||||||
|  |             <table class="reg-input-table"> | ||||||
|  |                 <tr> | ||||||
|  |                     <td class="reg-input-td1"><label for="input-nickname">{% W pres.register.directive-nickname %}</label></td> | ||||||
|  |                     <td class="reg-input-td2"> | ||||||
|  |                         <input type="text" name="nickname" id="input-nickname" | ||||||
|  |                                placeholder="{% W pres.register.placeholder-nickname %}" class="one-line-input" required> | ||||||
|  |                     </td> | ||||||
|  |                 </tr> | ||||||
|  |                 <tr> | ||||||
|  |                     <td class="reg-input-td1"><label for="input-name">{% W pres.register.directive-name %}</label></td> | ||||||
|  |                     <td class="reg-input-td2"> | ||||||
|  |                         <input type="text" name="name" id="input-name" | ||||||
|  |                                placeholder="{% W pres.register.placeholder-name %}" class="one-line-input" required> | ||||||
|  |                     </td> | ||||||
|  |                 </tr> | ||||||
|  |                 <tr> | ||||||
|  |                     <td class="reg-input-td1"><label for="input-password">{% W pres.register.directive-password %}</label></td> | ||||||
|  |                     <td class="reg-input-td2"> | ||||||
|  |                         <input name="password" id="input-password" type="password" | ||||||
|  |                                placeholder="{% W pres.register.placeholder-password %}" class="one-line-input" required> | ||||||
|  |                     </td> | ||||||
|  |                 </tr> | ||||||
|  |             </table> | ||||||
|  |             <button class="action-button centered-block-el" type="submit">{% W pres.register.act %}</button> | ||||||
|  |         </form> | ||||||
|  |     </div> | ||||||
|  | 
 | ||||||
|  | </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 %} |  | ||||||
							
								
								
									
										36
									
								
								assets/HypertextPages/view-profile.nytl.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,36 @@ | |||||||
|  | {% ELDEF main JSON pres JSON userinfo JSON alienprofile %} | ||||||
|  | <!DOCTYPE html> | ||||||
|  | <html lang="en"> | ||||||
|  | <head> | ||||||
|  |     <meta charset="UTF-8"> | ||||||
|  |     <meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||||||
|  |     <link rel="icon" type="image/png" href="/assets/img/favicon.png"> | ||||||
|  |     <link rel="stylesheet" href="/assets/css/common.css"> | ||||||
|  |     <!-- This page is so simple, that it does not even have it's separate css file --> | ||||||
|  |     <title>{%w pres.view-profile.header-profile-of %} {%w alienprofile.name %}</title> | ||||||
|  | </head> | ||||||
|  | <body> | ||||||
|  | 
 | ||||||
|  |     <div class="document-container resp-container"> | ||||||
|  |         <div id="navigation-panel" class="panel"> | ||||||
|  |             <a href="/list-rooms" id="go-to-chat-list" class="panel-thing"> | ||||||
|  |                 <img alt="Go to list of chats" src="/assets/img/list-rooms.svg" width="32px"> | ||||||
|  |             </a> | ||||||
|  |             <a href="/user/{% W userinfo.nickname %}" id="go-to-my-profile" class="panel-thing"> | ||||||
|  |                 <img alt="Go to my profile" src="/assets/img/user.svg" width="32px"> | ||||||
|  |             </a> | ||||||
|  |         </div> | ||||||
|  | 
 | ||||||
|  |         <div class="profile-container"> | ||||||
|  |             <h2 class="profile-name-text">{%w alienprofile.name %}</h2> | ||||||
|  |             <h3 class="profile-nickname-text">{%w pres.view-profile.directive-nickname%} {%w alienprofile.nickname %}</h3> | ||||||
|  |             <p class="profile-bio-text"> | ||||||
|  |                 {%w alienprofile.bio %} | ||||||
|  |             </p> | ||||||
|  |         </div> | ||||||
|  |     </div> | ||||||
|  | 
 | ||||||
|  | </body> | ||||||
|  | </html> | ||||||
|  | 
 | ||||||
|  | {% ENDELDEF%} | ||||||
							
								
								
									
										33
									
								
								assets/css/chat-members.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,33 @@ | |||||||
|  | #CM-btn-add { | ||||||
|  |     margin-top: 6px; | ||||||
|  |     margin-bottom: 4px; | ||||||
|  |     display: none; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .CM-member-box { | ||||||
|  |     display: flex; | ||||||
|  |     flex-direction: row; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .CL-member-box-nickname { | ||||||
|  |     margin-left: 8px; | ||||||
|  |     justify-self: flex-start; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .CM-member-box-name { | ||||||
|  |     margin-left: 14px; | ||||||
|  |     justify-self: flex-start; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .CM-member-box-role { | ||||||
|  |     margin-left: auto; | ||||||
|  |     justify-self: flex-end; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .CM-member-box-leave-btn { | ||||||
|  |     margin-left: 10px; | ||||||
|  |     margin-right: 8px; | ||||||
|  |     justify-self: flex-end; | ||||||
|  |     width: 16px; | ||||||
|  |     cursor: pointer; | ||||||
|  | } | ||||||
| @ -1,202 +1,143 @@ | |||||||
| body { | body, html { | ||||||
|     font-family: Arial, sans-serif; |  | ||||||
|     display: flex; |  | ||||||
|     justify-content: center; |  | ||||||
|     align-items: center; |  | ||||||
|     height: 100vh; |  | ||||||
|     margin: 0; |  | ||||||
|     background-color: #e5e5e5; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .chat-container { |  | ||||||
|     width: 100%; |  | ||||||
|     max-width: 800px; |  | ||||||
|     height: 90vh; |  | ||||||
|     background-color: white; |  | ||||||
|     box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); |  | ||||||
|     display: flex; |  | ||||||
|     flex-direction: column; |  | ||||||
|     border-radius: 8px; |  | ||||||
|     overflow: hidden; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .chat-header { |  | ||||||
|     background-color: #007bb5; |  | ||||||
|     color: white; |  | ||||||
|     padding: 25px; |  | ||||||
|     display: flex; |  | ||||||
|     justify-content: center; |  | ||||||
|     align-items: center; |  | ||||||
|     position: relative; |  | ||||||
| } |  | ||||||
| .room-name { |  | ||||||
|     position: absolute; |  | ||||||
|     left: 50%; |  | ||||||
|     font-size: 24px; |  | ||||||
| } |  | ||||||
| .members { |  | ||||||
|     border: none; |  | ||||||
|     position: absolute; |  | ||||||
|     left: 80%; |  | ||||||
|     border-radius: 10px; |  | ||||||
|     cursor: pointer; |  | ||||||
|     width: 150px; |  | ||||||
|     background-color: #f7f7f7; |  | ||||||
|     height: 25px; |  | ||||||
|     transition: background-color 0.3s ease; |  | ||||||
| } |  | ||||||
| .members:hover { |  | ||||||
|     background-color: #218838; |  | ||||||
| } |  | ||||||
| .chat-messages { |  | ||||||
|     flex: 1; |  | ||||||
|     padding: 15px; |  | ||||||
|     overflow-y: auto; |  | ||||||
|     background-color: #f7f7f7; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .chat-message { |  | ||||||
|     display: flex; |  | ||||||
|     align-items: flex-start; |  | ||||||
|     margin-bottom: 15px; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .chat-message .avatar { |  | ||||||
|     width: 40px; |  | ||||||
|     height: 40px; |  | ||||||
|     border-radius: 50%; |  | ||||||
|     overflow: hidden; |  | ||||||
|     margin-right: 10px; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .chat-message .avatar img { |  | ||||||
|     width: 100%; |  | ||||||
|     height: 100%; |     height: 100%; | ||||||
|     object-fit: cover; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .chat-message .message-content { | #chat-widget { | ||||||
|     max-width: 70%; |     position: relative; | ||||||
|  |     flex: 1; | ||||||
|  |     background-color: #f1f1f1; | ||||||
|  |     overflow: hidden; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .message-supercontainer{ | ||||||
|  |     position: absolute; | ||||||
|  |     width: 100%; | ||||||
|  |     left: 0; | ||||||
|  |     /*background-color: rgba(150, 0, 100, 50);*/ | ||||||
|  |     background-color: rgba(0, 0, 0, 0); | ||||||
|  |     /*display: flex;*/ | ||||||
|  |     /*flex-direction: row;*/ | ||||||
|  |     /*justify-content: center;*/ | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .message-box{ | ||||||
|  |     /*display: inline-block;*/ | ||||||
|  |     padding: 5px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .message-box-mine { | ||||||
|  |     margin-right: 5px; | ||||||
|  |     margin-left: auto; | ||||||
|  |     max-width: 400px; | ||||||
|  |     border: 2px solid #82a173; | ||||||
|  |     padding: 5px; | ||||||
|  |     background-color: #cdff9b; | ||||||
|  |     color: black; | ||||||
|  |     /*justify-self: flex-end;*/ | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .message-box-alien { | ||||||
|  |     margin-left: 5px; | ||||||
|  |     margin-right: auto; | ||||||
|  |     max-width: 400px; | ||||||
|  |     border: 2px solid dimgrey; | ||||||
|  |     padding: 5px; | ||||||
|     background-color: white; |     background-color: white; | ||||||
|     padding: 10px; |     color: black; | ||||||
|     border-radius: 8px; |     /*justify-self: flex-start;*/ | ||||||
|     box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .chat-message .message-content .username { | /* Only non-system messages can be deleted. Deleted messages do not have delete button | ||||||
|  |  This class should be used with (and, ofcourse, after) class message-box-my/message-box-alien */ | ||||||
|  | .message-box-deleted { | ||||||
|  |     border: 2px solid #cb0005; | ||||||
|  |     background-color: #ffc1bc; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .message-box-deleted .message-box-msg{ | ||||||
|     font-weight: bold; |     font-weight: bold; | ||||||
|     margin-bottom: 5px; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .chat-message .message-content .text { | .message-box-system { | ||||||
|  |     margin-left: auto; | ||||||
|  |     margin-right: auto; | ||||||
|  |     max-width: 500px; | ||||||
|  |     padding: 4px; | ||||||
|  |     background-color: #2d2d2d; | ||||||
|  |     color: white; | ||||||
|  |     font-weight: bold; | ||||||
|  |     justify-self: center; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* in #chat-widget .message-box */ | ||||||
|  | .message-box-top{ | ||||||
|  |     /* You see, each message contains a 20+2+2 px high icon that HAS TO BE LOADED FIRST. | ||||||
|  |     This happens after window.onload, so I added a crutch: loading won't update height in | ||||||
|  |      unpredictable moment. cause it will be already high enough. BUGA-GA-GA!! */ | ||||||
|  |     min-height: 30px; | ||||||
|  |     display: block; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .message-box-sender-name{ | ||||||
|  |     color: black; | ||||||
|  |     text-decoration: none; | ||||||
|  |     padding: 2px; | ||||||
|  |     display: inline; | ||||||
|  |     font-size: 0.8em; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* Additional to message-box-sender-name */ | ||||||
|  | .message-box-sender-shortname { | ||||||
|  |     font-weight: bold; | ||||||
|  |     padding-left: 3px; | ||||||
|  |     font-size: 0.94em; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .message-box-sender-name:hover{ | ||||||
|  |     color: #1060ff | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .message-box-button{ | ||||||
|  |     width: 20px; | ||||||
|  |     padding: 2px; | ||||||
|  |     cursor: pointer; | ||||||
|  |     display: inline; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .message-box-msg{ | ||||||
|     word-wrap: break-word; |     word-wrap: break-word; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .chat-footer { | #input-panel { | ||||||
|     display: flex; |     min-height: 20px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #message-input { | ||||||
|     padding: 15px; |     padding: 15px; | ||||||
|     padding-left: 50px; |     height: auto; | ||||||
|     border-top: 1px solid #ddd; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .chat-input { |  | ||||||
|     flex: 1; |  | ||||||
|     padding: 10px; |  | ||||||
|     border: 1px solid #ddd; |  | ||||||
|     border-radius: 20px; |  | ||||||
|     margin-right: 10px; |  | ||||||
|     outline: none; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .chat-send-button { |  | ||||||
|     padding: 10px 20px; |  | ||||||
|     border: none; |  | ||||||
|     background-color: #0088cc; |  | ||||||
|     color: white; |  | ||||||
|     border-radius: 20px; |  | ||||||
|     cursor: pointer; |  | ||||||
|     outline: none; |  | ||||||
| } |  | ||||||
| .members-list { |  | ||||||
|     display: none; |  | ||||||
|     position: fixed; |  | ||||||
|     background-color: #fff; |  | ||||||
|     margin: 10% auto; |  | ||||||
|     padding: 20px; |  | ||||||
|     border: 1px solid #888; |  | ||||||
|     width: 80%; |  | ||||||
|     max-width: 400px; |  | ||||||
|     border-radius: 10px; |  | ||||||
|     box-shadow: 0 0 10px rgba(0, 0, 0, 0.2); |  | ||||||
| } |  | ||||||
| .members-list-header { |  | ||||||
|     display: flex; |  | ||||||
| } |  | ||||||
| .all-members { |  | ||||||
|     position: absolute; |  | ||||||
|     left: 32%; |  | ||||||
|     top: 0%; |  | ||||||
|     margin-bottom: 30px; |  | ||||||
|     font-family: Arial, sans-serif; |  | ||||||
| } |  | ||||||
| .close { |  | ||||||
|     position: absolute; |  | ||||||
|     right: 5%; |  | ||||||
|     font-size: 24px; |  | ||||||
|     font-weight: bold; |  | ||||||
| } |  | ||||||
| .members-list span { |  | ||||||
|     cursor: pointer; |  | ||||||
| } |  | ||||||
| .members-list-body ul { |  | ||||||
|     list-style-type: none; |  | ||||||
|     left: 0%; |  | ||||||
| } |  | ||||||
| .members-list-body img { |  | ||||||
|     margin-top: 10px; |  | ||||||
|     left: 0%; |  | ||||||
|     height: 30px; |  | ||||||
|     width: 30px; |  | ||||||
|     border-radius: 50%; |  | ||||||
| } |  | ||||||
| .members-list-body a { |  | ||||||
|     margin-left: 5px; |  | ||||||
|     margin-top: 10px; |  | ||||||
|     text-decoration: none; |  | ||||||
|     color: black; |  | ||||||
| 
 |  | ||||||
| } |  | ||||||
| .members-list-body a:hover { |  | ||||||
|     text-decoration: underline; |  | ||||||
|     color: #0088cc; |  | ||||||
| } |  | ||||||
| .members-list-body button { |  | ||||||
|     padding: 5px 10px; |  | ||||||
|     border: none; |  | ||||||
|     background-color: #dc2e45; |  | ||||||
|     color: white; |  | ||||||
|     border-radius: 20px; |  | ||||||
|     position: absolute; |  | ||||||
|     left: 300px; |  | ||||||
|     margin-top: 20px; |  | ||||||
|     cursor: pointer; |  | ||||||
|     transition: background-color 0.3s ease; |  | ||||||
| } |  | ||||||
| .members-list-body button:hover { |  | ||||||
|     background-color: #881527; |  | ||||||
| } |  | ||||||
| .overlay { |  | ||||||
|     display: none; |  | ||||||
|     position: fixed; |  | ||||||
|     top: 0; |  | ||||||
|     left: 0; |  | ||||||
|     width: 100%; |     width: 100%; | ||||||
|     height: 100%; |     display: inline-block; | ||||||
|     background-color: rgba(0, 0, 0, 0.5); |     background-color: white; | ||||||
|     justify-content: center; |     border: 1px solid #1000d0; | ||||||
|     align-items: center; |     border-radius : 7px; | ||||||
|     z-index: 1000; |     font-size: .9rem; | ||||||
|  |     margin: 10px; | ||||||
| } | } | ||||||
| .chat-send-button:hover { | 
 | ||||||
|     background-color: #007bb5; | .message-in-popup-preview{ | ||||||
|  |     border: 4px solid red; | ||||||
|  |     width: 80%; | ||||||
|  |     max-width: 200px; | ||||||
|  |     margin-left: auto; | ||||||
|  |     margin-right: auto; | ||||||
|  |     max-height: 20%; | ||||||
|  |     word-wrap: break-word; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .loading-spinner{ | ||||||
|  |     margin-left: auto; | ||||||
|  |     margin-right: auto; | ||||||
|  |     background-color: rgba(0, 0, 0, 0); | ||||||
|  |     width: 25px; | ||||||
|  |     display: block; | ||||||
| } | } | ||||||
| @ -1,163 +0,0 @@ | |||||||
| body { |  | ||||||
|     font-family: Arial, sans-serif; |  | ||||||
|     display: flex; |  | ||||||
|     justify-content: center; |  | ||||||
|     align-items: center; |  | ||||||
|     height: 100vh; |  | ||||||
|     margin: 0; |  | ||||||
|     background-color: #e5e5e5; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .chat-settings-container { |  | ||||||
|     width: 100%; |  | ||||||
|     max-width: 800px; |  | ||||||
|     background-color: white; |  | ||||||
|     box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); |  | ||||||
|     display: flex; |  | ||||||
|     flex-direction: column; |  | ||||||
|     border-radius: 8px; |  | ||||||
|     overflow: hidden; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .chat-settings-container-header { |  | ||||||
|     background-color: #007bb5; |  | ||||||
|     color: white; |  | ||||||
|     padding: 25px; |  | ||||||
|     display: flex; |  | ||||||
|     justify-content: center; |  | ||||||
|     align-items: center; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .room-name { |  | ||||||
|     font-size: 24px; |  | ||||||
|     width: 80%; |  | ||||||
|     text-align: center; |  | ||||||
|     border-radius: 10px; |  | ||||||
|     border: none; |  | ||||||
| } |  | ||||||
| .changeName { |  | ||||||
|     padding: 8px 10px; |  | ||||||
|     background-color: #28a745; |  | ||||||
|     color: white; |  | ||||||
|     border-radius: 20px; |  | ||||||
|     border: none; |  | ||||||
|     cursor: pointer; |  | ||||||
| } |  | ||||||
| .changeName:hover { |  | ||||||
|     background-color: #005f8c; |  | ||||||
| } |  | ||||||
| .chat-settings-container-body { |  | ||||||
|     padding: 15px; |  | ||||||
|     background-color: #f7f7f7; |  | ||||||
|     flex: 1; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| #chat-settings-container-body { |  | ||||||
|     list-style-type: none; |  | ||||||
|     padding: 0; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| #chat-settings-container-body li { |  | ||||||
|     margin-bottom: 10px; |  | ||||||
|     background-color: white; |  | ||||||
|     padding: 10px; |  | ||||||
|     border-radius: 8px; |  | ||||||
|     box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); |  | ||||||
| } |  | ||||||
| .remove-member-button { |  | ||||||
|     background-color: red; |  | ||||||
|     color: white; |  | ||||||
|     border: none; |  | ||||||
|     padding: 5px 10px; |  | ||||||
|     cursor: pointer; |  | ||||||
|     margin-left: 10px; |  | ||||||
|     border-radius: 4px; |  | ||||||
| } |  | ||||||
| .remove-member-button:hover { |  | ||||||
|     background-color: darkred; |  | ||||||
| } |  | ||||||
| .chat-settings-container-invite { |  | ||||||
|     padding: 15px; |  | ||||||
|     background-color: white; |  | ||||||
|     text-align: center; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .invite-member { |  | ||||||
|     padding: 10px 20px; |  | ||||||
|     border: none; |  | ||||||
|     background-color: #28a745; |  | ||||||
|     color: white; |  | ||||||
|     border-radius: 20px; |  | ||||||
|     cursor: pointer; |  | ||||||
|     transition: background-color 0.3s ease; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .invite-member:hover { |  | ||||||
|     background-color: #218838; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .overlay { |  | ||||||
|     display: none; |  | ||||||
|     position: fixed; |  | ||||||
|     top: 0; |  | ||||||
|     left: 0; |  | ||||||
|     width: 100%; |  | ||||||
|     height: 100%; |  | ||||||
|     background-color: rgba(0, 0, 0, 0.5); |  | ||||||
|     justify-content: center; |  | ||||||
|     align-items: center; |  | ||||||
|     z-index: 1000; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .add-members { |  | ||||||
|     background-color: white; |  | ||||||
|     padding: 30px; |  | ||||||
|     border-radius: 8px; |  | ||||||
|     box-shadow: 0 0 10px rgba(0, 0, 0, 0.2); |  | ||||||
|     width: 100%; |  | ||||||
|     max-width: 400px; |  | ||||||
|     text-align: center; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .add-members-header { |  | ||||||
|     position: relative; |  | ||||||
|     margin-bottom: 20px; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .add-members-header h2 { |  | ||||||
|     margin: 0; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .close { |  | ||||||
|     position: absolute; |  | ||||||
|     right: 10px; |  | ||||||
|     top: 0; |  | ||||||
|     font-size: 24px; |  | ||||||
|     font-weight: bold; |  | ||||||
|     cursor: pointer; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .add-members-body input { |  | ||||||
|     width: 95%; |  | ||||||
|     padding: 10px; |  | ||||||
|     margin-bottom: 20px; |  | ||||||
|     border: 1px solid #ddd; |  | ||||||
|     border-radius: 4px; |  | ||||||
|     margin-right: 15%; |  | ||||||
|     outline: none; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .add-member-button { |  | ||||||
|     padding: 10px 20px; |  | ||||||
|     border: none; |  | ||||||
|     background-color: #007bb5; |  | ||||||
|     color: white; |  | ||||||
|     border-radius: 20px; |  | ||||||
|     cursor: pointer; |  | ||||||
|     outline: none; |  | ||||||
|     transition: background-color 0.3s ease; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .add-member-button:hover { |  | ||||||
|     background-color: #005f8c; |  | ||||||
| } |  | ||||||
							
								
								
									
										50
									
								
								assets/css/common-popup.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,50 @@ | |||||||
|  | .popup-overlay-veil { | ||||||
|  |     position: fixed; | ||||||
|  |     top: 0; | ||||||
|  |     left: 0; | ||||||
|  |     width: 100%; | ||||||
|  |     height: 100%; | ||||||
|  |     background-color: rgba(0, 0, 0, 0.6); | ||||||
|  | 
 | ||||||
|  |     z-index: 99; | ||||||
|  |     display: none; /* Hidden by default */ | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .popup-window { | ||||||
|  |     position: fixed; | ||||||
|  |     top: 50%; | ||||||
|  |     left: 50%; | ||||||
|  |     transform: translate(-50%, -50%); | ||||||
|  |     background: white; | ||||||
|  |     padding: 20px; | ||||||
|  |     box-shadow: 0 0 10px rgba(0, 0, 0, 0.5); | ||||||
|  | 
 | ||||||
|  |     z-index: 100; | ||||||
|  |     display: none; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .popup-btn { | ||||||
|  |     display: inline; | ||||||
|  |     padding: 5px; | ||||||
|  |     border-bottom: 3px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .popup-window-btn-yes { | ||||||
|  |     background-color: #0c7f0e; | ||||||
|  |     border-radius: 5px; | ||||||
|  |     padding: 12px; | ||||||
|  |     color: white; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .popup-window-btn-no { | ||||||
|  |     background-color: #ff0005; | ||||||
|  |     border-radius: 5px; | ||||||
|  |     padding: 12px; | ||||||
|  |     color: white; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .popup-window-msg { | ||||||
|  |     padding-left: 20px; | ||||||
|  |     font-weight: bold; | ||||||
|  |     font-size: 1.3em; | ||||||
|  | } | ||||||
							
								
								
									
										206
									
								
								assets/css/common.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,206 @@ | |||||||
|  | /* Profile view elements */ | ||||||
|  | .profile-container { | ||||||
|  |     background: white; | ||||||
|  |     border-radius: 5px; | ||||||
|  |     padding: 20px; | ||||||
|  |     margin-top: 60px; /* Space below the fixed panel */ | ||||||
|  |     box-shadow: 0 10px 15px rgba(0, 0, 0, 0.3); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .profile-name-text { | ||||||
|  |     color: black; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .profile-nickname-text{ | ||||||
|  |     color: #444; | ||||||
|  |     text-align: left; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .profile-bio-text { | ||||||
|  |     padding-top: 40px; | ||||||
|  |     text-align: left; | ||||||
|  |     line-height: 1.6; | ||||||
|  |     color: black; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* Panels */ | ||||||
|  | .panel { | ||||||
|  |     width: 100%; | ||||||
|  |     border: 2px solid blue; | ||||||
|  |     background-color: #54b3ff; | ||||||
|  |     display: flex; | ||||||
|  |     flex-direction: row; | ||||||
|  |     align-items: center; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .panel-thing { | ||||||
|  |     padding: 6px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .panel-header-txt{ | ||||||
|  |     color: white; | ||||||
|  |     font-size: 1.9em; | ||||||
|  |     flex: 1; | ||||||
|  |     text-align: center; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | /* Containers for the whole document */ | ||||||
|  | 
 | ||||||
|  | * { | ||||||
|  |     margin: 0; | ||||||
|  |     padding: 0; | ||||||
|  |     box-sizing: border-box; | ||||||
|  |     font-family: Arial, sans-serif; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .document-container { | ||||||
|  |     width: 80%; /* Full width of the viewport */ | ||||||
|  |     margin: 0 auto; /* Center the container horizontally */ | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .fullscreen-container { | ||||||
|  |     width: 80%; /* Full width of the viewport */ | ||||||
|  |     height: 100vh; /* Full height of the viewport */ | ||||||
|  |     display: flex; | ||||||
|  |     flex-direction: column; /* Stack children vertically */ | ||||||
|  |     margin: 0 auto; /* Center the container horizontally */ | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @media (orientation: landscape) { | ||||||
|  |     .resp-container{ | ||||||
|  |         width: 80%; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @media (orientation: portrait){ | ||||||
|  |     .resp-container{ | ||||||
|  |         width: 100%; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | body { | ||||||
|  |     background-color: #f000f0; | ||||||
|  |     background-image: url("/assets/img/clavicle-transparent.png"), url("/assets/img/broken-clavicle.png"); | ||||||
|  |     background-repeat: revert; | ||||||
|  |     background-size: 10%, 25%; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* Notifications, returned from server and embedded into html page at render-time */ | ||||||
|  | 
 | ||||||
|  | .server-notif-error-msg-box{ | ||||||
|  |     font-size: 1.3em; | ||||||
|  |     text-align: center; | ||||||
|  |     padding: 10px; | ||||||
|  |     border: 2px solid red; | ||||||
|  |     border-radius: 30px; | ||||||
|  |     background-color: #ff5050; | ||||||
|  |     max-width: 40%; | ||||||
|  |     margin: 15px auto; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* Centered headers */ | ||||||
|  | 
 | ||||||
|  | .wide-centered-header { | ||||||
|  |     width: 100%; | ||||||
|  |     text-align: center; | ||||||
|  |     font-size: 1.4em; | ||||||
|  | } | ||||||
|  | /* Cool buttons with text */ | ||||||
|  | 
 | ||||||
|  | .action-button { | ||||||
|  |     padding: 10px 15px; | ||||||
|  |     background-color: #007bff; | ||||||
|  |     color: white; | ||||||
|  |     border: none; | ||||||
|  |     border-radius: 5px; | ||||||
|  |     cursor: pointer; | ||||||
|  |     transition: background-color 0.3s; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | .action-button:hover { | ||||||
|  |     background-color: #0056b3; /* Darker blue on hover */ | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* This is for centering non-100%wide block */ | ||||||
|  | 
 | ||||||
|  | .centered-block-el { | ||||||
|  |     display: block; | ||||||
|  |     margin-left: auto; | ||||||
|  |     margin-right: auto; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* Beautiful text input */ | ||||||
|  | 
 | ||||||
|  | .one-line-input { | ||||||
|  |     width: 100%; | ||||||
|  |     padding: 8px; | ||||||
|  |     margin: 8px 0; | ||||||
|  |     border: 1px solid #ccc; | ||||||
|  |     border-radius: 4px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .multiline-input { | ||||||
|  |     width: 100%; | ||||||
|  |     /*max-width: 600px;*/ | ||||||
|  |     height: 200px; | ||||||
|  |     padding: 10px; | ||||||
|  |     font-size: 1.15em; | ||||||
|  |     border: 2px solid #ccc; | ||||||
|  |     border-radius: 5px; | ||||||
|  |     box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.1); /* Subtle shadow */ | ||||||
|  |     outline: none; /* Remove default outline on focus */ | ||||||
|  |     resize: vertical; /* Allow resizing vertically */ | ||||||
|  |     transition: border-color 0.15s, box-shadow 0.15s; /* Smooth transition for border color and shadow */ | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .multiline-input:focus { | ||||||
|  |     border-color: #007bff; /* Change border color on focus */ | ||||||
|  |     box-shadow: 0 0 5px rgba(0, 123, 255, 0.5); /* Shadow on focus */ | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* Handles the case of list of elements with dickanme, name, role and delete button | ||||||
|  |  For list of chats and list of users in chat */ | ||||||
|  | .dynamic-block-list { | ||||||
|  |     margin-top:12px; | ||||||
|  |     display: flex; | ||||||
|  |     flex-direction: column; | ||||||
|  |     background-color: white; | ||||||
|  |     border: 1px solid #c7c7c7; | ||||||
|  |     align-items: stretch; | ||||||
|  |     padding-left: 8px; | ||||||
|  |     padding-right: 8px; | ||||||
|  |     padding-bottom: 8px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .dynamic-block-list-el { | ||||||
|  |     margin-top: 8px; | ||||||
|  |     background-color: white; | ||||||
|  |     border: 1px solid #c7c7c7; | ||||||
|  |     color: black; | ||||||
|  |     padding: 5px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .button-add{ | ||||||
|  |     width: 50px; | ||||||
|  |     cursor: pointer; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .dynamic-block-list-el-container{ | ||||||
|  |     width: 100%; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .entity-nickname-txt { | ||||||
|  |     font-weight: bold; | ||||||
|  |     color: black; | ||||||
|  |     text-decoration: none; | ||||||
|  |     font-size: 1.5em; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .entity-reg-field-txt { | ||||||
|  |     /* For name and role */ | ||||||
|  |     color: #242424; | ||||||
|  |     text-decoration: none; | ||||||
|  |     font-size: 1.5em; | ||||||
|  | } | ||||||
							
								
								
									
										8
									
								
								assets/css/debug.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,8 @@ | |||||||
|  | .chat-debug-rect{ | ||||||
|  |     width: 100%; | ||||||
|  |     position: absolute; | ||||||
|  |     left: 0; | ||||||
|  |     opacity: 0.3; | ||||||
|  |     height: 3px; | ||||||
|  |     z-index: 2; | ||||||
|  | } | ||||||
							
								
								
									
										23
									
								
								assets/css/edit-profile.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,23 @@ | |||||||
|  | /* The morbid thing */ | ||||||
|  | table.logins-input-table { | ||||||
|  |     width: 100%; | ||||||
|  |     border-collapse: collapse; /* Combine borders */ | ||||||
|  | } | ||||||
|  | .logins-input-td1, .logins-input-td2 { | ||||||
|  |     border: none; | ||||||
|  | } | ||||||
|  | .logins-input-td1 { | ||||||
|  |     text-align: left; | ||||||
|  |     padding-right: 5px; | ||||||
|  |     white-space: nowrap; /* Prevent text wrap, keeping it in one line */ | ||||||
|  |     overflow: hidden; /* Hide overflow content */ | ||||||
|  |     text-overflow: ellipsis; /* Show ellipsis for overflowing text */ | ||||||
|  | } | ||||||
|  | .logins-input-td2 { | ||||||
|  |     width: 100%; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #input-change-bio{ | ||||||
|  |     margin-top: 5px; | ||||||
|  |     margin-bottom: 5px; | ||||||
|  | } | ||||||
| @ -1,253 +1,51 @@ | |||||||
| body { | #CL-bacbe { | ||||||
|     font-family: Arial, sans-serif; |     margin-top: 6px; | ||||||
|     background-color: #f0f0f0; |     margin-bottom: 4px; | ||||||
|     margin: 0; |  | ||||||
|     padding: 0; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .container { | .CL-my-chat-box { | ||||||
|     max-width: 800px; |  | ||||||
|     margin: 30px auto; |  | ||||||
|     padding: 20px; |  | ||||||
|     background-color: #007bff; |  | ||||||
|     box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); |  | ||||||
|     border-radius: 8px; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| h1 { |  | ||||||
|     text-align: center; |  | ||||||
|     color: #fff; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .room-list { |  | ||||||
|     list-style-type: none; |  | ||||||
|     padding: 0; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .room-item { |  | ||||||
|     display: flex; |     display: flex; | ||||||
|     justify-content: space-between; |     flex-direction: row; | ||||||
|     align-items: center; |  | ||||||
|     padding: 15px; |  | ||||||
|     margin: 10px 0; |  | ||||||
|     background-color: #fafafa; |  | ||||||
|     border: 1px solid #ddd; |  | ||||||
|     border-radius: 5px; |  | ||||||
|     transition: background-color 0.3s ease; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .room-item:hover { | .CL-my-chat-box-nickname { | ||||||
|     background-color: #eaeaea; |     margin-left: 8px; | ||||||
|  |     justify-self: flex-start; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .room-name { | .CL-my-chat-box-name { | ||||||
|     font-size: 18px; |     margin-left: 14px; | ||||||
|     color: #555; |     justify-self: flex-start; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .join-button { | .CL-my-chat-box-my-role { | ||||||
|     padding: 10px 15px; |     margin-left: auto; | ||||||
|     font-size: 16px; |     justify-self: flex-end; | ||||||
|     color: white; |  | ||||||
|     background-color: #007bff; |  | ||||||
|     border: none; |  | ||||||
|     border-radius: 5px; |  | ||||||
|     cursor: pointer; |  | ||||||
|     transition: background-color 0.3s ease; |  | ||||||
| } |  | ||||||
| .add-members-header { |  | ||||||
|     text-align: center; |  | ||||||
| } |  | ||||||
| .add-members-footer { |  | ||||||
|     text-align: right; |  | ||||||
|     margin-top: 5px; |  | ||||||
| } |  | ||||||
| .add-members-button { |  | ||||||
|     background-color: #218838; |  | ||||||
|     padding: 10px 15px; |  | ||||||
|     font-size: 16px; |  | ||||||
|     color: white; |  | ||||||
|     border: none; |  | ||||||
|     border-radius: 5px; |  | ||||||
|     position: absolute; |  | ||||||
|     margin-left: 502px; |  | ||||||
|     cursor: pointer; |  | ||||||
|     transition: background-color 0.3s ease; |  | ||||||
| } |  | ||||||
| .add-members-button:hover { |  | ||||||
|     background-color: #006509 |  | ||||||
| } |  | ||||||
| .delete-chat-button { |  | ||||||
|     background-color: #dc2e45; |  | ||||||
|     border: none; |  | ||||||
|     color: white; |  | ||||||
|     font-size: 16px; |  | ||||||
|     border-radius: 5px; |  | ||||||
|     position: absolute; |  | ||||||
|     cursor: pointer; |  | ||||||
|     transition: background-color 0.3s ease; |  | ||||||
|     padding: 10px 15px; |  | ||||||
|     margin-left: 380px; |  | ||||||
| } |  | ||||||
| .delete-chat-button:hover { |  | ||||||
|     background-color: #881527; |  | ||||||
| } |  | ||||||
| #newMemberLogin { |  | ||||||
|     width: 93.5%; |  | ||||||
|     padding: 10px; |  | ||||||
|     margin: 10px 0; |  | ||||||
|     border: 1px solid #ddd; |  | ||||||
|     border-radius: 5px; |  | ||||||
| } |  | ||||||
| .add-member-button { |  | ||||||
|     background-color: #218838; |  | ||||||
|     padding: 10px 15px; |  | ||||||
|     font-size: 16px; |  | ||||||
|     color: white; |  | ||||||
|     border: none; |  | ||||||
|     border-radius: 5px; |  | ||||||
|     position: absolute; |  | ||||||
|     margin-left: -105px; |  | ||||||
|     cursor: pointer; |  | ||||||
|     transition: background-color 0.3s ease; |  | ||||||
| } |  | ||||||
| .join-button:hover { |  | ||||||
|     background-color: #0056b3; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .modal { | .CL-my-chat-box-leave-btn { | ||||||
|     display: none; |     margin-left: 10px; | ||||||
|     position: fixed; |     margin-right: 8px; | ||||||
|     z-index: 1; |     justify-self: flex-end; | ||||||
|     left: 0; |     width: 16px; | ||||||
|     top: 0; |     cursor: pointer; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* The morbid thing */ | ||||||
|  | table.id-str-input-table { | ||||||
|     width: 100%; |     width: 100%; | ||||||
|     height: 100%; |     border-collapse: collapse; /* Combine borders */ | ||||||
|     overflow: auto; |  | ||||||
|     background-color: rgba(0, 0, 0, 0.4); |  | ||||||
| } | } | ||||||
| 
 | .id-str-input-td1, .id-str-input-td2 { | ||||||
| .modal-content { |  | ||||||
|     background-color: #fff; |  | ||||||
|     margin: 10% auto; |  | ||||||
|     padding: 20px; |  | ||||||
|     border: 1px solid #888; |  | ||||||
|     width: 80%; |  | ||||||
|     max-width: 400px; |  | ||||||
|     border-radius: 10px; |  | ||||||
|     box-shadow: 0 0 10px rgba(0, 0, 0, 0.2); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .modal-header, .modal-footer { |  | ||||||
|     padding: 10px; |  | ||||||
|     color: #333; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .modal-header { |  | ||||||
|     text-align: center; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .modal-footer { |  | ||||||
|     text-align: right; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .modal input { |  | ||||||
|     width: 93.5%; |  | ||||||
|     padding: 10px; |  | ||||||
|     margin: 10px 0; |  | ||||||
|     border: 1px solid #ddd; |  | ||||||
|     border-radius: 5px; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .create-room-button { |  | ||||||
|     display: block; |  | ||||||
|     width: 100%; |  | ||||||
|     padding: 10px; |  | ||||||
|     font-size: 16px; |  | ||||||
|     color: white; |  | ||||||
|     background-color: #1609ab; |  | ||||||
|     border: none; |     border: none; | ||||||
|     border-radius: 5px; |  | ||||||
|     cursor: pointer; |  | ||||||
|     transition: background-color 0.3s ease; |  | ||||||
|     margin-top: 20px; |  | ||||||
| } | } | ||||||
| 
 | .id-str-input-td1 { | ||||||
| .create-room-button:hover { |     text-align: left; | ||||||
|     background-color: #218838; |     padding-right: 5px; | ||||||
|  |     white-space: nowrap; /* Prevent text wrap, keeping it in one line */ | ||||||
|  |     overflow: hidden; /* Hide overflow content */ | ||||||
|  |     text-overflow: ellipsis; /* Show ellipsis for overflowing text */ | ||||||
| } | } | ||||||
| 
 | .id-str-input-td2 { | ||||||
| .overlay { |  | ||||||
|     display: none; |  | ||||||
|     position: fixed; |  | ||||||
|     top: 0; |  | ||||||
|     left: 0; |  | ||||||
|     width: 100%; |     width: 100%; | ||||||
|     height: 100%; | } | ||||||
|     background-color: rgba(0, 0, 0, 0.5); |  | ||||||
|     justify-content: center; |  | ||||||
|     align-items: center; |  | ||||||
|     z-index: 1000; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .overlay .add-members { |  | ||||||
|     background-color: white; |  | ||||||
|     padding: 30px; |  | ||||||
|     border-radius: 10px; |  | ||||||
|     box-shadow: 0 0 10px rgba(0, 0, 0, 0.2); |  | ||||||
|     max-width: 400px; |  | ||||||
|     width: 100%; |  | ||||||
|     height: 18%; |  | ||||||
|     position: fixed; |  | ||||||
| } |  | ||||||
| .overlay .delete-chat { |  | ||||||
|     background-color: white; |  | ||||||
|     padding: 30px; |  | ||||||
|     border-radius: 10px; |  | ||||||
|     box-shadow: 0 0 10px rgba(0, 0, 0, 0.2); |  | ||||||
|     max-width: 400px; |  | ||||||
|     width: 100%; |  | ||||||
|     height: 18%; |  | ||||||
|     position: fixed; |  | ||||||
| } |  | ||||||
| .delete-close { |  | ||||||
|     color: #aaa; |  | ||||||
|     float: right; |  | ||||||
|     font-size: 28px; |  | ||||||
|     font-weight: bold; |  | ||||||
|     cursor: pointer; |  | ||||||
| } |  | ||||||
| .delete-chat-header { |  | ||||||
|     text-align: center; |  | ||||||
| } |  | ||||||
| .confirm { |  | ||||||
|     background-color: #1609ab; |  | ||||||
|     padding: 20px 70px; |  | ||||||
|     font-size: 16px; |  | ||||||
|     color: white; |  | ||||||
|     border: none; |  | ||||||
|     border-radius: 5px; |  | ||||||
|     position: absolute; |  | ||||||
|     margin-left: 20px; |  | ||||||
|     cursor: pointer; |  | ||||||
|     transition: background-color 0.3s ease; |  | ||||||
| } |  | ||||||
| .cancel { |  | ||||||
|     background-color: #1609ab; |  | ||||||
|     padding: 20px 70px; |  | ||||||
|     font-size: 16px; |  | ||||||
|     color: white; |  | ||||||
|     border: none; |  | ||||||
|     border-radius: 5px; |  | ||||||
|     position: absolute; |  | ||||||
|     margin-left: 220px; |  | ||||||
|     cursor: pointer; |  | ||||||
|     transition: background-color 0.3s ease; |  | ||||||
| } |  | ||||||
| .close { |  | ||||||
|     color: #aaa; |  | ||||||
|     float: right; |  | ||||||
|     font-size: 28px; |  | ||||||
|     font-weight: bold; |  | ||||||
|     cursor: pointer; |  | ||||||
| } |  | ||||||
| @ -1,82 +1,46 @@ | |||||||
| body { | body { | ||||||
|     font-family: Arial, sans-serif; |  | ||||||
|     display: flex; |     display: flex; | ||||||
|  |     flex-direction: column; | ||||||
|     justify-content: center; |     justify-content: center; | ||||||
|     align-items: center; |     align-items: center; | ||||||
|     height: 100vh; |     height: 100vh; /* Full viewport height */ | ||||||
|     margin: 0; |     margin: 0; | ||||||
|     background-color: #ffffff; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .form-container { | .form-container { | ||||||
|  |     background-color: #ffffff; | ||||||
|  |     padding: 20px; | ||||||
|  |     border-radius: 10px; | ||||||
|  |     box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @media (orientation: landscape) { | ||||||
|  |     .form-container{ | ||||||
|  |         width: 50%; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @media (orientation: portrait){ | ||||||
|  |     .form-container{ | ||||||
|  |         width: 85%; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | /* The morbid thing */ | ||||||
|  | table.logins-input-table { | ||||||
|     width: 100%; |     width: 100%; | ||||||
|     max-width: 450px;  |     border-collapse: collapse; /* Combine borders */ | ||||||
|     background-color: #0c39ce; |  | ||||||
|     box-shadow: 0 0 15px rgba(0, 0, 0, 0.1); |  | ||||||
|     display: flex; |  | ||||||
|     flex-direction: column; |  | ||||||
|     border-radius: 12px; |  | ||||||
|     padding: 50px; |  | ||||||
|     text-align: center; |  | ||||||
|     color: white; |  | ||||||
|     transform: translateY(-40px); |  | ||||||
| } | } | ||||||
| 
 | .logins-input-td1, .logins-input-td2 { | ||||||
| h1 { |  | ||||||
|     margin-bottom: 20px; |  | ||||||
|     color: white; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| input { |  | ||||||
|     width: calc(100% - 50px); |  | ||||||
|     background: #f0f0f0; |  | ||||||
|     font-size: 16px; |  | ||||||
|     padding: 12px; |  | ||||||
|     border: 1px solid #ccc; |  | ||||||
|     border-radius: 20px; |  | ||||||
|     margin-bottom: 20px; |  | ||||||
|     outline: none; |  | ||||||
|     color: black; |  | ||||||
|     margin-left: 25px; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| button { |  | ||||||
|     width: calc(100% - 50px); |  | ||||||
|     padding: 15px; |  | ||||||
|     border: none; |     border: none; | ||||||
|     background-color: #024a7e; |  | ||||||
|     color: white; |  | ||||||
|     border-radius: 20px; |  | ||||||
|     cursor: pointer; |  | ||||||
|     outline: none; |  | ||||||
|     font-size: 18px; |  | ||||||
|     font-weight: bold; |  | ||||||
|     transition: background-color 0.3s; |  | ||||||
|     margin-left: 25px; |  | ||||||
| } | } | ||||||
| 
 | .logins-input-td1 { | ||||||
| button:hover, |     padding-right: 5px; | ||||||
| button:focus-visible { |     white-space: nowrap; /* Prevent text wrap, keeping it in one line */ | ||||||
|     background-color: #28a745; |     overflow: hidden; /* Hide overflow content */ | ||||||
|  |     text-overflow: ellipsis; /* Show ellipsis for overflowing text */ | ||||||
| } | } | ||||||
| 
 | .logins-input-td2 { | ||||||
| .hide-cursor::placeholder { |     width: 100%; | ||||||
|     color: #777; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .hide-cursor { |  | ||||||
|     caret-color: transparent; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .no-select { |  | ||||||
|     -webkit-user-select: none; |  | ||||||
|     -moz-user-select: none; |  | ||||||
|     user-select: none; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| div { |  | ||||||
|     color: rgba(255, 0, 0, 0.911); |  | ||||||
|     font-size: 15px; |  | ||||||
|     margin-top: 10px; |  | ||||||
|     display: none; |  | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,129 +0,0 @@ | |||||||
| body { |  | ||||||
|     display: flex; |  | ||||||
|     justify-content: center; |  | ||||||
|     align-items: center; |  | ||||||
|     height: 90vh; |  | ||||||
|     background-color: #e5e5e5; |  | ||||||
|     font-family: Arial, sans-serif; |  | ||||||
| } |  | ||||||
| .main-container { |  | ||||||
|     width: 700px; |  | ||||||
|     height: 700px; |  | ||||||
|     border-color: antiquewhite; |  | ||||||
|     background-color: white; |  | ||||||
|     display: flex; |  | ||||||
|     flex-direction: column; |  | ||||||
|     align-items: center; |  | ||||||
|     justify-content: center; |  | ||||||
|     text-align: center; |  | ||||||
|     border-radius: 10px; |  | ||||||
| } |  | ||||||
| .profile-header { |  | ||||||
|     width: 700px; |  | ||||||
|     height: 160px; |  | ||||||
|     border-color: antiquewhite; |  | ||||||
|     background-color: #0088cc; |  | ||||||
|     border-radius: 10px; |  | ||||||
|     position: relative; |  | ||||||
| } |  | ||||||
| .return { |  | ||||||
|     background-color: #f0f0f0; |  | ||||||
|     cursor: pointer; |  | ||||||
|     width: 100px; |  | ||||||
|     text-decoration: none; |  | ||||||
|     color: black; |  | ||||||
|     display: flex; |  | ||||||
|     justify-content: center; |  | ||||||
|     align-items: center; |  | ||||||
|     height: 30px; |  | ||||||
|     border-radius: 10px; |  | ||||||
|     position: absolute; |  | ||||||
|     left: 20px; |  | ||||||
|     top: 25px; |  | ||||||
|     border: none; |  | ||||||
| } |  | ||||||
| .return:hover{ |  | ||||||
|     text-decoration: underline; |  | ||||||
|     color: #0088cc; |  | ||||||
| } |  | ||||||
| form { |  | ||||||
|     display: flex; |  | ||||||
|     flex-direction: column; |  | ||||||
|     align-items: center; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .columns { |  | ||||||
|     display: flex; |  | ||||||
|     justify-content: center; |  | ||||||
|     align-items: flex-start; |  | ||||||
|     gap: 20px; |  | ||||||
|     margin-bottom: 20px; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .column { |  | ||||||
|     display: flex; |  | ||||||
|     flex-direction: column; |  | ||||||
|     align-items: center; |  | ||||||
| } |  | ||||||
| .add { |  | ||||||
|     width: 100px; |  | ||||||
|     height: 40px; |  | ||||||
|     border-width: 2px; |  | ||||||
|     cursor: pointer; |  | ||||||
|     font-size: 16px; |  | ||||||
|     border-radius: 10px; |  | ||||||
| } |  | ||||||
| .add:hover { |  | ||||||
|     background-color: #007bb5; |  | ||||||
| } |  | ||||||
| .image-button:hover { |  | ||||||
|     opacity: 0.8; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .image-button:active { |  | ||||||
|     transform: scale(0.95); |  | ||||||
| } |  | ||||||
| #login { |  | ||||||
|     font-family: Arial, sans-serif; |  | ||||||
|     font-size:16px; |  | ||||||
|     width: 150px; |  | ||||||
|     height: 20px; |  | ||||||
|     border-radius: 10px; |  | ||||||
|     border-color: #2F4F4F; |  | ||||||
| } |  | ||||||
| #username { |  | ||||||
|     font-family: Arial, sans-serif; |  | ||||||
|     font-size:16px; |  | ||||||
|     width: 150px; |  | ||||||
|     height: 20px; |  | ||||||
|     margin-bottom: 1px; |  | ||||||
|     margin-top: 50px; |  | ||||||
|     border-radius: 10px; |  | ||||||
|     border-color: #2F4F4F; |  | ||||||
| } |  | ||||||
| #bio { |  | ||||||
|     height: 150px; |  | ||||||
|     width: 500px; |  | ||||||
|     padding: 10px; |  | ||||||
|     box-sizing: border-box; |  | ||||||
|     font-family: Arial, sans-serif; |  | ||||||
|     font-size:14px; |  | ||||||
|     text-align: left; |  | ||||||
|     vertical-align: top; |  | ||||||
|     margin-bottom: 5px; |  | ||||||
| } |  | ||||||
| .save { |  | ||||||
|     cursor:pointer; |  | ||||||
|     font-size: 16px; |  | ||||||
|     border-radius: 15px; |  | ||||||
|     border-color: #2F4F4F; |  | ||||||
|     height: 40px; |  | ||||||
|     width: 150px; |  | ||||||
| } |  | ||||||
| .save:hover { |  | ||||||
|     background-color: #007bb5; |  | ||||||
| } |  | ||||||
| .avatar { |  | ||||||
|     border-radius: 50%; |  | ||||||
|     object-fit: cover; |  | ||||||
| } |  | ||||||
							
								
								
									
										45
									
								
								assets/css/register.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,45 @@ | |||||||
|  | body { | ||||||
|  |     display: flex; | ||||||
|  |     flex-direction: column; | ||||||
|  |     justify-content: center; | ||||||
|  |     align-items: center; | ||||||
|  |     height: 100vh; /* Full viewport height */ | ||||||
|  |     margin: 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .form-container { | ||||||
|  |     background-color: #ffffff; | ||||||
|  |     padding: 20px; | ||||||
|  |     border-radius: 10px; | ||||||
|  |     box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @media (orientation: landscape) { | ||||||
|  |     .form-container{ | ||||||
|  |         width: 60%; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @media (orientation: portrait){ | ||||||
|  |     .form-container{ | ||||||
|  |         width: 90%; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* The morbid thing */ | ||||||
|  | table.reg-input-table { | ||||||
|  |     width: 100%; | ||||||
|  |     border-collapse: collapse; /* Combine borders */ | ||||||
|  | } | ||||||
|  | .reg-input-td1, .reg-input-td2 { | ||||||
|  |     border: none; | ||||||
|  | } | ||||||
|  | .reg-input-td1 { | ||||||
|  |     padding-right: 5px; | ||||||
|  |     white-space: nowrap; /* Prevent text wrap, keeping it in one line */ | ||||||
|  |     overflow: hidden; /* Hide overflow content */ | ||||||
|  |     text-overflow: ellipsis; /* Show ellipsis for overflowing text */ | ||||||
|  | } | ||||||
|  | .reg-input-td2 { | ||||||
|  |     width: 100%; | ||||||
|  | } | ||||||
| @ -1,77 +0,0 @@ | |||||||
| dy { |  | ||||||
|     font-family: Arial, sans-serif; |  | ||||||
|     display: flex; |  | ||||||
|     justify-content: center; |  | ||||||
|     align-items: center; |  | ||||||
|     height: 100vh; |  | ||||||
|     margin: 0; |  | ||||||
|     background-color: #e5e5e5; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .form-container { |  | ||||||
|     width: 100%; |  | ||||||
|     max-width: 400px; |  | ||||||
|     background-color: white; |  | ||||||
|     box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); |  | ||||||
|     display: flex; |  | ||||||
|     flex-direction: column; |  | ||||||
|     border-radius: 8px; |  | ||||||
|     padding: 40px; |  | ||||||
|     text-align: center; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| h1 { |  | ||||||
|     margin-bottom: 20px; |  | ||||||
|     color: #2F4F4F; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| input { |  | ||||||
|     width: 100%; |  | ||||||
|     background: #f7f7f7; |  | ||||||
|     font-size: 16px; |  | ||||||
|     padding: 10px; |  | ||||||
|     border: 1px solid #ddd; |  | ||||||
|     border-radius: 20px; |  | ||||||
|     margin-bottom: 15px; |  | ||||||
|     outline: none; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| button { |  | ||||||
|     width: 100%; |  | ||||||
|     padding: 15px; |  | ||||||
|     border: none; |  | ||||||
|     background-color: #0088cc; |  | ||||||
|     color: white; |  | ||||||
|     border-radius: 20px; |  | ||||||
|     cursor: pointer; |  | ||||||
|     outline: none; |  | ||||||
|     font-size: 16px; |  | ||||||
|     font-weight: bold; |  | ||||||
|     transition: background-color 0.3s; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| button:hover, |  | ||||||
| button:focus-visible { |  | ||||||
|     background-color: #007bb5; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .hide-cursor::placeholder { |  | ||||||
|     color: #000; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .hide-cursor { |  | ||||||
|     caret-color: transparent; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .no-select { |  | ||||||
|     -webkit-user-select: none; /* Для Safari */ |  | ||||||
|     -moz-user-select: none;    /* Для Firefox */ |  | ||||||
|     user-select: none;         /* Для всех остальных браузеров */ |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| div { |  | ||||||
|     color: red; |  | ||||||
|     font-size: 15px; |  | ||||||
|     margin-top: 10px; |  | ||||||
|     display: none; |  | ||||||
| } |  | ||||||
							
								
								
									
										
											BIN
										
									
								
								assets/gif/loading.gif
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 136 KiB | 
							
								
								
									
										20
									
								
								assets/img/add.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,20 @@ | |||||||
|  | <?xml version="1.0" encoding="UTF-8" standalone="no"?> | ||||||
|  | <svg | ||||||
|  |    width="128" | ||||||
|  |    height="128" | ||||||
|  |    viewBox="0 0 3.84 3.84" | ||||||
|  |    fill="none" | ||||||
|  |    version="1.1" | ||||||
|  |    id="svg1" | ||||||
|  |    xmlns="http://www.w3.org/2000/svg" | ||||||
|  |    xmlns:svg="http://www.w3.org/2000/svg"> | ||||||
|  |   <defs | ||||||
|  |      id="defs1" /> | ||||||
|  |   <path | ||||||
|  |      fill-rule="evenodd" | ||||||
|  |      clip-rule="evenodd" | ||||||
|  |      d="m 1.9142674,3.7842088 c -0.8814997,0 -1.32224859,0 -1.59609402,-0.2738529 C 0.04432607,3.2365216 0.04432607,2.7957577 0.04432607,1.9142674 c 0,-0.8814997 0,-1.32224859 0.27384731,-1.59609402 C 0.59201881,0.04432607 1.0327677,0.04432607 1.9142674,0.04432607 c 0.8814903,0 1.3222542,0 1.5960885,0.27384731 0.2738529,0.27384543 0.2738529,0.71459432 0.2738529,1.59609402 0,0.8814903 0,1.3222542 -0.2738529,1.5960885 C 3.2365216,3.7842088 2.7957577,3.7842088 1.9142674,3.7842088 Z m 0,-2.5711694 c 0.077453,0 0.1402457,0.062791 0.1402457,0.1402456 v 0.4207368 h 0.4207367 c 0.077453,0 0.1402456,0.062793 0.1402456,0.1402456 0,0.077453 -0.062793,0.1402457 -0.1402456,0.1402457 H 2.0545131 v 0.4207367 c 0,0.077453 -0.062793,0.1402456 -0.1402457,0.1402456 -0.077453,0 -0.1402456,-0.062793 -0.1402456,-0.1402456 V 2.0545131 H 1.353285 c -0.077455,0 -0.1402456,-0.062793 -0.1402456,-0.1402457 0,-0.077453 0.062791,-0.1402456 0.1402456,-0.1402456 H 1.7740218 V 1.353285 c 0,-0.077455 0.062793,-0.1402456 0.1402456,-0.1402456 z" | ||||||
|  |      fill="#1c274c" | ||||||
|  |      id="path1" | ||||||
|  |      style="stroke-width:0.186994" /> | ||||||
|  | </svg> | ||||||
| After Width: | Height: | Size: 1.4 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/img/broken-clavicle.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 258 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/img/clavicle-transparent.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 288 KiB | 
							
								
								
									
										18
									
								
								assets/img/delete.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,18 @@ | |||||||
|  | <?xml version="1.0" encoding="UTF-8" standalone="no"?> | ||||||
|  | <svg | ||||||
|  |    fill="#000000" | ||||||
|  |    width="128" | ||||||
|  |    height="128" | ||||||
|  |    viewBox="-1.7 0 3.264 3.264" | ||||||
|  |    class="cf-icon-svg" | ||||||
|  |    version="1.1" | ||||||
|  |    id="svg1" | ||||||
|  |    xmlns="http://www.w3.org/2000/svg" | ||||||
|  |    xmlns:svg="http://www.w3.org/2000/svg"> | ||||||
|  |   <defs | ||||||
|  |      id="defs1" /> | ||||||
|  |   <path | ||||||
|  |      d="M 1.4376583,1.6137431 A 1.5211864,1.5211864 0 1 1 -0.08352807,0.09255663 1.5209944,1.5209944 0 0 1 1.4376583,1.6137431 Z m -1.30733258,0.00192 0.58257383,-0.582766 A 0.15217629,0.15217629 0 0 0 0.49770077,0.81769971 L -0.08468094,1.4004657 -0.66763909,0.81769971 A 0.15217629,0.15217629 0 0 0 -0.88283792,1.0328985 l 0.5829582,0.5827659 -0.58276597,0.5827661 a 0.15217629,0.15217629 0 0 0 0.21519874,0.2150066 l 0.58257388,-0.582766 0.58276608,0.582766 A 0.15217629,0.15217629 0 0 0 0.71309179,2.1982382 Z" | ||||||
|  |      id="path1" | ||||||
|  |      style="display:inline;fill:#ff0000;fill-opacity:1;stroke-width:0.192143" /> | ||||||
|  | </svg> | ||||||
| After Width: | Height: | Size: 929 B | 
| Before Width: | Height: | Size: 81 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/img/exit.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 4.8 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/img/favicon.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 824 B | 
							
								
								
									
										30
									
								
								assets/img/link.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,30 @@ | |||||||
|  | <?xml version="1.0" encoding="UTF-8" standalone="no"?> | ||||||
|  | <svg | ||||||
|  |    fill="#000000" | ||||||
|  |    version="1.1" | ||||||
|  |    id="Capa_1" | ||||||
|  |    width="128" | ||||||
|  |    height="128" | ||||||
|  |    viewBox="0 0 70.75936 70.75936" | ||||||
|  |    xml:space="preserve" | ||||||
|  |    xmlns="http://www.w3.org/2000/svg" | ||||||
|  |    xmlns:svg="http://www.w3.org/2000/svg"><defs | ||||||
|  |    id="defs3" /> | ||||||
|  | <g | ||||||
|  |    id="g3" | ||||||
|  |    style="fill:#c8f5ff;fill-opacity:1;stroke:#6adeef;stroke-width:8.62702;stroke-dasharray:none;stroke-opacity:1" | ||||||
|  |    transform="matrix(0.1490509,0,0,0.14948634,3.4471925,2.5065977)"> | ||||||
|  | 	<g | ||||||
|  |    id="g2" | ||||||
|  |    style="fill:#c8f5ff;fill-opacity:1;stroke:#6adeef;stroke-width:8.62702;stroke-dasharray:none;stroke-opacity:1"> | ||||||
|  | 		<path | ||||||
|  |    d="m 409.657,32.474 c -43.146,-43.146 -113.832,-43.146 -156.978,0 l -84.763,84.762 c 29.07,-8.262 60.589,-6.12 88.129,6.732 l 44.063,-44.064 c 17.136,-17.136 44.982,-17.136 62.118,0 17.136,17.136 17.136,44.982 0,62.118 l -55.386,55.386 -36.414,36.414 c -17.136,17.136 -44.982,17.136 -62.119,0 l -47.43,47.43 c 11.016,11.017 23.868,19.278 37.332,24.48 36.415,14.382 78.643,8.874 110.467,-16.219 3.06,-2.447 6.426,-5.201 9.18,-8.262 l 57.222,-57.222 34.578,-34.578 c 43.453,-43.145 43.453,-113.525 10e-4,-156.977 z" | ||||||
|  |    id="path1" | ||||||
|  |    style="fill:#c8f5ff;fill-opacity:1;stroke:#6adeef;stroke-width:8.62702;stroke-dasharray:none;stroke-opacity:1" /> | ||||||
|  | 		<path | ||||||
|  |    d="m 184.135,320.114 -42.228,42.228 c -17.136,17.137 -44.982,17.137 -62.118,0 -17.136,-17.136 -17.136,-44.981 0,-62.118 l 91.8,-91.799 c 17.136,-17.136 44.982,-17.136 62.119,0 l 47.43,-47.43 c -11.016,-11.016 -23.868,-19.278 -37.332,-24.48 -38.25,-15.3 -83.232,-8.262 -115.362,20.502 -1.53,1.224 -3.06,2.754 -4.284,3.978 l -91.8,91.799 c -43.146,43.146 -43.146,113.832 0,156.979 43.146,43.146 113.832,43.146 156.978,0 l 82.927,-83.845 c -42.23,9.791 -52.022,8.568 -88.13,-5.814 z" | ||||||
|  |    id="path2" | ||||||
|  |    style="fill:#c8f5ff;fill-opacity:1;stroke:#6adeef;stroke-width:8.62702;stroke-dasharray:none;stroke-opacity:1" /> | ||||||
|  | 	</g> | ||||||
|  | </g> | ||||||
|  | </svg> | ||||||
| After Width: | Height: | Size: 1.9 KiB | 
							
								
								
									
										28
									
								
								assets/img/list-rooms.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,28 @@ | |||||||
|  | <?xml version="1.0" encoding="UTF-8" standalone="no"?> | ||||||
|  | 
 | ||||||
|  | <svg | ||||||
|  |    width="127.99999" | ||||||
|  |    height="127.99999" | ||||||
|  |    viewBox="0 0 33.866664 33.866664" | ||||||
|  |    version="1.1" | ||||||
|  |    id="svg1" | ||||||
|  |    xmlns="http://www.w3.org/2000/svg" | ||||||
|  |    xmlns:svg="http://www.w3.org/2000/svg"> | ||||||
|  |   <defs | ||||||
|  |      id="defs1" /> | ||||||
|  |   <g | ||||||
|  |      id="layer1"> | ||||||
|  |     <path | ||||||
|  |        style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1" | ||||||
|  |        d="M 6.2911505,3.4844006 V 6.6404405 H 3.0362141 V 28.776832 h 4.7418555 v 2.929792 H 0.58956039 V 3.438992 Z" | ||||||
|  |        id="path63" /> | ||||||
|  |     <path | ||||||
|  |        style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1" | ||||||
|  |        d="m 27.557741,31.386555 v -3.15604 h 3.254937 V 6.0941238 h -4.741856 v -2.929792 h 7.188509 V 31.431963 Z" | ||||||
|  |        id="path63-2" /> | ||||||
|  |     <path | ||||||
|  |        id="rect63" | ||||||
|  |        style="fill:#fdffff;fill-opacity:1;fill-rule:nonzero;stroke:#e1e1e1;stroke-width:0.529167;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1" | ||||||
|  |        d="m 8.7183307,8.3901854 c -2.467802,0 -4.4545084,1.9867066 -4.4545084,4.4545086 v 5.357812 c 0,2.467802 1.9867064,4.454509 4.4545084,4.454509 0,0 6.6668103,0 10.2365843,0 1.184667,0 1.8523,1.573101 2.973462,1.946651 1.827305,0.608823 5.758305,0.474906 5.758305,0.474906 0,0 -1.204106,-0.984163 -2.336589,-2.042039 -0.307888,-0.287603 -0.182877,-1.488944 0.134028,-1.896921 0.674916,-0.868872 0.918918,-1.680982 0.918918,-2.937106 v -5.357812 c 0,-2.467802 -1.986706,-4.4545086 -4.454508,-4.4545086 z" /> | ||||||
|  |   </g> | ||||||
|  | </svg> | ||||||
| After Width: | Height: | Size: 1.6 KiB | 
							
								
								
									
										20
									
								
								assets/img/return.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,20 @@ | |||||||
|  | <?xml version="1.0" encoding="UTF-8" standalone="no"?> | ||||||
|  | 
 | ||||||
|  | <svg | ||||||
|  |    width="128" | ||||||
|  |    height="127.99999" | ||||||
|  |    viewBox="0 0 33.866667 33.866664" | ||||||
|  |    version="1.1" | ||||||
|  |    id="svg1" | ||||||
|  |    xmlns="http://www.w3.org/2000/svg" | ||||||
|  |    xmlns:svg="http://www.w3.org/2000/svg"> | ||||||
|  |   <defs | ||||||
|  |      id="defs1" /> | ||||||
|  |   <g | ||||||
|  |      id="layer1"> | ||||||
|  |     <path | ||||||
|  |        style="fill:#000000;fill-opacity:1;stroke:#ffffff;stroke-width:0.294159;stroke-linejoin:round;stroke-opacity:1" | ||||||
|  |        d="m 0.65443061,23.377766 10.20536539,-7.351693 -0.06427,3.749871 c 0,0 6.384296,0.115304 9.607291,0.115304 1.824632,0 4.010317,0.117998 5.361398,-1.065432 0.797635,-0.698657 1.041209,-1.943308 0.954461,-2.97946 -0.118465,-1.414973 -0.417733,-2.830021 -1.492491,-3.799298 -0.964892,-0.870193 -2.338758,-1.449022 -3.659301,-1.465666 -1.347581,-0.01698 -4.311633,-0.01476 -4.311633,-0.01476 l 0.02678,-6.0468658 c 0,0 5.209174,-0.1269759 7.836958,-0.1269759 2.090574,0 3.730335,0.2813604 5.374446,1.5274776 1.165995,0.8837399 2.874757,2.1212447 2.874757,3.8971142 0,3.5737799 -0.03341,6.8479459 -0.03341,10.6502529 0,3.155641 -2.159421,4.126978 -3.036427,4.888332 -1.783594,1.548393 -4.069763,1.553329 -6.468859,1.553329 -4.709142,0 -13.295569,0 -13.295569,0 v 3.244296 z" | ||||||
|  |        id="path1" /> | ||||||
|  |   </g> | ||||||
|  | </svg> | ||||||
| After Width: | Height: | Size: 1.2 KiB | 
							
								
								
									
										20
									
								
								assets/img/settings-iron.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,20 @@ | |||||||
|  | <?xml version="1.0" encoding="UTF-8" standalone="no"?> | ||||||
|  | 
 | ||||||
|  | <svg | ||||||
|  |    width="127.99999" | ||||||
|  |    height="127.99999" | ||||||
|  |    viewBox="0 0 33.866664 33.866664" | ||||||
|  |    version="1.1" | ||||||
|  |    id="svg1" | ||||||
|  |    xmlns="http://www.w3.org/2000/svg" | ||||||
|  |    xmlns:svg="http://www.w3.org/2000/svg"> | ||||||
|  |   <defs | ||||||
|  |      id="defs1" /> | ||||||
|  |   <g | ||||||
|  |      id="layer1"> | ||||||
|  |     <path | ||||||
|  |        id="path11" | ||||||
|  |        style="fill:#e6e6e6;fill-opacity:1;stroke:#b3b3b3;stroke-width:0.792427;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1" | ||||||
|  |        d="m 9.8376422,3.4995279 c -0.413454,0.04422 -0.9249931,0.2733392 -1.5859497,0.7720459 -2.15303,1.6245101 -1.0645387,2.7213368 -0.2780192,3.6576578 0.9643744,1.1480481 0.5272069,2.4297214 -0.2444295,3.5413854 -1.0846469,1.562605 -1.8094411,2.689497 -3.1812011,2.618445 -1.7185528,-0.08902 -3.2061983,0.950419 -3.1817179,2.618962 0.020151,1.373449 0.960489,2.763844 2.9987833,2.826184 1.3729571,0.04199 2.35275,1.613748 2.9993001,2.825667 0.6465497,1.211919 1.3490594,3.417259 0.6769613,4.064868 -0.989134,0.953094 -1.312826,2.263816 0.6769612,4.064868 1.2829023,1.161218 2.6693573,0.639536 3.7666953,-0.946195 1.179214,-1.70405 2.337505,-1.523233 4.127396,-1.422652 1.371435,0.07707 2.886284,-0.325958 3.858679,1.446423 0.660704,1.204262 1.722209,2.911554 3.85868,1.445906 1.132687,-0.77704 2.468135,-2.085182 0.947745,-4.010091 -0.610564,-0.773014 -0.532201,-2.186383 0.948263,-4.010607 1.255317,-1.546798 1.98091,-2.751433 3.181201,-2.618962 1.365308,0.150686 2.84107,-0.224934 3.181718,-2.618445 0.366273,-2.57357 -1.941907,-2.695322 -3.312976,-2.778641 C 28.1324,14.906856 28.128014,13.488893 26.590624,12.103137 25.053235,10.717381 25.023658,9.341954 26.190649,8.2930337 27.35764,7.2441129 27.633021,6.3291961 25.719877,4.5222045 24.596532,3.461188 23.254965,3.1719841 21.630204,4.9759236 20.911774,5.7735833 19.336054,7.1894888 17.196366,7.1086099 15.002057,7.0256665 13.838949,7.4611865 12.285555,5.8053303 11.434493,4.8981315 11.078004,3.3668691 9.8376422,3.4995279 Z m 6.7892498,6.5215661 a 7.4172139,7.0961218 0 0 1 7.417118,7.096207 7.4172139,7.0961218 0 0 1 -7.417118,7.096208 7.4172139,7.0961218 0 0 1 -7.4171184,-7.096208 7.4172139,7.0961218 0 0 1 7.4171184,-7.096207 z" /> | ||||||
|  |   </g> | ||||||
|  | </svg> | ||||||
| After Width: | Height: | Size: 2.1 KiB | 
							
								
								
									
										24
									
								
								assets/img/user.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,24 @@ | |||||||
|  | <?xml version="1.0" encoding="UTF-8" standalone="no"?> | ||||||
|  | 
 | ||||||
|  | <svg | ||||||
|  |    width="127.99999" | ||||||
|  |    height="127.99999" | ||||||
|  |    viewBox="0 0 33.866664 33.866664" | ||||||
|  |    version="1.1" | ||||||
|  |    id="svg1" | ||||||
|  |    xmlns="http://www.w3.org/2000/svg" | ||||||
|  |    xmlns:svg="http://www.w3.org/2000/svg"> | ||||||
|  |   <defs | ||||||
|  |      id="defs1" /> | ||||||
|  |   <g | ||||||
|  |      id="layer1"> | ||||||
|  |     <path | ||||||
|  |        style="fill:#000080;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.465;stroke-linejoin:round;stroke-dasharray:none" | ||||||
|  |        d="M 9.3745975,32.81176 H 25.632941 c 0,0 0.08996,-6.044498 -0.03778,-10.988966 0.0321,-3.524476 -1.327193,-6.290541 -8.363226,-6.155275 -7.036034,0.135265 -7.7455423,2.805955 -7.7693511,5.331649 -0.034594,3.669826 -0.087981,11.812592 -0.087981,11.812592 z" | ||||||
|  |        id="path7" /> | ||||||
|  |     <path | ||||||
|  |        id="path8" | ||||||
|  |        style="fill:#000080;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.965;stroke-linejoin:round;stroke-dasharray:none" | ||||||
|  |        d="M 17.963761,1.3260172 A 11.612947,6.192831 0 0 0 6.3510334,7.5189208 11.612947,6.192831 0 0 0 17.963761,13.711824 11.612947,6.192831 0 0 0 29.576489,7.5189208 11.612947,6.192831 0 0 0 17.963761,1.3260172 Z m -3.947046,2.876827 a 1.5186517,3.4351659 0 0 1 1.518771,3.4349323 1.5186517,3.4351659 0 0 1 -1.518771,3.4354495 1.5186517,3.4351659 0 0 1 -1.518253,-3.4354495 1.5186517,3.4351659 0 0 1 1.518253,-3.4349323 z m 6.47299,0.2160074 a 1.5186517,3.4351659 0 0 1 1.518254,3.4349324 1.5186517,3.4351659 0 0 1 -1.518254,3.435449 1.5186517,3.4351659 0 0 1 -1.51877,-3.435449 1.5186517,3.4351659 0 0 1 1.51877,-3.4349324 z" /> | ||||||
|  |   </g> | ||||||
|  | </svg> | ||||||
| After Width: | Height: | Size: 1.5 KiB | 
							
								
								
									
										188
									
								
								assets/js/chat-members.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,188 @@ | |||||||
|  | let LocalHistoryId = 0; | ||||||
|  | 
 | ||||||
|  | function genSentBase(){ | ||||||
|  |     return { | ||||||
|  |         'chatUpdReq': { | ||||||
|  |             'LocalHistoryId': LocalHistoryId, | ||||||
|  |             'chatId': openedchat.id | ||||||
|  |         } | ||||||
|  |     }; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | let members = new Map(); | ||||||
|  | let memberBoxes = new Map(); | ||||||
|  | let myRoleHere = null;  // Dung local state updates should be updated first
 | ||||||
|  | 
 | ||||||
|  | let userDeletionWinStoredUId = -1; | ||||||
|  | 
 | ||||||
|  | function shouldShowDeleteButton(memberSt){ | ||||||
|  |     return userinfo.uid !== memberSt.userId && myRoleHere === userChatRoleAdmin && memberSt.roleHere !== userChatRoleDeleted; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function updateBoxWithSt(box, memberSt){ | ||||||
|  |     let ID = memberSt.userId; | ||||||
|  |     let roleP = box.querySelector(".CM-member-box-role"); | ||||||
|  |     roleP.innerText = memberSt.roleHere; | ||||||
|  |     box.style.backgroundColor = roleToColor(memberSt.roleHere); | ||||||
|  |     box.querySelector(".CM-member-box-leave-btn").style.display = | ||||||
|  |         (shouldShowDeleteButton(memberSt) ? "block" : "none"); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function convertMemberStToBox(memberSt){ | ||||||
|  |     let ID = memberSt.userId; | ||||||
|  |     let userProfileURI = "/user/" + memberSt.nickname; | ||||||
|  | 
 | ||||||
|  |     let box = document.createElement("div"); | ||||||
|  |     box.className = "dynamic-block-list-el CM-member-box"; | ||||||
|  |     box.style.backgroundColor = roleToColor(memberSt.roleHere); | ||||||
|  | 
 | ||||||
|  |     let inBoxNickname = document.createElement("a"); | ||||||
|  |     box.appendChild(inBoxNickname); | ||||||
|  |     inBoxNickname.className = "entity-nickname-txt CM-member-box-nickname"; | ||||||
|  |     inBoxNickname.innerText = memberSt.nickname; | ||||||
|  |     inBoxNickname.href = userProfileURI; | ||||||
|  | 
 | ||||||
|  |     let inBoxName = document.createElement("a"); | ||||||
|  |     box.appendChild(inBoxName); | ||||||
|  |     inBoxName.className = "entity-reg-field-txt CM-member-box-name"; | ||||||
|  |     inBoxName.innerText = memberSt.name; | ||||||
|  |     inBoxName.href = userProfileURI; | ||||||
|  | 
 | ||||||
|  |     let inBoxUserRoleHere = document.createElement("p"); | ||||||
|  |     box.appendChild(inBoxUserRoleHere); | ||||||
|  |     inBoxUserRoleHere.className = "entity-reg-field-txt CM-member-box-role"; | ||||||
|  |     inBoxUserRoleHere.innerText = memberSt.roleHere; | ||||||
|  | 
 | ||||||
|  |     let inBoxLeaveBtn = document.createElement("img"); | ||||||
|  |     box.appendChild(inBoxLeaveBtn); | ||||||
|  |     inBoxLeaveBtn.className = "CM-member-box-leave-btn"; | ||||||
|  |     inBoxLeaveBtn.src = "/assets/img/delete.svg"; | ||||||
|  |     inBoxLeaveBtn.onclick = function (ev) { | ||||||
|  |         if (ev.button !== 0) | ||||||
|  |             return; | ||||||
|  |         userDeletionWinStoredUId = ID; | ||||||
|  |         document.getElementById("user-deletion-win-title").innerText = | ||||||
|  |             pres['chat-members']['reask-kick-user-X'] + " " + memberSt.nickname + "?"; | ||||||
|  |         activatePopupWindowById("user-deletion-win"); | ||||||
|  |     }; | ||||||
|  |     box.querySelector(".CM-member-box-leave-btn").style.display = | ||||||
|  |         (shouldShowDeleteButton(memberSt) ? "block" : "none"); | ||||||
|  | 
 | ||||||
|  |     return box; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function updateLocalStateFromChatUpdResp(chatUpdResp){ | ||||||
|  |     LocalHistoryId = chatUpdResp.HistoryId; | ||||||
|  |     // If my role is updated, we need to update all the boes of already set users (kick button can appear and disappear)
 | ||||||
|  |     let literalMemberList = document.getElementById("CM-list"); | ||||||
|  |     // We ignore messages and everything related to them. Dang, I really should add an argument to disable message lookup here
 | ||||||
|  |     for (let memberSt of chatUpdResp.members){ | ||||||
|  |         console.log([memberSt, userinfo.uid, myRoleHere]); | ||||||
|  |         if (memberSt.userId === userinfo.uid && myRoleHere !== memberSt.roleHere){ | ||||||
|  |             myRoleHere = memberSt.roleHere; | ||||||
|  |             for (let [id, memberSt] of members){ | ||||||
|  |                 let box = memberBoxes.get(id); | ||||||
|  |                 updateBoxWithSt(box, memberSt); | ||||||
|  |             } | ||||||
|  |             document.getElementById("CM-btn-add").style.display = | ||||||
|  |                 (memberSt.roleHere === userChatRoleAdmin ? "block" : "none"); | ||||||
|  |             console.log("DEBUG " + (memberSt.roleHere === userChatRoleAdmin ? "block" : "none")); | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     for (let memberSt of chatUpdResp.members){ | ||||||
|  |         let id = memberSt.userId; | ||||||
|  |         if (members.has(id)){ | ||||||
|  |             updateBoxWithSt(memberBoxes.get(id), memberSt); | ||||||
|  |         } else { | ||||||
|  |             if (memberSt.roleHere !== userChatRoleDeleted){ | ||||||
|  |                 members.set(id, memberSt); | ||||||
|  |                 let box = convertMemberStToBox(memberSt); | ||||||
|  |                 memberBoxes.set(id, box); | ||||||
|  |                 literalMemberList.appendChild(box); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function updateLocalStateFromRecv(Recv){ | ||||||
|  |     updateLocalStateFromChatUpdResp(Recv.chatUpdResp); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function configureSummonUserInterface(){ | ||||||
|  |     document.getElementById("user-summoning-yes").onclick = function(ev ){ | ||||||
|  |         if (ev.button !==0) | ||||||
|  |             return; | ||||||
|  |         let nickname = String(document.getElementById("summoned-user-nickname").value); | ||||||
|  |         let isReadOnly = document.getElementById("summoned-user-is-read-only").checked; | ||||||
|  |         deactivateActivePopup(); | ||||||
|  |         let Sent = genSentBase(); | ||||||
|  |         Sent.nickname = nickname; | ||||||
|  |         Sent.makeReadOnly = Boolean(isReadOnly); | ||||||
|  |         apiRequest("addMemberToChat", Sent). | ||||||
|  |         then((Recv) => { | ||||||
|  |             updateLocalStateFromRecv(Recv); | ||||||
|  |         }).catch((e) => { | ||||||
|  |            console.log(e); | ||||||
|  |            alert(pres['chat-members']["failed-summon-member"]); | ||||||
|  |         }); | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     document.getElementById("user-summoning-no").onclick = function (ev) { | ||||||
|  |         if (ev.button !== 0) | ||||||
|  |             return; | ||||||
|  |         deactivateActivePopup(); | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     document.getElementById("CM-btn-add").onclick = function(ev) { | ||||||
|  |         if (ev.button !== 0) | ||||||
|  |             return; | ||||||
|  |         document.getElementById("summoned-user-nickname").value = ""; | ||||||
|  |         // read-only flag persists throughout user summoning sessions, and IT IS NOT A BUG
 | ||||||
|  |         activatePopupWindowById("user-summoning-win"); | ||||||
|  |     }; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* Popup activation button is configured for each box separately */ | ||||||
|  | function configureKickUserInterfaceWinPart(){ | ||||||
|  |     document.getElementById("user-deletion-yes").onclick = function (ev){ | ||||||
|  |         if (ev.button !== 0) | ||||||
|  |             return; | ||||||
|  |         deactivateActivePopup(); | ||||||
|  |         if (userDeletionWinStoredUId < 0) | ||||||
|  |             throw new Error("Karaul"); | ||||||
|  |         let Sent = genSentBase(); | ||||||
|  |         Sent.userId = userDeletionWinStoredUId; | ||||||
|  |         apiRequest("removeMemberFromChat", Sent). | ||||||
|  |         then((Recv) => { | ||||||
|  |             updateLocalStateFromRecv(Recv); | ||||||
|  |         }).catch((e) => { | ||||||
|  |             console.log(e); | ||||||
|  |             alert(pres['chat-members']["failed-kick-member"]); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     document.getElementById("user-deletion-no").onclick = function (ev) { | ||||||
|  |         if (ev.button !== 0) | ||||||
|  |             return; | ||||||
|  |         deactivateActivePopup(); | ||||||
|  |     }; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | __mainloopDelayMS = 5000; | ||||||
|  | __guestMainloopPollerAction = function (){ | ||||||
|  |     console.log("Hello, world"); | ||||||
|  |     apiRequest("chatPollEvents", genSentBase()). | ||||||
|  |     then((Recv) => { | ||||||
|  |         console.log(Recv); | ||||||
|  |         updateLocalStateFromRecv(Recv); | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | window.onload = function(){ | ||||||
|  |     console.log("Page loaded"); | ||||||
|  |     configureSummonUserInterface(); | ||||||
|  |     configureKickUserInterfaceWinPart(); | ||||||
|  |     updateLocalStateFromChatUpdResp(initial_chatUpdResp); | ||||||
|  |     mainloopPoller(); | ||||||
|  | } | ||||||
| @ -1,162 +1,477 @@ | |||||||
| let members = [ | let LocalHistoryId = 0; | ||||||
|     { username: 'Адель', nickname: 'cold_siemens52', avatar: 'https://sun9-59.userapi.com/impg/t8GhZ7FkynVifY1FQCnaf31tGprbV_rfauZzgg/fSq4lyc6V0U.jpg?size=1280x1280&quality=96&sign=e3c309a125cb570d2e18465eba65f940&type=album' }, |  | ||||||
|     { username: 'Антон', nickname: 'antyak_01', avatar: 'https://cdn.pixabay.com/photo/2015/10/05/22/37/blank-profile-picture-973460_1280.png' }, |  | ||||||
|     { username: 'Владимир', nickname: 'kkrkk2006', avatar: 'https://cdn.pixabay.com/photo/2015/10/05/22/37/blank-profile-picture-973460_1280.png' } |  | ||||||
| ]; |  | ||||||
| let currentHistoryId = 0; |  | ||||||
| let currentChatID = null; |  | ||||||
| function renderMembersList() { |  | ||||||
|     const membersListBody = document.getElementById('members-list-body'); |  | ||||||
|     membersListBody.innerHTML = ''; |  | ||||||
| 
 | 
 | ||||||
|     members.forEach((member, index) => { | let members = new Map(); | ||||||
|         const memberItem = document.createElement('li'); |  | ||||||
|         memberItem.innerHTML = ` |  | ||||||
|             <img src="${member.avatar}" alt="${member.username}"> |  | ||||||
|             <a href="profile.html">${member.username}</a> |  | ||||||
|             <button class="delete-member" onclick="deleteMember(${index})">Удалить из чата</button> |  | ||||||
|         `;
 |  | ||||||
|         membersListBody.appendChild(memberItem); |  | ||||||
|     }); |  | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| function deleteMember(index) { | let loadedMessages = new Map();  // messageSt objects
 | ||||||
|     members.splice(index, 1); | /* | ||||||
|     renderMembersList(); | container: EL, box: EL, offset: number (msgPres) */ | ||||||
| } | let visibleMessages = new Map();  // HTMLElement objects
 | ||||||
| 
 | 
 | ||||||
| async function sendMessage() { | let anchoredMsg = -1; | ||||||
|     const chatMessages = document.getElementById('chat-messages'); | let visibleMsgSegStart = -1; | ||||||
|     const chatInput = document.getElementById('chat-input'); | let visibleMsgSegEnd = -2; | ||||||
|     const message = chatInput.value; | let offsetOfAnchor = 500; | ||||||
|  | let highestPoint = null; | ||||||
|  | let lowestPoint = null; | ||||||
| 
 | 
 | ||||||
|     if (message.trim() !== '') { | let lastMsgId = -1; | ||||||
|         const request = { | let myRoleHere = null;  // Dung local state updates should be updated first
 | ||||||
|             'chatId': currentChatID, |  | ||||||
|             'LocalHistoryId': currentHistoryId, |  | ||||||
|             'content': { |  | ||||||
|                 'text': message |  | ||||||
|             } |  | ||||||
|         }; |  | ||||||
| 
 | 
 | ||||||
|         const response = await fetch("/internalapi/sendMessage", { | // Would start with true if opened `/chat/<>`
 | ||||||
|             method: 'POST', | let bumpedAtBottom = false; | ||||||
|             headers: { |  | ||||||
|                 'Content-Type': 'application/json' |  | ||||||
|             }, |  | ||||||
|             body: JSON.stringify(request) |  | ||||||
|         }); |  | ||||||
| 
 | 
 | ||||||
|         const res = await response.json(); | // Hidden variable. When deletion window popup is active
 | ||||||
|  | // Persists from popup activation until popup deactivation
 | ||||||
|  | let storeHiddenMsgIdForDeletionWin = -1; | ||||||
| 
 | 
 | ||||||
|         if (res.update) { | let debugMode = false; | ||||||
|             const update = res.update[0]; |  | ||||||
|             currentHistoryId = update.HistoryId; |  | ||||||
| 
 | 
 | ||||||
|             const messageElement = document.createElement('div'); | // Positive in production, negative for debug
 | ||||||
|             messageElement.classList.add('chat-message'); | let softZoneSz = debugMode ? -150 : 300; | ||||||
|  | let chatPadding = debugMode ? 300 : 5; | ||||||
|  | let msgGap = 5; | ||||||
|  | const msgErased = pres.chat.msgErased; | ||||||
| 
 | 
 | ||||||
|             const avatarElement = document.createElement('div'); | function genSentBase(){ | ||||||
|             avatarElement.classList.add('avatar'); |     return { | ||||||
| 
 |         'chatUpdReq': { | ||||||
|             const avatarImage = document.createElement('img'); |             'LocalHistoryId': LocalHistoryId, | ||||||
|             avatarImage.src = 'https://sun9-59.userapi.com/impg/t8GhZ7FkynVifY1FQCnaf31tGprbV_rfauZzgg/fSq4lyc6V0U.jpg?size=1280x1280&quality=96&sign=e3c309a125cb570d2e18465eba65f940&type=album'; |             'chatId': openedchat.id | ||||||
|             avatarElement.appendChild(avatarImage); |  | ||||||
| 
 |  | ||||||
|             const messageContentElement = document.createElement('div'); |  | ||||||
|             messageContentElement.classList.add('message-content'); |  | ||||||
| 
 |  | ||||||
|             const usernameElement = document.createElement('div'); |  | ||||||
|             usernameElement.classList.add('username'); |  | ||||||
|             usernameElement.textContent = await getUserName(); |  | ||||||
| 
 |  | ||||||
|             const textElement = document.createElement('div'); |  | ||||||
|             textElement.classList.add('text'); |  | ||||||
|             textElement.textContent = message; |  | ||||||
| 
 |  | ||||||
|             messageContentElement.appendChild(usernameElement); |  | ||||||
|             messageContentElement.appendChild(textElement); |  | ||||||
| 
 |  | ||||||
|             messageElement.appendChild(avatarElement); |  | ||||||
|             messageElement.appendChild(messageContentElement); |  | ||||||
| 
 |  | ||||||
|             chatMessages.appendChild(messageElement); |  | ||||||
| 
 |  | ||||||
|             chatInput.value = ''; |  | ||||||
|             chatMessages.scrollTop = chatMessages.scrollHeight; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| function openMembersList() { |  | ||||||
|     renderMembersList(); |  | ||||||
|     document.getElementById("members-list").style.display = "block"; |  | ||||||
|     document.getElementById("overlay").style.display = "flex"; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| function closeMembersList() { |  | ||||||
|     document.getElementById("members-list").style.display = "none"; |  | ||||||
|     document.getElementById("overlay").style.display = "none"; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| document.getElementById('chat-input').addEventListener('keydown', function (event) { |  | ||||||
|     if (event.key === 'Enter') { |  | ||||||
|         sendMessage(); |  | ||||||
|     } |  | ||||||
| }); |  | ||||||
| 
 |  | ||||||
| async function getUserID() { |  | ||||||
|     const response = await fetch('/internalapi/mirror', { |  | ||||||
|         method: 'POST', |  | ||||||
|         headers: { |  | ||||||
|             'Content-Type': 'application/json' |  | ||||||
|         }, |  | ||||||
|         body: JSON.stringify({}) |  | ||||||
|     }); |  | ||||||
| 
 |  | ||||||
|     const res = await response.json(); |  | ||||||
|     return res.id; |  | ||||||
| } |  | ||||||
| async function getChatID() { |  | ||||||
|     const chatNickname = window.location.pathname.split('/').pop(); |  | ||||||
|     const response = await fetch('/internalapi/getChatList', { |  | ||||||
|         method: 'POST', |  | ||||||
|         headers: { |  | ||||||
|             'Content-Type': 'application/json' |  | ||||||
|         }, |  | ||||||
|         body: JSON.stringify({}) |  | ||||||
|     }); |  | ||||||
| 
 |  | ||||||
|     const res = await response.json(); |  | ||||||
|     for (const chat of res.chats) { |  | ||||||
|         if (chat.content.nickname === chatNickname) { |  | ||||||
|             return chat.id; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|     return -1; |  | ||||||
| } |  | ||||||
| async function editMessage(new_message) { |  | ||||||
|     const req = { |  | ||||||
|         'chatId': currentChatID, |  | ||||||
|         'LocalHistoryId': currentHistoryId, |  | ||||||
|         'id': getUserID(), |  | ||||||
|         'content': { |  | ||||||
|             'text': new_message |  | ||||||
|         } |         } | ||||||
|     }; |     }; | ||||||
|     const res = await fetch('/internalapi/editMessage', { | } | ||||||
|         method: 'POST', |  | ||||||
|         headers: { |  | ||||||
|             'Content-Type': 'application/json' |  | ||||||
|         }, |  | ||||||
|         body: JSON.stringify(req) |  | ||||||
|     }); |  | ||||||
| 
 | 
 | ||||||
|     const response = await res.json(); | function genSentBaseGMN(){ | ||||||
|     if (response.update) { |     let Sent = genSentBase(); | ||||||
|         currentHistoryId = response.update[0].HistoryId; |     Sent.amount = debugMode ? 2 : 14; | ||||||
|  |     return Sent; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function getChatWgSz(){ | ||||||
|  |     let chatWg = document.getElementById("chat-widget"); | ||||||
|  |     return [chatWg.offsetWidth, chatWg.offsetHeight]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function elSetOffsetInChat(el, offset){ | ||||||
|  |     el.style.bottom = String(offset) + "px"; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function isMissingPrimaryMsgHeap(){ | ||||||
|  |     return lastMsgId >= 0 && anchoredMsg < 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function isMissingTopMsgHeap(){ | ||||||
|  |     let [W, H] = getChatWgSz(); | ||||||
|  |     return anchoredMsg >= 0 && (highestPoint < H + softZoneSz && visibleMsgSegStart > 0); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function isMissingBottomMsgHeap(){ | ||||||
|  |     return anchoredMsg >= 0 && (lowestPoint > - softZoneSz && visibleMsgSegEnd < lastMsgId); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function updateOffsetOfVisibleMsg(msgId, offset){ | ||||||
|  |     visibleMessages.get(msgId).container.style.bottom = String(offset) + "px"; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function updateOffsetsUpToTop(){ | ||||||
|  |     let offset = offsetOfAnchor; | ||||||
|  |     for (let curMsg = anchoredMsg; curMsg >= visibleMsgSegStart; curMsg--){ | ||||||
|  |         updateOffsetOfVisibleMsg(curMsg, offset); | ||||||
|  |         let height = visibleMessages.get(curMsg).container.offsetHeight; | ||||||
|  |         offset += height + msgGap; | ||||||
|  |     } | ||||||
|  |     return offset - msgGap; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function updateOffsetsDown(){ | ||||||
|  |     let offset = offsetOfAnchor; | ||||||
|  |     for (let curMsg = anchoredMsg + 1; curMsg <= visibleMsgSegEnd; curMsg++){ | ||||||
|  |         let height = visibleMessages.get(curMsg).container.offsetHeight; | ||||||
|  |         offset -= (height + msgGap); | ||||||
|  |         updateOffsetOfVisibleMsg(curMsg, offset); | ||||||
|  |     } | ||||||
|  |     return offset; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function updateOffsetsSane(){ | ||||||
|  |     if (anchoredMsg < 0) | ||||||
|  |         return; | ||||||
|  |     highestPoint = updateOffsetsUpToTop(); | ||||||
|  |     lowestPoint = updateOffsetsDown(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function heightOfPreloadGhost(){ | ||||||
|  |     let [W, H] = getChatWgSz(); | ||||||
|  |     return Math.min(H * 0.9, Math.max(H * 0.69, 30)); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function updateOffsets(){ | ||||||
|  |     let spinnerTop = document.getElementById("top-loading"); | ||||||
|  |     let spinnerBottom = document.getElementById("bottom-loading"); | ||||||
|  |     let SbH = spinnerBottom.offsetHeight; | ||||||
|  |     if (anchoredMsg < 0){ | ||||||
|  |         hideHTMLElement(spinnerBottom); | ||||||
|  |         elSetOffsetInChat(spinnerTop, chatPadding); | ||||||
|  |         setElementVisibility(spinnerTop, isMissingPrimaryMsgHeap()); | ||||||
|  |     } else { | ||||||
|  |         let [W, H] = getChatWgSz(); | ||||||
|  |         updateOffsetsSane(); | ||||||
|  |         let lowestLowestPoint = isMissingBottomMsgHeap() ? lowestPoint - heightOfPreloadGhost(): lowestPoint; | ||||||
|  |         let highestHighestPoint = isMissingTopMsgHeap() ? highestPoint + heightOfPreloadGhost() : highestPoint; | ||||||
|  |         if (lowestLowestPoint > chatPadding || (highestHighestPoint - lowestLowestPoint) <= H - chatPadding * 2 || | ||||||
|  |             (!isMissingBottomMsgHeap() && bumpedAtBottom)) { | ||||||
|  | 
 | ||||||
|  |             offsetOfAnchor += (-lowestLowestPoint + chatPadding); | ||||||
|  |             updateOffsetsSane(); | ||||||
|  |         } else if (highestHighestPoint < H - chatPadding) { | ||||||
|  |             offsetOfAnchor += (-highestHighestPoint + (H - chatPadding)); | ||||||
|  |             updateOffsetsSane(); | ||||||
|  |         } | ||||||
|  |         /* Messages weere updated (and only them). They were talking with ghosts. | ||||||
|  |         Now we are trying to show spinners of ghosts */ | ||||||
|  |         elSetOffsetInChat(spinnerTop, highestPoint); | ||||||
|  |         setElementVisibility(spinnerTop, isMissingTopMsgHeap()); | ||||||
|  |         elSetOffsetInChat(spinnerBottom, lowestPoint - SbH); | ||||||
|  |         setElementVisibility(spinnerBottom, isMissingBottomMsgHeap()); | ||||||
|  |         /* Fix anchor */ | ||||||
|  |         let oldAnchor = anchoredMsg; | ||||||
|  |         while (true){ | ||||||
|  |             let h = visibleMessages.get(anchoredMsg).container.offsetHeight; | ||||||
|  |             if (!(offsetOfAnchor + h < chatPadding && visibleMsgSegStart < anchoredMsg)) | ||||||
|  |                 break | ||||||
|  |             offsetOfAnchor += (msgGap + h); | ||||||
|  |             anchoredMsg--; | ||||||
|  |         } | ||||||
|  |         while (offsetOfAnchor > H - chatPadding && anchoredMsg < visibleMsgSegEnd){ | ||||||
|  |             anchoredMsg++; | ||||||
|  |             let h = visibleMessages.get(anchoredMsg).container.offsetHeight; | ||||||
|  |             offsetOfAnchor -= (msgGap + h); | ||||||
|  |         } | ||||||
|  |         if (oldAnchor !== anchoredMsg) | ||||||
|  |             console.log("anchoredMsg: " + String(oldAnchor) + " -> " + String(anchoredMsg)) | ||||||
|     } |     } | ||||||
| } | } | ||||||
| document.addEventListener("DOMContentLoaded", async function() { | 
 | ||||||
|     currentChatID = await getChatID(); | function shouldShowDeleteMesgBtn(messageSt){ | ||||||
| }); |     return !messageSt.isSystem && messageSt.exists && (myRoleHere !== userChatRoleReadOnly) &&( | ||||||
|  |         myRoleHere === userChatRoleAdmin || messageSt.senderUserId === userinfo.uid); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function getMsgTypeClassSenderBased(messageSt){ | ||||||
|  |     if (messageSt.isSystem) | ||||||
|  |         return "message-box-system" | ||||||
|  |     if (messageSt.senderUserId === userinfo.uid) | ||||||
|  |         return "message-box-mine" | ||||||
|  |     return "message-box-alien"; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function getMsgFullTypeClassName(messageSt){ | ||||||
|  |     return getMsgTypeClassSenderBased(messageSt) + (messageSt.exists ? "" : " message-box-deleted"); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* Two things can be updated: messages existance and delete button visibility | ||||||
|  | * Supercontainer.container is persistent, Supercontainer.box can change it's class */ | ||||||
|  | function updateMessageSupercontainer(supercontainer, messageSt){ | ||||||
|  |     let box = supercontainer.box; | ||||||
|  |     if (messageSt.isSystem) | ||||||
|  |         return; | ||||||
|  |     setElementVisibility(box.querySelector(".message-box-button-delete"), shouldShowDeleteMesgBtn(messageSt), "inline"); | ||||||
|  |     box.className = getMsgFullTypeClassName(messageSt); | ||||||
|  |     // Notice, that no check of previous state is performed. Double loading is a rare event, I can afford to be slow
 | ||||||
|  |     if (!messageSt.exists) | ||||||
|  |         box.querySelector(".message-box-msg").innerText = msgErased; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function decodeSystemMessage(text){ | ||||||
|  |     let [subject, verb, object] = text.split(','); | ||||||
|  |     let subjectId = Number(subject); | ||||||
|  |     let objectId = Number(object); | ||||||
|  |     let subjectRef = members.has(subjectId) ? members.get(subjectId).nickname : "???"; | ||||||
|  |     let objectRef = members.has(objectId) ? members.get(objectId).nickname : "???"; | ||||||
|  |     if (verb === "kicked"){ | ||||||
|  |         return subjectRef + " " + pres.chat.syslog.kicked + " " + objectRef; | ||||||
|  |     } else if (verb === "summoned"){ | ||||||
|  |         return subjectRef + " " + pres.chat.syslog.summoned + " " + objectRef; | ||||||
|  |     } else if (verb === "left"){ | ||||||
|  |         return subjectRef + " " + pres.chat.syslog.left; | ||||||
|  |     } else if (verb === "created"){ | ||||||
|  |         return subjectRef + " " + pres.chat.syslog.created; | ||||||
|  |     } | ||||||
|  |     return "... Bad log ..."; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function convertMessageStToSupercontainer(messageSt){ | ||||||
|  |     let container = document.createElement("div"); | ||||||
|  |     container.className = "message-supercontainer"; | ||||||
|  | 
 | ||||||
|  |     let box = document.createElement("div"); | ||||||
|  |     container.appendChild(box); | ||||||
|  |     box.className = getMsgFullTypeClassName(messageSt); | ||||||
|  | 
 | ||||||
|  |     let ID = messageSt.id; | ||||||
|  | 
 | ||||||
|  |     if (messageSt.isSystem){ | ||||||
|  | 
 | ||||||
|  |     } else { | ||||||
|  |         let topPart = document.createElement("div"); | ||||||
|  |         box.appendChild(topPart); | ||||||
|  |         topPart.className = "message-box-top"; | ||||||
|  | 
 | ||||||
|  |         if (!members.has(messageSt.senderUserId)) | ||||||
|  |             throw new Error("First - update members"); | ||||||
|  |         let senderMemberSt = members.get(messageSt.senderUserId); | ||||||
|  |         let senderProfileURI = "/user/" + senderMemberSt.nickname; | ||||||
|  | 
 | ||||||
|  |         let inTopPartSenderName = document.createElement("a"); | ||||||
|  |         topPart.appendChild(inTopPartSenderName); | ||||||
|  |         inTopPartSenderName.className = "message-box-sender-name"; | ||||||
|  |         inTopPartSenderName.innerText = senderMemberSt.name; | ||||||
|  |         inTopPartSenderName.href = senderProfileURI; | ||||||
|  | 
 | ||||||
|  |         let inTopPartSenderNickname = document.createElement("a"); | ||||||
|  |         topPart.appendChild(inTopPartSenderNickname); | ||||||
|  |         inTopPartSenderNickname.className = "message-box-sender-name message-box-sender-shortname" | ||||||
|  |         inTopPartSenderNickname.innerText = senderMemberSt.nickname; | ||||||
|  |         inTopPartSenderNickname.href = senderProfileURI; | ||||||
|  | 
 | ||||||
|  |         let inTopPartButtonDelete = document.createElement("img"); | ||||||
|  |         topPart.appendChild(inTopPartButtonDelete); | ||||||
|  |         inTopPartButtonDelete.className = "message-box-button message-box-button-delete"; | ||||||
|  |         inTopPartButtonDelete.src = "/assets/img/delete.svg"; | ||||||
|  |         inTopPartButtonDelete.onclick = (ev) => { | ||||||
|  |             if (ev.button !== 0) | ||||||
|  |                 return; | ||||||
|  |             let msgText = box.querySelector(".message-box-msg").innerText; | ||||||
|  |             let previewText = senderMemberSt.nickname + ":\n" + msgText; | ||||||
|  |             if (previewText.length > 1000) | ||||||
|  |                 previewText = previewText.substring(0, 1000 - 3); | ||||||
|  |             document.getElementById("win-deletion-msg-preview").innerText = previewText; | ||||||
|  |             storeHiddenMsgIdForDeletionWin = ID; | ||||||
|  |             activatePopupWindowById("msg-deletion-win"); | ||||||
|  |         }; | ||||||
|  |         setElementVisibility(inTopPartButtonDelete, shouldShowDeleteMesgBtn(messageSt), "inline"); | ||||||
|  | 
 | ||||||
|  |         let inTopPartButtonGetLink = document.createElement("img"); | ||||||
|  |         topPart.appendChild(inTopPartButtonGetLink); | ||||||
|  |         inTopPartButtonGetLink.className = "message-box-button"; | ||||||
|  |         inTopPartButtonGetLink.src = "/assets/img/link.svg"; | ||||||
|  |         inTopPartButtonGetLink.onclick = (ev) => { | ||||||
|  |             if (ev.button !== 0) | ||||||
|  |                 return; | ||||||
|  |             let URI = window.location.host + "/chat/" + openedchat.nickname + "/m/" + String(ID); | ||||||
|  |             document.getElementById("message-input").innerText += (" " + URI + ""); | ||||||
|  |         }; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     let msgPart = document.createElement("p"); | ||||||
|  |     box.appendChild(msgPart); | ||||||
|  |     msgPart.className = "message-box-msg"; | ||||||
|  |     if (messageSt.exists){ | ||||||
|  |         if (messageSt.isSystem) | ||||||
|  |             msgPart.innerText = decodeSystemMessage(messageSt.text); | ||||||
|  |         else | ||||||
|  |             msgPart.innerText = messageSt.text; | ||||||
|  |     } else | ||||||
|  |         msgPart.innerText = msgErased; | ||||||
|  | 
 | ||||||
|  |     return {'container': container, 'box': box}; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function makeVisible(msgId){ | ||||||
|  |     let supercontainer = convertMessageStToSupercontainer(loadedMessages.get(msgId)); | ||||||
|  |     const chatWin = document.getElementById("chat-widget"); | ||||||
|  |     chatWin.appendChild(supercontainer.container); | ||||||
|  |     visibleMessages.set(msgId, supercontainer); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function opaNewMessageSt(messageSt){ | ||||||
|  |     let msgId = messageSt.id; | ||||||
|  |     if (loadedMessages.has(msgId)){ | ||||||
|  |         loadedMessages.set(msgId, messageSt); | ||||||
|  |         if (visibleMessages.has(msgId)){ | ||||||
|  |             updateMessageSupercontainer(visibleMessages.get(msgId), messageSt); | ||||||
|  |         } | ||||||
|  |     } else { | ||||||
|  |         loadedMessages.set(msgId, messageSt); | ||||||
|  |         if (anchoredMsg < 0){ | ||||||
|  |             anchoredMsg = msgId; | ||||||
|  |             visibleMsgSegStart = msgId; | ||||||
|  |             visibleMsgSegEnd = msgId; | ||||||
|  |             makeVisible(msgId); | ||||||
|  |         } else if (msgId + 1 === visibleMsgSegStart) { | ||||||
|  |             visibleMsgSegStart--; | ||||||
|  |             makeVisible(msgId); | ||||||
|  |             while (loadedMessages.has(visibleMsgSegStart - 1)){ | ||||||
|  |                 visibleMsgSegStart--; | ||||||
|  |                 makeVisible(visibleMsgSegStart); | ||||||
|  |             } | ||||||
|  |         } else if (msgId - 1 === visibleMsgSegEnd){ | ||||||
|  |             visibleMsgSegEnd++; | ||||||
|  |             makeVisible(msgId); | ||||||
|  |             while (loadedMessages.has(visibleMsgSegEnd + 1)){ | ||||||
|  |                 visibleMsgSegEnd++; | ||||||
|  |                 makeVisible(visibleMsgSegEnd); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function canISendMessages(){ | ||||||
|  |     return myRoleHere === userChatRoleRegular || myRoleHere === userChatRoleAdmin; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function updateLocalStateFromChatUpdRespBlind(chatUpdResp){ | ||||||
|  |     LocalHistoryId = chatUpdResp.HistoryId; | ||||||
|  |     for (let memberSt of chatUpdResp.members){ | ||||||
|  |         let id = memberSt.userId; | ||||||
|  |         if (id === userinfo.uid && myRoleHere !== memberSt.roleHere) { | ||||||
|  |             myRoleHere = memberSt.roleHere; | ||||||
|  |             for (let [msgId, sc] of visibleMessages){ | ||||||
|  |                 updateMessageSupercontainer(sc, loadedMessages.get(msgId)); | ||||||
|  |             } | ||||||
|  |             setElementVisibility(document.getElementById("message-input"), canISendMessages()); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     for (let memberSt of chatUpdResp.members){ | ||||||
|  |         let id = memberSt.userId; | ||||||
|  |         members.set(id, memberSt); | ||||||
|  |     } | ||||||
|  |     lastMsgId = chatUpdResp.lastMsgId; | ||||||
|  |     for (let messageSt of chatUpdResp.messages){ | ||||||
|  |         opaNewMessageSt(messageSt); | ||||||
|  |     } | ||||||
|  |     updateOffsets(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function updateLocalStateFromRecvBlind(Recv){ | ||||||
|  |     updateLocalStateFromChatUpdRespBlind(Recv.chatUpdResp); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | async function requestMessageNeighbours(fromMsg, direction){ | ||||||
|  |     let Sent = genSentBaseGMN(); | ||||||
|  |     Sent.msgId = fromMsg; | ||||||
|  |     Sent.direction = direction; | ||||||
|  |     let Recv = await apiRequest("getMessageNeighbours", Sent); | ||||||
|  |     updateLocalStateFromRecvBlind(Recv);  // Blind to non-loaded whitespaces
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function needToLoadWhitespace(){ | ||||||
|  |     return isMissingPrimaryMsgHeap() || isMissingTopMsgHeap() || isMissingBottomMsgHeap(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | async function tryLoadWhitespaceSingle(){ | ||||||
|  |     if (isMissingPrimaryMsgHeap()){ | ||||||
|  |         await requestMessageNeighbours(-1, "backward"); | ||||||
|  |     } else if (isMissingTopMsgHeap()){ | ||||||
|  |         await requestMessageNeighbours(visibleMsgSegStart, "backward"); | ||||||
|  |     } else if (isMissingBottomMsgHeap()){ | ||||||
|  |         await requestMessageNeighbours(visibleMsgSegEnd, "forward"); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | async function loadWhitespaceMultitry(){ | ||||||
|  |     if (needToLoadWhitespace()){ | ||||||
|  |         cancelMainloopTimeout(); | ||||||
|  |         do { | ||||||
|  |             try { | ||||||
|  |                 await tryLoadWhitespaceSingle(); | ||||||
|  |                 if (debugMode) | ||||||
|  |                     await sleep(900); | ||||||
|  |             } catch (e) { | ||||||
|  |                 console.error(e); | ||||||
|  |                 await sleep(1500); | ||||||
|  |             } | ||||||
|  |         } while (needToLoadWhitespace()); | ||||||
|  |         setMainloopTimeout(); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | async function updateLocalStateFromRecv(Recv){ | ||||||
|  |     updateLocalStateFromRecvBlind(Recv); | ||||||
|  |     await loadWhitespaceMultitry(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | async function safeApiRequestWithLocalStUpdate(type, Sent, errMsg){ | ||||||
|  |     try { | ||||||
|  |         let Recv = await apiRequest(type, Sent) | ||||||
|  |         await updateLocalStateFromRecv(Recv); | ||||||
|  |     } catch(e) { | ||||||
|  |         console.error(e); | ||||||
|  |         alert(errMsg); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function configureMsgDeletionPopupButtons(){ | ||||||
|  |     document.getElementById("msg-deletion-yes").onclick = function(ev){ | ||||||
|  |         if (ev.button !== 0) | ||||||
|  |             return; | ||||||
|  |         deactivateActivePopup(); | ||||||
|  |         let Sent = genSentBase(); | ||||||
|  |         Sent.id = storeHiddenMsgIdForDeletionWin; | ||||||
|  |         safeApiRequestWithLocalStUpdate("deleteMessage", Sent, pres.chat['failed-delete-message']); | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     document.getElementById("msg-deletion-no").onclick = function (ev){ | ||||||
|  |         if (ev.button !== 0) | ||||||
|  |             return; | ||||||
|  |         deactivateActivePopup(); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | __mainloopDelayMs = 1000; | ||||||
|  | async function UPDATE(){ | ||||||
|  |     let Recv = await apiRequest("chatPollEvents", genSentBase()); | ||||||
|  |     await updateLocalStateFromRecv(Recv); | ||||||
|  | } | ||||||
|  | __guestMainloopPollerAction = UPDATE; | ||||||
|  | 
 | ||||||
|  | window.onload = function (){ | ||||||
|  |     console.log("Page was loaded"); | ||||||
|  | 
 | ||||||
|  |     document.body.addEventListener("wheel", function (event) { | ||||||
|  |         let offset = event.deltaY / 3; | ||||||
|  |         if (offset < 0){ | ||||||
|  |             bumpedAtBottom = false; | ||||||
|  |         } else if (offset > 0 && !isMissingBottomMsgHeap() && lowestPoint + offset > chatPadding){ | ||||||
|  |             bumpedAtBottom = true; | ||||||
|  |         } | ||||||
|  |         offsetOfAnchor += offset; | ||||||
|  |         updateOffsets(); | ||||||
|  |         loadWhitespaceMultitry().then(dopDopYesYes); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     document.getElementById("message-input").addEventListener("keyup", function (event) { | ||||||
|  |         if (event.ctrlKey && event.key === 'Enter'){ | ||||||
|  |             let textarea = document.getElementById("message-input"); | ||||||
|  |             let text = String(textarea.innerText); | ||||||
|  |             textarea.innerText = ""; | ||||||
|  |             let Sent = genSentBase(); | ||||||
|  |             Sent.content = {}; | ||||||
|  |             Sent.content.text = text; | ||||||
|  |             safeApiRequestWithLocalStUpdate("sendMessage", Sent, pres.chat['failed-send-message']); | ||||||
|  |         } | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     bumpedAtBottom = (openedchat.selectedMessageId < 0); | ||||||
|  | 
 | ||||||
|  |     let chatWg = document.getElementById("chat-widget"); | ||||||
|  |     let chatWgDebugLinesFnc = function (){ | ||||||
|  |         let H = chatWg.offsetHeight; | ||||||
|  |         elSetOffsetInChat(document.getElementById("debug-line-lowest"), -softZoneSz); | ||||||
|  |         elSetOffsetInChat(document.getElementById("debug-line-highest"), H + softZoneSz); | ||||||
|  |         elSetOffsetInChat(document.getElementById("debug-line-top-padding"), H - chatPadding); | ||||||
|  |         elSetOffsetInChat(document.getElementById("debug-line-bottom-padding"), chatPadding) | ||||||
|  |     }; | ||||||
|  |     if (debugMode){ | ||||||
|  |         window.addEventListener("resize", chatWgDebugLinesFnc); | ||||||
|  |         chatWgDebugLinesFnc(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     configureMsgDeletionPopupButtons(); | ||||||
|  | 
 | ||||||
|  |     updateLocalStateFromChatUpdRespBlind(initial_chatUpdResp); | ||||||
|  | 
 | ||||||
|  |     setMainloopTimeout(); | ||||||
|  | 
 | ||||||
|  |     loadWhitespaceMultitry(); | ||||||
|  | } | ||||||
|  | |||||||
| @ -1,146 +0,0 @@ | |||||||
| const chatId = 123; |  | ||||||
| let localHistoryId = 0; |  | ||||||
| 
 |  | ||||||
| function handleChangeName() { |  | ||||||
|     const newName = document.getElementById('room-name').value; |  | ||||||
|     changeChatName(chatId, localHistoryId, newName).then(() => { |  | ||||||
|     }); |  | ||||||
| } |  | ||||||
| function handleAddMember() { |  | ||||||
|     const login = document.getElementById('newMemberLogin').value; |  | ||||||
|     if (login) { |  | ||||||
|         addMemberToChat(chatId, localHistoryId, login).then(() => { |  | ||||||
|             const list = document.getElementById("chat-settings-container-body"); |  | ||||||
|             const listItem = document.createElement("li"); |  | ||||||
|             listItem.textContent = login; |  | ||||||
|             list.appendChild(listItem); |  | ||||||
|             closeAdd(); |  | ||||||
|         }); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| function handleRemoveMember(userId) { |  | ||||||
|     removeMemberFromChat(chatId, localHistoryId, userId).then(() => { |  | ||||||
|         const listItem = document.getElementById(`member-${userId}`); |  | ||||||
|         if (listItem) { |  | ||||||
|             listItem.remove(); |  | ||||||
|         } |  | ||||||
|     }); |  | ||||||
| } |  | ||||||
| function openInvite() { |  | ||||||
|     document.getElementById("add_members").style.display = "flex"; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| function closeAdd() { |  | ||||||
|     document.getElementById("add_members").style.display = "none"; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| function updateChat() { |  | ||||||
|     pollChatEvents(chatId, localHistoryId).then(() => { |  | ||||||
|     }); |  | ||||||
| } |  | ||||||
| document.addEventListener('DOMContentLoaded', () => { |  | ||||||
|     updateChat(); |  | ||||||
| }); |  | ||||||
| async function changeChatName(chatId, localHistoryId, newName) { |  | ||||||
|     try { |  | ||||||
|         const response = await fetch('/api/changeChatName', { |  | ||||||
|             method: 'POST', |  | ||||||
|             headers: { |  | ||||||
|                 'Content-Type': 'application/json', |  | ||||||
|             }, |  | ||||||
|             body: JSON.stringify({ |  | ||||||
|                 chatUpdReq: { |  | ||||||
|                     chatId: chatId, |  | ||||||
|                     LocalHistoryId: localHistoryId |  | ||||||
|                 }, |  | ||||||
|                 content: { |  | ||||||
|                     name: newName |  | ||||||
|                 } |  | ||||||
|             }) |  | ||||||
|         }); |  | ||||||
|         const data = await response.json(); |  | ||||||
|         if (data.status === 0) { |  | ||||||
|             console.log('Название комнаты успешно изменено'); |  | ||||||
|         } else { |  | ||||||
|             console.error('Ошибка при изменении названия комнаты:', data.error); |  | ||||||
|         } |  | ||||||
|     } catch (error) { |  | ||||||
|         console.error('Ошибка сети при изменении названия комнаты:', error); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| async function addMemberToChat(chatId, localHistoryId, nickname) { |  | ||||||
|     try { |  | ||||||
|         const response = await fetch('/api/addMemberToChat', { |  | ||||||
|             method: 'POST', |  | ||||||
|             headers: { |  | ||||||
|                 'Content-Type': 'application/json', |  | ||||||
|             }, |  | ||||||
|             body: JSON.stringify({ |  | ||||||
|                 chatUpdReq: { |  | ||||||
|                     chatId: chatId, |  | ||||||
|                     LocalHistoryId: localHistoryId |  | ||||||
|                 }, |  | ||||||
|                 nickname: nickname |  | ||||||
|             }) |  | ||||||
|         }); |  | ||||||
|         const data = await response.json(); |  | ||||||
|         if (data.status === 0) { |  | ||||||
|             console.log('Участник успешно добавлен'); |  | ||||||
|         } else { |  | ||||||
|             console.error('Ошибка при добавлении участника:', data.error); |  | ||||||
|         } |  | ||||||
|     } catch (error) { |  | ||||||
|         console.error('Ошибка сети при добавлении участника:', error); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| async function removeMemberFromChat(chatId, localHistoryId, userId) { |  | ||||||
|     try { |  | ||||||
|         const response = await fetch('/api/removeMemberFromChat', { |  | ||||||
|             method: 'POST', |  | ||||||
|             headers: { |  | ||||||
|                 'Content-Type': 'application/json', |  | ||||||
|             }, |  | ||||||
|             body: JSON.stringify({ |  | ||||||
|                 chatUpdReq: { |  | ||||||
|                     chatId: chatId, |  | ||||||
|                     LocalHistoryId: localHistoryId |  | ||||||
|                 }, |  | ||||||
|                 userId: userId |  | ||||||
|             }) |  | ||||||
|         }); |  | ||||||
|         const data = await response.json(); |  | ||||||
|         if (data.status === 0) { |  | ||||||
|             console.log('Участник успешно удален'); |  | ||||||
|         } else { |  | ||||||
|             console.error('Ошибка при удалении участника:', data.error); |  | ||||||
|         } |  | ||||||
|     } catch (error) { |  | ||||||
|         console.error('Ошибка сети при удалении участника:', error); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| async function pollChatEvents(chatId, localHistoryId) { |  | ||||||
|     try { |  | ||||||
|         const response = await fetch('/api/chatPollEvents', { |  | ||||||
|             method: 'POST', |  | ||||||
|             headers: { |  | ||||||
|                 'Content-Type': 'application/json', |  | ||||||
|             }, |  | ||||||
|             body: JSON.stringify({ |  | ||||||
|                 chatUpdReq: { |  | ||||||
|                     chatId: chatId, |  | ||||||
|                     LocalHistoryId: localHistoryId |  | ||||||
|                 } |  | ||||||
|             }) |  | ||||||
|         }); |  | ||||||
|         const data = await response.json(); |  | ||||||
|         if (data.status === 0) { |  | ||||||
|             console.log('События чата успешно обновлены'); |  | ||||||
|         } else { |  | ||||||
|             console.error('Ошибка при обновлении событий чата:', data.error); |  | ||||||
|         } |  | ||||||
|     } catch (error) { |  | ||||||
|         console.error('Ошибка сети при обновлении событий чата:', error); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
							
								
								
									
										26
									
								
								assets/js/common-popup.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,26 @@ | |||||||
|  | let activePopupWinId = ""; | ||||||
|  | 
 | ||||||
|  | function activatePopupWindow__(el){ | ||||||
|  |     let veil = document.createElement("div"); | ||||||
|  |     veil.id = "popup-overlay-veil-OBJ" | ||||||
|  |     veil.className = "popup-overlay-veil"; | ||||||
|  |     veil.style.display = "block"; | ||||||
|  |     document.body.appendChild(veil); | ||||||
|  |     el.style.display = "block"; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function activatePopupWindowById(id){ | ||||||
|  |     if (activePopupWinId !== "") | ||||||
|  |         return; | ||||||
|  |     /* Lmao, this thing is just... SO unsafe */ | ||||||
|  |     activePopupWinId = id; | ||||||
|  |     activatePopupWindow__(document.getElementById(id)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function deactivateActivePopup(){ | ||||||
|  |     if (activePopupWinId === "") | ||||||
|  |         return | ||||||
|  |     document.getElementById("popup-overlay-veil-OBJ").remove(); | ||||||
|  |     document.getElementById(activePopupWinId).style.display = "none"; | ||||||
|  |     activePopupWinId = ""; | ||||||
|  | } | ||||||
							
								
								
									
										77
									
								
								assets/js/common.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,77 @@ | |||||||
|  | let dopDopYesYes = (ign) => {}; | ||||||
|  | 
 | ||||||
|  | function sleep(ms){ | ||||||
|  |     return new Promise(res => setTimeout(res, ms)); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | async function apiRequest(type, req){ | ||||||
|  |     let A = await fetch("/api/" + type, | ||||||
|  |         {method: 'POST', body: JSON.stringify(req)}); | ||||||
|  |     let B = await A.json(); | ||||||
|  |     if (B.status !== 0) | ||||||
|  |         throw Error("Server returned non-zero status"); | ||||||
|  |     return B; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* Framework for pages with mainloop (it can be npt only polling, but also literally anything else */ | ||||||
|  | let __mainloopDelayMs = 3000; | ||||||
|  | let mainloopTimeout = null; | ||||||
|  | let __guestMainloopPollerAction = null; | ||||||
|  | function setMainloopTimeout(){ | ||||||
|  |     if (mainloopTimeout !== null) | ||||||
|  |         return; | ||||||
|  |     mainloopTimeout = setTimeout(mainloopPoller, __mainloopDelayMs); | ||||||
|  | } | ||||||
|  | function cancelMainloopTimeout(){ | ||||||
|  |     if (mainloopTimeout === null){ | ||||||
|  |         console.log("cancelling nothing") | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  |     clearTimeout(mainloopTimeout); | ||||||
|  |     mainloopTimeout = null; | ||||||
|  | } | ||||||
|  | function mainloopPoller(){ | ||||||
|  |     mainloopTimeout = null; | ||||||
|  |     try { | ||||||
|  |         if (__guestMainloopPollerAction) | ||||||
|  |             __guestMainloopPollerAction(); | ||||||
|  |     } catch (error){ | ||||||
|  |         console.log(error) | ||||||
|  |     } | ||||||
|  |     setMainloopTimeout(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 1
 | ||||||
|  | const userChatRoleAdmin = "admin"; | ||||||
|  | // 2
 | ||||||
|  | const userChatRoleRegular = "regular"; | ||||||
|  | // 3
 | ||||||
|  | const userChatRoleReadOnly = "read-only"; | ||||||
|  | // 4
 | ||||||
|  | const userChatRoleDeleted = "not-a-member"; | ||||||
|  | 
 | ||||||
|  | function roleToColor(role) { | ||||||
|  |     if (role === userChatRoleAdmin) { | ||||||
|  |         return "#aafff3"; | ||||||
|  |     } else if (role === userChatRoleRegular){ | ||||||
|  |         return "#ffffff"; | ||||||
|  | 
 | ||||||
|  |     } else if (role === userChatRoleReadOnly){ | ||||||
|  |         return "#bfb2b2"; | ||||||
|  |     } else if (role === userChatRoleDeleted) { | ||||||
|  |         return "#fb4a4a"; | ||||||
|  |     } | ||||||
|  |     return "#286500"  // Bug
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function hideHTMLElement(el){ | ||||||
|  |     el.style.display = "none"; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function showHTMLElement(el){ | ||||||
|  |     el.style.display = "block"; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function setElementVisibility(el, isVisible, howVisible = "block"){ | ||||||
|  |     el.style.display = isVisible ? howVisible : "none"; | ||||||
|  | } | ||||||
| @ -1,186 +1,188 @@ | |||||||
| let rooms = {}; | let LocalHistoryId = 0; | ||||||
| let roomToDelete = null; |  | ||||||
| let currentRoom = null; |  | ||||||
| let currentHistoryId = 0; |  | ||||||
| 
 | 
 | ||||||
| function openRoom(currentRoom) { | function genSentBase(){ | ||||||
|     alert('Вы вошли в комнату: ' + currentRoom); |     return { | ||||||
| } |         'chatListUpdReq': { | ||||||
| 
 |             'LocalHistoryId': LocalHistoryId | ||||||
| function closeAdd() { |  | ||||||
|     document.getElementById('add_members').style.display = 'none'; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| function openAdd() { |  | ||||||
|     document.getElementById('add_members').style.display = 'flex'; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| function openConfirm(roomNickname) { |  | ||||||
|     roomToDelete = roomNickname; |  | ||||||
|     document.getElementById("delete-chat").style.display = "flex"; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| function closeConfirm() { |  | ||||||
|     roomToDelete = null; |  | ||||||
|     document.getElementById("delete-chat").style.display = "none"; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| function deleteChat() { |  | ||||||
|     if (roomToDelete && rooms[roomToDelete]) { |  | ||||||
|         delete rooms[roomToDelete]; |  | ||||||
|         removeRoomFromList(roomToDelete); |  | ||||||
|         closeConfirm(); |  | ||||||
|     } else { |  | ||||||
|         alert("Не удалось найти выбранную комнату."); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| function addMember() { |  | ||||||
|     const login = document.getElementById('newMemberLogin').value; |  | ||||||
|     if (login) { |  | ||||||
|         alert(`Участник с никнеймом '${login}' добавлен`); |  | ||||||
|         closeAdd(); |  | ||||||
|     } else { |  | ||||||
|         alert('Пожалуйста, введите логин участника'); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| function openCreateRoomModal() { |  | ||||||
|     document.getElementById('createRoomModal').style.display = 'block'; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| function closeCreateRoomModal() { |  | ||||||
|     document.getElementById('createRoomModal').style.display = 'none'; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| async function createRoom() { |  | ||||||
|     const errorElement = document.getElementById('error'); |  | ||||||
|     const roomName = document.getElementById('newRoomName').value.trim(); |  | ||||||
|     const roomNickname = document.getElementById('newRoomNickname').value.trim(); |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|     errorElement.style.display = 'none'; |  | ||||||
|     errorElement.textContent = ''; |  | ||||||
| 
 |  | ||||||
|     if (roomName === '' || roomNickname === '') { |  | ||||||
|         errorElement.textContent = 'Пожалуйста, заполните все поля'; |  | ||||||
|         errorElement.style.display = 'block'; |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     const request = { |  | ||||||
|         LocalHistoryId: currentHistoryId, |  | ||||||
|         content: { |  | ||||||
|             name: roomName, |  | ||||||
|             nickname: roomNickname |  | ||||||
|         } |         } | ||||||
|     }; |     }; | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
|     try { | let myChats = new Map(); | ||||||
|         const response = await fetch('/internalapi/createChat', { | let chatBoxes = new Map(); | ||||||
|             method: 'POST', |  | ||||||
|             headers: { |  | ||||||
|                 'Content-Type': 'application/json' |  | ||||||
|             }, |  | ||||||
|             body: JSON.stringify(request) |  | ||||||
|         }); |  | ||||||
| 
 | 
 | ||||||
|         const res = await response.json(); | /* Generate text that is displayed on the right side of chat intro box */ | ||||||
|  | function youAreXHere(myRoleHere){ | ||||||
|  |     // todo: TRANSLATE IT
 | ||||||
|  |     return pres['list-rooms']['you-are-X-here'][0] + " " + myRoleHere + " " + pres['list-rooms']['you-are-X-here'][1]; | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
|         if (res.status === 0) { | 
 | ||||||
|             addRoomToList(roomName, roomNickname); | let chatRenunciationWinStoredId = -1; | ||||||
|             rooms[roomNickname] = true; | 
 | ||||||
|             closeCreateRoomModal(); | function shouldShowDeleteButton(myMembershipSt){ | ||||||
|             currentHistoryId = res.update.LocalHistoryId; |     return myMembershipSt.myRoleHere === userChatRoleDeleted; | ||||||
|             window.location.href = '/chat/' + roomNickname; | } | ||||||
|  | 
 | ||||||
|  | /* Updating chat html box after myMembershipSt in it was updated */ | ||||||
|  | function updateBoxWithNewSt(box, myMembershipSt){ | ||||||
|  |     let ID = myMembershipSt.chatId; | ||||||
|  |     let roleP = box.querySelector(".CL-my-chat-box-my-role"); | ||||||
|  |     roleP.innerText = youAreXHere(myMembershipSt.myRoleHere); | ||||||
|  |     box.style.backgroundColor = roleToColor(myMembershipSt.myRoleHere); | ||||||
|  |     box.querySelector(".CL-my-chat-box-leave-btn").style.display = | ||||||
|  |         (shouldShowDeleteButton(myMembershipSt) ? "none" : "block"); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function convertMyMembershipStToBox(myMembershipSt){ | ||||||
|  |     let chatURI = "/chat/" + myMembershipSt.chatNickname; | ||||||
|  |     let ID = myMembershipSt.chatId; | ||||||
|  | 
 | ||||||
|  |     let box = document.createElement("div"); | ||||||
|  |     box.className = "dynamic-block-list-el CL-my-chat-box"; | ||||||
|  |     box.style.backgroundColor = roleToColor(myMembershipSt.myRoleHere); | ||||||
|  | 
 | ||||||
|  |     let inBoxNickname = document.createElement("a"); | ||||||
|  |     box.appendChild(inBoxNickname); | ||||||
|  |     inBoxNickname.className = "entity-nickname-txt CL-my-chat-box-nickname"; | ||||||
|  |     inBoxNickname.innerText = myMembershipSt.chatNickname; | ||||||
|  |     inBoxNickname.href = chatURI; | ||||||
|  | 
 | ||||||
|  |     let inBoxName = document.createElement("a"); | ||||||
|  |     box.appendChild(inBoxName); | ||||||
|  |     inBoxName.className = "entity-reg-field-txt CL-my-chat-box-name"; | ||||||
|  |     inBoxName.innerText = myMembershipSt.chatName; | ||||||
|  |     inBoxName.href = chatURI; | ||||||
|  | 
 | ||||||
|  |     let inBoxMyRoleHere = document.createElement("p"); | ||||||
|  |     box.appendChild(inBoxMyRoleHere); | ||||||
|  |     inBoxMyRoleHere.className = "entity-reg-field-txt CL-my-chat-box-my-role"; | ||||||
|  |     inBoxMyRoleHere.innerText = youAreXHere(myMembershipSt.myRoleHere); | ||||||
|  | 
 | ||||||
|  |     let inBoxLeaveBtn = document.createElement("img"); | ||||||
|  |     box.appendChild(inBoxLeaveBtn); | ||||||
|  |     inBoxLeaveBtn.className = "CL-my-chat-box-leave-btn"; | ||||||
|  |     inBoxLeaveBtn.src = "/assets/img/delete.svg"; | ||||||
|  |     inBoxLeaveBtn.onclick = function (ev) { | ||||||
|  |         if (ev.button !== 0) | ||||||
|  |             return; | ||||||
|  |         chatRenunciationWinStoredId = ID; | ||||||
|  |         document.getElementById("chat-renunciation-win-title").innerText = | ||||||
|  |             pres['list-rooms']['reask-leave-chat-X'] + " " + myMembershipSt.chatNickname + "?"; | ||||||
|  |         activatePopupWindowById("chat-renunciation-win"); | ||||||
|  |     }; | ||||||
|  |     box.querySelector(".CL-my-chat-box-leave-btn").style.display = | ||||||
|  |         (shouldShowDeleteButton(myMembershipSt) ? "none" : "block"); | ||||||
|  |     return box; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function updateLocalStateFromChatListUpdResp(chatListUpdResp){ | ||||||
|  |     LocalHistoryId = chatListUpdResp.HistoryId; | ||||||
|  | 
 | ||||||
|  |     let literalChatList = document.getElementById("CL-dblec"); | ||||||
|  | 
 | ||||||
|  |     for (let myMembershipSt of chatListUpdResp.myChats){ | ||||||
|  |         let chatId = myMembershipSt.chatId; | ||||||
|  |         console.log(myMembershipSt); | ||||||
|  |         if (myChats.has(chatId)){ | ||||||
|  |             myChats.set(chatId, myMembershipSt); | ||||||
|  |             updateBoxWithNewSt(chatBoxes.get(chatId), myMembershipSt); | ||||||
|         } else { |         } else { | ||||||
|             throw new Error(res.error || 'Ошибка'); |             if (myMembershipSt.myRoleHere === userChatRoleDeleted) | ||||||
|  |                 continue; | ||||||
|  |             myChats.set(chatId, myMembershipSt); | ||||||
|  |             let box = convertMyMembershipStToBox(myMembershipSt) | ||||||
|  |             chatBoxes.set(chatId, box); | ||||||
|  |             literalChatList.appendChild(box); | ||||||
|         } |         } | ||||||
|     } catch (error) { |  | ||||||
|         alert('Ошибка создания чата: ' + error.message); |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function addRoomToList(roomName) { | /* Use it ONLY if `Recv` reported success */ | ||||||
|     const roomList = document.querySelector('.room-list'); | function updateLocalStateFromRecv(Recv){ | ||||||
|     const existingRoomItem = Array.from(roomList.children).find(item => item.querySelector('.room-name').textContent === roomName); |     updateLocalStateFromChatListUpdResp(Recv.chatListUpdResp); | ||||||
|     if (existingRoomItem) { |  | ||||||
|         existingRoomItem.remove(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     const roomItem = document.createElement('li'); |  | ||||||
|     roomItem.classList.add('room-item'); |  | ||||||
| 
 |  | ||||||
|     roomItem.innerHTML = ` |  | ||||||
|         <span class="room-name">${roomName}</span> |  | ||||||
|         <button class="delete-chat-button" onclick="openConfirm('${roomNickname}')">Удалить чат</button> |  | ||||||
|         <button class="add-members-button" onclick="openAdd()">Добавить участников</button> |  | ||||||
|         <button class="join-button" onclick="window.location.href = '/chat/${roomNickname}'">Войти</button> |  | ||||||
|     `;
 |  | ||||||
| 
 |  | ||||||
|     roomList.appendChild(roomItem); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function removeRoomFromList(roomName, roomNickname) { | function configureChatCreationInterface(){ | ||||||
|     const roomList = document.querySelector('.room-list'); |     document.getElementById("chat-creation-win-yes").onclick = function (ev) { | ||||||
|     const roomItem = Array.from(roomList.children).find(item => item.querySelector('.room-name').textContent === roomName); |         if (ev.button !== 0) | ||||||
|     if (roomItem) { |             return; | ||||||
|         roomList.removeChild(roomItem); |         let chatNicknameInput = document.getElementById("chat-nickname-input"); | ||||||
|     } |         let chatNameInput = document.getElementById("chat-name-input"); | ||||||
| } |         let nickname = String(chatNicknameInput.value); | ||||||
| 
 |         let name = String(chatNameInput.value); | ||||||
| async function initializeRoomList() { |         deactivateActivePopup(); | ||||||
|     try { |         let Sent = genSentBase(); | ||||||
|         const response = await fetch('/internalapi/getChatList', { |         Sent.content = {}; | ||||||
|             method: 'POST', |         Sent.content.nickname = nickname; | ||||||
|             headers: { |         Sent.content.name = name; | ||||||
|                 'Content-Type': 'application/json' |         apiRequest("createChat", Sent | ||||||
|             }, |         ).then((Recv) => { | ||||||
|             body: JSON.stringify({}) |             updateLocalStateFromRecv(Recv); | ||||||
|  |         }).catch((e) => { | ||||||
|  |             alert(pres['list-rooms']["failed-create-chat"]); | ||||||
|  |             console.log(e); | ||||||
|         }); |         }); | ||||||
|  |     }; | ||||||
| 
 | 
 | ||||||
|         const res = await response.json(); |     document.getElementById("chat-creation-win-no").onclick = function (ev) { | ||||||
|  |         if (ev.button !== 0) | ||||||
|  |             return; | ||||||
|  |         deactivateActivePopup(); | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|         if (res.status === 0) { |     document.getElementById("CL-bacbe").onclick = function (ev){ | ||||||
|             res.chats.forEach(chat => { |         if (ev.button !== 0) | ||||||
|                 addRoomToList(chat.content.name, chat.content.nickname); |             return; | ||||||
|             }); |         let chatNicknameInput = document.getElementById("chat-nickname-input"); | ||||||
|         } else { |         let chatNameInput = document.getElementById("chat-name-input"); | ||||||
|             throw new Error(res.error || 'Неизвестная ошибка'); |         chatNicknameInput.value = ""; | ||||||
|         } |         chatNameInput.value = ""; | ||||||
|     } catch (error) { |         activatePopupWindowById("chat-creation-win"); | ||||||
|         alert('Ошибка загрузки списка чатов: ' + error.message); |     }; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function configureChatRenunciationInterfaceWinPart(){ | ||||||
|  |     document.getElementById("chat-renunciation-win-yes").onclick = function (ev){ | ||||||
|  |         if (ev.button !== 0) | ||||||
|  |             return; | ||||||
|  |         deactivateActivePopup(); | ||||||
|  |         if (chatRenunciationWinStoredId < 0) | ||||||
|  |             throw new Error("chatRenunciationWinStoredId < 0"); | ||||||
|  |         let chatId = chatRenunciationWinStoredId; | ||||||
|  |         let Sent = genSentBase(); | ||||||
|  |         Sent.chatId = chatId; | ||||||
|  |         apiRequest("leaveChat", Sent | ||||||
|  |         ).then((Recv) => { | ||||||
|  |             updateLocalStateFromRecv(Recv); | ||||||
|  |         }).catch((e) => { | ||||||
|  |             alert(pres['list-rooms']["failed-create-chat"]); | ||||||
|  |             console.log(e); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     document.getElementById("chat-renunciation-win-no").onclick = function(ev) { | ||||||
|  |         if (ev.button !== 0) | ||||||
|  |             return; | ||||||
|  |         deactivateActivePopup(); | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 | __mainloopDelayMs = 3000; | ||||||
| async function getChatID() { | __guestMainloopPollerAction = function(){ | ||||||
|     const chatNickname = window.location.pathname.split('/').pop(); |     let Sent = genSentBase(); | ||||||
|     const response = await fetch('/internalapi/getChatList', { |     apiRequest("chatListPollEvents", Sent | ||||||
|         method: 'POST', |     ).then((Recv) => { | ||||||
|         headers: { |         console.log("Got a response"); | ||||||
|             'Content-Type': 'application/json' |         console.log(Recv); | ||||||
|         }, |         updateLocalStateFromRecv(Recv); | ||||||
|         body: JSON.stringify({}) |  | ||||||
|     }); |     }); | ||||||
| 
 |  | ||||||
|     const res = await response.json(); |  | ||||||
|     for (const chat of res.chats) { |  | ||||||
|         if (chat.content.nickname === chatNickname) { |  | ||||||
|             return chat.id; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|     return -1; |  | ||||||
| } |  | ||||||
| window.onclick = function(event) { |  | ||||||
|     if (event.target === document.getElementById('createRoomModal')) { |  | ||||||
|         closeCreateRoomModal(); |  | ||||||
|     } |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| document.getElementById('newRoomName').addEventListener('keydown', function(event) { | window.onload = function () { | ||||||
|     if (event.key === 'Enter') { |     console.log("Loading complete"); | ||||||
|         createRoom(); |     updateLocalStateFromChatListUpdResp(initial_chatListUpdResp); | ||||||
|     } |     configureChatCreationInterface(); | ||||||
| }); |     configureChatRenunciationInterfaceWinPart(); | ||||||
| document.addEventListener('DOMContentLoaded', initializeRoomList); |     mainloopPoller(); | ||||||
|  | }; | ||||||
|  | |||||||
| @ -1,14 +0,0 @@ | |||||||
| document.addEventListener('DOMContentLoaded', function() { |  | ||||||
|     function handleSubmit(event) { |  | ||||||
|         event.preventDefault();  |  | ||||||
| 
 |  | ||||||
|         const nickname = document.getElementById('nickname').value; |  | ||||||
|         const password = document.getElementById('password').value; |  | ||||||
| 
 |  | ||||||
|         window.location.href = '/assets/HypertextPages/list-rooms.nytl.html'; |  | ||||||
|          |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     const form = document.querySelector('form'); |  | ||||||
|     form.addEventListener('submit', handleSubmit); |  | ||||||
| }); |  | ||||||
| @ -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); |  | ||||||
|     } |  | ||||||
| }); |  | ||||||
							
								
								
									
										91
									
								
								assets/lang/en-US.lang.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,91 @@ | |||||||
|  | { | ||||||
|  |   "lang": "en", | ||||||
|  |   "login": { | ||||||
|  |     "header": "Login", | ||||||
|  |     "directive-nickname": "Enter your nickname:", | ||||||
|  |     "placeholder-nickname": "Nickname", | ||||||
|  |     "directive-password": "Enter password:", | ||||||
|  |     "placeholder-password": "Password", | ||||||
|  |     "act": "Login", | ||||||
|  |     "incorrect-nickname-or-password": "Incorrect nickname or password" | ||||||
|  |   }, | ||||||
|  |   "view-profile": { | ||||||
|  |     "header-profile-of": "Profile of", | ||||||
|  |     "directive-nickname": "Nickname:" | ||||||
|  |   }, | ||||||
|  |   "edit-profile": { | ||||||
|  |     "header-profile-of": "Profile of", | ||||||
|  |     "change-user-attributes": "Change user attributes", | ||||||
|  |     "directive-nickname": "Nickname:", | ||||||
|  |     "directive-name": "Enter new name:", | ||||||
|  |     "placeholder-name": "New name", | ||||||
|  |     "directive-password": "Enter new password:", | ||||||
|  |     "placeholder-password": "New password", | ||||||
|  |     "directive-bio": "Change description:", | ||||||
|  |     "act-submit": "Submit changes", | ||||||
|  |     "incorrect-profile-data": "Incorrec profile data" | ||||||
|  |   }, | ||||||
|  |   "list-rooms": { | ||||||
|  |     "header": "List of chat rooms", | ||||||
|  |     "new-chat-header": "Input identifying information for your new chat", | ||||||
|  |     "directive-nickname": "Enter nickname for new chat:", | ||||||
|  |     "placeholder-nickname": "Take a nickname", | ||||||
|  |     "directive-name": "Enter name for new chat:", | ||||||
|  |     "placeholder-name": "Come up with name", | ||||||
|  |     "reask-create-new-chat": "Create new chat?", | ||||||
|  |     "yes-create": "Yes, create", | ||||||
|  |     "no-create": "No, cancel", | ||||||
|  |     "reask-leave-chat-X": "Do you really want to leave chat", | ||||||
|  |     "yes-leave": "Yes, leave", | ||||||
|  |     "no-leave": "No, cancel", | ||||||
|  |     "page-description": "List of available rooms", | ||||||
|  |     "you-are-X-here": ["You are", "here"], | ||||||
|  | 
 | ||||||
|  |     "failed-create-chat": "Failed to create chat", | ||||||
|  |     "failed-to-leave-chat": "Failed to leave chat" | ||||||
|  |   }, | ||||||
|  |   "chat-members": { | ||||||
|  |     "members-of": "Members of", | ||||||
|  |     "summon-label-nickname": "Nickname for summoned user", | ||||||
|  |     "summon-label-ro": "Make read only", | ||||||
|  |     "yes-summon": "Yes, summon", | ||||||
|  |     "no-summon": "No, cancel", | ||||||
|  |     "yes-kick": "Yes, delete", | ||||||
|  |     "no-kick": "No, cancel", | ||||||
|  |     "members-list-of": "Members list of", | ||||||
|  |     "reask-kick-user-X" : "Do you really want to kick user", | ||||||
|  | 
 | ||||||
|  |     "failed-summon-member": "Failed to add user to chat", | ||||||
|  |     "failed-kick-member": "Failed to kick user from chat" | ||||||
|  |   }, | ||||||
|  |   "chat": { | ||||||
|  |     "header-chat": "Chat", | ||||||
|  |     "reask-delete-message": "Are you sure you want to delete this message?", | ||||||
|  |     "yes-delete": "Yes, delete", | ||||||
|  |     "no-delete": "No, cancel", | ||||||
|  |     "msgErased": "[ ERASED ]", | ||||||
|  |     "syslog": { | ||||||
|  |       "kicked": "kicked", | ||||||
|  |       "summoned": "summoned", | ||||||
|  |       "left": "left chat", | ||||||
|  |       "created": "created this chat" | ||||||
|  |     }, | ||||||
|  |     "failed-delete-message": "Failed to delete message", | ||||||
|  |     "failed-send-message": "Failed to send message" | ||||||
|  |   }, | ||||||
|  |   "register": { | ||||||
|  |     "header": "Admin control - Registration", | ||||||
|  |     "directive-nickname": "Nickname for new user", | ||||||
|  |     "placeholder-nickname": "Nickname", | ||||||
|  |     "directive-name": "Name for new user:", | ||||||
|  |     "placeholder-name": "Name", | ||||||
|  |     "directive-password": "Temporary password:", | ||||||
|  |     "placeholder-password": "Password", | ||||||
|  |     "act": "Register him", | ||||||
|  |     "incorrect-nickname": "Incorrect nickname", | ||||||
|  |     "incorrect-name": "Incorrect name", | ||||||
|  |     "incorrect-password": "Incorrect password", | ||||||
|  |     "nickname-taken": "Nickname already taken", | ||||||
|  |     "add_user_error": "add_user failed" | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										91
									
								
								assets/lang/ru-RU.lang.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,91 @@ | |||||||
|  | { | ||||||
|  |   "lang": "ru", | ||||||
|  |   "login": { | ||||||
|  |     "header": "Вход", | ||||||
|  |     "directive-nickname": "Введите свой никнейм:", | ||||||
|  |     "placeholder-nickname": "Никнейм", | ||||||
|  |     "directive-password": "Введите пароль:", | ||||||
|  |     "placeholder-password": "Пароль", | ||||||
|  |     "act": "Войти", | ||||||
|  |     "incorrect-nickname-or-password": "Неверный логин или пароль" | ||||||
|  |   }, | ||||||
|  |   "view-profile": { | ||||||
|  |     "header-profile-of": "Профиль", | ||||||
|  |     "directive-nickname": "Никнейм:" | ||||||
|  |   }, | ||||||
|  |   "edit-profile": { | ||||||
|  |     "header-profile-of": "Профиль", | ||||||
|  |     "change-user-attributes": "Изменить аттрибуты пользователя", | ||||||
|  |     "directive-nickname": "Никнейм:", | ||||||
|  |     "directive-name": "Введите новое имя:", | ||||||
|  |     "placeholder-name": "Новое имя", | ||||||
|  |     "directive-password": "Введите новый пароль", | ||||||
|  |     "placeholder-password": "Новый пароль", | ||||||
|  |     "directive-bio": "Изменить 'о себе':", | ||||||
|  |     "act-submit": "Применить", | ||||||
|  |     "incorrect-profile-data": "Недопустимые данные профиля" | ||||||
|  |   }, | ||||||
|  |   "list-rooms": { | ||||||
|  |     "header": "Список чат-комнат", | ||||||
|  |     "new-chat-header": "Введите идентификационные данные для вашего нового чата", | ||||||
|  |     "directive-nickname": "Введите никнейм для нового чата:", | ||||||
|  |     "placeholder-nickname": "Займите никнейм", | ||||||
|  |     "directive-name": "Введите имя для нового чата:", | ||||||
|  |     "placeholder-name": "Придумайте имя", | ||||||
|  |     "reask-create-new-chat": "Создать новый чат?", | ||||||
|  |     "yes-create": "Да, создай", | ||||||
|  |     "no-create": "Нет, отмена", | ||||||
|  |     "reask-leave-chat-X": "Вы действительно хотите покинуть чат", | ||||||
|  |     "yes-leave": "Да, покидаю", | ||||||
|  |     "no-leave": "Нет, отмена", | ||||||
|  |     "page-description": "Список доступных чат-комнат", | ||||||
|  |     "you-are-X-here": ["Вы", "здесь"], | ||||||
|  | 
 | ||||||
|  |     "failed-create-chat": "Не смог создать чат", | ||||||
|  |     "failed-to-leave-chat": "Не смог покинуть чат" | ||||||
|  |   }, | ||||||
|  |   "chat-members": { | ||||||
|  |     "members-of": "Участники", | ||||||
|  |     "summon-label-nickname": "Никнейм для призываемого пользователя", | ||||||
|  |     "summon-label-ro": "Сделать 'лишь читающим'", | ||||||
|  |     "yes-summon": "Да, призваю", | ||||||
|  |     "no-summon": "Нет, отмена", | ||||||
|  |     "yes-kick": "Да, выкидываю", | ||||||
|  |     "no-kick": "Нет, отмена", | ||||||
|  |     "members-list-of": "Список участников", | ||||||
|  |     "reask-kick-user-X" : "Вы действительно хотите выкинуть участника", | ||||||
|  | 
 | ||||||
|  |     "failed-summon-member": "Не смог добавить участника", | ||||||
|  |     "failed-kick-member": "Не смог выкинуть участника" | ||||||
|  |   }, | ||||||
|  |   "chat": { | ||||||
|  |     "header-chat": "Чат", | ||||||
|  |     "reask-delete-message": "Удалить это сообщение?", | ||||||
|  |     "yes-delete": "Да, удаляю", | ||||||
|  |     "no-delete": "Нет, отмена", | ||||||
|  |     "msgErased": "[ СТЁРТО ]", | ||||||
|  |     "syslog": { | ||||||
|  |       "kicked": "выкинул", | ||||||
|  |       "summoned": "призвал", | ||||||
|  |       "left": "покинул чат", | ||||||
|  |       "created": "создал этот чат" | ||||||
|  |     }, | ||||||
|  |     "failed-delete-message": "Не смог удалить сообщение", | ||||||
|  |     "failed-send-message": "Не смог отправить сообщение" | ||||||
|  |   }, | ||||||
|  |   "register": { | ||||||
|  |     "header": "Admin control - Регистрация", | ||||||
|  |     "directive-nickname": "Никнейм для нового пользователя:", | ||||||
|  |     "placeholder-nickname": "Никнейм", | ||||||
|  |     "directive-name": "Имя для нового пользователя:", | ||||||
|  |     "placeholder-name": "Имя", | ||||||
|  |     "directive-password": "Временный пароль:", | ||||||
|  |     "placeholder-password": "Пароль", | ||||||
|  |     "act": "Зарегистрируй его", | ||||||
|  |     "incorrect-nickname": "Плохой никнейм", | ||||||
|  |     "incorrect-name": "Плохое имя", | ||||||
|  |     "incorrect-password": "Плохой пароль", | ||||||
|  |     "nickname-taken": "Никнейм уже занят", | ||||||
|  |     "add_user_error": "add_user failed" | ||||||
|  |   } | ||||||
|  | } | ||||||
| @ -78,6 +78,7 @@ struct CAWebChat { | |||||||
|                 "http_structures/client_request_parse.cpp", |                 "http_structures/client_request_parse.cpp", | ||||||
|                 "http_structures/response_gen.cpp", |                 "http_structures/response_gen.cpp", | ||||||
|                 "http_structures/cookies.cpp", |                 "http_structures/cookies.cpp", | ||||||
|  |                 "http_structures/accept_language.cpp", | ||||||
|                 "connecting_assets/static_asset_manager.cpp", |                 "connecting_assets/static_asset_manager.cpp", | ||||||
|                 "running_mainloop.cpp", |                 "running_mainloop.cpp", | ||||||
|                 "form_data_structure/urlencoded_query.cpp", |                 "form_data_structure/urlencoded_query.cpp", | ||||||
| @ -97,6 +98,7 @@ struct CAWebChat { | |||||||
|                 "http_structures/client_request.h", |                 "http_structures/client_request.h", | ||||||
|                 "http_structures/cookies.h", |                 "http_structures/cookies.h", | ||||||
|                 "http_structures/response_gen.h", |                 "http_structures/response_gen.h", | ||||||
|  |                 "http_structures/accept_language.h", | ||||||
|                 "running_mainloop.h", |                 "running_mainloop.h", | ||||||
|                 "form_data_structure/urlencoded_query.h", |                 "form_data_structure/urlencoded_query.h", | ||||||
|                 "socket_address.h", |                 "socket_address.h", | ||||||
| @ -141,6 +143,7 @@ struct CAWebChat { | |||||||
|                 CTargetDependenceOnExternalLibrary{"sqlite3", {true, true}} |                 CTargetDependenceOnExternalLibrary{"sqlite3", {true, true}} | ||||||
|             }; |             }; | ||||||
|             T.units = { |             T.units = { | ||||||
|  |                 "localizator.cpp", | ||||||
|                 "initialize.cpp", |                 "initialize.cpp", | ||||||
|                 "run.cpp", |                 "run.cpp", | ||||||
|                 "str_fields.cpp", |                 "str_fields.cpp", | ||||||
| @ -148,9 +151,21 @@ struct CAWebChat { | |||||||
|                 "sqlite3_wrapper.cpp", |                 "sqlite3_wrapper.cpp", | ||||||
|                 "login_cookie.cpp", |                 "login_cookie.cpp", | ||||||
|                 "backend_logic/server_data_interact.cpp", |                 "backend_logic/server_data_interact.cpp", | ||||||
|  |                 "backend_logic/client_server_interact.cpp", | ||||||
|  | 
 | ||||||
|                 "backend_logic/when_login.cpp", |                 "backend_logic/when_login.cpp", | ||||||
|                 "backend_logic/when_internalapi_pollevents.cpp", |                 "backend_logic/when_list_rooms.cpp", | ||||||
|                 "backend_logic/when_internalapi_getchatlist.cpp", |                 "backend_logic/when_chat.cpp", | ||||||
|  |                 "backend_logic/when_user.cpp", | ||||||
|  |                 "backend_logic/when_register.cpp", | ||||||
|  |                 "backend_logic/polling.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_leavechat.cpp", | ||||||
|  |                 "backend_logic/admin_control_procedure.cpp", | ||||||
|             }; |             }; | ||||||
|             for (std::string& u: T.units) |             for (std::string& u: T.units) | ||||||
|                 u = "web_chat/iu9_ca_web_chat_lib/" + u; |                 u = "web_chat/iu9_ca_web_chat_lib/" + u; | ||||||
|  | |||||||
| @ -1,33 +1,13 @@ | |||||||
| { | { | ||||||
|   "presentation": { |   "lang": { | ||||||
|     "lang": "ru", |     "whitelist": ["*"], | ||||||
|     "instance-identity": { |     "force-order": [ | ||||||
|       "top-title": "Вэб чат от ИУ9" |       "ru" | ||||||
|     }, |     ] | ||||||
|     "phr": { |  | ||||||
|       "decl": { |  | ||||||
|         "enter": "Вход", |  | ||||||
|         "nickname": "Никнейм", |  | ||||||
|         "password": "Пароль", |  | ||||||
|         "page-login": "Вход", |  | ||||||
|         "list-of-chat-rooms": "Список Чат-Коsмнат", |  | ||||||
|         "name-of-room": "Название комнаты", |  | ||||||
|         "create-room": "Создать комнату" |  | ||||||
|       }, |  | ||||||
|       "ask" : { |  | ||||||
|         "select-chat-room": "Выберете чат комнату" |  | ||||||
|       }, |  | ||||||
|       "act": { |  | ||||||
|         "enter": "Войти", |  | ||||||
|         "create-room": "Создать комнату", |  | ||||||
|         "confirm": "Подтвердить", |  | ||||||
|         "create": "Создать" |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|   }, |   }, | ||||||
|   "assets": "./assets", |   "assets": "./assets", | ||||||
|   "database": { |   "database": { | ||||||
|     "type": "sqlite", |     "type": "sqlite3", | ||||||
|     "file": "./iu9-ca-web-chat.db" |     "file": "./iu9-ca-web-chat.db" | ||||||
|   }, |   }, | ||||||
|   "limits": { |   "limits": { | ||||||
| @ -37,7 +17,7 @@ | |||||||
|     "storage-size-limit": 100000000000 |     "storage-size-limit": 100000000000 | ||||||
|   }, |   }, | ||||||
|   "server": { |   "server": { | ||||||
|     "workers": 8, |     "workers": 16, | ||||||
|     "http-listen": ["127.0.0.1:1025"], |     "http-listen": ["127.0.0.1:1025"], | ||||||
|     "admin-command-listen": ["[::1]:1026"] |     "admin-command-listen": ["[::1]:1026"] | ||||||
|   } |   } | ||||||
|  | |||||||
| @ -0,0 +1,72 @@ | |||||||
|  | #include "accept_language.h" | ||||||
|  | #include <algorithm> | ||||||
|  | #include "grammar.h" | ||||||
|  | #include "../baza_inter.h" | ||||||
|  | 
 | ||||||
|  | namespace een9 { | ||||||
|  |     bool AcceptLanguageSpec(char ch) { | ||||||
|  |         return ch == ',' || ch == ';' || ch == '='; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /* todo: This is one of many places in een9, where bad alloc does not interrupt request,
 | ||||||
|  |      * todo:  completely changing response instead. (see cookies and login cookies lol) | ||||||
|  |      * todo: I have to do something about it. Maybe add more exception types */ | ||||||
|  |     std::vector<std::string> parse_header_Accept_Language(const std::string &AcceptLanguage) { | ||||||
|  |         size_t n = AcceptLanguage.size(); | ||||||
|  |         struct LR { | ||||||
|  |             std::string lr; | ||||||
|  |             float q = 1; | ||||||
|  |         }; | ||||||
|  |         size_t i = 0; | ||||||
|  |         auto skipOWS = [&]() { | ||||||
|  |             while (i < n && isSPACE(AcceptLanguage[i])) | ||||||
|  |                 i++; | ||||||
|  |         }; | ||||||
|  |         auto isThis = [&](char ch) { | ||||||
|  |             skipOWS(); | ||||||
|  |             return i >= n ? false : AcceptLanguage[i] == ch; | ||||||
|  |         }; | ||||||
|  |         auto readTkn = [&]() -> std::string { | ||||||
|  |             skipOWS(); | ||||||
|  |             if (i >= n) | ||||||
|  |                 return ""; | ||||||
|  |             size_t bg = i; | ||||||
|  |             while (i < n && !AcceptLanguageSpec(AcceptLanguage[i]) && !isSPACE(AcceptLanguage[i])) | ||||||
|  |                 i++; | ||||||
|  |             return AcceptLanguage.substr(bg, i - bg); | ||||||
|  |         }; | ||||||
|  |         std::vector<LR> lrs; | ||||||
|  | #define myMsg "Bad Accept-Language" | ||||||
|  |         while (i < n) { | ||||||
|  |             skipOWS(); | ||||||
|  |             if (i >= n) | ||||||
|  |                 break; | ||||||
|  |             if (!lrs.empty()) { | ||||||
|  |                 if (isThis(',')) | ||||||
|  |                     i++; | ||||||
|  |                 else | ||||||
|  |                     break; | ||||||
|  |             } | ||||||
|  |             lrs.emplace_back(); | ||||||
|  |             lrs.back().lr = readTkn(); | ||||||
|  |             LR lr{readTkn(), 0}; | ||||||
|  |             if (isThis(';')) { | ||||||
|  |                 i++; | ||||||
|  |                 if (readTkn() != "q") | ||||||
|  |                     THROW(myMsg); | ||||||
|  |                 if (!isThis('=')) | ||||||
|  |                     THROW(myMsg); | ||||||
|  |                 i++; | ||||||
|  |                 lrs.back().q = std::stof(readTkn()); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         std::sort(lrs.begin(), lrs.end(), [](const LR& A, const LR& B) { | ||||||
|  |             return A.q > B.q; | ||||||
|  |         }); | ||||||
|  |         std::vector<std::string> result; | ||||||
|  |         result.reserve(lrs.size()); | ||||||
|  |         for (const LR& lr: lrs) | ||||||
|  |             result.push_back(lr.lr == "*" ? "" : lr.lr); | ||||||
|  |         return result; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -0,0 +1,13 @@ | |||||||
|  | #ifndef ENGINE_ENGINE_NUMBER_9_HTTP_STRUCTURES_ACCEPT_LANGUAGE_H | ||||||
|  | #define ENGINE_ENGINE_NUMBER_9_HTTP_STRUCTURES_ACCEPT_LANGUAGE_H | ||||||
|  | 
 | ||||||
|  | #include <vector> | ||||||
|  | #include <string> | ||||||
|  | 
 | ||||||
|  | namespace een9 { | ||||||
|  |     /* Returns language ranges, sorted by priority (reverse)
 | ||||||
|  |      * throws std::exception if header is incorrect! But it is not guaranteed. Maybe it won't */ | ||||||
|  |     std::vector<std::string> parse_header_Accept_Language(const std::string& AcceptLanguage); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #endif | ||||||
| @ -111,7 +111,11 @@ namespace een9 { | |||||||
|                             status = -1; |                             status = -1; | ||||||
|                             return status; |                             return status; | ||||||
|                         } |                         } | ||||||
|                         res.body.reserve(body_size); |                         res.body.reserve(std::min(100000ul, body_size)); | ||||||
|  |                         if (body_size == 0) { | ||||||
|  |                             status = 1; | ||||||
|  |                         } | ||||||
|  |                         break; | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|                 if (!res.has_body) { |                 if (!res.has_body) { | ||||||
|  | |||||||
| @ -1,14 +1,20 @@ | |||||||
| #include "cookies.h" | #include "cookies.h" | ||||||
| #include "../baza_inter.h" | #include "../baza_inter.h" | ||||||
| 
 | 
 | ||||||
|  | #include "grammar.h" | ||||||
|  | 
 | ||||||
| namespace een9 { | namespace een9 { | ||||||
|     bool isSPACE(char ch) { |     bool isSPACE(char ch) { | ||||||
|         return ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n'; |         return ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n'; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     bool isALPHANUM(char ch) { | ||||||
|  |         return ('a' <= ch && ch <= 'z') || ('A' <= ch && ch <= 'Z') || ('0' <= ch && ch <= '9'); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     bool isToken(const std::string &str) { |     bool isToken(const std::string &str) { | ||||||
|         for (char ch : str) { |         for (char ch : str) { | ||||||
|             if (!(('a' <= ch && ch <= 'z') || ('A' <= ch && ch <= 'Z') || ('0' <= ch && ch <= '9') |             if (!(isALPHANUM(ch) | ||||||
|                 || ch == '!' || ch == '#' || ch == '$' || ch == '%' || ch == '&' || ch == '\'' || ch == '*' |                 || ch == '!' || ch == '#' || ch == '$' || ch == '%' || ch == '&' || ch == '\'' || ch == '*' | ||||||
|                 || ch == '+' || ch == '-' || ch == '.' || ch == '^' || ch == '_' || ch == '`' || ch == '|' |                 || ch == '+' || ch == '-' || ch == '.' || ch == '^' || ch == '_' || ch == '`' || ch == '|' | ||||||
|                 || ch == '~' )) |                 || ch == '~' )) | ||||||
| @ -43,7 +49,7 @@ namespace een9 { | |||||||
|                 pos++; |                 pos++; | ||||||
|             return hv.substr(S, pos - S); |             return hv.substr(S, pos - S); | ||||||
|         }; |         }; | ||||||
|         auto read_to_space_or_dq_or_semc = [&]() -> std::string { |         auto read_to_space_or_semc = [&]() -> std::string { | ||||||
|             size_t S = pos; |             size_t S = pos; | ||||||
|             while (hv.size() > pos && !isSPACE(hv[pos]) && hv[pos] != '"' && hv[pos] != ';') |             while (hv.size() > pos && !isSPACE(hv[pos]) && hv[pos] != '"' && hv[pos] != ';') | ||||||
|                 pos++; |                 pos++; | ||||||
| @ -55,26 +61,22 @@ namespace een9 { | |||||||
|         }; |         }; | ||||||
|         skip_ows(); |         skip_ows(); | ||||||
|         while (pos < hv.size()) { |         while (pos < hv.size()) { | ||||||
|  |             if (!result.empty()) { | ||||||
|  |                 if (!isThis(';')) | ||||||
|  |                     THROW("Incorrect Cookie header line, missing ;"); | ||||||
|  |                 pos++; | ||||||
|  |                 skip_ows(); | ||||||
|  |             } | ||||||
|             std::string name_of_pechenye = read_to_space_or_eq(); |             std::string name_of_pechenye = read_to_space_or_eq(); | ||||||
|             ASSERT(isCookieName(name_of_pechenye), "Incorrect Cookie name"); |             // ASSERT(isCookieName(name_of_pechenye), "Incorrect Cookie name");
 | ||||||
|             skip_ows(); |             skip_ows(); | ||||||
|             ASSERT(isThis('='), "Incorrect Cookie header line, missing ="); |             if (!isThis('=')) | ||||||
|  |                 THROW("Incorrect Cookie header line, missing ="); | ||||||
|             pos++; |             pos++; | ||||||
|             skip_ows(); |             skip_ows(); | ||||||
|             std::string value_of_pechenye; |             std::string value_of_pechenye = read_to_space_or_semc(); | ||||||
|             if (isThis('"')) { |             // ASSERT(isCookieValue(value_of_pechenye), "Incorrect Cookie value");
 | ||||||
|                 pos++; |             result.emplace_back(name_of_pechenye, value_of_pechenye); | ||||||
|                 value_of_pechenye = read_to_space_or_dq_or_semc(); |  | ||||||
|                 ASSERT(isThis('"'), "Incorrect Cookie header line, missing \""); |  | ||||||
|                 pos++; |  | ||||||
|             } else { |  | ||||||
|                 value_of_pechenye = read_to_space_or_dq_or_semc(); |  | ||||||
|             } |  | ||||||
|             ASSERT(isCookieValue(value_of_pechenye), "Incorrect Cookie value"); |  | ||||||
|             if (result.empty()) |  | ||||||
|                 result.emplace_back(); |  | ||||||
|             result.back().first = std::move(name_of_pechenye); |  | ||||||
|             result.back().second = std::move(value_of_pechenye); |  | ||||||
|             skip_ows(); |             skip_ows(); | ||||||
|         } |         } | ||||||
|         return result; |         return result; | ||||||
| @ -84,11 +86,13 @@ namespace een9 { | |||||||
|     findAllClientCookies(const std::vector<std::pair<std::string, std::string>>& header) { |     findAllClientCookies(const std::vector<std::pair<std::string, std::string>>& header) { | ||||||
|         std::vector<std::pair<std::string, std::string>> result; |         std::vector<std::pair<std::string, std::string>> result; | ||||||
|         for (const std::pair<std::string, std::string>& line: header) { |         for (const std::pair<std::string, std::string>& line: header) { | ||||||
|             if (line.first == "Cookie") { |             try { | ||||||
|                 std::vector<std::pair<std::string, std::string>> new_cookies = parseCookieHeader(line.second); |                 if (line.first == "Cookie") { | ||||||
|                 result.reserve(result.size() + new_cookies.size()); |                     std::vector<std::pair<std::string, std::string>> new_cookies = parseCookieHeader(line.second); | ||||||
|                 result.insert(result.end(), new_cookies.begin(), new_cookies.end()); |                     result.reserve(result.size() + new_cookies.size()); | ||||||
|             } |                     result.insert(result.end(), new_cookies.begin(), new_cookies.end()); | ||||||
|  |                 } | ||||||
|  |             } catch (const std::exception& e) {} | ||||||
|         } |         } | ||||||
|         return result; |         return result; | ||||||
|     } |     } | ||||||
| @ -97,7 +101,7 @@ namespace een9 { | |||||||
|                     std::vector<std::pair<std::string, std::string>>& res_header_lines) { |                     std::vector<std::pair<std::string, std::string>>& res_header_lines) { | ||||||
|         for (const std::pair<std::string, std::string>& cookie : new_cookies) { |         for (const std::pair<std::string, std::string>& cookie : new_cookies) { | ||||||
|             ASSERT_pl(isCookieName(cookie.first) && isCookieValue(cookie.second)); |             ASSERT_pl(isCookieName(cookie.first) && isCookieValue(cookie.second)); | ||||||
|             res_header_lines.emplace_back("Set-Cookie", cookie.first + "=\"" + cookie.second + "\";SameSite=Strict;Path=/"); |             res_header_lines.emplace_back("Set-Cookie", cookie.first + "=" + cookie.second + ";SameSite=Strict;Path=/"); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -0,0 +1,11 @@ | |||||||
|  | #ifndef ENGINE_ENGINE_NUMBER_9_HTTP_STRUCTURES_GRAMMAR_H | ||||||
|  | #define ENGINE_ENGINE_NUMBER_9_HTTP_STRUCTURES_GRAMMAR_H | ||||||
|  | 
 | ||||||
|  | #include <string> | ||||||
|  | 
 | ||||||
|  | namespace een9 { | ||||||
|  |     bool isSPACE(char ch); | ||||||
|  |     bool isALPHANUM(char ch); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #endif | ||||||
| @ -41,14 +41,14 @@ namespace een9 { | |||||||
|         }, body); |         }, body); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     std::string form_http_server_response_307(const std::string& Location) { |     std::string form_http_server_response_303(const std::string& Location) { | ||||||
|         return form_http_server_response_header_only("307", {{"Location", Location}}); |         return form_http_server_response_header_only("303", {{"Location", Location}}); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     std::string form_http_server_response_307_spec_head(const std::string &Location, |     std::string form_http_server_response_303_spec_head(const std::string &Location, | ||||||
|         const std::vector<std::pair<std::string, std::string>>& headers) { |         const std::vector<std::pair<std::string, std::string>>& headers) { | ||||||
|         std::vector<std::pair<std::string, std::string>> cp = headers; |         std::vector<std::pair<std::string, std::string>> cp = headers; | ||||||
|         cp.emplace_back("Location", Location); |         cp.emplace_back("Location", Location); | ||||||
|         return form_http_server_response_header_only("307", cp); |         return form_http_server_response_header_only("303", cp); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -20,9 +20,9 @@ namespace een9 { | |||||||
| 
 | 
 | ||||||
|     std::string form_http_server_response_404(const std::string& Content_Type, const std::string& body); |     std::string form_http_server_response_404(const std::string& Content_Type, const std::string& body); | ||||||
| 
 | 
 | ||||||
|     std::string form_http_server_response_307(const std::string& Location); |     std::string form_http_server_response_303(const std::string& Location); | ||||||
| 
 | 
 | ||||||
|     std::string form_http_server_response_307_spec_head(const std::string &Location, |     std::string form_http_server_response_303_spec_head(const std::string &Location, | ||||||
|         const std::vector<std::pair<std::string, std::string>>& headers); |         const std::vector<std::pair<std::string, std::string>>& headers); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -212,7 +212,7 @@ namespace een9 { | |||||||
|             pthread_create(&workers[i], NULL, worker_func, wtes[i].get()); |             pthread_create(&workers[i], NULL, worker_func, wtes[i].get()); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         // todo: move this try block inside the loop
 |         // todo: right now this try block protects threads. So I need to put pthreads in some kind of guarding object
 | ||||||
|         try { |         try { | ||||||
|             int ret; |             int ret; | ||||||
|             struct Ear { |             struct Ear { | ||||||
| @ -251,19 +251,17 @@ namespace een9 { | |||||||
|             } |             } | ||||||
|             ASSERT(params.mainloop_recheck_interval_us > 0, "Incorrect poll timeout"); |             ASSERT(params.mainloop_recheck_interval_us > 0, "Incorrect poll timeout"); | ||||||
|             while (true) { |             while (true) { | ||||||
|                 // MutexLockGuard lg1(wtec.corvee_bed, "poller termination check");
 |  | ||||||
|                 if (wtec.termination) |                 if (wtec.termination) | ||||||
|                     break; |                     break; | ||||||
|                 // lg1.unlock();
 |  | ||||||
|                 for (size_t i = 0; i < Nip; i++) { |                 for (size_t i = 0; i < Nip; i++) { | ||||||
|                     pollfds[i].revents = 0; |                     pollfds[i].revents = 0; | ||||||
|                 } |                 } | ||||||
|                 errno = 0; |                 errno = 0; | ||||||
|                 ret = poll(pollfds.data(), Nip, params.mainloop_recheck_interval_us); |                 ret = poll(pollfds.data(), Nip, params.mainloop_recheck_interval_us); | ||||||
|                 if (errno == EINTR) |                 if (ret != 0 && errno != 0) { | ||||||
|                     break; |                     printf("poll() error :> %s\n", een9::prettyprint_errno("").c_str()); | ||||||
|                 // todo: do not end program here (in the loop. Nothing in the loop should be able to end program)
 |                     continue; | ||||||
|                 ASSERT_on_iret(ret, "poll()"); |                 } | ||||||
|                 for (size_t i = 0; i < Nip; i++) { |                 for (size_t i = 0; i < Nip; i++) { | ||||||
|                     if ((pollfds[i].revents & POLLRDNORM)) { |                     if ((pollfds[i].revents & POLLRDNORM)) { | ||||||
|                         try { |                         try { | ||||||
| @ -294,7 +292,7 @@ namespace een9 { | |||||||
|         } catch (const std::exception& e) { |         } catch (const std::exception& e) { | ||||||
|             printf("System failure 2\n"); |             printf("System failure 2\n"); | ||||||
|             printf("%s\n", e.what()); |             printf("%s\n", e.what()); | ||||||
|             /* There is no need to tiptoe around this multi-access field. It is write-onle-and-for-good-kind  */ |             /* There is no need to tiptoe around this multi-access field. It is write-once-and-for-good-kind  */ | ||||||
|             wtec.termination = true; |             wtec.termination = true; | ||||||
|             wtec.corvee_bed.wake_them_all(); |             wtec.corvee_bed.wake_them_all(); | ||||||
|         } |         } | ||||||
|  | |||||||
| @ -1,8 +1,6 @@ | |||||||
| {% ELDEF main JSON cba %} | {% ELDEF main JSON userprofile %} | ||||||
|     AAA |     AAA | ||||||
|         {% FOR val IN cba.arr %} |     --> {% WRITE userprofile.name %} | ||||||
|             --> {% WRITE val %} |  | ||||||
|         {% ENDFOR %} |  | ||||||
|     AAA |     AAA | ||||||
| 
 | 
 | ||||||
| {% ENDELDEF %} | {% ENDELDEF %} | ||||||
|  | |||||||
							
								
								
									
										35
									
								
								src/http_server/misc_tests/accept_language_test.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,35 @@ | |||||||
|  | #include <engine_engine_number_9/baza_inter.h> | ||||||
|  | #include <engine_engine_number_9/http_structures/accept_language.h> | ||||||
|  | 
 | ||||||
|  | using namespace een9; | ||||||
|  | 
 | ||||||
|  | void test(const std::string& al, const std::vector<std::string>& rls) { | ||||||
|  |     std::vector<std::string> got = parse_header_Accept_Language(al); | ||||||
|  |     if (got != rls) { | ||||||
|  |         printf("Test failed: wrong answer\n"); | ||||||
|  |         abort(); | ||||||
|  |     } | ||||||
|  |     printf("Test passed\n"); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void btest(const std::string& al) { | ||||||
|  |     try { | ||||||
|  |         parse_header_Accept_Language(al); | ||||||
|  |     } catch (std::exception& e) {} | ||||||
|  |     printf("...\n"); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | int main() { | ||||||
|  |     test("RU-RU, uk-EN; q = 12.22", {"uk-EN", "RU-RU"}); | ||||||
|  |     test("   RU-RU   ,uk-EN; q = 12.22  ", {"uk-EN", "RU-RU"}); | ||||||
|  |     test(" AAA; q=0.1,  BBB-bb ; q=3, *; q=3", {"BBB-bb", "", "AAA"}); | ||||||
|  |     test(" AAA; q=0.1,  BBB-bb ; q=2.5, *; q=4.5", {"", "BBB-bb", "AAA"}); | ||||||
|  |     test("ABB, AAA; q=0.1,AAB,  BBB-bb ; q=2.5, *; q=4.5", {"", "BBB-bb", "ABB", "AAB", "AAA"}); | ||||||
|  |     test("", {}); | ||||||
|  |     test("   ", {}); | ||||||
|  |     btest(";;;;"); | ||||||
|  |     btest(";;==;;"); | ||||||
|  |     btest("-;"); | ||||||
|  |     btest("-=="); | ||||||
|  |     return 0; | ||||||
|  | } | ||||||
| @ -9,16 +9,19 @@ int main(int argc, char** argv) { | |||||||
|         exit(1); |         exit(1); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     std::string dir_path = argv[1]; |     // std::string dir_path = "./src/http_server/misc_tests/HypertextPages";
 | ||||||
|  |     std::string dir_path = "/home/gregory/cpp_projects/iu9-ca-web-chat/assets/HypertextPages"; | ||||||
|     nytl::Templater templater(nytl::TemplaterSettings{nytl::TemplaterDetourRules{dir_path}}); |     nytl::Templater templater(nytl::TemplaterSettings{nytl::TemplaterDetourRules{dir_path}}); | ||||||
|     templater.update(); |     templater.update(); | ||||||
| 
 | 
 | ||||||
|     std::string config_file = argv[2]; |     json::JSON userprofile; | ||||||
|     std::string config_text; |     userprofile["uid"].asInteger() = json::Integer(0l); | ||||||
|     een9::readFile(config_file, config_text); |     userprofile["name"].asString() = "radasdasdasdadsdasd"; | ||||||
|     const json::JSON config = json::parse_str_flawless(config_text); |     userprofile["nickname"].asString() = "root"; | ||||||
| 
 |     userprofile["bio"].asString() = "Your mother"; | ||||||
|     std::string answer2 = templater.render("login", {&config["presentation"].g()}); |     json::JSON errors; | ||||||
|  |     errors = json::JSON(json::array); | ||||||
|  |     std::string answer2 = templater.render("err-404", {}); | ||||||
|     printf("%s\n<a><f><t><e><r><><l><f>\n", answer2.c_str()); |     printf("%s\n<a><f><t><e><r><><l><f>\n", answer2.c_str()); | ||||||
| 
 | 
 | ||||||
|     return 0; |     return 0; | ||||||
|  | |||||||
| @ -7,8 +7,13 @@ namespace nytl { | |||||||
|     void debug_print_templater(const Templater& T) { |     void debug_print_templater(const Templater& T) { | ||||||
|         printf("===== TEMPLATER INTERNAL RESOURCES =====\n"); |         printf("===== TEMPLATER INTERNAL RESOURCES =====\n"); | ||||||
|         for (auto& p: T.elements) { |         for (auto& p: T.elements) { | ||||||
|  |             if (!p.second.is_element) { | ||||||
|  |                 printf("=== %s is empty =====\n", p.first.c_str()); | ||||||
|  |                 continue; | ||||||
|  |             } | ||||||
|             printf("=== %s element =====\n", p.first.c_str()); |             printf("=== %s element =====\n", p.first.c_str()); | ||||||
|             const Element& el = p.second; |             assert(p.second.when_element); | ||||||
|  |             const Element& el = *p.second.when_element; | ||||||
|             printf("%s, %s\n", el.base ? "BASE" : "NOT BASE", el.is_hidden ? "HIDDEN" : "NOT HIDDEN"); |             printf("%s, %s\n", el.base ? "BASE" : "NOT BASE", el.is_hidden ? "HIDDEN" : "NOT HIDDEN"); | ||||||
|             if (!el.is_hidden) { |             if (!el.is_hidden) { | ||||||
|                 std::string signature; |                 std::string signature; | ||||||
|  | |||||||
| @ -55,13 +55,16 @@ namespace nytl { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     char skip(ParsingContext& ctx) { |     char skip(ParsingContext& ctx) { | ||||||
|         ASSERT(ctx.pos < ctx.text.size(), "Unexpected EOF"); |         if (ctx.pos >= ctx.text.size()) | ||||||
|  |             THROW("Unexpected EOF"); | ||||||
|         return advance(ctx); |         return advance(ctx); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     void skip(ParsingContext& ctx, char ch) { |     void skip(ParsingContext& ctx, char ch) { | ||||||
|         ASSERT(ctx.pos < ctx.text.size(), "Unexpected EOF"); |         if (ctx.pos >= ctx.text.size()) | ||||||
|         ASSERT(ctx.text[ctx.pos] == ch, "Unexpected character"); |             THROW("Unexpected EOF"); | ||||||
|  |         if (ctx.text[ctx.pos] != ch) | ||||||
|  |             THROW("Unexpected character"); | ||||||
|         advance(ctx); |         advance(ctx); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -147,16 +150,41 @@ namespace nytl { | |||||||
|         return concatenateLines(lines); |         return concatenateLines(lines); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     void parse_bare_file(const std::string& filename, const std::string& content, |     Element& add_hidden_element(const std::string& new_el_name, global_elem_set_t& result) { | ||||||
|                              global_elem_set_t& result) |         if (result.count(new_el_name) != 0) | ||||||
|     { |             THROW("Repated element " + new_el_name); | ||||||
|         ASSERT(result.count(filename) == 0, "Repeated element " + filename); |         TemplaterRegPref& rp = result[new_el_name]; | ||||||
|  |         rp.is_element = 1; | ||||||
|  |         rp.when_element = std::make_unique<Element>(); | ||||||
|  |         rp.when_element->is_hidden = true; | ||||||
|  |         return *rp.when_element; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     Element& add_new_element(const std::string& new_el_name, global_elem_set_t& result) { | ||||||
|  |         if (!is_uname_dotted_sequence(new_el_name)) | ||||||
|  |             THROW("Krabovaya oshibka"); | ||||||
|  |         if (result.count(new_el_name) != 0 && result.at(new_el_name).is_element) | ||||||
|  |             THROW("Repated element " + new_el_name); | ||||||
|  |         size_t n = new_el_name.size(); | ||||||
|  |         for (size_t i = 0; i < n; i++) { | ||||||
|  |             if (new_el_name[i] == '.') { | ||||||
|  |                 std::string pref = new_el_name.substr(0, i); | ||||||
|  |                 result[pref]; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         TemplaterRegPref& rp = result[new_el_name]; | ||||||
|  |         rp.is_element = 1; | ||||||
|  |         rp.when_element = std::make_unique<Element>(); | ||||||
|  |         return *rp.when_element; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     void parse_bare_file(const std::string& filename, const std::string& content, global_elem_set_t& result) { | ||||||
|  |         Element& el = add_new_element(filename, result); | ||||||
|         std::string txt = clement_lstrip(content); |         std::string txt = clement_lstrip(content); | ||||||
|         rstrip(txt); |         rstrip(txt); | ||||||
|         size_t cut = 9999999999999; |         size_t cut = 9999999999999; | ||||||
|         one_part_update_min_start_wsp_non_empty(txt, 0, 1, cut); |         one_part_update_min_start_wsp_non_empty(txt, 0, 1, cut); | ||||||
|         txt = one_part_cut_excess_tab(txt, 0, 1, cut); |         txt = one_part_cut_excess_tab(txt, 0, 1, cut); | ||||||
|         Element& el = result[filename]; |  | ||||||
|         el.parts = {ElementPart{}}; |         el.parts = {ElementPart{}}; | ||||||
|         el.parts[0].when_code.lines = mv(txt); |         el.parts[0].when_code.lines = mv(txt); | ||||||
|     } |     } | ||||||
| @ -170,15 +198,17 @@ namespace nytl { | |||||||
|         uptr<TPFrame> toMe(bool returned, ParsingContext& ctx) { |         uptr<TPFrame> toMe(bool returned, ParsingContext& ctx) { | ||||||
|             if (!returned) { |             if (!returned) { | ||||||
|                 std::string nm = readName(ctx); |                 std::string nm = readName(ctx); | ||||||
|                 ASSERT(!nm.empty(), "Type specification expected"); |                 if (nm.empty()) | ||||||
|  |                     THROW("Type specification expected"); | ||||||
|                 nm = make_uppercase(nm); |                 nm = make_uppercase(nm); | ||||||
|                 if (nm == "JSON") { |                 if (nm == "JSON") { | ||||||
|                     result = json::JSON(true); |                     result = json::JSON(true); | ||||||
|                     return NULL; |                     return NULL; | ||||||
|                 } |                 } | ||||||
|                 ASSERT(nm == "EL", "Type of argument variable is either JSON or EL(...signature)") |                 if (nm != "EL") | ||||||
|  |                     THROW("Type of argument variable is either JSON or EL(...signature)"); | ||||||
|                 skip(ctx, '('); |                 skip(ctx, '('); | ||||||
|                 result = json::JSON(json::array); |                 result.asArray(); | ||||||
|                 assert(result.isArray()); |                 assert(result.isArray()); | ||||||
|             } |             } | ||||||
|             skipWhitespace(ctx); |             skipWhitespace(ctx); | ||||||
| @ -217,19 +247,21 @@ namespace nytl { | |||||||
|         uptr<EPFrame> toMe(bool returned, ParsingContext& ctx, const arg_name_list_t& local_var_names) { |         uptr<EPFrame> toMe(bool returned, ParsingContext& ctx, const arg_name_list_t& local_var_names) { | ||||||
|             if (!returned) { |             if (!returned) { | ||||||
|                 std::string first = readName(ctx); |                 std::string first = readName(ctx); | ||||||
|                 ASSERT(!first.empty(), "Expression should start with 'root' name of global package or local variable"); |                 if (first.empty()) | ||||||
|                 ASSERT(first != "_", "_ ??? ARE YOU KIDDING???"); |                     THROW("Expression should start with 'root' name of global package or local variable"); | ||||||
|  |                 if (first == "_") | ||||||
|  |                     THROW("Expression root can't be _"); | ||||||
|                 if (local_var_names.count(first) == 1) { |                 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 { |                 } else { | ||||||
|                     result["V"] = json::JSON(first); |                     result["V"].asString() = first; | ||||||
|                 } |                 } | ||||||
|                 result["C"] = json::JSON(json::array); |                 result["C"].asArray(); | ||||||
|             } else { |             } else { | ||||||
|                 skipWhitespace(ctx); |                 skipWhitespace(ctx); | ||||||
|                 skip(ctx, ']'); |                 skip(ctx, ']'); | ||||||
|             } |             } | ||||||
|             std::vector<json::JSON>& chain = result["C"].g().asArray(); |             std::vector<json::JSON>& chain = result["C"].asArray(); | ||||||
|             while (true) { |             while (true) { | ||||||
|                 if (peep(ctx) == '.') { |                 if (peep(ctx) == '.') { | ||||||
|                     skip(ctx, '.'); |                     skip(ctx, '.'); | ||||||
| @ -243,7 +275,8 @@ namespace nytl { | |||||||
|                     t = readUint(ctx); |                     t = readUint(ctx); | ||||||
|                     if (!t.empty()) { |                     if (!t.empty()) { | ||||||
|                         size_t v = std::stoul(t); |                         size_t v = std::stoul(t); | ||||||
|                         ASSERT(v < INT64_MAX, "Index is too big"); |                         if (v >= INT64_MAX) | ||||||
|  |                             THROW("Index is too big"); | ||||||
|                         chain.back() = json::JSON((int64_t)v); |                         chain.back() = json::JSON((int64_t)v); | ||||||
|                         continue; |                         continue; | ||||||
|                     } |                     } | ||||||
| @ -352,7 +385,8 @@ namespace nytl { | |||||||
|                 ElementPart::when_for_put_S& P = result.parts.back().when_for_put; |                 ElementPart::when_for_put_S& P = result.parts.back().when_for_put; | ||||||
|                 skipWhitespace(ctx); |                 skipWhitespace(ctx); | ||||||
|                 std::string V1 = readName(ctx); |                 std::string V1 = readName(ctx); | ||||||
|                 ASSERT(!V1.empty(), "Expected variable name"); |                 if (V1.empty()) | ||||||
|  |                     THROW("Expected variable name"); | ||||||
|                 skipWhitespace(ctx); |                 skipWhitespace(ctx); | ||||||
|                 bool have_colon_and_2 = false; |                 bool have_colon_and_2 = false; | ||||||
|                 std::string V2; |                 std::string V2; | ||||||
| @ -364,21 +398,23 @@ namespace nytl { | |||||||
|                     skipWhitespace(ctx); |                     skipWhitespace(ctx); | ||||||
|                 } |                 } | ||||||
|                 op = make_uppercase(readName(ctx)); |                 op = make_uppercase(readName(ctx)); | ||||||
|                 ASSERT(op == "IN", "Expected IN"); |                 if (op != "IN") | ||||||
|  |                     THROW("Expected IN"); | ||||||
|                 skipWhitespace(ctx); |                 skipWhitespace(ctx); | ||||||
|                 P.ref_over = parse_expression(ctx, local_var_names); |                 P.ref_over = parse_expression(ctx, local_var_names); | ||||||
|                 P.internal_element = el_name + ".~" + std::to_string(free_hidden++); |                 P.internal_element = el_name + ".~" + std::to_string(free_hidden++); | ||||||
|                 Element& newborn = elem_ns[P.internal_element]; |                 Element& newborn = add_hidden_element(P.internal_element, elem_ns); | ||||||
|                 newborn.is_hidden = true; |  | ||||||
|                 arg_name_list_t local_var_names_of_nxt = local_var_names; |                 arg_name_list_t local_var_names_of_nxt = local_var_names; | ||||||
|                 if (V1 != "_") { |                 if (V1 != "_") { | ||||||
|                     ASSERT(local_var_names_of_nxt.count(V1) == 0, "Repeated local variable"); |                     if (local_var_names_of_nxt.count(V1) != 0) | ||||||
|  |                         THROW("Repeated local variable"); | ||||||
|                     size_t k = local_var_names_of_nxt.size(); |                     size_t k = local_var_names_of_nxt.size(); | ||||||
|                     local_var_names_of_nxt.emplace(V1, k); |                     local_var_names_of_nxt.emplace(V1, k); | ||||||
|                     (have_colon_and_2 ? P.where_key_var : P.where_value_var) = (ssize_t)k; |                     (have_colon_and_2 ? P.where_key_var : P.where_value_var) = (ssize_t)k; | ||||||
|                 } |                 } | ||||||
|                 if (have_colon_and_2 && V2 != "_") { |                 if (have_colon_and_2 && V2 != "_") { | ||||||
|                     ASSERT(local_var_names_of_nxt.count(V2) == 0, "Repeated local variable"); |                     if (local_var_names_of_nxt.count(V2) != 0) | ||||||
|  |                         THROW("Repeated local variable"); | ||||||
|                     size_t k = local_var_names_of_nxt.size(); |                     size_t k = local_var_names_of_nxt.size(); | ||||||
|                     local_var_names_of_nxt.emplace(V2, k); |                     local_var_names_of_nxt.emplace(V2, k); | ||||||
|                     P.where_value_var = (ssize_t)k; |                     P.where_value_var = (ssize_t)k; | ||||||
| @ -395,16 +431,16 @@ namespace nytl { | |||||||
|                 ElementPart::when_ref_put_S& P = result.parts.back().when_ref_put; |                 ElementPart::when_ref_put_S& P = result.parts.back().when_ref_put; | ||||||
|                 skipWhitespace(ctx); |                 skipWhitespace(ctx); | ||||||
|                 std::string Vn = readName(ctx); |                 std::string Vn = readName(ctx); | ||||||
|                 ASSERT(!Vn.empty(), "Expected variable name"); |                 if (Vn.empty() || Vn == "_") | ||||||
|                 ASSERT(Vn != "_", "Are you kidding???"); |                     THROW("REF: expected variable name"); | ||||||
|                 skipWhitespace(ctx); |                 skipWhitespace(ctx); | ||||||
|                 op = make_uppercase(readName(ctx)); |                 op = make_uppercase(readName(ctx)); | ||||||
|                 ASSERT(op == "AS", "Expected AS"); |                 if (op != "AS") | ||||||
|  |                     THROW("Expected AS"); | ||||||
|                 skipWhitespace(ctx); |                 skipWhitespace(ctx); | ||||||
|                 P.ref_over = parse_expression(ctx, local_var_names); |                 P.ref_over = parse_expression(ctx, local_var_names); | ||||||
|                 P.internal_element = el_name + ".~" + std::to_string(free_hidden++); |                 P.internal_element = el_name + ".~" + std::to_string(free_hidden++); | ||||||
|                 Element& newborn = elem_ns[P.internal_element]; |                 Element& newborn = add_hidden_element(P.internal_element, elem_ns); | ||||||
|                 newborn.is_hidden = true; |  | ||||||
|                 arg_name_list_t local_var_names_of_nxt = local_var_names; |                 arg_name_list_t local_var_names_of_nxt = local_var_names; | ||||||
|                 size_t k = local_var_names_of_nxt.size(); |                 size_t k = local_var_names_of_nxt.size(); | ||||||
|                 local_var_names_of_nxt.emplace(Vn, k); |                 local_var_names_of_nxt.emplace(Vn, k); | ||||||
| @ -413,7 +449,7 @@ namespace nytl { | |||||||
|                 return std::make_unique<ECPFrame>(P.internal_element, gone_for_ref, local_var_names_of_nxt, |                 return std::make_unique<ECPFrame>(P.internal_element, gone_for_ref, local_var_names_of_nxt, | ||||||
|                     ret_data_int, newborn); |                     ret_data_int, newborn); | ||||||
|             } |             } | ||||||
|             if (op == "PUT") { |             if (op == "PUT" || op == "P") { | ||||||
|                 result.parts.emplace_back(); |                 result.parts.emplace_back(); | ||||||
|                 result.parts.back().type = ElementPart::p_put; |                 result.parts.back().type = ElementPart::p_put; | ||||||
|                 ElementPart::when_put_S& P = result.parts.back().when_put; |                 ElementPart::when_put_S& P = result.parts.back().when_put; | ||||||
| @ -439,11 +475,11 @@ namespace nytl { | |||||||
|                 P.passed_arguments = {parse_expression(ctx, local_var_names)}; |                 P.passed_arguments = {parse_expression(ctx, local_var_names)}; | ||||||
|                 skip_magic_block_end(ctx, syntax); |                 skip_magic_block_end(ctx, syntax); | ||||||
|             }; |             }; | ||||||
|             if (op == "WRITE") { |             if (op == "WRITE" || op == "W") { | ||||||
|                 mediocre_operator("str2text"); |                 mediocre_operator("str2text"); | ||||||
|                 goto ya_e_ya_h_i_ya_g_d_o;; |                 goto ya_e_ya_h_i_ya_g_d_o;; | ||||||
|             } |             } | ||||||
|             if (op == "ROUGHINSERT") { |             if (op == "ROUGHINSERT" || op == "RI") { | ||||||
|                 mediocre_operator("str2code"); |                 mediocre_operator("str2code"); | ||||||
|                 goto ya_e_ya_h_i_ya_g_d_o;; |                 goto ya_e_ya_h_i_ya_g_d_o;; | ||||||
|             } |             } | ||||||
| @ -467,13 +503,15 @@ namespace nytl { | |||||||
|                 } |                 } | ||||||
|             }; |             }; | ||||||
|             if (op == "ENDELDEF") { |             if (op == "ENDELDEF") { | ||||||
|                 ASSERT(myself == gone_for_nothing, "Unexpected end of element"); |                 if (myself != gone_for_nothing) | ||||||
|  |                     THROW("Unexpected ENDELDEF"); | ||||||
|                 skip_magic_block_end(ctx, syntax); |                 skip_magic_block_end(ctx, syntax); | ||||||
|                 prepare_to_depart_parts(); |                 prepare_to_depart_parts(); | ||||||
|                 return NULL; |                 return NULL; | ||||||
|             } |             } | ||||||
|             if (op == "ENDFOR") { |             if (op == "ENDFOR") { | ||||||
|                 ASSERT(myself == gone_for_for, "Unexpected end of for cycle"); |                 if (myself != gone_for_for) | ||||||
|  |                     THROW("Unexpected ENDFOR"); | ||||||
|                 skipWhitespace(ctx); |                 skipWhitespace(ctx); | ||||||
|                 /* Here I am using ret_data_int to return info about NOLF(1)/LF(2) decision */ |                 /* Here I am using ret_data_int to return info about NOLF(1)/LF(2) decision */ | ||||||
|                 ret_data_int = 2;  // Default is to do LF
 |                 ret_data_int = 2;  // Default is to do LF
 | ||||||
| @ -491,7 +529,8 @@ namespace nytl { | |||||||
|                 return NULL; |                 return NULL; | ||||||
|             } |             } | ||||||
|             if (op == "ENDREF") { |             if (op == "ENDREF") { | ||||||
|                 assert(myself == gone_for_ref); |                 if (myself != gone_for_ref) | ||||||
|  |                     THROW("Unexpected ENDREF"); | ||||||
|                 skip_magic_block_end(ctx, syntax); |                 skip_magic_block_end(ctx, syntax); | ||||||
|                 prepare_to_depart_parts(); |                 prepare_to_depart_parts(); | ||||||
|                 return NULL; |                 return NULL; | ||||||
| @ -525,13 +564,14 @@ namespace nytl { | |||||||
|             if (peep(ctx) == EOFVAL) |             if (peep(ctx) == EOFVAL) | ||||||
|                 break; |                 break; | ||||||
|             skip_magic_block_start(ctx, syntax); |             skip_magic_block_start(ctx, syntax); | ||||||
|             ASSERT(make_uppercase(readName(ctx)) == "ELDEF", "Expected ELDEF"); |             if (make_uppercase(readName(ctx)) != "ELDEF") | ||||||
|  |                 THROW("Expected ELDEF"); | ||||||
|             skipWhitespace(ctx); |             skipWhitespace(ctx); | ||||||
|             std::string elname_postfix = readName(ctx); |             std::string elname_postfix = readName(ctx); | ||||||
|             ASSERT(elname_postfix != "_", "please don't"); |             if (elname_postfix == "_") | ||||||
|  |                 THROW("Can't use _ as element name"); | ||||||
|             std::string fullname = elname_postfix == "main" ? filename : filename + "." + elname_postfix; |             std::string fullname = elname_postfix == "main" ? filename : filename + "." + elname_postfix; | ||||||
|             ASSERT(result.count(fullname) == 0, "Element " + fullname + " has been already defined"); |             Element& newborn = add_new_element(fullname, result); | ||||||
|             Element& newborn = result[fullname]; |  | ||||||
|             arg_name_list_t arglist; |             arg_name_list_t arglist; | ||||||
|             while (true) { |             while (true) { | ||||||
|                 skipWhitespace(ctx); |                 skipWhitespace(ctx); | ||||||
| @ -540,9 +580,11 @@ namespace nytl { | |||||||
|                 newborn.arguments.push_back(parse_type(ctx)); |                 newborn.arguments.push_back(parse_type(ctx)); | ||||||
|                 skipWhitespace(ctx); |                 skipWhitespace(ctx); | ||||||
|                 std::string argname = readName(ctx); |                 std::string argname = readName(ctx); | ||||||
|                 ASSERT(!argname.empty(), "Expected argument name"); |                 if (argname.empty()) | ||||||
|  |                     THROW("Expected argument name"); | ||||||
|                 if (argname != "_") { |                 if (argname != "_") { | ||||||
|                     ASSERT(arglist.count(argname) == 0, "Repeated argument (" + argname + ")"); |                     if (arglist.count(argname) != 0) | ||||||
|  |                         THROW("Repeated argument (" + argname + ")"); | ||||||
|                     size_t k = arglist.size(); |                     size_t k = arglist.size(); | ||||||
|                     arglist[argname] = k; |                     arglist[argname] = k; | ||||||
|                 } |                 } | ||||||
|  | |||||||
| @ -23,58 +23,64 @@ namespace nytl { | |||||||
|               result(result) { |               result(result) { | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         void descend(const json::JSON& what) { |         void descend(const json::JSON& what, const global_elem_set_t& global_elems) { | ||||||
|             if (result.is_json) { |             if (result.is_json) { | ||||||
|                 const json::JSON& P = *result.JSON_subval; |                 const json::JSON& P = *result.JSON_subval; | ||||||
|                 if (P.isArray() && what.isInteger()) { |                 if (P.isArray() && what.isInteger()) { | ||||||
|                     const std::vector<json::JSON>& arr_p = P.asArray(); |                     const std::vector<json::JSON>& arr_p = P.asArray(); | ||||||
|                     int64_t ind_w = what.asInteger().get_int(); |                     int64_t ind_w = what.asInteger().get_int(); | ||||||
|                     ASSERT(ind_w > 0 && ind_w < arr_p.size(), "Expression \"array[integer]\" caused out-of-bound situation"); |                     if (!(ind_w > 0 && ind_w < arr_p.size())) | ||||||
|  |                         THROW("Expression \"array[integer]\" caused out-of-bound situation"); | ||||||
|                     result = LocalVarValue{true, "", &arr_p[ind_w]}; |                     result = LocalVarValue{true, "", &arr_p[ind_w]}; | ||||||
|                 } else if (P.isDictionary() && what.isString()) { |                 } else if (P.isDictionary() && what.isString()) { | ||||||
|                     const std::map<std::string, json::JSON>& dict_p = P.asDictionary(); |                     const std::map<std::string, json::JSON>& dict_p = P.asDictionary(); | ||||||
|                     const std::string& key_w = what.asString(); |                     const std::string& key_w = what.asString(); | ||||||
|                     ASSERT(dict_p.count(key_w) == 1, "No such key exception (" + key_w + ")"); |                     if (dict_p.count(key_w) != 1) | ||||||
|  |                         THROW("No such key exception (" + key_w + ")"); | ||||||
|                     result = LocalVarValue{true, "", &dict_p.at(key_w)}; |                     result = LocalVarValue{true, "", &dict_p.at(key_w)}; | ||||||
|                 } else |                 } else | ||||||
|                     THROW("Incorrect type of \"json[json]\" expression. Unallowed signature of [] operator"); |                     THROW("Incorrect type of \"json[json]\" expression. Unallowed signature of [] operator"); | ||||||
|             } else { |             } else { | ||||||
|                 ASSERT(what.isString(), "Expression \"element[X]\" allowed only if X is string (json object)"); |                 if (!what.isString()) | ||||||
|                 if (what.asString().empty()) |                     THROW("Expression \"element[X]\" allowed only if X is string (json object)"); | ||||||
|                     return; |                 if (!isUname(what.asString())) | ||||||
|                 if (!is_uname_dotted_sequence(what.asString())) |                     THROW("Expression \"element[str]\" has incorrect str (" + what.asString() + ")"); | ||||||
|                     THROW("Incorrect X in \"element[X]\""); |  | ||||||
|                 result.EL_name += ("." + what.asString()); |                 result.EL_name += ("." + what.asString()); | ||||||
|  |                 if (global_elems.count(result.EL_name) != 1) | ||||||
|  |                     THROW("Can't descend. No such element (" + result.EL_name + ")"); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         uptr<EEFrame> toMe(bool returned, const global_elem_set_t& global_elems, |         uptr<EEFrame> toMe(bool returned, const global_elem_set_t& global_elems, | ||||||
|                 const std::vector<LocalVarValue>& local_vars) { |                 const std::vector<LocalVarValue>& local_vars) { | ||||||
|             if (returned) { |             if (returned) { | ||||||
|                 ASSERT(temp_ret.is_json, "Expression \"X[ element ]\" is not allowed"); |                 if (!temp_ret.is_json) | ||||||
|  |                     THROW("Expression \"X[ element ]\" is not allowed"); | ||||||
|                 assert(temp_ret.JSON_subval); |                 assert(temp_ret.JSON_subval); | ||||||
|                 descend(*(temp_ret.JSON_subval)); |                 descend(*(temp_ret.JSON_subval), global_elems); | ||||||
|             } else { |             } else { | ||||||
|                 assert(expr.isDictionary()); |                 assert(expr.isDictionary()); | ||||||
|                 const json::JSON& val = expr["V"].g(); |                 const json::JSON& val = expr["V"]; | ||||||
|                 if (val.isInteger()) { |                 if (val.isInteger()) { | ||||||
|                     size_t lv_ind = val.asInteger().get_int(); |                     size_t lv_ind = val.asInteger().get_int(); | ||||||
|                     assert(lv_ind < local_vars.size()); |                     assert(lv_ind < local_vars.size()); | ||||||
|                     result = local_vars[lv_ind]; |                     result = local_vars[lv_ind]; | ||||||
|                 } else if (val.isString()) { |                 } else if (val.isString()) { | ||||||
|                     std::string cur_el_name_str = expr["V"].g().asString(); |                     std::string cur_el_name_str = expr["V"].asString(); | ||||||
|  |                     if (global_elems.count(cur_el_name_str) != 1) | ||||||
|  |                         THROW("Bad expression, no such element (" + cur_el_name_str + ")"); | ||||||
|                     result = LocalVarValue{false, cur_el_name_str, NULL}; |                     result = LocalVarValue{false, cur_el_name_str, NULL}; | ||||||
|                 } else |                 } else | ||||||
|                     assert(false); |                     assert(false); | ||||||
|             } |             } | ||||||
|             const std::vector<json::JSON>& chain = expr["C"].g().asArray(); |             const std::vector<json::JSON>& chain = expr["C"].asArray(); | ||||||
|             while (true) { |             while (true) { | ||||||
|                 if (chain_el >= chain.size()) |                 if (chain_el >= chain.size()) | ||||||
|                     return NULL; |                     return NULL; | ||||||
|                 const json::JSON& t = chain[chain_el++]; |                 const json::JSON& t = chain[chain_el++]; | ||||||
|                 if (t.isDictionary()) |                 if (t.isDictionary()) | ||||||
|                     return std::make_unique<EEFrame>(t, temp_ret); |                     return std::make_unique<EEFrame>(t, temp_ret); | ||||||
|                 descend(t); |                 descend(t, global_elems); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     }; |     }; | ||||||
| @ -84,6 +90,8 @@ namespace nytl { | |||||||
|      * and dictionaries. They are stored in json in rendering stack */ |      * and dictionaries. They are stored in json in rendering stack */ | ||||||
|     LocalVarValue rendering_core_execute_expression(const global_elem_set_t& global_elems, |     LocalVarValue rendering_core_execute_expression(const global_elem_set_t& global_elems, | ||||||
|         const std::vector<LocalVarValue>& local_vars, const json::JSON& expr) { |         const std::vector<LocalVarValue>& local_vars, const json::JSON& expr) { | ||||||
|  | 
 | ||||||
|  |         // todo: check if root element exists (if root value is not local variable, then it is element of package)
 | ||||||
|         bool returned = false; |         bool returned = false; | ||||||
|         std::vector<uptr<EEFrame>> stack; |         std::vector<uptr<EEFrame>> stack; | ||||||
|         LocalVarValue result; |         LocalVarValue result; | ||||||
| @ -211,8 +219,9 @@ namespace nytl { | |||||||
|     uptr<RFrame> RFrame_OverParts::toMe(bool returned, const global_elem_set_t &elem_ns, Ditch &result, |     uptr<RFrame> RFrame_OverParts::toMe(bool returned, const global_elem_set_t &elem_ns, Ditch &result, | ||||||
|         const std::function<std::string(std::string)> &escape) { |         const std::function<std::string(std::string)> &escape) { | ||||||
|         if (!returned) |         if (!returned) | ||||||
|             ASSERT(elem_ns.count(name) == 1, "No such element"); |             if ((elem_ns.count(name) != 1) || (!elem_ns.at(name).is_element)) | ||||||
|         const Element& el = elem_ns.at(name); |                 THROW("Can't render. No such element (" + name + ")"); | ||||||
|  |         const Element& el = *elem_ns.at(name).when_element; | ||||||
|         if (!returned) { |         if (!returned) { | ||||||
|             /* Continue to do checks */ |             /* Continue to do checks */ | ||||||
|             /* hidden elements (internal) do not need any check */ |             /* hidden elements (internal) do not need any check */ | ||||||
| @ -221,14 +230,17 @@ namespace nytl { | |||||||
|                 ASSERT(n == passed_args.size(), "Argument count mismatch"); |                 ASSERT(n == passed_args.size(), "Argument count mismatch"); | ||||||
|                 for (size_t i = 0; i < n; i++) { |                 for (size_t i = 0; i < n; i++) { | ||||||
|                     if (el.arguments[i].type == json::true_symbol) { |                     if (el.arguments[i].type == json::true_symbol) { | ||||||
|                         ASSERT(passed_args[i].is_json, "Expected json element argument, got element"); |                         if (!passed_args[i].is_json) | ||||||
|  |                             THROW("Expected json element argument, got element"); | ||||||
|                     } else { |                     } else { | ||||||
|                         // If not json is expected, element must be expected
 |                         // If not json is expected, element must be expected
 | ||||||
|                         assert(el.arguments[i].isArray()); |                         assert(el.arguments[i].isArray()); | ||||||
|                         ASSERT(!passed_args[i].is_json, "Expected element element arguemnt, got json"); |                         if (passed_args[i].is_json) | ||||||
|                         ASSERT(elem_ns.count(passed_args[i].EL_name), "No such element, can't compare signatures of argument value"); |                             THROW("Expected element element arguemnt, got json"); | ||||||
|                         const Element& arg_element = elem_ns.at(passed_args[i].EL_name); |                         const std::string& passed_el_as_arg = passed_args[i].EL_name; | ||||||
|                         // ASSERT(passed_args);
 |                         if ((elem_ns.count(passed_el_as_arg) != 1) || !elem_ns.at(passed_el_as_arg).is_element) | ||||||
|  |                             THROW("No such element, can't compare signatures of argument value (" + passed_el_as_arg + ")"); | ||||||
|  |                         const Element& arg_element = elem_ns.at(passed_el_as_arg).when_element.operator*(); | ||||||
|                         if(el.arguments[i].asArray() != arg_element.arguments) |                         if(el.arguments[i].asArray() != arg_element.arguments) | ||||||
|                             THROW("Signature of argument " + std::to_string(i) + " does not match"); |                             THROW("Signature of argument " + std::to_string(i) + " does not match"); | ||||||
|                     } |                     } | ||||||
|  | |||||||
| @ -109,16 +109,17 @@ namespace nytl { | |||||||
|         return result; |         return result; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     TemplaterRegPref gen_base_element() { | ||||||
|  |         Element* e = new Element{{json::JSON(true)}, true, false, {}}; | ||||||
|  |         return {1, std::unique_ptr<Element>(e)}; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     void Templater::update() { |     void Templater::update() { | ||||||
|         elements = { |         elements[""] = TemplaterRegPref{0, NULL}; | ||||||
|             {"jsinsert", Element{{json::JSON(true)}, true}}, |         elements["jsinsert"] = gen_base_element(); | ||||||
|             {"jesc", Element{{json::JSON(true)}, true}}, |         elements["jesc"] = gen_base_element(); | ||||||
|             {"jesccomp", Element{{json::JSON(true)}, true}}, |         elements["str2text"] = gen_base_element(); | ||||||
|             /* str2text base element has a dedicated operator - WRITE */ |         elements["str2code"] = gen_base_element(); | ||||||
|             {"str2text", Element{{json::JSON(true)}, true}}, |  | ||||||
|             /* str2code base element has a dedicated operator - ROUGHINSERT */ |  | ||||||
|             {"str2code", Element{{json::JSON(true)}, true}}, |  | ||||||
|         }; |  | ||||||
|         std::vector<InterestingFile> intersting_files = indexing_detour(settings.det); |         std::vector<InterestingFile> intersting_files = indexing_detour(settings.det); | ||||||
|         for (const InterestingFile& file: intersting_files) { |         for (const InterestingFile& file: intersting_files) { | ||||||
|             std::string content = readFile(file.path); |             std::string content = readFile(file.path); | ||||||
| @ -132,7 +133,8 @@ namespace nytl { | |||||||
| 
 | 
 | ||||||
|     /* Still can throw some stuff derived from std::exception (like bad alloc) */ |     /* Still can throw some stuff derived from std::exception (like bad alloc) */ | ||||||
|     std::string Templater::render(const std::string& element, const std::vector<const json::JSON*> &arguments) const { |     std::string Templater::render(const std::string& element, const std::vector<const json::JSON*> &arguments) const { | ||||||
|         ASSERT(is_uname_dotted_sequence(element), "Incorrect entry element name"); |         if (!is_uname_dotted_sequence(element)) | ||||||
|  |             THROW("Incorrect entry element name"); | ||||||
|         return rendering_core(element, arguments, elements, settings.escape); |         return rendering_core(element, arguments, elements, settings.escape); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -6,6 +6,8 @@ | |||||||
| #include <jsonincpp/jsonobj.h> | #include <jsonincpp/jsonobj.h> | ||||||
| #include <functional> | #include <functional> | ||||||
| #include "html_case.h" | #include "html_case.h" | ||||||
|  | #include <memory> | ||||||
|  | #include <forward_list> | ||||||
| 
 | 
 | ||||||
| namespace nytl { | namespace nytl { | ||||||
|     typedef json::JSON expression_t; |     typedef json::JSON expression_t; | ||||||
| @ -61,7 +63,12 @@ namespace nytl { | |||||||
|         std::function<std::string(std::string)> escape = html_case_espace_string; |         std::function<std::string(std::string)> escape = html_case_espace_string; | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     typedef std::map<std::string, Element> global_elem_set_t; |     struct TemplaterRegPref { | ||||||
|  |         int is_element = 0; | ||||||
|  |         std::unique_ptr<Element> when_element = NULL; | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     typedef std::map<std::string, TemplaterRegPref> global_elem_set_t; | ||||||
| 
 | 
 | ||||||
|     struct Templater { |     struct Templater { | ||||||
|         TemplaterSettings settings; |         TemplaterSettings settings; | ||||||
|  | |||||||
| @ -0,0 +1,81 @@ | |||||||
|  | #include "server_data_interact.h" | ||||||
|  | #include <engine_engine_number_9/baza_throw.h> | ||||||
|  | #include "../str_fields.h" | ||||||
|  | #include <string.h> | ||||||
|  | 
 | ||||||
|  | namespace iu9cawebchat { | ||||||
|  |     /* nagative `forced_id` means id isn't forced  */ | ||||||
|  |     void add_user(SqliteConnection& conn, const std::string& nickname, const std::string& name, | ||||||
|  |         const std::string& password, const std::string& bio, int64_t forced_id) { | ||||||
|  |         if (!check_nickname(nickname)) | ||||||
|  |             een9_THROW("Bad user nickname " + nickname + ". Can't reg"); | ||||||
|  |         if (!check_name(name)) | ||||||
|  |             een9_THROW("Bad user name " + name + ". Can't reg"); | ||||||
|  |         if (!check_strong_password(password)) | ||||||
|  |             een9_THROW("Bad user password. Can't reg"); | ||||||
|  |         if (!is_orthodox_string(bio)) | ||||||
|  |             een9_THROW("Bad user bio. Can't reg"); | ||||||
|  |         if (is_nickname_taken(conn, nickname)) | ||||||
|  |             een9_THROW("Nickname taken already. Can't reg"); | ||||||
|  |         reserve_nickname(conn, nickname); | ||||||
|  |         SqliteStatement req(conn, | ||||||
|  |             "INSERT INTO `user` (`id`, `nickname`, `name`, `chatList_HistoryId`, `password`, `bio`) " | ||||||
|  |             "VALUES (?1, ?2, ?3, 0, ?4, ?5)", {}, {{2, nickname}, {3, name}, {4, password}, {5, bio}}); | ||||||
|  |         if (forced_id >= 0) | ||||||
|  |             sqlite_stmt_bind_int64(req, 1, forced_id); | ||||||
|  |         int must_be_done = sqlite_stmt_step(req, {}, {}); | ||||||
|  |         if (must_be_done != SQLITE_DONE) | ||||||
|  |             een9_THROW("sqlite error"); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     std::string admin_control_procedure(SqliteConnection& conn, const std::string& req, bool& termination) { | ||||||
|  |         size_t nid = 0; | ||||||
|  |         auto read_thing = [&]() -> std::string { | ||||||
|  |             while (nid < req.size() && isSPACE(req[nid])) | ||||||
|  |                 nid++; | ||||||
|  |             std::string result; | ||||||
|  |             bool esc = false; | ||||||
|  |             while (nid < req.size() && (esc || !isSPACE(req[nid]))) { | ||||||
|  |                 if (esc) { | ||||||
|  |                     result += req[nid]; | ||||||
|  |                     esc = false; | ||||||
|  |                 } else if (req[nid] == '\\') { | ||||||
|  |                     esc = true; | ||||||
|  |                 } else { | ||||||
|  |                     result += req[nid]; | ||||||
|  |                 } | ||||||
|  |                 nid++; | ||||||
|  |             } | ||||||
|  |             return result; | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         std::string cmd = read_thing(); | ||||||
|  |         if (cmd == "hello") { | ||||||
|  |             return ":0 omg! hiii!! Hewwou :3 !!!!\n"; | ||||||
|  |         } | ||||||
|  |         if (cmd == "8") { | ||||||
|  |             termination = true; | ||||||
|  |             return "Bye\n"; | ||||||
|  |         } | ||||||
|  |         const char* adduser_pref = "adduser"; | ||||||
|  |         if (cmd == "updaterootpw") { | ||||||
|  | 
 | ||||||
|  |             std::string new_password = read_thing(); | ||||||
|  |             if (!check_strong_password(new_password)) | ||||||
|  |                 return "Bad password. Can't update"; | ||||||
|  |             sqlite_nooutput(conn, | ||||||
|  |                 "UPDATE `user` SET `password` = ?1 WHERE `id` = 0", {}, {{1, new_password}}); | ||||||
|  |             return "Successul update\n"; | ||||||
|  |         } | ||||||
|  |         /* adduser <nickname> <name> <password> <bio> */ | ||||||
|  |         if (cmd == "adduser") { | ||||||
|  |             std::string nickname = read_thing(); | ||||||
|  |             std::string name = read_thing(); | ||||||
|  |             std::string password = read_thing(); | ||||||
|  |             std::string bio = read_thing(); | ||||||
|  |             add_user(conn, nickname, name, password, bio); | ||||||
|  |             return "User " + nickname + " successfully registered"; | ||||||
|  |         } | ||||||
|  |         return "Incorrect command\n"; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -0,0 +1,75 @@ | |||||||
|  | #include "server_data_interact.h" | ||||||
|  | #include <engine_engine_number_9/baza_throw.h> | ||||||
|  | #include <assert.h> | ||||||
|  | 
 | ||||||
|  | namespace iu9cawebchat { | ||||||
|  |     bool is_membership_row_present(SqliteConnection& conn, int64_t chatId, int64_t alienUserId) { | ||||||
|  |         SqliteStatement req(conn, | ||||||
|  |             "SELECT EXISTS(SELECT 1 FROM `user_chat_membership` WHERE `chatId` = ?1 AND `userId` = ?2)", | ||||||
|  |             {{1, chatId}, {2, alienUserId}}, {}); | ||||||
|  |         fsql_integer_or_null r{true, 0}; | ||||||
|  |         int status = sqlite_stmt_step(req, {{0, &r}}, {}); | ||||||
|  |         return (bool)r.value; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     void alter_user_chat_role(SqliteConnection& conn, int64_t chatId, int64_t alienUserId, int64_t role) { | ||||||
|  |         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); | ||||||
|  |         if (!is_membership_row_present(conn, chatId, 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}}, {}); | ||||||
|  | 
 | ||||||
|  |         } else { | ||||||
|  |             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, 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}}, {}); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     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); | ||||||
|  |         alter_user_chat_role(conn, chatId, alienUserId, role); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     json::JSON internalapi_addMemberToChat(SqliteConnection& conn, int64_t uid, const json::JSON& Sent) { | ||||||
|  |         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_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 at_api_error_gen_bad_recv(-1l); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         bool makeReadOnly = Sent["makeReadOnly"].toBool(); | ||||||
|  | 
 | ||||||
|  |         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, | ||||||
|  |                 makeReadOnly ? user_chat_role_read_only : user_chat_role_regular); | ||||||
|  |         } else { | ||||||
|  |             return at_api_error_gen_bad_recv(-2l); | ||||||
|  |         } | ||||||
|  |         insert_system_message_svo(conn, chatId, uid, "summoned", alien.id); | ||||||
|  | 
 | ||||||
|  |         json::JSON Recv; | ||||||
|  |         poll_update_chat(conn, Sent, Recv); | ||||||
|  |         return Recv; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -0,0 +1,30 @@ | |||||||
|  | #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 at_api_error_gen_bad_recv(-1l); | ||||||
|  |         if (is_nickname_taken(conn, new_chat_nickname)) | ||||||
|  |             return at_api_error_gen_bad_recv(-2l); | ||||||
|  |         if (is_nickname_taken(conn, new_chat_nickname)) | ||||||
|  |             return at_api_error_gen_bad_recv(-3l); | ||||||
|  |         reserve_nickname(conn, new_chat_nickname); | ||||||
|  | 
 | ||||||
|  |         sqlite_nooutput(conn, | ||||||
|  |             "INSERT INTO `chat` (`nickname`, `name`, `it_HistoryId`, `lastMsgId`) VALUES (?1, ?2, 0, -1)", | ||||||
|  |             {}, {{1, new_chat_nickname}, {2, new_chat_name}}); | ||||||
|  | 
 | ||||||
|  |         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; | ||||||
|  |         poll_update_chat_list(conn, uid, Sent, Recv); | ||||||
|  |         return Recv; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -0,0 +1,33 @@ | |||||||
|  | #include "server_data_interact.h" | ||||||
|  | #include <engine_engine_number_9/baza_throw.h> | ||||||
|  | #include "../debug.h" | ||||||
|  | 
 | ||||||
|  | namespace iu9cawebchat { | ||||||
|  |     json::JSON internalapi_deleteMessage(SqliteConnection& conn, int64_t uid, const json::JSON& Sent) { | ||||||
|  |         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_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; | ||||||
|  |         poll_update_chat(conn, Sent, Recv); | ||||||
|  |         return Recv; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -0,0 +1,15 @@ | |||||||
|  | #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, chatId, uid); | ||||||
|  |         insert_system_message_svo(conn, chatId, uid, "left", -1); | ||||||
|  |         json::JSON Recv; | ||||||
|  |         poll_update_chat_list(conn, uid, Sent, Recv); | ||||||
|  |         return Recv; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -0,0 +1,21 @@ | |||||||
|  | #include "server_data_interact.h" | ||||||
|  | #include <engine_engine_number_9/baza_throw.h> | ||||||
|  | 
 | ||||||
|  | namespace iu9cawebchat { | ||||||
|  |     void kick_from_chat(SqliteConnection& conn, int64_t chatId, int64_t alienUserId) { | ||||||
|  |         alter_user_chat_role(conn, chatId, alienUserId, user_chat_role_deleted); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     json::JSON internalapi_removeMemberFromChat(SqliteConnection& conn, int64_t uid, const json::JSON& Sent) { | ||||||
|  |         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_admin) | ||||||
|  |             een9_THROW("Only admin can delete members of chat"); | ||||||
|  |         int64_t badAlienId = Sent["userId"].asInteger().get_int(); | ||||||
|  |         kick_from_chat(conn, chatId, badAlienId); | ||||||
|  |         insert_system_message_svo(conn, chatId, uid, "kicked", badAlienId); | ||||||
|  |         json::JSON Recv; | ||||||
|  |         poll_update_chat(conn, Sent, Recv); | ||||||
|  |         return Recv; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -0,0 +1,50 @@ | |||||||
|  | #include "server_data_interact.h" | ||||||
|  | #include <engine_engine_number_9/baza_throw.h> | ||||||
|  | #include "../str_fields.h" | ||||||
|  | #include "../debug.h" | ||||||
|  | 
 | ||||||
|  | namespace iu9cawebchat { | ||||||
|  |     /* No authorization check is performed
 | ||||||
|  |      * Chat's HistoryId will increment after this operation | ||||||
|  |      * if adding system message, uid is ignored | ||||||
|  |      */ | ||||||
|  |     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); | ||||||
|  |         int64_t chat_lastMsgId = get_lastMsgId_of_chat(conn, chatId); | ||||||
|  |         SqliteStatement req(conn, | ||||||
|  |             "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); | ||||||
|  |         if (sqlite_stmt_step(req, {}, {}) != SQLITE_DONE) | ||||||
|  |             een9_THROW("There must be something wrong"); | ||||||
|  |         sqlite_nooutput(conn, "UPDATE `chat` SET `lastMsgId` = ?1, `it_HistoryId` = ?2 WHERE `id` = ?3", | ||||||
|  |             {{1, chat_lastMsgId + 1}, {2, chat_HistoryId_BEFORE_MSG + 1}, {3, chatId}}, {}); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     void insert_system_message_svo(SqliteConnection& conn, int64_t chatId, | ||||||
|  |         int64_t subject, const std::string& verb, int64_t object) { | ||||||
|  | 
 | ||||||
|  |         insert_new_message(conn, -1, chatId, | ||||||
|  |             std::to_string(subject) + "," + verb + "," + std::to_string(object), true); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     json::JSON internalapi_sendMessage(SqliteConnection& conn, int64_t uid, const json::JSON& Sent) { | ||||||
|  |         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_read_only) | ||||||
|  |             een9_THROW("read-only user can't send messages"); | ||||||
|  | 
 | ||||||
|  |         std::string text = Sent["content"]["text"].asString(); | ||||||
|  |         if (!is_orthodox_string(text) || text.empty()) | ||||||
|  |             een9_THROW("Bad input text"); | ||||||
|  |         insert_new_message(conn, uid, chatId, text, false); | ||||||
|  | 
 | ||||||
|  |         json::JSON Recv; | ||||||
|  |         poll_update_chat(conn, Sent, Recv); | ||||||
|  |         return Recv; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -0,0 +1,91 @@ | |||||||
|  | #include "client_server_interact.h" | ||||||
|  | #include <engine_engine_number_9/http_structures/cookies.h> | ||||||
|  | #include <functional> | ||||||
|  | 
 | ||||||
|  | namespace iu9cawebchat { | ||||||
|  |     void initial_extraction_of_all_the_useful_info_from_cookies( | ||||||
|  |         SqliteConnection& conn, const een9::ClientRequest& req, | ||||||
|  |         std::vector<std::pair<std::string, std::string>> &ret_cookies, | ||||||
|  |         std::vector<LoginCookie> &ret_login_cookies, | ||||||
|  |         json::JSON &ret_userinfo, | ||||||
|  |         int64_t& ret_logged_in_user | ||||||
|  |     ) { | ||||||
|  |         ret_cookies = een9::findAllClientCookies(req.headers); | ||||||
|  |         ret_login_cookies = select_login_cookies(ret_cookies); | ||||||
|  |         ret_logged_in_user = -1; /* Negative means that user is not logged in */ | ||||||
|  |         if (!ret_login_cookies.empty()){ | ||||||
|  |             size_t oldest_ind = select_oldest_login_cookie(ret_login_cookies); | ||||||
|  |             LoginCookie& tried = ret_login_cookies[oldest_ind]; | ||||||
|  |             ret_logged_in_user = find_user_by_credentials(conn, tried.nickname, tried.password); | ||||||
|  |             if (ret_logged_in_user >= 0) { | ||||||
|  |                 ret_userinfo["uid"] = json::JSON(ret_logged_in_user); | ||||||
|  |                 ret_userinfo["nickname"] = json::JSON(tried.nickname); | ||||||
|  |                 ret_userinfo["name"] = json::JSON(get_user_name(conn, ret_logged_in_user)); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     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); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     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"].asString() = messages[i].class_; | ||||||
|  |             jmessages[i]["text"].asString() = messages[i].text; | ||||||
|  |         } | ||||||
|  |         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 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_chatpollevents(WorkerGuestData& wgd, const een9::ClientRequest& req, int64_t uid) { | ||||||
|  |         return when_internalapi(wgd, req, uid, internalapi_chatPollEvents); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     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) { | ||||||
|  |         return when_internalapi(wgd, req, uid, internalapi_getMessageNeighbours); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     std::string when_internalapi_sendmessage(WorkerGuestData &wgd, const een9::ClientRequest &req, int64_t uid) { | ||||||
|  |         return when_internalapi(wgd, req, uid, internalapi_sendMessage); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     std::string when_internalapi_deletemessage(WorkerGuestData &wgd, const een9::ClientRequest &req, int64_t uid) { | ||||||
|  |         return when_internalapi(wgd, req, uid, internalapi_deleteMessage); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     std::string when_internalapi_addmembertochat(WorkerGuestData &wgd, const een9::ClientRequest &req, int64_t uid) { | ||||||
|  |         return when_internalapi(wgd, req, uid, internalapi_addMemberToChat); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     std::string when_internalapi_removememberfromchat(WorkerGuestData &wgd, const een9::ClientRequest &req, int64_t uid) { | ||||||
|  |         return when_internalapi(wgd, req, uid, internalapi_removeMemberFromChat); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     std::string when_internalapi_createchat(WorkerGuestData &wgd, const een9::ClientRequest &req, int64_t uid) { | ||||||
|  |         return when_internalapi(wgd, req, uid, internalapi_createChat); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     std::string when_internalapi_leavechat(WorkerGuestData &wgd, const een9::ClientRequest &req, int64_t uid) { | ||||||
|  |         return when_internalapi(wgd, req, uid, internalapi_leaveChat); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -0,0 +1,81 @@ | |||||||
|  | #ifndef IU9_CA_WEB_CHAT_LIB_BACKEND_LOGIC_SERVER_DATA_INTERACT_PAGES_H | ||||||
|  | #define IU9_CA_WEB_CHAT_LIB_BACKEND_LOGIC_SERVER_DATA_INTERACT_PAGES_H | ||||||
|  | 
 | ||||||
|  | #include "server_data_interact.h" | ||||||
|  | 
 | ||||||
|  | #include "../sqlite3_wrapper.h" | ||||||
|  | #include <engine_engine_number_9/http_structures/client_request.h> | ||||||
|  | #include <engine_engine_number_9/http_structures/response_gen.h> | ||||||
|  | #include "../login_cookie.h" | ||||||
|  | 
 | ||||||
|  | #include <jsonincpp/jsonobj.h> | ||||||
|  | #include <new_york_transit_line/templater.h> | ||||||
|  | #include <memory> | ||||||
|  | #include "../localizator.h" | ||||||
|  | 
 | ||||||
|  | namespace iu9cawebchat { | ||||||
|  |     struct WorkerGuestData { | ||||||
|  |         /* Because templaters use libjsonincpp, they can't be READ by two thread simultaneously */ | ||||||
|  |         std::unique_ptr<nytl::Templater> templater; | ||||||
|  |         std::unique_ptr<SqliteConnection> db; | ||||||
|  |         std::unique_ptr<Localizator> locales; | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     void initial_extraction_of_all_the_useful_info_from_cookies( | ||||||
|  |             SqliteConnection& conn, const een9::ClientRequest& req, | ||||||
|  |             std::vector<std::pair<std::string, std::string>>& ret_cookies, | ||||||
|  |             std::vector<LoginCookie>& ret_login_cookies, | ||||||
|  |             json::JSON& ret_userinfo, | ||||||
|  |             int64_t& ret_logged_in_user | ||||||
|  |         ); | ||||||
|  | 
 | ||||||
|  |     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; | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     json::JSON jsonify_html_message_list(const std::vector<HtmlMsgBox>& messages); | ||||||
|  | 
 | ||||||
|  |     std::string page_E404(WorkerGuestData& wgd); | ||||||
|  | 
 | ||||||
|  |     /*  ========================== PAGES ================================== */ | ||||||
|  | 
 | ||||||
|  |     std::string when_page_list_rooms(WorkerGuestData& wgd, const json::JSON& config_presentation, | ||||||
|  |                                      const een9::ClientRequest& req, const json::JSON& userinfo); | ||||||
|  | 
 | ||||||
|  |     std::string when_page_login(WorkerGuestData& wgd, const json::JSON& config_presentation, | ||||||
|  |                                 const een9::ClientRequest& req, const std::vector<LoginCookie>& login_cookies, const json::JSON& userinfo); | ||||||
|  | 
 | ||||||
|  |     std::string when_page_chat(WorkerGuestData& wgd, const json::JSON& config_presentation, | ||||||
|  |                                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 std::vector<LoginCookie>& login_cookies, const json::JSON& userinfo); | ||||||
|  | 
 | ||||||
|  |     std::string when_page_register(WorkerGuestData& wgd, const json::JSON& config_presentation, | ||||||
|  |                     const een9::ClientRequest& req, const json::JSON& userinfo); | ||||||
|  | 
 | ||||||
|  |     /* ========================  API  ============================== */ | ||||||
|  |     std::string when_internalapi_chatpollevents(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); | ||||||
|  | 
 | ||||||
|  |     std::string when_internalapi_sendmessage(WorkerGuestData &wgd, const een9::ClientRequest &req, int64_t uid); | ||||||
|  | 
 | ||||||
|  |     std::string when_internalapi_deletemessage(WorkerGuestData &wgd, const een9::ClientRequest &req, int64_t uid); | ||||||
|  | 
 | ||||||
|  |     std::string when_internalapi_addmembertochat(WorkerGuestData &wgd, const een9::ClientRequest &req, int64_t uid); | ||||||
|  | 
 | ||||||
|  |     std::string when_internalapi_removememberfromchat(WorkerGuestData &wgd, const een9::ClientRequest &req, int64_t uid); | ||||||
|  | 
 | ||||||
|  |     std::string when_internalapi_createchat(WorkerGuestData &wgd, const een9::ClientRequest &req, int64_t uid); | ||||||
|  | 
 | ||||||
|  |     std::string when_internalapi_leavechat(WorkerGuestData &wgd, const een9::ClientRequest &req, int64_t uid); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #endif | ||||||
							
								
								
									
										198
									
								
								src/web_chat/iu9_ca_web_chat_lib/backend_logic/polling.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,198 @@ | |||||||
|  | #include "server_data_interact.h" | ||||||
|  | #include <assert.h> | ||||||
|  | #include <engine_engine_number_9/baza_throw.h> | ||||||
|  | #include "../debug.h" | ||||||
|  | 
 | ||||||
|  | namespace iu9cawebchat { | ||||||
|  |     json::JSON poll_update_chat_list_resp(SqliteConnection& conn, int64_t userId, int64_t LocalHistoryId) { | ||||||
|  |         printf("Userid: %ld\n", userId); | ||||||
|  |         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
 | ||||||
|  |         Recv["chatListUpdResp"] = 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 `message` " | ||||||
|  |             "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["chatUpdResp"] = 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["chatUpdReq"]["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,8 +2,13 @@ | |||||||
| 
 | 
 | ||||||
| #include <engine_engine_number_9/baza_throw.h> | #include <engine_engine_number_9/baza_throw.h> | ||||||
| #include <engine_engine_number_9/http_structures/cookies.h> | #include <engine_engine_number_9/http_structures/cookies.h> | ||||||
|  | #include "../str_fields.h" | ||||||
| 
 | 
 | ||||||
| namespace iu9cawebchat { | namespace iu9cawebchat { | ||||||
|  |     json::JSON at_api_error_gen_bad_recv(int64_t code) { | ||||||
|  |         return json::JSON(json::jdict{{"status", json::JSON(code)}}); | ||||||
|  |     } | ||||||
|  |      | ||||||
|     const char* stringify_user_chat_role(int64_t role) { |     const char* stringify_user_chat_role(int64_t role) { | ||||||
|         if (role == user_chat_role_admin) |         if (role == user_chat_role_admin) | ||||||
|             return "admin"; |             return "admin"; | ||||||
| @ -27,7 +32,7 @@ namespace iu9cawebchat { | |||||||
|         return -1; |         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?"); |         een9_ASSERT(uid >= 0, "Are you crazy?"); | ||||||
|         SqliteStatement sql_req(conn, |         SqliteStatement sql_req(conn, | ||||||
|            "SELECT `name` FROM `user` WHERE `id` = ?1", |            "SELECT `name` FROM `user` WHERE `id` = ?1", | ||||||
| @ -38,7 +43,7 @@ namespace iu9cawebchat { | |||||||
|             een9_ASSERT_pl(name_col.exist); |             een9_ASSERT_pl(name_col.exist); | ||||||
|             return name_col.value; |             return name_col.value; | ||||||
|         } |         } | ||||||
|         return ""; |         een9_THROW("No such user"); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     RowUser_Content lookup_user_content(SqliteConnection &conn, int64_t uid) { |     RowUser_Content lookup_user_content(SqliteConnection &conn, int64_t uid) { | ||||||
| @ -50,47 +55,68 @@ namespace iu9cawebchat { | |||||||
|         fsql_text8_or_null name_col; |         fsql_text8_or_null name_col; | ||||||
|         int status = sqlite_stmt_step(sql_req, {}, {{0, &nickname_col}, {1, &name_col}}); |         int status = sqlite_stmt_step(sql_req, {}, {{0, &nickname_col}, {1, &name_col}}); | ||||||
|         if (status == SQLITE_ROW) { |         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)}; | ||||||
|         } |         } | ||||||
|         return {}; |         een9_THROW("No such user"); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     RowChat_Content lookup_chat_content(SqliteConnection &conn, int64_t uid) { |     RowUser_Content lookup_user_content_by_nickname(SqliteConnection& conn, const std::string& nickname) { | ||||||
|         een9_ASSERT(uid >= 0, "Are you crazy?"); |         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"); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     RowChat_Content lookup_chat_content(SqliteConnection &conn, int64_t chatId) { | ||||||
|  |         een9_ASSERT(chatId >= 0, "Are you crazy?"); | ||||||
|         SqliteStatement sql_req(conn, |         SqliteStatement sql_req(conn, | ||||||
|            "SELECT `nickname`, `name`, `lastMsgId` FROM `chat` WHERE `id` = ?1", |            "SELECT `nickname`, `name`, `lastMsgId` FROM `chat` WHERE `id` = ?1", | ||||||
|            {{1, uid}}, {}); |            {{1, chatId}}, {}); | ||||||
|         fsql_text8_or_null nickname_col; |         fsql_text8_or_null nickname_col; | ||||||
|         fsql_text8_or_null name_col; |         fsql_text8_or_null name_col; | ||||||
|         fsql_integer_or_null last_msg_id_col; |         fsql_integer_or_null last_msg_id_col; | ||||||
|         int status = sqlite_stmt_step(sql_req, {{2, &last_msg_id_col}}, {{0, &nickname_col}, {1, &name_col}}); |         int status = sqlite_stmt_step(sql_req, {{2, &last_msg_id_col}}, {{0, &nickname_col}, {1, &name_col}}); | ||||||
|         if (status == SQLITE_ROW) { |         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}; |                 last_msg_id_col.exist ? last_msg_id_col.value : -1}; | ||||||
|         } |         } | ||||||
|         return {}; |         een9_THROW("No such chat"); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     void initial_extraction_of_all_the_useful_info_from_cookies( |     RowChat_Content lookup_chat_content_by_nickname(SqliteConnection &conn, const std::string& nickname) { | ||||||
|         SqliteConnection& conn, const een9::ClientRequest& req, |         SqliteStatement sql_req(conn, | ||||||
|         std::vector<std::pair<std::string, std::string>> &ret_cookies, |            "SELECT `id`, `name`, `lastMsgId` FROM `chat` WHERE `nickname` = ?1", | ||||||
|         std::vector<LoginCookie> &ret_login_cookies, |            {}, {{1, nickname}}); | ||||||
|         json::JSON &ret_userinfo, |         fsql_integer_or_null id_col; | ||||||
|         int64_t& ret_logged_in_user |         fsql_text8_or_null name_col; | ||||||
|     ) { |         fsql_integer_or_null last_msg_id_col; | ||||||
|         ret_cookies = een9::findAllClientCookies(req.headers); |         int status = sqlite_stmt_step(sql_req, {{0, &id_col}, {2, &last_msg_id_col}}, {{1, &name_col}}); | ||||||
|         ret_login_cookies = select_login_cookies(ret_cookies); |         if (status == SQLITE_ROW) { | ||||||
|         ret_logged_in_user = -1; /* Negative means that user is not logged in */ |             return {id_col.value, nickname, std::move(name_col.value), | ||||||
|         if (!ret_login_cookies.empty()){ |                 last_msg_id_col.exist ? last_msg_id_col.value : -1}; | ||||||
|             size_t oldest_ind = select_oldest_login_cookie(ret_login_cookies); |  | ||||||
|             LoginCookie& tried = ret_login_cookies[oldest_ind]; |  | ||||||
|             ret_logged_in_user = find_user_by_credentials(conn, tried.nickname, tried.password); |  | ||||||
|             if (ret_logged_in_user >= 0) { |  | ||||||
|                 ret_userinfo["uid"] = json::JSON(ret_logged_in_user); |  | ||||||
|                 ret_userinfo["nickname"] = json::JSON(tried.nickname); |  | ||||||
|                 ret_userinfo["name"] = json::JSON(find_user_name(conn, ret_logged_in_user)); |  | ||||||
|             } |  | ||||||
|         } |         } | ||||||
|  |         een9_THROW("No such chat"); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     RowMessage_Content lookup_message_content(SqliteConnection& conn, int64_t chatId, int64_t msgId) { | ||||||
|  |         SqliteStatement req(conn, | ||||||
|  |             "SELECT `senderUserId`, `exists`, `isSystem`, `text` FROM `message` WHERE " | ||||||
|  |             "`chatId` = ?1 AND `id` = ?2", {{1, chatId}, {2, msgId}}, {}); | ||||||
|  |         fsql_integer_or_null senderUserId, exists, isSystem; | ||||||
|  |         fsql_text8_or_null msg_text; | ||||||
|  |         int status = sqlite_stmt_step(req, {{0, &senderUserId}, {1, &exists}, {2, &isSystem}}, | ||||||
|  |             {{3, &msg_text}}); | ||||||
|  |         if (status == SQLITE_ROW) { | ||||||
|  |             return {msgId, senderUserId.exist ? senderUserId.value : -1, (bool)exists.value, | ||||||
|  |                 (bool)isSystem.value, msg_text.value}; | ||||||
|  |         } | ||||||
|  |         een9_THROW("No such message"); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     int64_t get_role_of_user_in_chat(SqliteConnection& conn, int64_t userId, int64_t chatId) { |     int64_t get_role_of_user_in_chat(SqliteConnection& conn, int64_t userId, int64_t chatId) { | ||||||
| @ -105,10 +131,116 @@ namespace iu9cawebchat { | |||||||
|         return user_chat_role_deleted; |         return user_chat_role_deleted; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     std::string RTEE(const std::string& el_name, |     int64_t get_lastMsgId_of_chat(SqliteConnection &conn, int64_t chatId) { | ||||||
|                      const json::JSON& config_presentation, WorkerGuestData& wgd, |         een9_ASSERT(chatId >= 0, "Are you crazy?"); | ||||||
|                      const json::JSON& userinfo) { |         SqliteStatement sql_req(conn, | ||||||
|         std::string page = wgd.templater->render(el_name, {&config_presentation, &userinfo}); |            "SELECT `lastMsgId` FROM `chat` WHERE `id` = ?1", {{1, chatId}}, {}); | ||||||
|         return een9::form_http_server_response_200("text/html", page); |         fsql_integer_or_null last_msg_id_col; | ||||||
|  |         int status = sqlite_stmt_step(sql_req, {{0, &last_msg_id_col}}, {}); | ||||||
|  |         if (status == SQLITE_ROW) { | ||||||
|  |             return last_msg_id_col.exist ? last_msg_id_col.value : -1; | ||||||
|  |         } | ||||||
|  |         een9_THROW("No such chat"); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /* All the api calls processing is done in dedicated files.
 | ||||||
|  |      * All functions related to polling are defined in api_pollevents.cpp */ | ||||||
|  | 
 | ||||||
|  |     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}}); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -4,15 +4,12 @@ | |||||||
| /* This folder covers all code that helps to interact with database and templater,
 | /* This folder covers all code that helps to interact with database and templater,
 | ||||||
| *  or dictates the logic of how this web chat functions */ | *  or dictates the logic of how this web chat functions */ | ||||||
| 
 | 
 | ||||||
| #include <memory> |  | ||||||
| #include <new_york_transit_line/templater.h> |  | ||||||
| #include "../sqlite3_wrapper.h" | #include "../sqlite3_wrapper.h" | ||||||
| #include "../login_cookie.h" |  | ||||||
| #include <engine_engine_number_9/http_structures/client_request.h> |  | ||||||
| #include <engine_engine_number_9/http_structures/response_gen.h> |  | ||||||
| #include <jsonincpp/string_representation.h> | #include <jsonincpp/string_representation.h> | ||||||
| 
 | 
 | ||||||
| namespace iu9cawebchat { | namespace iu9cawebchat { | ||||||
|  |     json::JSON at_api_error_gen_bad_recv(int64_t code = -1); | ||||||
|  | 
 | ||||||
|     constexpr int64_t user_chat_role_admin = 1; |     constexpr int64_t user_chat_role_admin = 1; | ||||||
|     constexpr int64_t user_chat_role_regular = 2; |     constexpr int64_t user_chat_role_regular = 2; | ||||||
|     constexpr int64_t user_chat_role_read_only = 3; |     constexpr int64_t user_chat_role_read_only = 3; | ||||||
| @ -20,60 +17,78 @@ namespace iu9cawebchat { | |||||||
| 
 | 
 | ||||||
|     const char* stringify_user_chat_role(int64_t role); |     const char* stringify_user_chat_role(int64_t role); | ||||||
| 
 | 
 | ||||||
|     struct WorkerGuestData { |  | ||||||
|         /* Because templaters use libjsonincpp, they can't be READ by two thread simultaneously */ |  | ||||||
|         std::unique_ptr<nytl::Templater> templater; |  | ||||||
|         std::unique_ptr<SqliteConnection> db; |  | ||||||
|         int debug_trans_op; // todo: delete when debug is over
 |  | ||||||
|     }; |  | ||||||
| 
 |  | ||||||
|     int64_t find_user_by_credentials (SqliteConnection& conn, const std::string& nickname, const std::string& password); |     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 { |     struct RowUser_Content { | ||||||
|  |         int64_t id; | ||||||
|         std::string nickname; |         std::string nickname; | ||||||
|         std::string name; |         std::string name; | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     struct RowChat_Content { |     struct RowChat_Content { | ||||||
|  |         int64_t id; | ||||||
|         std::string nickname; |         std::string nickname; | ||||||
|         std::string name; |         std::string name; | ||||||
|         int64_t lastMsgId;  // Negative if it does not exist
 |         int64_t lastMsgId;  // Negative if it does not exist
 | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     RowUser_Content lookup_user_content(SqliteConnection& conn, int64_t uid); |     RowUser_Content lookup_user_content(SqliteConnection& conn, int64_t uid); | ||||||
|     RowChat_Content lookup_chat_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; | ||||||
|  |     }; | ||||||
| 
 | 
 | ||||||
|     void initial_extraction_of_all_the_useful_info_from_cookies( |     RowMessage_Content lookup_message_content(SqliteConnection& conn, int64_t chatId, int64_t msgId); | ||||||
|         SqliteConnection& conn, const een9::ClientRequest& req, |  | ||||||
|         std::vector<std::pair<std::string, std::string>>& ret_cookies, |  | ||||||
|         std::vector<LoginCookie>& ret_login_cookies, |  | ||||||
|         json::JSON& ret_userinfo, |  | ||||||
|         int64_t& ret_logged_in_user |  | ||||||
|     ); |  | ||||||
| 
 | 
 | ||||||
|  |     /* Does not make authorization check */ | ||||||
|     int64_t get_role_of_user_in_chat(SqliteConnection& conn, int64_t userId, int64_t chatId); |     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); | ||||||
| 
 | 
 | ||||||
|     std::string RTEE(const std::string& el_name, |     int64_t get_current_history_id_of_chat(SqliteConnection& conn, int64_t chatId); | ||||||
|         const json::JSON& config_presentation, WorkerGuestData& wgd, |     int64_t get_current_history_id_of_user_chatList(SqliteConnection& conn, int64_t userId); | ||||||
|         const json::JSON& userinfo); |  | ||||||
| 
 | 
 | ||||||
|     /*  ========================== PAGES ================================== */ |     json::JSON poll_update_chat_list_resp(SqliteConnection& conn, int64_t userId, int64_t LocalHistoryId); | ||||||
|     std::string when_page_login(WorkerGuestData& wgd, const json::JSON& config_presentation, |     void poll_update_chat_list(SqliteConnection& conn, int64_t userId, const json::JSON& Sent, json::JSON& Recv); | ||||||
|         const een9::ClientRequest& req, const std::vector<LoginCookie>& login_cookies, const json::JSON& userinfo); |  | ||||||
| 
 | 
 | ||||||
|  |     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); | ||||||
| 
 | 
 | ||||||
|     json::JSON internalapi_pollEvents(SqliteConnection& conn, int64_t uid, const json::JSON& Sent); |     void alter_user_chat_role(SqliteConnection& conn, int64_t chatId, int64_t alienUserId, int64_t role); | ||||||
|  |     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 chatId, int64_t alienUserId); | ||||||
| 
 | 
 | ||||||
|     std::string when_internalapi_pollevents(WorkerGuestData& wgd, |     bool is_nickname_taken(SqliteConnection& conn, const std::string& nickname); | ||||||
|             const een9::ClientRequest& req, int64_t uid); |     void reserve_nickname(SqliteConnection& conn, const std::string& nickname); | ||||||
| 
 | 
 | ||||||
|  |     void insert_new_message(SqliteConnection& conn, int64_t uid, int64_t chatId, const std::string& text, bool isSystem); | ||||||
|  |     void insert_system_message_svo(SqliteConnection& conn, int64_t chatId, | ||||||
|  |         int64_t subject, const std::string& verb, int64_t object); | ||||||
| 
 | 
 | ||||||
|     json::JSON internalapi_getChatList(SqliteConnection& conn, int64_t uid); |     /* ============================= 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_leaveChat(SqliteConnection& conn, int64_t uid, const json::JSON& Sent); | ||||||
| 
 | 
 | ||||||
|     std::string when_internalapi_getchatlist(WorkerGuestData& wgd, |     /**/ | ||||||
|                 const een9::ClientRequest& req, int64_t uid); |     void add_user(SqliteConnection& conn, const std::string& nickname, const std::string& name, | ||||||
|  |         const std::string& password, const std::string& bio, int64_t forced_id = -1); | ||||||
|  |     std::string admin_control_procedure(SqliteConnection& conn, const std::string& req, bool& termination); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #endif | #endif | ||||||
|  | |||||||
							
								
								
									
										61
									
								
								src/web_chat/iu9_ca_web_chat_lib/backend_logic/when_chat.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,61 @@ | |||||||
|  | #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) { | ||||||
|  | 
 | ||||||
|  |         std::vector<std::string> path_segs = {}; | ||||||
|  |         path_segs.reserve(4); | ||||||
|  |         if (req.uri_path.empty() || req.uri_path[0] != '/') | ||||||
|  |             return page_E404(wgd); | ||||||
|  |         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); | ||||||
|  |         bool show_chat_members = (path_segs[0] == "chat-members"); | ||||||
|  |         if (path_segs.size() == 4 && !show_chat_members) { | ||||||
|  |             if (path_segs[2] != "m") | ||||||
|  |                 return page_E404(wgd); | ||||||
|  |             selected_message_id = std::stoll(path_segs[3]); | ||||||
|  |         } else if (path_segs.size() != 2) { | ||||||
|  |             return page_E404(wgd); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if (userinfo.isNull()) | ||||||
|  |             return een9::form_http_server_response_303("/"); | ||||||
|  | 
 | ||||||
|  |         RowChat_Content chatInfo; | ||||||
|  |         try { | ||||||
|  |             chatInfo = lookup_chat_content_by_nickname(*wgd.db, chat_nickname); | ||||||
|  |         } catch (const std::exception& e) { | ||||||
|  |             return page_E404(wgd); | ||||||
|  |         } | ||||||
|  |         if (get_role_of_user_in_chat(*wgd.db, userinfo["uid"].asInteger().get_int(), chatInfo.id) == user_chat_role_deleted) { | ||||||
|  |             return page_E404(wgd); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         json::JSON openedchat; | ||||||
|  |         openedchat["name"].asString() = chatInfo.name; | ||||||
|  |         openedchat["nickname"].asString() = chatInfo.nickname; | ||||||
|  |         openedchat["id"].asInteger() = json::Integer(chatInfo.id); | ||||||
|  |         // -1 means that nothing was selected
 | ||||||
|  |         openedchat["selectedMessageId"].asInteger() = json::Integer(selected_message_id); | ||||||
|  |         json::JSON initial_chatUpdResp = poll_update_chat_ONE_MSG_resp(*wgd.db, chatInfo.id, selected_message_id); | ||||||
|  |         if (show_chat_members) | ||||||
|  |             return http_R200("chat-members", wgd, {&config_presentation, &userinfo, &openedchat, &initial_chatUpdResp}); | ||||||
|  |         return http_R200("chat", wgd, {&config_presentation, &userinfo, &openedchat, &initial_chatUpdResp}); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -1,42 +0,0 @@ | |||||||
| #include "server_data_interact.h" |  | ||||||
| #include <engine_engine_number_9/form_data_structure/urlencoded_query.h> |  | ||||||
| #include <engine_engine_number_9/baza_throw.h> |  | ||||||
| #include "../str_fields.h" |  | ||||||
| 
 |  | ||||||
| namespace iu9cawebchat { |  | ||||||
|     json::JSON internalapi_getChatList(SqliteConnection& conn, int64_t uid) { |  | ||||||
|         json::JSON Recv; |  | ||||||
|         Recv["status"] = json::JSON(0l); |  | ||||||
|         Recv["chats"] = json::JSON(json::array); |  | ||||||
|         std::vector<json::JSON>& chats = Recv["chats"].g().asArray(); |  | ||||||
|         SqliteStatement req(conn, |  | ||||||
|             "SELECT `chat`.`id`, `chat`.`nickname`, `chat`.`name`, `chat`.`lastMsgId`, " |  | ||||||
|             "`user_chat_membership`.`role` FROM `chat` " |  | ||||||
|             "RIGHT JOIN `user_chat_membership` ON `chat`.`id` = `user_chat_membership`.`chatId` " |  | ||||||
|             "WHERE `user_chat_membership`.`userId` = ?1 ", {{1, uid}}, {}); |  | ||||||
|         while (true) { |  | ||||||
|             fsql_integer_or_null chat_id; |  | ||||||
|             fsql_text8_or_null chat_nickname, chat_name; |  | ||||||
|             fsql_integer_or_null chat_lastMsgId, role_here; |  | ||||||
|             int status = sqlite_stmt_step(req, {{0, &chat_id}, {3, &chat_lastMsgId}, {4, &role_here}}, |  | ||||||
|                 {{1, &chat_nickname}, {2, &chat_name}}); |  | ||||||
|             if (status != SQLITE_ROW) |  | ||||||
|                 break; |  | ||||||
|             chats.emplace_back(); |  | ||||||
|             json::JSON& chat = chats.back(); |  | ||||||
|             chat["id"] = json::JSON(chat_id.value); |  | ||||||
|             chat["content"]["nickname"] = json::JSON(chat_nickname.value); |  | ||||||
|             chat["content"]["name"] = json::JSON(chat_name.value); |  | ||||||
|             chat["content"]["lastMsgId"] = json::JSON(chat_lastMsgId.exist ? chat_lastMsgId.value : -1); |  | ||||||
|             chat["content"]["roleHere"] = json::JSON(stringify_user_chat_role(role_here.value)); |  | ||||||
|         } |  | ||||||
|         return Recv; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     std::string when_internalapi_getchatlist(WorkerGuestData& wgd, |  | ||||||
|             const een9::ClientRequest& req, int64_t uid) { |  | ||||||
|         const json::JSON& Sent = json::parse_str_flawless(req.body); |  | ||||||
|         std::string result = json::generate_str(internalapi_getChatList(*wgd.db, uid), json::print_pretty); |  | ||||||
|         return een9::form_http_server_response_200("text/json", result); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @ -1,142 +0,0 @@ | |||||||
| #include "server_data_interact.h" |  | ||||||
| #include <engine_engine_number_9/form_data_structure/urlencoded_query.h> |  | ||||||
| #include <engine_engine_number_9/baza_throw.h> |  | ||||||
| #include "../str_fields.h" |  | ||||||
| #include <engine_engine_number_9/http_structures/response_gen.h> |  | ||||||
| 
 |  | ||||||
| namespace iu9cawebchat { |  | ||||||
|     int64_t get_current_history_id_of_chat(SqliteConnection& conn, int64_t chatId) { |  | ||||||
|         SqliteStatement req(conn, "SELECT `it_HistoryId` FROM `chat` WHERE `id` = ?1", {{1, chatId}}, {}); |  | ||||||
|         fsql_integer_or_null HistoryId; |  | ||||||
|         int status = sqlite_stmt_step(req, {{0, &HistoryId}}, {}); |  | ||||||
|         een9_ASSERT_pl(status == SQLITE_ROW); |  | ||||||
|         return HistoryId.value; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     int64_t get_current_history_id_of_user_chatList(SqliteConnection& conn, int64_t userId) { |  | ||||||
|         SqliteStatement req(conn, "SELECT `chatList_HistoryId` FROM `user` WHERE `id` = ?1", {{1, userId}}, {}); |  | ||||||
|         fsql_integer_or_null HistoryId; |  | ||||||
|         int status = sqlite_stmt_step(req, {{0, &HistoryId}}, {}); |  | ||||||
|         een9_ASSERT_pl(status == SQLITE_ROW); |  | ||||||
|         return HistoryId.value; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     void internalapi_pollEvents_in_chat_collect_membership_events(SqliteConnection& conn, |  | ||||||
|         std::vector<json::JSON>& events, int64_t chatId, int64_t LocalHistoryId) { |  | ||||||
|         SqliteStatement membership_changes(conn, |  | ||||||
|                     "SELECT `userId`, `role`, FROM `user_chat_membership` WHERE `chatId` = ?1 " |  | ||||||
|                     "AND `chat_IncHistoryId` > ?2", {{1, chatId}, {2, LocalHistoryId}}, {}); |  | ||||||
|         fsql_integer_or_null ev_userId; |  | ||||||
|         fsql_integer_or_null ev_user_role; |  | ||||||
|         while (true) { |  | ||||||
|             int status = sqlite_stmt_step(membership_changes, |  | ||||||
|             {{0, &ev_userId}, {1, &ev_user_role}}, {}); |  | ||||||
|             if (status != SQLITE_ROW) |  | ||||||
|                 break; |  | ||||||
|             events.emplace_back(); |  | ||||||
|             json::JSON& event = events.back(); |  | ||||||
|             event["member"] = json::JSON(ev_userId.value); |  | ||||||
|             if (ev_user_role.value == user_chat_role_deleted) { |  | ||||||
|                 event["type"] = json::JSON("removedMember"); |  | ||||||
|             } else { |  | ||||||
|                 event["type"] = json::JSON("addedMember"); |  | ||||||
|                 RowUser_Content USER = lookup_user_content(conn, ev_userId.value); |  | ||||||
|                 event["content"]["name"] = json::JSON(USER.name); |  | ||||||
|                 event["content"]["nickname"] = json::JSON(USER.nickname); |  | ||||||
|                 event["content"]["role"] = json::JSON(stringify_user_chat_role(ev_user_role.value)); |  | ||||||
|             } |  | ||||||
|             events.push_back(event); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     void internalapi_pollEvents_in_chat_collect_messages_events(SqliteConnection& conn, |  | ||||||
|         std::vector<json::JSON>& events, int64_t chatId, int64_t LocalHistoryId) { |  | ||||||
|         SqliteStatement messages_changes(conn, |  | ||||||
|             "SELECT `id`, `previous`, `senderUserId`, `exists`, `isSystem`, `text` FROM `messages` WHERE " |  | ||||||
|             "WHERE `chatId` = ?1 AND `chat_IncHistoryId` > ?2", {{1, chatId}, {2, LocalHistoryId}}, {}); |  | ||||||
|         fsql_integer_or_null ev_msgId, ev_previousMsgId, msgSenderUserId, msgExists, msgIsSystem; |  | ||||||
|         fsql_text8_or_null msgText; |  | ||||||
|         while (true) { |  | ||||||
|             int status = sqlite_stmt_step(messages_changes, |  | ||||||
|                 {{0, &ev_msgId}, {1, &ev_previousMsgId}, {2, &msgSenderUserId}, {3, &msgExists}, {4, &msgIsSystem}}, |  | ||||||
|                 {{5, &msgText}}); |  | ||||||
|             if (status != SQLITE_ROW) |  | ||||||
|                 break; |  | ||||||
|             events.emplace_back(); |  | ||||||
|             json::JSON& event = events.back(); |  | ||||||
|             event["type"] = json::JSON("newMessage"); |  | ||||||
|             event["id"] = json::JSON(ev_msgId.value); |  | ||||||
|             event["previous"] = json::JSON(ev_previousMsgId.value); |  | ||||||
|             event["content"]["sender"] = json::JSON(msgSenderUserId.value); |  | ||||||
|             event["content"]["isSystem"] = json::JSON((bool)msgIsSystem.value); |  | ||||||
|             event["content"]["text"] = json::JSON(msgText.value); |  | ||||||
|             events.push_back(event); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     void internalapi_pollEvents_in_user_chatList_collect_events(SqliteConnection& conn, |  | ||||||
|         std::vector<json::JSON>& events, int64_t userId, int64_t LocalHistoryId) { |  | ||||||
|         SqliteStatement membership_changes(conn, |  | ||||||
|             "SELECT `chatId`, `role` FROM `user_chat_membership` WHERE `userId` = ?1 " |  | ||||||
|             "AND `user_chatList_IncHistoryId` > ?2", {{1, userId}, {2, LocalHistoryId}}); |  | ||||||
|         fsql_integer_or_null ev_chatId, usersRoleHere; |  | ||||||
|         while (true) { |  | ||||||
|             int status = sqlite_stmt_step(membership_changes, {{0, &ev_chatId}, {1, &usersRoleHere}}, {}); |  | ||||||
|             if (status != SQLITE_ROW) |  | ||||||
|                 break; |  | ||||||
|             events.emplace_back(); |  | ||||||
|             json::JSON& event = events.back(); |  | ||||||
|             event["id"] = json::JSON(ev_chatId.value); |  | ||||||
|             if (usersRoleHere.value == user_chat_role_deleted) { |  | ||||||
|                 event["type"] = json::JSON("removedChat"); |  | ||||||
|             } else { |  | ||||||
|                 event["type"] = json::JSON("addedChat"); |  | ||||||
|                 RowChat_Content CHAT = lookup_chat_content(conn, ev_chatId.value); |  | ||||||
|                 event["content"]["name"] = json::JSON(CHAT.name); |  | ||||||
|                 event["content"]["nickname"] = json::JSON(CHAT.nickname); |  | ||||||
|                 event["content"]["lastMsgId"] = json::JSON(CHAT.lastMsgId); |  | ||||||
|                 event["content"]["roleHere"] = json::JSON(stringify_user_chat_role(usersRoleHere.value)); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     json::JSON internalapi_pollEvents(SqliteConnection& conn, int64_t uid, const json::JSON& Sent) { |  | ||||||
|         json::JSON Recv; |  | ||||||
|         Recv["status"] = json::JSON(0l); |  | ||||||
|         Recv["update"] = json::JSON(json::array); |  | ||||||
|         const std::vector<json::JSON>& req_scope = Sent["scope"].g().asArray(); |  | ||||||
|         std::vector<json::JSON>& updated = Recv["update"].g().asArray(); |  | ||||||
|         for (const json::JSON& hist_entity_request: req_scope) { |  | ||||||
|             updated.emplace_back(); |  | ||||||
|             json::JSON& hist_entity_response = updated.back(); |  | ||||||
|             hist_entity_response["type"] = hist_entity_request["type"].g(); |  | ||||||
|             hist_entity_response["events"] = json::JSON(json::array); |  | ||||||
|             std::vector<json::JSON>& events = hist_entity_response["events"].g().asArray(); |  | ||||||
|             const int64_t LocalHistoryId = hist_entity_request["LocalHistoryId"].g().asInteger().get_int(); |  | ||||||
|             if (hist_entity_request["type"].g().asString() == "chat") { |  | ||||||
|                 int64_t chatId = hist_entity_request["chatId"].g().asInteger().get_int(); |  | ||||||
|                 if (get_role_of_user_in_chat(conn, uid, chatId) == user_chat_role_deleted) |  | ||||||
|                     een9_THROW("internalapi: trying to access chat that user does not belong to"); |  | ||||||
|                 hist_entity_response["chatId"] = json::JSON(chatId); |  | ||||||
|                 hist_entity_response["HistoryId"] = json::JSON(get_current_history_id_of_chat(conn, chatId)); |  | ||||||
|                 /* Two classes of 'real events' can happen to chat: membership table change, message table change */ |  | ||||||
|                 /* Here, I collect membership changes (related to this chat) */ |  | ||||||
|                 internalapi_pollEvents_in_chat_collect_membership_events(conn, events, chatId, LocalHistoryId); |  | ||||||
|                 /* Here, I collect message changes (related to this chat) */ |  | ||||||
|                 internalapi_pollEvents_in_chat_collect_messages_events(conn, events, chatId, LocalHistoryId); |  | ||||||
|             } else if (hist_entity_request["type"].g().asString() == "chatlist") { |  | ||||||
|                 hist_entity_response["HistotyId"] = json::JSON(get_current_history_id_of_user_chatList(conn, uid)); |  | ||||||
|                 internalapi_pollEvents_in_user_chatList_collect_events(conn, events, uid, LocalHistoryId); |  | ||||||
|             } else |  | ||||||
|                 een9_THROW("Bad request"); |  | ||||||
|         } |  | ||||||
|         return Recv; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     std::string when_internalapi_pollevents(WorkerGuestData& wgd, |  | ||||||
|             const een9::ClientRequest& req, int64_t uid) { |  | ||||||
|         const json::JSON& Sent = json::parse_str_flawless(req.body); |  | ||||||
|         std::string result = json::generate_str(internalapi_pollEvents(*wgd.db, uid, Sent), json::print_pretty); |  | ||||||
|         return een9::form_http_server_response_200("text/json", result); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @ -0,0 +1,14 @@ | |||||||
|  | #include "client_server_interact.h" | ||||||
|  | 
 | ||||||
|  | namespace iu9cawebchat { | ||||||
|  |     std::string when_page_list_rooms(WorkerGuestData& wgd, const json::JSON& config_presentation, | ||||||
|  |             const een9::ClientRequest& req, const json::JSON& userinfo) { | ||||||
|  |         if (userinfo.isNull()) { | ||||||
|  |             return een9::form_http_server_response_303("/login"); | ||||||
|  |         } | ||||||
|  |         json::JSON initial_chatListUpdResp = poll_update_chat_list_resp(*wgd.db, | ||||||
|  |             userinfo["uid"].asInteger().get_int(), 0); | ||||||
|  |         printf("%s\n", json::generate_str(initial_chatListUpdResp, json::print_pretty).c_str()); | ||||||
|  |         return http_R200("list-rooms", wgd, {&config_presentation, &userinfo, &initial_chatListUpdResp}); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -1,4 +1,4 @@ | |||||||
| #include "server_data_interact.h" | #include "client_server_interact.h" | ||||||
| #include <engine_engine_number_9/form_data_structure/urlencoded_query.h> | #include <engine_engine_number_9/form_data_structure/urlencoded_query.h> | ||||||
| #include <engine_engine_number_9/baza_throw.h> | #include <engine_engine_number_9/baza_throw.h> | ||||||
| #include "../str_fields.h" | #include "../str_fields.h" | ||||||
| @ -18,21 +18,27 @@ namespace iu9cawebchat { | |||||||
|                     if (cmp.first == "password") |                     if (cmp.first == "password") | ||||||
|                         password = cmp.second; |                         password = cmp.second; | ||||||
|                 } |                 } | ||||||
|                 een9_ASSERT(check_nickname(nickname), "/login/accpet-data rejected impossible nickname"); |                 if (!check_nickname(nickname)) | ||||||
|                 een9_ASSERT(check_password(password), "/login/accpet-data rejected impossible password"); |                     een9_THROW("/login/accpet-data rejected impossible nickname"); | ||||||
|  |                 if (!check_password(password)) | ||||||
|  |                     een9_THROW("/login/accpet-data rejected impossible password"); | ||||||
|                 uid = find_user_by_credentials(*wgd.db, nickname, password); |                 uid = find_user_by_credentials(*wgd.db, nickname, password); | ||||||
| 
 | 
 | ||||||
|             } catch(const std::exception& e){} |             } catch(const std::exception& e){} | ||||||
|             if (uid < 0) { |             if (uid < 0) { | ||||||
|                 printf("Redirecting back to /login because of incorrect credentials\n"); |                 printf("Redirecting back to /login because of incorrect credentials\n"); | ||||||
|                 /* todo: Here I need to tell somehow to user (through fancy red box, maybe), that login was incorrect */ |                 json::JSON msg_list = jsonify_html_message_list({{"", | ||||||
|                 return RTEE("login", config_presentation, wgd, userinfo); |                     config_presentation["login"]["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; |             std::vector<std::pair<std::string, std::string>> response_hlines; | ||||||
|             LoginCookie new_login_cookie = create_login_cookie(nickname, password); |             LoginCookie new_login_cookie = create_login_cookie(nickname, password); | ||||||
|             add_set_cookie_headers_to_login(login_cookies, response_hlines, new_login_cookie); |             add_set_cookie_headers_to_login(login_cookies, response_hlines, new_login_cookie); | ||||||
|             return een9::form_http_server_response_307_spec_head("/", response_hlines); |             return een9::form_http_server_response_303_spec_head("/", response_hlines); | ||||||
|         } |         } | ||||||
|         return RTEE("login", config_presentation, wgd, 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}); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -0,0 +1,56 @@ | |||||||
|  | #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" | ||||||
|  | 
 | ||||||
|  | namespace iu9cawebchat { | ||||||
|  |     std::string when_page_register(WorkerGuestData& wgd, const json::JSON& config_presentation, | ||||||
|  |                 const een9::ClientRequest& req, const json::JSON& userinfo) { | ||||||
|  | 
 | ||||||
|  |         const json::JSON& reg_pres = config_presentation["register"]; | ||||||
|  |         json::JSON msg_list = json::JSON(json::array); | ||||||
|  |         if (req.method == "POST") { | ||||||
|  |             if (userinfo.isNull() || userinfo["uid"].asInteger().get_int() != 0) | ||||||
|  |                 een9_THROW("Unauthorized access"); | ||||||
|  |             // Kod dlya dobaldal lkslkfjgk
 | ||||||
|  |             std::vector<std::pair<std::string, std::string>> query = een9::split_html_query(req.body); | ||||||
|  |             std::string nickname; | ||||||
|  |             std::string name; | ||||||
|  |             std::string password; | ||||||
|  |             std::vector<HtmlMsgBox> problems;  // We explain problem to root
 | ||||||
|  |             for (const std::pair<std::string, std::string>& cmp: query) { | ||||||
|  |                 if (cmp.first == "nickname") | ||||||
|  |                     nickname = cmp.second; | ||||||
|  |                 if (cmp.first == "name") | ||||||
|  |                     name = cmp.second; | ||||||
|  |                 if (cmp.first == "password") | ||||||
|  |                     password = cmp.second; | ||||||
|  |             } | ||||||
|  |             if (!check_nickname(nickname)) { | ||||||
|  |                 problems.push_back({"", reg_pres["incorrect-nickname"].asString()}); | ||||||
|  |             } | ||||||
|  |             if (!check_name(name)) { | ||||||
|  |                 problems.push_back({"", reg_pres["incorrect-name"].asString()}); | ||||||
|  |             } | ||||||
|  |             if (!check_strong_password(password)) { | ||||||
|  |                 problems.push_back({"", reg_pres["incorrect-password"].asString()}); | ||||||
|  |             } | ||||||
|  |             if (is_nickname_taken(*wgd.db, nickname)) { | ||||||
|  |                 problems.push_back({"", reg_pres["nickname-taken"].asString()}); | ||||||
|  |             } | ||||||
|  |             if (problems.empty()) { | ||||||
|  |                 try { | ||||||
|  |                     add_user(*wgd.db, nickname, name, password, ""); | ||||||
|  |                 } catch (std::exception& err) { | ||||||
|  |                     problems.push_back({"", reg_pres["add_user_error"].asString()}); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             msg_list = jsonify_html_message_list(problems); | ||||||
|  |             return http_R200("register", wgd, {&config_presentation, &userinfo, &msg_list}); | ||||||
|  |         } | ||||||
|  |         if (userinfo.isNull() || userinfo["uid"].asInteger().get_int() != 0) | ||||||
|  |             return page_E404(wgd); | ||||||
|  |         return http_R200("register", wgd, {&config_presentation, &userinfo, &msg_list}); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										123
									
								
								src/web_chat/iu9_ca_web_chat_lib/backend_logic/when_user.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,123 @@ | |||||||
|  | #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 std::vector<LoginCookie>& login_cookies, const json::JSON& userinfo) { | ||||||
|  |         if (userinfo.isNull()) | ||||||
|  |             return een9::form_http_server_response_303("/"); | ||||||
|  |         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'
 | ||||||
|  |         bool can_edit = false; | ||||||
|  |         int64_t myuid = -1; | ||||||
|  |         if (userinfo.isDictionary()) { | ||||||
|  |             myuid = userinfo["uid"].asInteger().get_int(); | ||||||
|  |             can_edit = (alien.id == myuid && myuid >= 0); | ||||||
|  |         } | ||||||
|  |         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 WHERE `id` = ?2", | ||||||
|  |                         {{2, alien.id}}, {{1, bio}}); | ||||||
|  |                 } | ||||||
|  |                 if (!name.empty()) { | ||||||
|  |                     if (!check_name(name)) | ||||||
|  |                         een9_THROW("Incorrect `name`"); | ||||||
|  |                     sqlite_nooutput(conn, | ||||||
|  |                         "UPDATE `user` SET `name` = ?1 WHERE `id` = ?2", | ||||||
|  |                         {{2, alien.id}}, {{1, name}}); | ||||||
|  |                 } | ||||||
|  |                 if (!password.empty()) { | ||||||
|  |                     if (!check_strong_password(password)) | ||||||
|  |                         een9_THROW("Incorrect `password`"); | ||||||
|  |                     sqlite_nooutput(conn, | ||||||
|  |                         "UPDATE `user` SET `password` = ?1 WHERE `id` = ?2", | ||||||
|  |                         {{2, alien.id}}, {{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["edit-profile"]["incorrect-profile-data"].asString()}}); | ||||||
|  |                 json::JSON alien_userprofile = user_row_to_userprofile_obj(conn, alien); | ||||||
|  |                 return http_R200("edit-profile", wgd, {&config_presentation, &userinfo, &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, &userinfo, &alien_userprofile, &empty_msg_list}); | ||||||
|  |             } | ||||||
|  |             return http_R200("view-profile", wgd, {&config_presentation, &userinfo, &alien_userprofile}); | ||||||
|  |         } | ||||||
|  |         een9_THROW("Bad method"); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										0
									
								
								src/web_chat/iu9_ca_web_chat_lib/debug.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										6
									
								
								src/web_chat/iu9_ca_web_chat_lib/debug.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,6 @@ | |||||||
|  | #ifndef IU9_CA_WEB_CHAT_LIB_DEBUG_H | ||||||
|  | #define IU9_CA_WEB_CHAT_LIB_DEBUG_H | ||||||
|  | 
 | ||||||
|  | #define debug_print_json(x) printf("%s\n", json::generate_str(x, json::print_pretty).c_str()) | ||||||
|  | 
 | ||||||
|  | #endif | ||||||
| @ -2,15 +2,17 @@ | |||||||
| 
 | 
 | ||||||
| namespace iu9cawebchat{ | namespace iu9cawebchat{ | ||||||
|     int find_db_sqlite_file_path(const json::JSON& config, std::string& res_path) { |     int find_db_sqlite_file_path(const json::JSON& config, std::string& res_path) { | ||||||
|         const json::JSON& type = config["database"]["type"].g(); |         try { | ||||||
|         if (!type.isString() && type.asString() == "sqlite3") |             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; |             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; |         return 0; | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -7,24 +7,27 @@ | |||||||
| #include <unistd.h> | #include <unistd.h> | ||||||
| #include <assert.h> | #include <assert.h> | ||||||
| #include "sqlite3_wrapper.h" | #include "sqlite3_wrapper.h" | ||||||
|  | #include "backend_logic/server_data_interact.h" | ||||||
| 
 | 
 | ||||||
| namespace iu9cawebchat { | namespace iu9cawebchat { | ||||||
|     void initialize_website(const json::JSON& config, const std::string& root_pw) { |     void initialize_website(const json::JSON& config, const std::string& root_pw) { | ||||||
|         printf("Initialization...\n"); |         printf("Initialization...\n"); | ||||||
|         een9_ASSERT(check_password(root_pw), "Bad root password"); |         if (!check_strong_password(root_pw)) | ||||||
|  |             een9_THROW("Bad root password"); | ||||||
|         std::string db_path; |         std::string db_path; | ||||||
|         int ret; |         int ret; | ||||||
|         ret = find_db_sqlite_file_path(config, db_path); |         ret = find_db_sqlite_file_path(config, db_path); | ||||||
|         een9_ASSERT(ret == 0, "Invalid settings[\"database\"] field"); |         if (ret != 0) | ||||||
|  |             een9_THROW("Invalid settings[\"database\"] field"); | ||||||
|         if (een9::isRegularFile(db_path)) { |         if (een9::isRegularFile(db_path)) { | ||||||
|             // todo: plaese, don't do this
 |             // todo: plaese, don't do this
 | ||||||
|             ret = unlink(db_path.c_str()); |             ret = unlink(db_path.c_str()); | ||||||
|             een9_ASSERT_pl(ret == 0); |             if (ret != 0) | ||||||
|  |                 een9_THROW("unlink"); | ||||||
|         } |         } | ||||||
|         een9_ASSERT(!een9::isRegularFile(db_path), "Database file exists prior to initialization. " |         if (een9::isRegularFile(db_path)) | ||||||
|             "Can't preceed withut harming existing data"); |             een9_THROW("Database file exists prior to initialization. Can't preceed withut harming existing data"); | ||||||
|         SqliteConnection conn(db_path.c_str()); |         SqliteConnection conn(db_path.c_str()); | ||||||
|         assert(sqlite3_errcode(conn.hand) == SQLITE_OK); |  | ||||||
|         /* Role of memeber of chat:
 |         /* Role of memeber of chat:
 | ||||||
|          * 1 - admin |          * 1 - admin | ||||||
|          * 2 - regular |          * 2 - regular | ||||||
| @ -44,36 +47,38 @@ namespace iu9cawebchat { | |||||||
|                                              "`nickname` TEXT UNIQUE REFERENCES `nickname` NOT NULL," |                                              "`nickname` TEXT UNIQUE REFERENCES `nickname` NOT NULL," | ||||||
|                                              "`name` TEXT NOT NULL," |                                              "`name` TEXT NOT NULL," | ||||||
|                                              "`chatList_HistoryId` INTEGER 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` (" |             sqlite_nooutput(conn, "CREATE TABLE `chat` (" | ||||||
|                                              "`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL," |                                              "`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL," | ||||||
|                                              "`nickname` TEXT UNIQUE REFERENCES `nickname` NOT NULL," |                                              "`nickname` TEXT UNIQUE REFERENCES `nickname` NOT NULL," | ||||||
|                                              "`name` TEXT NOT NULL," |                                              "`name` TEXT NOT NULL," | ||||||
|                                              "`it_HistoryId` INTEGER NOT NULL," |                                              "`it_HistoryId` INTEGER NOT NULL," | ||||||
|                                              "`lastMsgId` INTEGER REFERENCES `message`" |                                              "`lastMsgId` INTEGER NOT NULL" | ||||||
|                                              ")"); |                                              ")"); | ||||||
|             sqlite_nooutput(conn, "CREATE TABLE `user_chat_membership` (" |             sqlite_nooutput(conn, "CREATE TABLE `user_chat_membership` (" | ||||||
|                                              "`userId` INTEGER REFERENCES `user` NOT NULL," |                                              "`userId` INTEGER REFERENCES `user` NOT NULL," | ||||||
|                                              "`chatId` INTEGER REFERENCES `chat` NOT NULL," |                                              "`chatId` INTEGER REFERENCES `chat` NOT NULL," | ||||||
|                                              "`user_chatList_IncHistoryId` INTEGER NOT NULL," |                                              "`user_chatList_IncHistoryId` INTEGER NOT NULL," | ||||||
|                                              "`chat_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` (" |             sqlite_nooutput(conn, "CREATE TABLE `message` (" | ||||||
|                                              "`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL," |  | ||||||
|                                              "`chatId` INTEGER REFERENCES `chat` NOT NULL," |                                              "`chatId` INTEGER REFERENCES `chat` NOT NULL," | ||||||
|                                              "`previous` INTEGER REFERENCES `message`," |                                              "`id` INTEGER NOT NULL," | ||||||
|                                              "`senderUserId` INTEGER REFERENCES `user` NOT NULL," |                                              "`senderUserId` INTEGER REFERENCES `user`," | ||||||
|                                              "`exists` BOOLEAN NOT NULL," |                                              "`exists` BOOLEAN NOT NULL," | ||||||
|                                              "`isSystem` BOOLEAN NOT NULL," |                                              "`isSystem` BOOLEAN NOT NULL," | ||||||
|                                              "`text` TEXT NOT NULL," |                                              "`text` TEXT," | ||||||
|                                              "`chat_IncHistoryId` INTEGER NOT NULL" |                                              "`chat_IncHistoryId` INTEGER NOT NULL," | ||||||
|  |                                              "PRIMARY KEY (`chatId`, `id`)" | ||||||
|                                              ")"); |                                              ")"); | ||||||
|             sqlite_nooutput(conn, "INSERT INTO `nickname` VALUES (?1)", {}, {{1, "root"}}); |             std::vector<std::string> sus = {"unknown", "undefined", "null", "none", "None", "NaN"}; | ||||||
|             sqlite_nooutput(conn, "INSERT INTO `user` (`id`, `nickname`, `name`, `chatList_HistoryId`, `password`) VALUES " |             for (auto& s: sus) | ||||||
|                                              "(0, ?1, ?2, 0, ?3)", {}, |                 reserve_nickname(conn, s); | ||||||
|                                              {{1, "root"}, {2, "Rootov Root Rootovich"}, {3, root_pw}}); |             add_user(conn, "root", "Rootov Root Rootovich", root_pw, "One admin to rule them all", 0); | ||||||
|             sqlite_nooutput(conn, "END"); |             sqlite_nooutput(conn, "END"); | ||||||
|         } catch (const std::exception& e) { |         } catch (const std::exception& e) { | ||||||
|             sqlite_nooutput(conn, "ROLLBACK", {}, {}); |             sqlite_nooutput(conn, "ROLLBACK", {}, {}); | ||||||
|  | |||||||
							
								
								
									
										154
									
								
								src/web_chat/iu9_ca_web_chat_lib/localizator.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,154 @@ | |||||||
|  | #include "localizator.h" | ||||||
|  | 
 | ||||||
|  | #include <jsonincpp/string_representation.h> | ||||||
|  | #include <sys/stat.h> | ||||||
|  | #include <dirent.h> | ||||||
|  | #include <engine_engine_number_9/os_utils.h> | ||||||
|  | #include <engine_engine_number_9/baza_throw.h> | ||||||
|  | #include <assert.h> | ||||||
|  | 
 | ||||||
|  | namespace iu9cawebchat { | ||||||
|  |     std::string languageRangeSimpler(const std::string& a) { | ||||||
|  |         return a == "*" ? "" : a; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // I won't use iterators. c plus plus IS a scripting language and I do not want to mess with iterators
 | ||||||
|  |     std::vector<std::string> languageRangeGetPrefixes(const std::string& lr) { | ||||||
|  |         if (lr.empty()) | ||||||
|  |             return {""}; | ||||||
|  |         std::vector<std::string> result = {"", ""}; | ||||||
|  |         for (size_t i = 0; i < lr.size(); i++) { | ||||||
|  |             if (lr[i] == '-') | ||||||
|  |                 result.push_back(result.back()); | ||||||
|  |             result.back() += lr[i]; | ||||||
|  | 
 | ||||||
|  |         } | ||||||
|  |         return result; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     bool isInWhitelist(const std::string& lr, const std::vector<std::string>& whitelist) { | ||||||
|  |         for (const std::string& prefix : languageRangeGetPrefixes(lr)) | ||||||
|  |             for (const std::string& nicePrefix: whitelist) | ||||||
|  |                 if (prefix == nicePrefix) | ||||||
|  |                     return true; | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     std::vector<LanguageFile> collect_lang_dir_content(const std::string& lang_dir, | ||||||
|  |         const std::vector<std::string>& whitelist) { | ||||||
|  | 
 | ||||||
|  |         std::vector<LanguageFile> result; | ||||||
|  |         errno = 0; | ||||||
|  |         DIR* D = opendir(lang_dir.c_str()); | ||||||
|  |         struct Guard1{ DIR*& D; ~Guard1(){ closedir(D); } } g1{D}; | ||||||
|  |         if (!D) | ||||||
|  |             een9_THROW_on_errno("opendir (" + lang_dir + ")"); | ||||||
|  |         while (true) { | ||||||
|  |             errno = 0; | ||||||
|  |             struct dirent* Dent = readdir(D); | ||||||
|  |             if (Dent == NULL) { | ||||||
|  |                 if (errno == 0) | ||||||
|  |                     break; | ||||||
|  |                 een9_THROW_on_errno("dirent"); | ||||||
|  |             } | ||||||
|  |             std::string entry = Dent->d_name; | ||||||
|  |             if (entry == "." || entry == "..") | ||||||
|  |                 continue; | ||||||
|  |             std::string filename = lang_dir + "/" + entry; | ||||||
|  |             struct stat info; | ||||||
|  |             int ret = stat(filename.c_str(), &info); | ||||||
|  |             een9_ASSERT_on_iret(ret, "stat(" + filename + ")"); | ||||||
|  |             if (!S_ISREG(info.st_mode)) | ||||||
|  |                 continue; | ||||||
|  |             const std::string postfix = ".lang.json"; | ||||||
|  |             if (!een9::endsWith(entry, postfix)) | ||||||
|  |                 continue; | ||||||
|  |             std::string lang_antirange = entry.substr(0, entry.size() - postfix.size()); | ||||||
|  |             if (!isInWhitelist(lang_antirange, whitelist)) | ||||||
|  |                 continue; | ||||||
|  |             std::string content; | ||||||
|  |             een9::readFile(filename, content); | ||||||
|  | 
 | ||||||
|  |             result.emplace_back(); | ||||||
|  |             result.back().languagerange = languageRangeSimpler(lang_antirange); | ||||||
|  |             result.back().content = json::parse_str_flawless(content); | ||||||
|  |         } | ||||||
|  |         return result; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     Localizator::Localizator(const LocalizatorSettings &settings) : settings(settings) { | ||||||
|  |         /* First - length of the longest prefix that was found so far (in force_order)
 | ||||||
|  |          * Second - index in force_order that was assigned to this thingy | ||||||
|  |          */ | ||||||
|  | 
 | ||||||
|  |         files = collect_lang_dir_content(settings.lang_dir, settings.whitelist); | ||||||
|  |         size_t n = files.size(); | ||||||
|  | #define redundantFileMsg "Redundant localization file" | ||||||
|  |         for (size_t i = 0; i < n; i++) { | ||||||
|  |             for (size_t j = i + 1; j < n; j++) { | ||||||
|  |                 std::string A = files[i].languagerange; | ||||||
|  |                 std::string B = files[j].languagerange; | ||||||
|  |                 for (std::string& pa: languageRangeGetPrefixes(A)) | ||||||
|  |                     if (pa == B) | ||||||
|  |                         een9_THROW(redundantFileMsg); | ||||||
|  |                 for (std::string& pb: languageRangeGetPrefixes(B)) | ||||||
|  |                     if (pb == A) | ||||||
|  |                         een9_THROW(redundantFileMsg); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         std::map<std::string, std::vector<std::size_t>> pref_to_files; | ||||||
|  |         for (size_t k = 0; k < n; k++) { | ||||||
|  |             for (const std::string& prefix: languageRangeGetPrefixes(files[k].languagerange)) { | ||||||
|  |                 pref_to_files[prefix].push_back(k); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         std::vector<std::pair<size_t, size_t>> assignment; | ||||||
|  |         constexpr size_t inf_bad_order = 999999999; | ||||||
|  |         assignment.assign(n, {0, inf_bad_order}); | ||||||
|  |         if (settings.force_order.size() >= inf_bad_order - 2) | ||||||
|  |             een9_THROW("o_O"); | ||||||
|  |         for (ssize_t i = 0; i < settings.force_order.size(); i++) { | ||||||
|  |             const std::string& ip = settings.force_order[i]; | ||||||
|  |             if (pref_to_files.count(ip) != 1) | ||||||
|  |                 een9_THROW("force-order list contains entries that match no files (" + ip + ")"); | ||||||
|  |             for (size_t k: pref_to_files.at(ip)) { | ||||||
|  |                 if (assignment[k].first <= ip.size()) { | ||||||
|  |                     assignment[k].first = ip.size(); | ||||||
|  |                     assignment[k].second = i; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         for (auto& p: pref_to_files) { | ||||||
|  |             const std::vector<size_t>& candidates = p.second; | ||||||
|  |             assert(!candidates.empty()); | ||||||
|  |             size_t bestSoFar = candidates[0]; | ||||||
|  |             size_t f = inf_bad_order; | ||||||
|  |             for (size_t k: candidates) { | ||||||
|  |                 if (assignment[k].second <= f) { | ||||||
|  |                     f = assignment[k].second; | ||||||
|  |                     bestSoFar = k; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             prefix_to_file[p.first] = bestSoFar; | ||||||
|  |         } | ||||||
|  |         if (prefix_to_file.count("") != 1) | ||||||
|  |             een9_THROW("No locales were provided"); | ||||||
|  |         // todo: remove DEBUG
 | ||||||
|  |         // for (size_t k = 0; k < n; k++) {
 | ||||||
|  |             // printf("%s has priority %lu\n", files[k].languagerange.c_str(), assignment[k].second);
 | ||||||
|  |         // }
 | ||||||
|  |         // printf("==============\n");
 | ||||||
|  |         // for (const auto& p : prefix_to_file) {
 | ||||||
|  |             // printf("%s  ->  %s\n", p.first.c_str(), files[p.second].languagerange.c_str());
 | ||||||
|  |         // }
 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     const LanguageFile& Localizator::get_right_locale(const std::vector<std::string> &preferred_langs) { | ||||||
|  |         for (const std::string& lr: preferred_langs) { | ||||||
|  |             if (prefix_to_file.count(lr) == 1) | ||||||
|  |                 return files[prefix_to_file.at(lr)]; | ||||||
|  |         } | ||||||
|  |         return files[prefix_to_file.at("")]; | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										36
									
								
								src/web_chat/iu9_ca_web_chat_lib/localizator.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,36 @@ | |||||||
|  | #ifndef IU9_CA_WEB_CHAT_LIB_LOCALIZATOR_H | ||||||
|  | #define IU9_CA_WEB_CHAT_LIB_LOCALIZATOR_H | ||||||
|  | 
 | ||||||
|  | #include <jsonincpp/jsonobj.h> | ||||||
|  | 
 | ||||||
|  | namespace iu9cawebchat { | ||||||
|  |     /* '*' -> ''; X -> X */ | ||||||
|  |     std::string languageRangeSimpler(const std::string& a); | ||||||
|  | 
 | ||||||
|  |     struct LocalizatorSettings { | ||||||
|  |         std::string lang_dir; | ||||||
|  |         std::vector<std::string> whitelist; | ||||||
|  |         std::vector<std::string> force_order; | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     /* There is no need to put http Content-Language response value into json file. When is is in the name */ | ||||||
|  |     struct LanguageFile { | ||||||
|  |         std::string languagerange; | ||||||
|  |         json::JSON content; | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     /* Localizator uses libjsonincpp internally, and thus can't be read by two treads simultaneously */ | ||||||
|  |     struct Localizator { | ||||||
|  |         LocalizatorSettings settings; | ||||||
|  |         std::vector<LanguageFile> files; | ||||||
|  |         std::map<std::string, size_t> prefix_to_file; | ||||||
|  | 
 | ||||||
|  |         /* Throws std::exception if something goes wrong */ | ||||||
|  |         explicit Localizator(const LocalizatorSettings& settings); | ||||||
|  | 
 | ||||||
|  |         /* Returns a reference to object inside Localizator */ | ||||||
|  |         const LanguageFile& get_right_locale(const std::vector<std::string>& preferred_langs); | ||||||
|  |     }; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #endif | ||||||
| @ -23,8 +23,8 @@ namespace iu9cawebchat { | |||||||
|         uint64_t nsec = std::stoull(ft.substr(s_ + 1, ft.size() - s_ - 1)); |         uint64_t nsec = std::stoull(ft.substr(s_ + 1, ft.size() - s_ - 1)); | ||||||
|         een9_ASSERT_pl(nsec < 1000000000); |         een9_ASSERT_pl(nsec < 1000000000); | ||||||
|         const json::JSON cnt = json::parse_str_flawless(base64_decode(login_cookie_encoded.second)); |         const json::JSON cnt = json::parse_str_flawless(base64_decode(login_cookie_encoded.second)); | ||||||
|         std::string nickname = cnt[0].g().asString(); |         std::string nickname = cnt[0].asString(); | ||||||
|         std::string password = cnt[1].g().asString(); |         std::string password = cnt[1].asString(); | ||||||
|         return LoginCookie{{(time_t)sec, (time_t)nsec}, nickname, password}; |         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) { |     std::pair<std::string, std::string> encode_login_cookie(const LoginCookie& cookie) { | ||||||
|             json::JSON cnt; |             json::JSON cnt; | ||||||
|             cnt[1].g() = cookie.password; |             cnt[1].asString() = cookie.password; | ||||||
|             cnt[0].g() = cookie.nickname; |             cnt[0].asString() = cookie.nickname; | ||||||
|         return {"login_" + std::to_string(cookie.login_time.tv_sec) + "_" + std::to_string(cookie.login_time.tv_nsec), |         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))}; |             base64_encode(json::generate_str(cnt, json::print_compact))}; | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -1,29 +1,14 @@ | |||||||
| #include "actions.h" | #include "actions.h" | ||||||
|  | 
 | ||||||
| #include <engine_engine_number_9/baza_throw.h> | #include <engine_engine_number_9/baza_throw.h> | ||||||
| #include <engine_engine_number_9/os_utils.h> | #include <engine_engine_number_9/os_utils.h> | ||||||
| #include <engine_engine_number_9/connecting_assets/static_asset_manager.h> | #include <engine_engine_number_9/connecting_assets/static_asset_manager.h> | ||||||
| #include "find_db.h" | #include "find_db.h" | ||||||
| #include <engine_engine_number_9/running_mainloop.h> | #include <engine_engine_number_9/running_mainloop.h> | ||||||
|  | #include <engine_engine_number_9/http_structures/accept_language.h> | ||||||
| #include <signal.h> | #include <signal.h> | ||||||
| #include "str_fields.h" | #include "str_fields.h" | ||||||
| 
 | #include "backend_logic/client_server_interact.h" | ||||||
| // #include <engine_engine_number_9/baza_throw.h>
 |  | ||||||
| // #include <engine_engine_number_9/running_mainloop.h>
 |  | ||||||
| // #include <engine_engine_number_9/http_structures/response_gen.h>
 |  | ||||||
| // #include <engine_engine_number_9/connecting_assets/static_asset_manager.h>
 |  | ||||||
| // #include <assert.h>
 |  | ||||||
| // #include <engine_engine_number_9/form_data_structure/urlencoded_query.h>
 |  | ||||||
| // #include <new_york_transit_line/templater.h>
 |  | ||||||
| // #include <sqlite3.h>
 |  | ||||||
| // #include <engine_engine_number_9/socket_address.h>
 |  | ||||||
| // #include "sqlite3_wrapper.h"
 |  | ||||||
| // #include "str_fields.h"
 |  | ||||||
| // #include "find_db.h"
 |  | ||||||
| // #include "login_cookie.h"
 |  | ||||||
| // #include <engine_engine_number_9/http_structures/cookies.h>
 |  | ||||||
| // #include <jsonincpp/string_representation.h>
 |  | ||||||
| 
 |  | ||||||
| #include "backend_logic/server_data_interact.h" |  | ||||||
| 
 | 
 | ||||||
| namespace iu9cawebchat { | namespace iu9cawebchat { | ||||||
|     bool termination = false; |     bool termination = false; | ||||||
| @ -32,22 +17,50 @@ namespace iu9cawebchat { | |||||||
|         termination = true; |         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", {}, {}); | ||||||
|  |         } | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     LocalizatorSettings make_localizator_settings(const std::string& assets_dir, const json::JSON& config) { | ||||||
|  |         std::vector<std::string> whitelist; | ||||||
|  |         for (const json::JSON& entry: config["lang"]["whitelist"].asArray()) | ||||||
|  |             whitelist.push_back(languageRangeSimpler(entry.asString())); | ||||||
|  |         std::vector<std::string> force_order; | ||||||
|  |         for (const json::JSON& entry: config["lang"]["force-order"].asArray()) | ||||||
|  |             force_order.push_back(languageRangeSimpler(entry.asString())); | ||||||
|  |         return LocalizatorSettings{assets_dir + "/lang", whitelist, force_order}; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     void run_website(const json::JSON& config) { |     void run_website(const json::JSON& config) { | ||||||
|         een9_ASSERT(config["assets"].g().isString(), "config[\"assets\"] is not string"); |         een9_ASSERT(config["assets"].isString(), "config[\"assets\"] is not string"); | ||||||
|         std::string assets_dir = config["assets"].g().asString(); |         const std::string& assets_dir = config["assets"].asString(); | ||||||
|         een9_ASSERT(een9::isDirectory(assets_dir), "\"" + assets_dir + "\" is not a directory"); |         een9_ASSERT(een9::isDirectory(assets_dir), "\"" + assets_dir + "\" is not a directory"); | ||||||
| 
 | 
 | ||||||
|  |         LocalizatorSettings localizator_settings = make_localizator_settings(assets_dir, config); | ||||||
|  | 
 | ||||||
|         een9::StaticAssetManagerSlaveModule samI; |         een9::StaticAssetManagerSlaveModule samI; | ||||||
|         samI.update({ |         samI.update({ | ||||||
|             een9::StaticAssetManagerRule{assets_dir + "/css", "/assets/css", {{".css", "text/css"}} }, |             een9::StaticAssetManagerRule{assets_dir + "/css", "/assets/css", {{".css", "text/css"}} }, | ||||||
|             een9::StaticAssetManagerRule{assets_dir + "/js", "/assets/js", {{".js", "text/javascript"}} }, |             een9::StaticAssetManagerRule{assets_dir + "/js", "/assets/js", {{".js", "text/javascript"}} }, | ||||||
|  |             een9::StaticAssetManagerRule{assets_dir + "/gif", "/assets/gif", {{".gif", "image/gif"}} }, | ||||||
|             een9::StaticAssetManagerRule{assets_dir + "/img", "/assets/img", { |             een9::StaticAssetManagerRule{assets_dir + "/img", "/assets/img", { | ||||||
|                 {".jpg", "image/jpg"}, {".png", "image/png"}, {".svg", "image/svg+xml"} |                 {".jpg", "image/jpg"}, {".png", "image/png"}, {".svg", "image/svg+xml"} | ||||||
|             } }, |             } }, | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|         const json::JSON& config_presentation = config["presentation"].g(); |         int64_t slave_number = config["server"]["workers"].asInteger().get_int(); | ||||||
|         int64_t slave_number = config["server"]["workers"].g().asInteger().get_int(); |  | ||||||
|         een9_ASSERT(slave_number > 0 && slave_number <= 200, "E"); |         een9_ASSERT(slave_number > 0 && slave_number <= 200, "E"); | ||||||
| 
 | 
 | ||||||
|         std::string sqlite_db_path; |         std::string sqlite_db_path; | ||||||
| @ -60,21 +73,24 @@ namespace iu9cawebchat { | |||||||
|                 nytl::TemplaterSettings{nytl::TemplaterDetourRules{assets_dir + "/HypertextPages"}}); |                 nytl::TemplaterSettings{nytl::TemplaterDetourRules{assets_dir + "/HypertextPages"}}); | ||||||
|             worker_guest_data[i].templater->update(); |             worker_guest_data[i].templater->update(); | ||||||
|             worker_guest_data[i].db = std::make_unique<SqliteConnection>(sqlite_db_path); |             worker_guest_data[i].db = std::make_unique<SqliteConnection>(sqlite_db_path); | ||||||
|  |             worker_guest_data[i].locales = std::make_unique<Localizator>(localizator_settings); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         een9::MainloopParameters params; |         een9::MainloopParameters params; | ||||||
|         params.guest_core = [&samI, &worker_guest_data, config_presentation] |         params.guest_core = [&samI, &worker_guest_data] | ||||||
|         (const een9::SlaveTask& task, const een9::ClientRequest& req, een9::worker_id_t worker_id) -> std::string { |         (const een9::SlaveTask& task, const een9::ClientRequest& req, een9::worker_id_t worker_id) -> std::string { | ||||||
|             een9_ASSERT_pl(0 <= worker_id && worker_id < worker_guest_data.size()); |             een9_ASSERT_pl(0 <= worker_id && worker_id < worker_guest_data.size()); | ||||||
|             WorkerGuestData& wgd = worker_guest_data[worker_id]; |             WorkerGuestData& wgd = worker_guest_data[worker_id]; | ||||||
|             een9::StaticAsset sa; |             een9::StaticAsset sa; | ||||||
|             sqlite_nooutput(*wgd.db, "BEGIN", {}, {}); |             ONE_SQLITE_TRANSACTION_GUARD conn_guard(*wgd.db); | ||||||
|             struct guard {SqliteConnection& conn; bool rollback = false; ~guard() { |             std::string AcceptLanguage; | ||||||
|                 if (rollback) |             for (const std::pair<std::string, std::string>& p: req.headers) { | ||||||
|                     sqlite_nooutput(conn, "ROLLBACK", {}, {}); |                 if (p.first == "Accept-Language") | ||||||
|                 else |                     AcceptLanguage = p.second; | ||||||
|                     sqlite_nooutput(conn, "END", {}, {}); |             } | ||||||
|                 }} guard_{*wgd.db}; |             std::vector<std::string> AcceptLanguageB = een9::parse_header_Accept_Language(AcceptLanguage); | ||||||
|  |             const LanguageFile& locale = wgd.locales->get_right_locale(AcceptLanguageB); | ||||||
|  |             const json::JSON& pres = locale.content; | ||||||
|             try { |             try { | ||||||
|                 std::vector<std::pair<std::string, std::string>> cookies; |                 std::vector<std::pair<std::string, std::string>> cookies; | ||||||
|                 std::vector<LoginCookie> login_cookies; |                 std::vector<LoginCookie> login_cookies; | ||||||
| @ -82,33 +98,51 @@ namespace iu9cawebchat { | |||||||
|                 int64_t logged_in_user = -1; |                 int64_t logged_in_user = -1; | ||||||
|                 initial_extraction_of_all_the_useful_info_from_cookies(*wgd.db, req, cookies, login_cookies, userinfo, logged_in_user); |                 initial_extraction_of_all_the_useful_info_from_cookies(*wgd.db, req, cookies, login_cookies, userinfo, logged_in_user); | ||||||
| 
 | 
 | ||||||
|                 std::string result; |  | ||||||
| 
 |  | ||||||
|                 if (req.uri_path == "/" || req.uri_path == "/list-rooms") { |                 if (req.uri_path == "/" || req.uri_path == "/list-rooms") { | ||||||
|                     if (logged_in_user < 0) |                     return when_page_list_rooms(wgd, pres, req, userinfo); | ||||||
|                         result = een9::form_http_server_response_307("/login"); |  | ||||||
|                     return RTEE("list-rooms", config_presentation, wgd, userinfo); |  | ||||||
|                 } |                 } | ||||||
|                 if (req.uri_path == "/login") { |                 if (req.uri_path == "/login") { | ||||||
|                     return when_page_login(wgd, config_presentation, req, login_cookies, userinfo); |                     return when_page_login(wgd, pres, req, login_cookies, userinfo); | ||||||
|                 } |                 } | ||||||
|                 if (req.uri_path == "/chat") { |                 // todo: split
 | ||||||
|                     return RTEE("chat", config_presentation, wgd, userinfo); |                 if (een9::beginsWith(req.uri_path, "/chat/") || een9::beginsWith(req.uri_path, "/chat-members/")) { | ||||||
|  |                     return when_page_chat(wgd, pres, req, userinfo); | ||||||
|                 } |                 } | ||||||
|                 if (req.uri_path == "/profile") { |                 if (een9::beginsWith(req.uri_path, "/user/")) { | ||||||
|                     return RTEE("profile", config_presentation, wgd, userinfo); |                     return when_page_user(wgd, pres, req, login_cookies, userinfo); | ||||||
|                 } |                 } | ||||||
|                 // if (req.uri_path == "/registration") {
 |                 if (req.uri_path == "/register") { | ||||||
|                 // RTEE("registration", config_presentation, wgd, userinfo);
 |                     return when_page_register(wgd, pres, req, userinfo); | ||||||
|                 // }
 |  | ||||||
|                 if (req.uri_path == "/internalapi/pollEvents") { |  | ||||||
|                     return when_internalapi_pollevents(wgd, req, logged_in_user); |  | ||||||
|                 } |                 } | ||||||
|                 if (req.uri_path == "/internalapi/getChatList") { |                 if (req.uri_path == "/api/chatPollEvents") { | ||||||
|                     return  when_internalapi_getchatlist(wgd, req, logged_in_user); |                     return when_internalapi_chatpollevents(wgd, req, logged_in_user); | ||||||
|  |                 } | ||||||
|  |                 if (req.uri_path == "/api/chatListPollEvents") { | ||||||
|  |                     return  when_internalapi_chatlistpollevents(wgd, req, logged_in_user); | ||||||
|  |                 } | ||||||
|  |                 if (req.uri_path == "/api/getMessageNeighbours") { | ||||||
|  |                     return when_internalapi_getmessageneighbours(wgd, req, logged_in_user); | ||||||
|  |                 } | ||||||
|  |                 if (req.uri_path == "/api/sendMessage") { | ||||||
|  |                     return when_internalapi_sendmessage(wgd, req, logged_in_user); | ||||||
|  |                 } | ||||||
|  |                 if (req.uri_path == "/api/deleteMessage") { | ||||||
|  |                     return when_internalapi_deletemessage(wgd, req, logged_in_user); | ||||||
|  |                 } | ||||||
|  |                 if (req.uri_path == "/api/addMemberToChat") { | ||||||
|  |                     return when_internalapi_addmembertochat(wgd, req, logged_in_user); | ||||||
|  |                 } | ||||||
|  |                 if (req.uri_path == "/api/removeMemberFromChat") { | ||||||
|  |                     return when_internalapi_removememberfromchat(wgd, req, logged_in_user); | ||||||
|  |                 } | ||||||
|  |                 if (req.uri_path == "/api/createChat") { | ||||||
|  |                     return when_internalapi_createchat(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) { |             } catch (const std::exception& e) { | ||||||
|                 guard_.rollback = true; |                 conn_guard.rollback = true; | ||||||
|                 throw; |                 throw; | ||||||
|             } |             } | ||||||
|             /* Trying to interpret request as asset lookup */ |             /* Trying to interpret request as asset lookup */ | ||||||
| @ -123,29 +157,11 @@ namespace iu9cawebchat { | |||||||
|         (const een9::SlaveTask& task, const std::string& req, een9::worker_id_t worker_id) -> std::string { |         (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()); |             een9_ASSERT_pl(0 <= worker_id && worker_id < worker_guest_data.size()); | ||||||
|             WorkerGuestData& wgd = worker_guest_data[worker_id]; |             WorkerGuestData& wgd = worker_guest_data[worker_id]; | ||||||
|  |             ONE_SQLITE_TRANSACTION_GUARD conn_guad(*wgd.db); | ||||||
|             try { |             try { | ||||||
|                 if (req == "hello") { |                 return admin_control_procedure(*wgd.db, req, termination); | ||||||
|                     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"; |  | ||||||
|             } catch (std::exception& e) { |             } catch (std::exception& e) { | ||||||
|  |                 conn_guad.rollback = true; | ||||||
|                 return std::string("Server error\n") + e.what(); |                 return std::string("Server error\n") + e.what(); | ||||||
|             } |             } | ||||||
|         }; |         }; | ||||||
| @ -161,8 +177,8 @@ namespace iu9cawebchat { | |||||||
|                 een9_ASSERT(ret == 0, "Incorrect ear address: " + source[i].asString()); |                 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.client_regular_listened, config["server"]["http-listen"].asArray()); | ||||||
|         translate_addr_list_conf(params.admin_control_listened, config["server"]["admin-command-listen"].g().asArray()); |         translate_addr_list_conf(params.admin_control_listened, config["server"]["admin-command-listen"].asArray()); | ||||||
| 
 | 
 | ||||||
|         signal(SIGINT, sigterm_action); |         signal(SIGINT, sigterm_action); | ||||||
|         signal(SIGTERM, sigterm_action); |         signal(SIGTERM, sigterm_action); | ||||||
|  | |||||||
| @ -76,7 +76,7 @@ namespace iu9cawebchat { | |||||||
| 
 | 
 | ||||||
|         int ret = sqlite3_prepare_v2(connection.hand, req_statement.c_str(), -1, &stmt_obj, NULL); |         int ret = sqlite3_prepare_v2(connection.hand, req_statement.c_str(), -1, &stmt_obj, NULL); | ||||||
|         if (ret != 0) { |         if (ret != 0) { | ||||||
|             int err_pos = -1; |             int err_pos = sqlite3_error_offset(connection.hand); | ||||||
|             een9_THROW("Compilation of request\n" + req_statement + "\nfailed" + |             een9_THROW("Compilation of request\n" + req_statement + "\nfailed" + | ||||||
|                 ((err_pos >= 0) ? " with offset " + std::to_string(err_pos) : "")); |                 ((err_pos >= 0) ? " with offset " + std::to_string(err_pos) : "")); | ||||||
|         } |         } | ||||||
| @ -101,9 +101,14 @@ namespace iu9cawebchat { | |||||||
|         sqlite3_finalize(stmt_obj); |         sqlite3_finalize(stmt_obj); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     void sqlite_stmt_bind_int64(SqliteStatement &stmt, int paramId, int64_t value) { | ||||||
|  |         int ret = sqlite3_bind_int64(stmt.stmt_obj, paramId, value); | ||||||
|  |         een9_ASSERT(ret == 0, "sqlite3_bind_int64"); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     int sqlite_stmt_step(SqliteStatement &stmt, |     int sqlite_stmt_step(SqliteStatement &stmt, | ||||||
|         const std::vector<std::pair<int, fsql_integer_or_null *>> &ret_of_integer_or_null, |                          const std::vector<std::pair<int, fsql_integer_or_null *>> &ret_of_integer_or_null, | ||||||
|         const std::vector<std::pair<int, fsql_text8_or_null *>> &ret_of_text8_or_null) { |                          const std::vector<std::pair<int, fsql_text8_or_null *>> &ret_of_text8_or_null) { | ||||||
|         int ret = sqlite3_step(stmt.stmt_obj); |         int ret = sqlite3_step(stmt.stmt_obj); | ||||||
|         if (ret == SQLITE_DONE) |         if (ret == SQLITE_DONE) | ||||||
|             return ret; |             return ret; | ||||||
| @ -137,4 +142,9 @@ namespace iu9cawebchat { | |||||||
|         } |         } | ||||||
|         return SQLITE_ROW; |         return SQLITE_ROW; | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     int64_t sqlite_trsess_last_insert_rowid(SqliteConnection& conn) { | ||||||
|  |         int64_t res = sqlite3_last_insert_rowid(conn.hand); | ||||||
|  |         return res; | ||||||
|  |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -41,9 +41,13 @@ namespace iu9cawebchat { | |||||||
|         ~SqliteStatement(); |         ~SqliteStatement(); | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|  |     void sqlite_stmt_bind_int64(SqliteStatement& stmt, int paramId, int64_t value); | ||||||
|  | 
 | ||||||
|     int sqlite_stmt_step(SqliteStatement& stmt, |     int sqlite_stmt_step(SqliteStatement& stmt, | ||||||
|         const std::vector<std::pair<int, fsql_integer_or_null*>>& ret_of_integer_or_null, |         const std::vector<std::pair<int, fsql_integer_or_null*>>& ret_of_integer_or_null, | ||||||
|         const std::vector<std::pair<int, fsql_text8_or_null*>>& ret_of_text8_or_null); |         const std::vector<std::pair<int, fsql_text8_or_null*>>& ret_of_text8_or_null); | ||||||
|  | 
 | ||||||
|  |     int64_t sqlite_trsess_last_insert_rowid(SqliteConnection& conn); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #endif | #endif | ||||||
|  | |||||||
| @ -28,11 +28,15 @@ namespace iu9cawebchat { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     bool check_password(const std::string &pwd) { |     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) { |     bool check_name(const std::string &name) { | ||||||
|         return is_orthodox_string(name); |         return is_orthodox_string(name) && name.size() <= 150 && !name.empty(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     bool check_nickname(const std::string &nickname) { |     bool check_nickname(const std::string &nickname) { | ||||||
| @ -42,7 +46,7 @@ namespace iu9cawebchat { | |||||||
|             if (!isUNCHAR(ch)) |             if (!isUNCHAR(ch)) | ||||||
|                 return false; |                 return false; | ||||||
|         } |         } | ||||||
|         return true; |         return nickname.size() <= 150; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /* Yeah baby, it's base64 time!!! */ |     /* Yeah baby, it's base64 time!!! */ | ||||||
|  | |||||||
| @ -13,6 +13,7 @@ namespace iu9cawebchat { | |||||||
|     bool is_orthodox_string(const std::string& str); |     bool is_orthodox_string(const std::string& str); | ||||||
| 
 | 
 | ||||||
|     bool check_password(const std::string& pwd); |     bool check_password(const std::string& pwd); | ||||||
|  |     bool check_strong_password(const std::string& pwd); | ||||||
|     bool check_name(const std::string& name); |     bool check_name(const std::string& name); | ||||||
|     bool check_nickname(const std::string& nickname); |     bool check_nickname(const std::string& nickname); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -34,6 +34,8 @@ int main(int argc, char** argv){ | |||||||
|             iu9cawebchat::initialize_website(config, root_pw); |             iu9cawebchat::initialize_website(config, root_pw); | ||||||
|         } else if (cmd == "run") { |         } else if (cmd == "run") { | ||||||
|             iu9cawebchat::run_website(config); |             iu9cawebchat::run_website(config); | ||||||
|  |         } else if (cmd == "version") { | ||||||
|  |             printf("IU9 Collarbone Annihilation Web Chat (service) V 1.0\n"); | ||||||
|         } else |         } else | ||||||
|             een9_THROW("unknown action (known are 'run', 'initialize')"); |             een9_THROW("unknown action (known are 'run', 'initialize')"); | ||||||
|     } catch (std::exception& e) { |     } catch (std::exception& e) { | ||||||
|  | |||||||