many folders have been updated, and html, js, and css extension files have also been changed
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) {
|
||||||
|