Compare commits

..

No commits in common. "master" and "remove" have entirely different histories.

124 changed files with 1127 additions and 6174 deletions

4
.gitignore vendored
View File

@ -13,7 +13,3 @@ compile_commands.json
local.sh local.sh
iu9-ca-web-chat.db iu9-ca-web-chat.db
log/
core
config/example.json

View File

@ -38,12 +38,12 @@ regexis024_build_system.sh
Помимо самого бинарника нужен файл с настройками сервиса. Формат настроек: JSON. Помимо самого бинарника нужен файл с настройками сервиса. Формат настроек: JSON.
Комментарии не поддерживаются. Пример такого файла находится в example/config.json. Комментарии не поддерживаются. Пример такого файла находится в example/config.json.
Вместе с бинарным фалом так же распространяются ассеты, необходимые для работы сайта. Вместе с бинарным фалом так же распространяются ассеты, необъходимые для работы сайта.
Их можно найти в папке assets. В настроках (поле `config.assets`) указывается путь до Их можно найти в папке assets. В настроках (поле `["assets"]`) указывается путь до
папки с ассетами. Путь может быть как абсолютным, так и относительным к рабочей директории. папки с ассетами. Путь может быть как абсолютным, так и относительным к рабочей директории.
Поле настроек `config.database` указывает как соединиться с базой данных. Поле настроек `["database"]` указывает как соединиться с базой данных.
Поддерживается только база данных sqlite3. Поддерживается только хранение в файле. Поддерживается только база данных sqlite. Поддерживается только хранение в файле.
Поле `config.database.file` указывает путь где хранится sqlite база данных. Поле `["database"]["file"]` указывает путь где хранится sqlite база данных.
Перед тем как использовать сервис нужно его проинициализировать (а точнее проинициализировать Перед тем как использовать сервис нужно его проинициализировать (а точнее проинициализировать
базу данных): базу данных):
@ -57,39 +57,6 @@ regexis024_build_system.sh
Для остановки сервиса киньте ему SIGTERM или SIGINT. Для остановки сервиса киньте ему SIGTERM или SIGINT.
Утилита `iu9-ca-web-chat-admin-cli` позволяет администратору сервиса контролировать его через сокет
(адрес указан в `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)
@ -102,7 +69,9 @@ iu9-ca-web-chat-admin-cli <server admin-control address> <command text> [<comman
Зачем писать комментарии в коде, если можно их вынести в отдельные пдф-ки? Зачем писать комментарии в коде, если можно их вынести в отдельные пдф-ки?
- [Документация для разработчиков]( - [API сервиса](
https://gitlab.yyyi.ru/collarbone-annihilation/iu9-ca-chat-api) https://gitlab.yyyi.ru/collarbone-annihilation/iu9-ca-chat-api)
- [Доки New York Transit Line](
https://gitlab.yyyi.ru/collarbone-annihilation/new_york_transit_line_documentation_rus)
О том как работает всё остальное можно только догадываться. О том как работает всё остальное можно только догадываться.

View File

@ -1,61 +0,0 @@
{% 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 %}

View File

@ -0,0 +1,25 @@
<!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/chat.css">
</head>
<body>
<div class="chat-container">
<div class="chat-header">
Веб чат
</div>
<div class="chat-messages" id="chat-messages">
<!-- Сообщения чата будут здесь -->
</div>
<div class="chat-footer">
<input type="text" class="chat-input" id="chat-input" placeholder="Введите сообщение...">
<button class="chat-send-button" onclick="sendMessage()">Отправить</button>
</div>
</div>
<script src="/assets/js/chat.js"></script>
</body>
</html>

View File

@ -1,68 +0,0 @@
{% ELDEF pass JSON pres JSON userinfo JSON openedchat JSON initial_chatUpdResp %}
<script>
let pres = {% PUT jsinsert pres %};
let userinfo = {% PUT jsinsert userinfo %};
let openedchat = {% PUT jsinsert openedchat %};
let initial_chatUpdResp = {% PUT jsinsert initial_chatUpdResp %};
</script>
{% ENDELDEF %}
{% ELDEF main JSON pres JSON userinfo JSON openedchat JSON initial_chatUpdResp %}
<!DOCTYPE html>
<html lang="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/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">
<title>{%w pres.chat.header-chat %} {%w openedchat.name %}</title>
</head>
<body>
{% PUT chat.pass pres userinfo openedchat initial_chatUpdResp %}
<div id="msg-deletion-win" class="popup-window">
<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 class="fullscreen-container resp-container">
<div class="panel" id="navigation-info-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 openedchat.name %} ({% W openedchat.nickname %})</p>
<a href="/chat-members/{% W openedchat.nickname %}" id="go-to-chat-settings" class="panel-thing">
<img alt="Settings of chat. List of members" src="/assets/img/settings-iron.svg" width="32px">
</a>
</div>
<div id="chat-widget">
<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>
<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 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>
</body>
</html>
{% ENDELDEF %}

View File

@ -1,71 +0,0 @@
{% 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%}

View File

@ -1,11 +0,0 @@
<!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>

View File

@ -1,73 +1,53 @@
{% ELDEF main JSON pres JSON userinfo JSON initial_chatListUpdResp %} {% ELDEF main JSON pres %}
<!DOCTYPE html> <!DOCTYPE html>
<html lang="{% W pres.lang %}"> <html lang="ru">
<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>{%w pres.list-rooms.header %}</title> <title>{% WRITE pres.phr.decl.list-of-chat-rooms %}</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>
<script> <div class="container">
let pres = {% PUT jsinsert pres %}; <h1 style="color: white;">{% WRITE pres.phr.decl.select-chat-room %}</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()">{% WRITE pres.phr.act.create-room %}</button>
<h1 class="popup-window-msg">{%w pres.list-rooms.new-chat-header %}</h1>
<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>
<div id="chat-renunciation-win" class="popup-window"> <div id="passwordModal" class="modal">
<h1 id="chat-renunciation-win-title" class="popup-window-msg">||||||||||</h1> <div class="modal-content">
<button class="popup-window-btn-yes" id="chat-renunciation-win-yes">{%w pres.list-rooms.yes-leave %}</button> <div class="modal-header">
<button class="popup-window-btn-no" id="chat-renunciation-win-no">{%w pres.list-rooms.no-leave %}</button> <span class="close" onclick="closeModal()">&times;</span>
</div> <h2>VVedite parol</h2> <!-- Nam ne nuzhen parol ot komnat -->
</div>
<div class="document-container resp-container"> <div class="modal-body">
<div id="navigation-panel" class="panel"> <input type="password" id="roomPassword" placeholder="Пароль">
<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="modal-footer">
</a> <button class="join-button" onclick="validatePassword()">{% WRITE pres.phr.act.confirm %}</button>
<p class="panel-thing panel-header-txt">
{%w pres.list-rooms.page-description %}
</p>
</div>
<div class="dynamic-block-list">
<img id="CL-bacbe" class="button-add centered-block-el" alt="New chat" src="/assets/img/add.svg">
<div class="dynamic-block-list-el-container" id="CL-dblec">
</div> </div>
</div> </div>
</div> </div>
<script src="/assets/js/common.js"></script>
<script src="/assets/js/common-popup.js"></script> <!-- Модальное окно для создания комнаты -->
<div id="createRoomModal" class="modal">
<div class="modal-content">
<div class="modal-header">
<span class="close" onclick="closeCreateRoomModal()">&times;</span>
<h2>{% WRITE pres.phr.decl.create-room %}</h2>
</div>
<div class="modal-body">
<input type="text" id="newRoomName" placeholder="{% WRITE pres.phr.decl.name-of-room %}">
<input type="password" id="newRoomPassword" placeholder="Пароль"> <!-- Fedya, nam ne nuzhen parol -->
</div>
<div class="modal-footer">
<button class="join-button" onclick="createRoom()">{% WRITE pres.phr.act.create %}</button>
</div>
</div>
</div>
<script src="/assets/js/list-rooms.js"></script> <script src="/assets/js/list-rooms.js"></script>
</body> </body>
</html> </html>

View File

@ -1,44 +0,0 @@
{% ELDEF main JSON pres JSON userinfo JSON errors %}
<!DOCTYPE html>
<html lang="{% W pres.lang %}">
<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/login.css">
<title>{% W pres.login.header %}</title>
</head>
<body>
{% FOR error IN errors %}
<div class="server-notif-error-msg-box">
{% W error.text %}
</div>
{% ENDFOR %}
<div class="form-container">
<h1 class="wide-centered-header">{% W pres.login.header %}</h1>
<form action="/login" method="post" enctype="application/x-www-form-urlencoded">
<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>
</html>
{% ENDELDEF %}

View File

@ -0,0 +1,36 @@
<!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>
</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>

View File

@ -1,51 +0,0 @@
{% 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%}

View File

@ -0,0 +1,25 @@
<!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>

View File

@ -1,36 +0,0 @@
{% 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%}

View File

@ -1,33 +0,0 @@
#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;
}

View File

@ -1,143 +1,103 @@
body, html { body {
height: 100%; font-family: Arial, sans-serif;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
background-color: #e5e5e5;
} }
#chat-widget { .chat-container {
position: relative; width: 100%;
flex: 1; max-width: 800px;
background-color: #f1f1f1; 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; overflow: hidden;
} }
.message-supercontainer{ .chat-header {
position: absolute; background-color: #0088cc;
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;
color: black;
/*justify-self: flex-start;*/
}
/* 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;
}
.message-box-system {
margin-left: auto;
margin-right: auto;
max-width: 500px;
padding: 4px;
background-color: #2d2d2d;
color: white; 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;
}
#input-panel {
min-height: 20px;
}
#message-input {
padding: 15px; padding: 15px;
height: auto; text-align: center;
width: 100%; font-size: 20px;
display: inline-block;
background-color: white;
border: 1px solid #1000d0;
border-radius : 7px;
font-size: .9rem;
margin: 10px;
} }
.message-in-popup-preview{ .chat-messages {
border: 4px solid red; flex: 1;
width: 80%; padding: 15px;
max-width: 200px; overflow-y: auto;
margin-left: auto; background-color: #f7f7f7;
margin-right: auto; }
max-height: 20%;
.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%;
object-fit: cover;
}
.chat-message .message-content {
max-width: 70%;
background-color: white;
padding: 10px;
border-radius: 8px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.chat-message .message-content .username {
font-weight: bold;
margin-bottom: 5px;
}
.chat-message .message-content .text {
word-wrap: break-word; word-wrap: break-word;
} }
.loading-spinner{ .chat-footer {
margin-left: auto; display: flex;
margin-right: auto; padding: 15px;
background-color: rgba(0, 0, 0, 0); padding-left: 50px;
width: 25px; border-top: 1px solid #ddd;
display: block; }
.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;
}
.chat-send-button:hover {
background-color: #007bb5;
} }

View File

@ -1,50 +0,0 @@
.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;
}

View File

@ -1,206 +0,0 @@
/* 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;
}

View File

@ -1,8 +0,0 @@
.chat-debug-rect{
width: 100%;
position: absolute;
left: 0;
opacity: 0.3;
height: 3px;
z-index: 2;
}

View File

@ -1,23 +0,0 @@
/* 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;
}

View File

@ -1,166 +1,136 @@
/* Общие стили */
body { body {
font-family: 'Roboto', sans-serif; font-family: Arial, sans-serif;
background-color: #f7f9fc; background-color: #f0f0f0;
color: #333;
margin: 0; margin: 0;
padding: 0; padding: 0;
box-sizing: border-box;
} }
/* Панель навигации */ .container {
.panel { max-width: 800px;
margin: 30px auto;
padding: 20px;
background-color: #007bff; background-color: #007bff;
padding: 10px; box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
color: white; 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;
align-items: center; align-items: center;
padding: 15px;
margin: 10px 0;
background-color: #fafafa;
border: 1px solid #ddd;
border-radius: 5px;
transition: background-color 0.3s ease;
} }
.panel-thing { .room-item:hover {
margin-right: 20px; background-color: #eaeaea;
text-decoration: none;
color: white;
} }
.panel-header-txt { .room-name {
font-size: 18px; font-size: 18px;
color: #555;
}
.join-button {
padding: 10px 15px;
font-size: 16px;
color: white;
background-color: #007bff;
border: none;
border-radius: 5px;
cursor: pointer;
transition: background-color 0.3s ease;
}
.join-button:hover {
background-color: #0056b3;
}
.modal {
display: none;
position: fixed;
z-index: 1;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgba(0, 0, 0, 0.4);
}
.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;
}
.close {
color: #aaa;
float: right;
font-size: 28px;
font-weight: bold; font-weight: bold;
} }
/* Стили динамических блоков */ .close:hover, .close:focus {
.dynamic-block-list { color: black;
display: flex; text-decoration: none;
flex-direction: column; cursor: pointer;
align-items: center;
padding: 20px;
} }
.dynamic-block-list-el-container { .modal input {
width: 93.5%;
padding: 10px;
margin: 10px 0;
border: 1px solid #ddd;
border-radius: 5px;
}
.create-room-button {
display: block;
width: 100%; width: 100%;
max-width: 600px; padding: 10px;
font-size: 16px;
color: white;
background-color: #1609ab;
border: none;
border-radius: 5px;
cursor: pointer;
transition: background-color 0.3s ease;
margin-top: 20px; margin-top: 20px;
} }
/* Кнопка добавления */ .create-room-button:hover {
.button-add { background-color: #218838;
width: 50px;
height: 50px;
cursor: pointer;
transition: transform 0.2s;
}
.button-add:hover {
transform: scale(1.1);
}
/* Всплывающие окна */
.popup-window {
background-color: white;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
padding: 20px;
max-width: 500px;
margin: 0 auto;
}
.popup-window-msg {
font-size: 20px;
margin-bottom: 15px;
color: #333;
}
.popup-window-btn-yes, .popup-window-btn-no {
padding: 10px 20px;
border: none;
border-radius: 5px;
font-size: 16px;
cursor: pointer;
}
.popup-window-btn-yes {
background-color: #28a745;
color: white;
margin-right: 10px;
}
.popup-window-btn-no {
background-color: #dc3545;
color: white;
}
/* Таблица ввода */
table.id-str-input-table {
width: 100%;
border-collapse: collapse;
margin-bottom: 20px;
}
.id-str-input-td1, .id-str-input-td2 {
border: none;
padding: 10px;
}
.id-str-input-td1 {
text-align: left;
padding-right: 10px;
font-weight: bold;
color: #555;
white-space: nowrap;
}
.id-str-input-td2 {
width: 100%;
}
.one-line-input {
width: 100%;
padding: 8px 12px;
border: 1px solid #ccc;
border-radius: 4px;
font-size: 14px;
}
/* Стили комнат */
.CL-my-chat-box {
display: flex;
flex-direction: row;
position: relative;
padding: 10px;
background-color: #e0f7fa;
border-radius: 8px;
margin-bottom: 10px;
align-items: center;
min-height: 40px;
width: 100%;
}
/* Текст внутри блока комнаты */
.CL-my-chat-box-nickname, .CL-my-chat-box-name, .CL-my-chat-box-my-role {
margin-left: 8px;
justify-self: flex-start;
}
/* Крестик в правом верхнем углу */
.CL-my-chat-box-leave-btn {
position: absolute;
top: 8px;
right: 8px;
width: 16px;
height: 16px;
background: url('/assets/img/close.svg') no-repeat center;
background-size: cover;
cursor: pointer;
border: none;
transition: transform 0.2s;
}
.CL-my-chat-box-leave-btn:hover {
transform: scale(1.2);
}
/* Дизайн списка комнат остается таким же */
#CL-bacbe {
margin-top: 6px;
margin-bottom: 4px;
} }

View File

@ -1,46 +0,0 @@
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: 50%;
}
}
@media (orientation: portrait){
.form-container{
width: 85%;
}
}
/* 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 {
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%;
}

105
assets/css/profile.css Normal file
View File

@ -0,0 +1,105 @@
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;
}
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 {
background-image: url("/assets/img/add_photo.svg");
background-size: cover;
width: 30px;
height: 30px;
border: none;
cursor: pointer;
border-radius: 10px;
}
.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;
color: white;
background-color: #0088cc;
width: 150px;
}
.avatar {
border-radius: 50%;
object-fit: cover;
}

View File

@ -1,45 +0,0 @@
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%;
}

View File

@ -0,0 +1,77 @@
body {
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;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 136 KiB

View File

@ -1,20 +0,0 @@
<?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>

Before

Width:  |  Height:  |  Size: 1.4 KiB

1
assets/img/add_photo.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" x="0px" y="0px" viewBox="-63 313 512 640" style="enable-background:new -63 313 512 512;" xml:space="preserve"><g><path d="M163.9,571.5l63.7,47.8l31.9-15.9l63.7,38.2v25.5H132v-63.7L163.9,571.5z"/><path d="M323.3,563.5c0,13.2-10.7,23.9-23.9,23.9c-13.2,0-23.9-10.7-23.9-23.9c0-13.2,10.7-23.9,23.9-23.9 C312.6,539.6,323.3,550.3,323.3,563.5z"/><g><path d="M63,407.2h42.7v170.7H63L63,407.2L63,407.2z"/><path d="M-1,471.2h170.7v42.7H-1V471.2L-1,471.2z"/></g><path d="M355.2,475.9H206.3v31.9h148.9V699h-255v-84.1H68.3V699c0,17.5,14.3,31.9,31.9,31.9h255c17.5,0,31.9-14.3,31.9-31.9V507.7 C387,490.2,372.7,475.9,355.2,475.9z"/></g><text x="-63" y="840" fill="#000000" font-size="5px" font-weight="bold" font-family="'Helvetica Neue', Helvetica, Arial-Unicode, Arial, Sans-serif">Created by VM</text><text x="-63" y="845" fill="#000000" font-size="5px" font-weight="bold" font-family="'Helvetica Neue', Helvetica, Arial-Unicode, Arial, Sans-serif">from the Noun Project</text></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 258 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 288 KiB

View File

@ -1,18 +0,0 @@
<?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>

Before

Width:  |  Height:  |  Size: 929 B

BIN
assets/img/empty_avatar.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 824 B

View File

@ -1,30 +0,0 @@
<?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>

Before

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -1,28 +0,0 @@
<?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>

Before

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -1,20 +0,0 @@
<?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>

Before

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -1,20 +0,0 @@
<?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>

Before

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -1,24 +0,0 @@
<?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>

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -1,188 +0,0 @@
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();
}

View File

@ -1,477 +1,44 @@
let LocalHistoryId = 0; function sendMessage() {
const chatMessages = document.getElementById('chat-messages');
const chatInput = document.getElementById('chat-input');
const message = chatInput.value;
if (message.trim() !== '') {
const messageElement = document.createElement('div');
messageElement.classList.add('chat-message');
let members = new Map(); const avatarElement = document.createElement('div');
avatarElement.classList.add('avatar');
let loadedMessages = new Map(); // messageSt objects const avatarImage = document.createElement('img');
/* avatarImage.src = 'https://sun9-59.userapi.com/impg/t8GhZ7FkynVifY1FQCnaf31tGprbV_rfauZzgg/fSq4lyc6V0U.jpg?size=1280x1280&quality=96&sign=e3c309a125cb570d2e18465eba65f940&type=album';
container: EL, box: EL, offset: number (msgPres) */ avatarElement.appendChild(avatarImage);
let visibleMessages = new Map(); // HTMLElement objects
let anchoredMsg = -1; const messageContentElement = document.createElement('div');
let visibleMsgSegStart = -1; messageContentElement.classList.add('message-content');
let visibleMsgSegEnd = -2;
let offsetOfAnchor = 500;
let highestPoint = null;
let lowestPoint = null;
let lastMsgId = -1; const usernameElement = document.createElement('div');
let myRoleHere = null; // Dung local state updates should be updated first usernameElement.classList.add('username');
usernameElement.textContent = 'Адель';
// Would start with true if opened `/chat/<>` const textElement = document.createElement('div');
let bumpedAtBottom = false; textElement.classList.add('text');
textElement.textContent = message;
// Hidden variable. When deletion window popup is active messageContentElement.appendChild(usernameElement);
// Persists from popup activation until popup deactivation messageContentElement.appendChild(textElement);
let storeHiddenMsgIdForDeletionWin = -1;
let debugMode = false; messageElement.appendChild(avatarElement);
messageElement.appendChild(messageContentElement);
// Positive in production, negative for debug chatMessages.appendChild(messageElement);
let softZoneSz = debugMode ? -150 : 300;
let chatPadding = debugMode ? 300 : 5;
let msgGap = 5;
const msgErased = pres.chat.msgErased;
function genSentBase(){ chatInput.value = '';
return { chatMessages.scrollTop = chatMessages.scrollHeight;
'chatUpdReq': {
'LocalHistoryId': LocalHistoryId,
'chatId': openedchat.id
}
};
}
function genSentBaseGMN(){
let Sent = genSentBase();
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))
} }
} }
function shouldShowDeleteMesgBtn(messageSt){ document.getElementById('chat-input').addEventListener('keydown', function (event) {
return !messageSt.isSystem && messageSt.exists && (myRoleHere !== userChatRoleReadOnly) &&( if (event.key === 'Enter') {
myRoleHere === userChatRoleAdmin || messageSt.senderUserId === userinfo.uid); sendMessage();
}
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();
}

View File

@ -1,26 +0,0 @@
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 = "";
}

View File

@ -1,77 +0,0 @@
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";
}

View File

@ -1,188 +1,83 @@
let LocalHistoryId = 0; let rooms = {};
function genSentBase(){ function openModal(roomName) {
return { currentRoom = roomName;
'chatListUpdReq': { document.getElementById('passwordModal').style.display = 'block';
'LocalHistoryId': LocalHistoryId
}
};
} }
let myChats = new Map(); function closeModal() {
let chatBoxes = new Map(); document.getElementById('passwordModal').style.display = 'none';
/* 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];
} }
function validatePassword() {
let chatRenunciationWinStoredId = -1; const enteredPassword = document.getElementById('roomPassword').value;
if (enteredPassword === rooms[currentRoom]) {
function shouldShowDeleteButton(myMembershipSt){ alert('Вы вошли в комнату: ' + currentRoom);
return myMembershipSt.myRoleHere === userChatRoleDeleted; closeModal();
} } else {
alert('Неверный пароль. Попробуйте снова.');
/* 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 {
if (myMembershipSt.myRoleHere === userChatRoleDeleted)
continue;
myChats.set(chatId, myMembershipSt);
let box = convertMyMembershipStToBox(myMembershipSt)
chatBoxes.set(chatId, box);
literalChatList.appendChild(box);
}
} }
} }
/* Use it ONLY if `Recv` reported success */ function openCreateRoomModal() {
function updateLocalStateFromRecv(Recv){ document.getElementById('createRoomModal').style.display = 'block';
updateLocalStateFromChatListUpdResp(Recv.chatListUpdResp);
} }
function configureChatCreationInterface(){ function closeCreateRoomModal() {
document.getElementById("chat-creation-win-yes").onclick = function (ev) { document.getElementById('createRoomModal').style.display = 'none';
if (ev.button !== 0)
return;
let chatNicknameInput = document.getElementById("chat-nickname-input");
let chatNameInput = document.getElementById("chat-name-input");
let nickname = String(chatNicknameInput.value);
let name = String(chatNameInput.value);
deactivateActivePopup();
let Sent = genSentBase();
Sent.content = {};
Sent.content.nickname = nickname;
Sent.content.name = name;
apiRequest("createChat", Sent
).then((Recv) => {
updateLocalStateFromRecv(Recv);
}).catch((e) => {
alert(pres['list-rooms']["failed-create-chat"]);
console.log(e);
});
};
document.getElementById("chat-creation-win-no").onclick = function (ev) {
if (ev.button !== 0)
return;
deactivateActivePopup();
}
document.getElementById("CL-bacbe").onclick = function (ev){
if (ev.button !== 0)
return;
let chatNicknameInput = document.getElementById("chat-nickname-input");
let chatNameInput = document.getElementById("chat-name-input");
chatNicknameInput.value = "";
chatNameInput.value = "";
activatePopupWindowById("chat-creation-win");
};
} }
function configureChatRenunciationInterfaceWinPart(){ function createRoom() {
document.getElementById("chat-renunciation-win-yes").onclick = function (ev){ const roomName = document.getElementById('newRoomName').value.trim();
if (ev.button !== 0) const roomPassword = document.getElementById('newRoomPassword').value.trim();
return;
deactivateActivePopup(); if (roomName === '' || roomPassword === '') {
if (chatRenunciationWinStoredId < 0) alert('Пожалуйста, заполните все поля.');
throw new Error("chatRenunciationWinStoredId < 0"); return;
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 (rooms[roomName]) {
if (ev.button !== 0) alert('Комната с таким названием уже существует.');
return; return;
deactivateActivePopup();
} }
rooms[roomName] = roomPassword;
addRoomToList(roomName);
closeCreateRoomModal();
} }
__mainloopDelayMs = 3000; function addRoomToList(roomName) {
__guestMainloopPollerAction = function(){ const roomList = document.querySelector('.room-list');
let Sent = genSentBase();
apiRequest("chatListPollEvents", Sent const existingRoomItem = Array.from(roomList.children).find(item => item.querySelector('.room-name').textContent === roomName);
).then((Recv) => { if (existingRoomItem) {
console.log("Got a response"); existingRoomItem.remove();
console.log(Recv); }
updateLocalStateFromRecv(Recv);
const roomItem = document.createElement('li');
roomItem.classList.add('room-item');
roomItem.innerHTML = `
<span class="room-name">${roomName}</span>
<button class="join-button" onclick="openModal('${roomName}')">Войти</button>
`;
roomList.appendChild(roomItem);
}
function initializeRoomList() {
Object.keys(rooms).forEach(roomName => {
addRoomToList(roomName);
}); });
} }
window.onload = function () { initializeRoomList();
console.log("Loading complete");
updateLocalStateFromChatListUpdResp(initial_chatListUpdResp); window.onclick = function(event) {
configureChatCreationInterface(); if (event.target === document.getElementById('passwordModal')) {
configureChatRenunciationInterfaceWinPart(); closeModal();
mainloopPoller(); }
}; if (event.target === document.getElementById('createRoomModal')) {
closeCreateRoomModal();
}
}

10
assets/js/profile.js Normal file
View File

@ -0,0 +1,10 @@
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);
}
});

50
assets/js/registration.js Normal file
View File

@ -0,0 +1,50 @@
document.addEventListener('DOMContentLoaded', function() {
const form = document.querySelector('form');
const submitButton = form.querySelector('button[type="submit"]');
const errorElement = document.getElementById('error');
form.addEventListener('keydown', function(event) {
const activeElement = document.activeElement;
const formElements = Array.from(form.elements);
const currentIndex = formElements.indexOf(activeElement);
if (activeElement.tagName === 'INPUT') {
if (event.key === 'Enter') {
event.preventDefault();
if (currentIndex === formElements.length - 1) {
submitButton.focus();
} else if (currentIndex !== -1 && formElements[currentIndex + 1]) {
formElements[currentIndex + 1].focus();
}
}
}
});
submitButton.addEventListener('focus', function() {
submitButton.classList.add('focus-visible');
});
submitButton.addEventListener('blur', function() {
submitButton.classList.remove('focus-visible');
});
form.addEventListener('submit', function(event) {
if (!validateForm()) {
event.preventDefault();
}
});
function validateForm() {
const username = document.getElementById('username').value.trim();
const login = document.getElementById('login').value.trim();
const password = document.getElementById('password').value.trim();
if (username === '' || login === '' || password === '') {
errorElement.textContent = 'Пожалуйста, заполните все поля';
errorElement.style.display = 'block';
return false;
}
return true;
}
});

View File

@ -1,91 +0,0 @@
{
"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"
}
}

View File

@ -1,91 +0,0 @@
{
"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"
}
}

View File

@ -25,11 +25,11 @@ struct CAWebChat {
BuildUnitsArray runlevel_2; BuildUnitsArray runlevel_2;
std::string build_type; std::string build_type;
bool make_tests = false; bool build_tests = false;
std::vector<std::string> warning_flags = {"-Wall", "-Wno-unused-variable", "-Werror=return-type","-pedantic", std::vector<std::string> warning_flags = {"-Wall", "-Wno-unused-variable", "-Werror=return-type","-pedantic",
"-Wno-unused-but-set-variable", "-Wno-reorder"}; "-Wno-unused-but-set-variable", "-Wno-reorder"};
std::vector<std::string> version_flags = {"--std", "c++17", "-D", "_POSIX_C_SOURCE=200809L"}; std::vector<std::string> version_flags = {"--std", "c++14", "-D", "_POSIX_C_SOURCE=200809L"};
std::vector<std::string> debug_defines_release = {"_GLIBCXX_DEBUG"}; std::vector<std::string> debug_defines_release = {"_GLIBCXX_DEBUG"};
std::vector<std::string> debug_defines_debug = {"_GLIBCXX_DEBUG", "DEBUG_ALLOW_LOUD"}; std::vector<std::string> debug_defines_debug = {"_GLIBCXX_DEBUG", "DEBUG_ALLOW_LOUD"};
std::vector<std::string> opt_flags_release = {"-g", "-O2"}; std::vector<std::string> opt_flags_release = {"-g", "-O2"};
@ -49,11 +49,9 @@ struct CAWebChat {
return my_flag_collection; return my_flag_collection;
} }
explicit CAWebChat(const NormalCBuildSystemCommandMeaning& cmd){ CAWebChat(const std::string& _build_type, bool _build_tests, const NormalCBuildSystemCommandMeaning& cmd)
const char* BSCRIPT_TYPE = getenv("BSCRIPT_TYPE"); : build_type(_build_type), build_tests(_build_tests)
const char* BSCRIPT_TESTS = getenv("BSCRIPT_TESTS"); {
build_type = BSCRIPT_TYPE ? BSCRIPT_TYPE : "release";
make_tests = (bool)BSCRIPT_TESTS;
ASSERT(build_type == "release" || build_type == "debug", "Unknown build type"); ASSERT(build_type == "release" || build_type == "debug", "Unknown build type");
std::vector<ExternalLibraryTarget> ext_targets = { std::vector<ExternalLibraryTarget> ext_targets = {
@ -77,13 +75,9 @@ struct CAWebChat {
"os_utils.cpp", "os_utils.cpp",
"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/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",
"socket_address.cpp",
"admin_control.cpp",
}; };
for (std::string& u: T.units) for (std::string& u: T.units)
u = "http_server/engine_engine_number_9/" + u; u = "http_server/engine_engine_number_9/" + u;
@ -96,17 +90,14 @@ struct CAWebChat {
"os_utils.h", "os_utils.h",
"connecting_assets/static_asset_manager.h", "connecting_assets/static_asset_manager.h",
"http_structures/client_request.h", "http_structures/client_request.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",
"admin_control.h",
}; };
for (std::string& u: T.exported_headers) for (std::string& u: T.exported_headers)
u = "engine_engine_number_9/" + u; u = "engine_engine_number_9/" + u;
T.installation_dir = "een9";
T.installation_dir = "";
my_targets.push_back(T); my_targets.push_back(T);
} }
{ CTarget T{"new_york_transit_line", "shared_library"}; { CTarget T{"new_york_transit_line", "shared_library"};
@ -130,76 +121,28 @@ struct CAWebChat {
}; };
for (std::string& u: T.exported_headers) for (std::string& u: T.exported_headers)
u = "new_york_transit_line/" + u; u = "new_york_transit_line/" + u;
T.installation_dir = "nytl";
my_targets.push_back(T); my_targets.push_back(T);
} }
{ CTarget T{"iu9_ca_web_chat_lib", "shared_library"}; { CTarget T{"iu9-ca-web-chat", "executable"};
T.additional_compilation_flags = getSomeRadFlags();
T.proj_deps = {
CTargetDependenceOnProjectsLibrary{"engine_engine_number_9", {true, true}},
CTargetDependenceOnProjectsLibrary{"new_york_transit_line", {true, true}},
};
T.external_deps = {
CTargetDependenceOnExternalLibrary{"sqlite3", {true, true}}
};
T.units = {
"localizator.cpp",
"initialize.cpp",
"run.cpp",
"str_fields.cpp",
"find_db.cpp",
"sqlite3_wrapper.cpp",
"login_cookie.cpp",
"backend_logic/server_data_interact.cpp",
"backend_logic/client_server_interact.cpp",
"backend_logic/when_login.cpp",
"backend_logic/when_list_rooms.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)
u = "web_chat/iu9_ca_web_chat_lib/" + u;
T.exported_headers = {
"actions.h"
};
for (std::string& u: T.exported_headers)
u = "iu9_ca_web_chat_lib/" + u;
T.include_pr = "web_chat";
T.installation_dir = "iu9cawebchat";
my_targets.push_back(T);
}
{ CTarget T{"iu9-ca-web-chat-service", "executable"};
T.additional_compilation_flags = getSomeRadFlags();
T.proj_deps = {
CTargetDependenceOnProjectsLibrary{"iu9_ca_web_chat_lib"},
};
T.units = {"service.cpp"};
for (std::string& u: T.units)
u = "web_chat/iu9_ca_web_chat_service/" + u;
T.include_pr = "web_chat";
my_targets.push_back(T);
}
{ CTarget T{"iu9-ca-web-chat-admin-cli", "executable"};
T.additional_compilation_flags = getSomeRadFlags(); T.additional_compilation_flags = getSomeRadFlags();
T.proj_deps = { T.proj_deps = {
CTargetDependenceOnProjectsLibrary{"engine_engine_number_9"}, CTargetDependenceOnProjectsLibrary{"engine_engine_number_9"},
CTargetDependenceOnProjectsLibrary{"new_york_transit_line"},
};
T.external_deps = {
CTargetDependenceOnExternalLibrary{"sqlite3"}
}; };
T.units = { T.units = {
"cli.cpp", // Main file "main.cpp",
"initialize.cpp",
"run.cpp",
"str_fields_check.cpp",
"find_db.cpp",
}; };
for (std::string& u: T.units) for (std::string& u: T.units)
u = "web_chat/iu9_ca_web_chat_admin_cli/" + u; u = "web_chat/" + u;
T.include_pr = "web_chat"; T.include_pr = "web_chat";
T.installation_dir = "";
my_targets.push_back(T); my_targets.push_back(T);
} }
regular_ctargets_to_2bus_conversion(ext_targets, my_targets, runlevel_1, runlevel_2, regular_ctargets_to_2bus_conversion(ext_targets, my_targets, runlevel_1, runlevel_2,
@ -216,7 +159,9 @@ int main(int argc, char** argv) {
} }
NormalCBuildSystemCommandMeaning cmd; NormalCBuildSystemCommandMeaning cmd;
regular_bs_cli_cmd_interpret(args, cmd); regular_bs_cli_cmd_interpret(args, cmd);
CAWebChat bs(cmd); const char* BS_SCRIPT_TYPE = getenv("BS_SCRIPT_TYPE");
const char* BS_SCRIPT_TESTS = getenv("BS_SCRIPT_TESTS");
CAWebChat bs(BS_SCRIPT_TYPE ? BS_SCRIPT_TYPE : "release", (bool)BS_SCRIPT_TESTS, cmd);
if (cmd.need_to_build) if (cmd.need_to_build)
complete_tasks_of_build_units(bs.runlevel_1); complete_tasks_of_build_units(bs.runlevel_1);
umask(~0755); umask(~0755);

View File

@ -1,13 +1,25 @@
{ {
"lang": { "presentation": {
"whitelist": ["*"], "instance-identity": {
"force-order": [ "top-title": "Вэб чат от ИУ9"
"ru" },
] "phr": {
"decl": {
"list-of-chat-rooms": "Список Чат-Комнат",
"select-chat-room": "Выберете чат комнату",
"name-of-room": "Название комнаты",
"create-room": "Создать комнату"
},
"act": {
"create-room": "Создать комнату",
"confirm": "Подтвердить",
"create": "Создать"
}
}
}, },
"assets": "./assets", "assets": "./assets",
"database": { "database": {
"type": "sqlite3", "type": "sqlite",
"file": "./iu9-ca-web-chat.db" "file": "./iu9-ca-web-chat.db"
}, },
"limits": { "limits": {
@ -17,8 +29,8 @@
"storage-size-limit": 100000000000 "storage-size-limit": 100000000000
}, },
"server": { "server": {
"workers": 16, "workers": 8,
"http-listen": ["127.0.0.1:1025"], "http-listen": ["127.0.0.1:1025"],
"admin-command-listen": ["[::1]:1026"] "command-listen": []
} }
} }

View File

@ -1,62 +0,0 @@
#include "admin_control.h"
#include <string.h>
#include <assert.h>
namespace een9 {
static const char* admin_to_server_ms = "a6m1n 2 server request ~~~";
static const char* server_to_admin_ms = "server to 4dm1n r3sponse ~~~";
AdminControlProtMsgRecvCtx::AdminControlProtMsgRecvCtx(const char *ms): magic_string(ms) {
ms_size = strlen(ms);
}
int AdminControlProtMsgRecvCtx::feedCharacter(char ch) {
assert(status == 0);
if (magic_string_progress < ms_size) {
if (magic_string[magic_string_progress] != ch) {
status = -1;
return status;
}
magic_string_progress++;
} else if (body_size_progress < 8) {
uint64_t bt = (uint64_t)(uint8_t)ch;
b_sz = ((b_sz << 8) | bt);
body_size_progress++;
if (body_size_progress == 8 && b_sz > 100000000) {
status = -1;
return status;
}
body.reserve(b_sz);
} else {
body += ch;
if (body.size() >= b_sz)
status = 1;
}
return status;
}
AdminControlRequestRCtx::AdminControlRequestRCtx(): AdminControlProtMsgRecvCtx(admin_to_server_ms) {
}
AdminControlResponseRCtx::AdminControlResponseRCtx(): AdminControlProtMsgRecvCtx(server_to_admin_ms) {
}
std::string generate_ac_msg_gen_case(const std::string& content, const char* ms) {
std::string result = ms;
uint64_t N = content.size();
for (int i = 0; i < 8; i++) {
result += (char)(uint8_t)((N & 0xff00000000000000) >> 56);
N <<= 8;
}
result += content;
return result;
}
std::string generate_admin_control_request(const std::string &content) {
return generate_ac_msg_gen_case(content, admin_to_server_ms);
}
std::string generate_admin_control_response(const std::string &content) {
return generate_ac_msg_gen_case(content, server_to_admin_ms);
}
}

View File

@ -1,37 +0,0 @@
#ifndef ENGINE_ENGINE_NUMBER_9_ADMIN_CONTROL_H
#define ENGINE_ENGINE_NUMBER_9_ADMIN_CONTROL_H
#include <string>
#include <stdint.h>
namespace een9 {
struct AdminControlProtMsgRecvCtx {
const char* magic_string;
size_t ms_size;
size_t magic_string_progress = 0;
size_t body_size_progress = 0;
uint64_t b_sz = 0;
std::string body;
int status = 0;
explicit AdminControlProtMsgRecvCtx(const char* ms);
int feedCharacter(char ch);
};
struct AdminControlRequestRCtx: public AdminControlProtMsgRecvCtx {
AdminControlRequestRCtx();
};
struct AdminControlResponseRCtx: public AdminControlProtMsgRecvCtx {
AdminControlResponseRCtx();
};
/* From ADMIN to server (will begin with admin_to_server_ms)*/
std::string generate_admin_control_request(const std::string& content);
/* From SERVER to admin (will begin with server_to_admin_ms).*/
std::string generate_admin_control_response(const std::string& content);
}
#endif

View File

@ -28,30 +28,14 @@ namespace een9 {
return false; return false;
} }
bool endsWith(const std::string &a, const std::string &b) { bool endsIn(const std::string &a, const std::string &b) {
if (b.size() > a.size()) if (b.size() > a.size())
return false; return false;
return std::equal(a.end() - (ssize_t)b.size(), a.end(), b.begin()); return std::equal(a.end() - (ssize_t)b.size(), a.end(), b.begin());
} }
bool beginsWith(const std::string &a, const std::string &b) {
if (b.size() > a.size())
return false;
return std::equal(a.begin(), a.begin() + (ssize_t)b.size(), b.begin());
}
std::string getSubstring(const std::string &str, size_t A, size_t B) { std::string getSubstring(const std::string &str, size_t A, size_t B) {
ASSERT(A <= B && B <= str.size(), "Incorrect substring segment"); ASSERT(A <= B && B <= str.size(), "Incorrect substring segment");
return str.substr(A, B - A); return str.substr(A, B - A);
} }
std::string make_uppercase(const std::string &source) {
std::string result(source);
for (size_t i = 0; i < source.size(); i++) {
char ch = source[i];
if ('a' <= ch && ch <= 'z')
result[i] = (char)(ch - 'a' + 'A');
}
return result;
}
} }

View File

@ -18,17 +18,11 @@ namespace een9 {
bool strIn(const std::string& str, const char* arr[]); bool strIn(const std::string& str, const char* arr[]);
// b is postfix of a bool endsIn(const std::string& a, const std::string& b);
bool endsWith(const std::string& a, const std::string& b);
// b is prefix of a
bool beginsWith(const std::string& a, const std::string& b);
/* In case of error, throws een9::ServerError */ /* In case of error, throws een9::ServerError */
std::string getSubstring(const std::string& str, size_t A, size_t B); std::string getSubstring(const std::string& str, size_t A, size_t B);
std::string make_uppercase(const std::string &source);
template<typename T> template<typename T>
using uptr = std::unique_ptr<T>; using uptr = std::unique_ptr<T>;
} }

View File

@ -53,7 +53,7 @@ namespace een9 {
std::vector<std::string> c_files = detour_over_regular_folder(dir_rule.directory); std::vector<std::string> c_files = detour_over_regular_folder(dir_rule.directory);
for (const std::string& file: c_files) { for (const std::string& file: c_files) {
for (const StaticAssetManagerRulePostfixFilter& ext: dir_rule.postfix_rules_type_assign) { for (const StaticAssetManagerRulePostfixFilter& ext: dir_rule.postfix_rules_type_assign) {
if (endsWith(file, ext.required_postfix)) { if (endsIn(file, ext.required_postfix)) {
/* Found it! */ /* Found it! */
StaticAsset etot{ext.assigned_type, }; StaticAsset etot{ext.assigned_type, };
readFile(dir_rule.directory + "/" + file, etot.content); readFile(dir_rule.directory + "/" + file, etot.content);

View File

@ -1,72 +0,0 @@
#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;
}
}

View File

@ -1,13 +0,0 @@
#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

View File

@ -3,14 +3,6 @@
#include <libregexis024tools/delayed_matching.h> #include <libregexis024tools/delayed_matching.h>
#include <algorithm> #include <algorithm>
#include <assert.h> #include <assert.h>
// Used for debug
#ifdef DEBUG_ALLOW_LOUD
#include "unistd.h"
#include <sys/stat.h>
#include "sys/dir.h"
#include "../os_utils.h"
#endif
namespace een9 { namespace een9 {
ClientRequestParser_CommonPrograms::ClientRequestParser_CommonPrograms() { ClientRequestParser_CommonPrograms::ClientRequestParser_CommonPrograms() {
@ -107,15 +99,9 @@ namespace een9 {
if (p.first == "Content-Length") { if (p.first == "Content-Length") {
collecting_body = res.has_body = true; collecting_body = res.has_body = true;
body_size = std::stoull(p.second); body_size = std::stoull(p.second);
if (body_size > 100000000) { if (body_size > 100000000)
status = -1; THROW("Message content is too big");
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) {
@ -123,11 +109,8 @@ namespace een9 {
} }
/* We either finish now or we finish later */ /* We either finish now or we finish later */
} else if (!vm.haveSurvivors()) { } else if (!vm.haveSurvivors()) {
#ifdef DEBUG_ALLOW_LOUD
mkdir("log", 0750);
writeFile("log/req", header);
#endif
status = -1; status = -1;
THROW("bad request");
} }
} }
return status; return status;

View File

@ -37,6 +37,7 @@ namespace een9 {
explicit ClientRequestParser_WorkerBuffers(const ClientRequestParser_CommonPrograms& common_comp_program); explicit ClientRequestParser_WorkerBuffers(const ClientRequestParser_CommonPrograms& common_comp_program);
}; };
/* Ou yeah, baby, it's time for more OOP */
struct ClientHttpRequestParser_Ctx { struct ClientHttpRequestParser_Ctx {
ClientRequest& res; ClientRequest& res;
regexis024::VirtualMachine& vm; regexis024::VirtualMachine& vm;

View File

@ -1,107 +0,0 @@
#include "cookies.h"
#include "../baza_inter.h"
#include "grammar.h"
namespace een9 {
bool isSPACE(char ch) {
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) {
for (char ch : str) {
if (!(isALPHANUM(ch)
|| ch == '!' || ch == '#' || ch == '$' || ch == '%' || ch == '&' || ch == '\'' || ch == '*'
|| ch == '+' || ch == '-' || ch == '.' || ch == '^' || ch == '_' || ch == '`' || ch == '|'
|| ch == '~' ))
return false;
} // '*+-.^_`|~
return !str.empty();
}
bool isCookieName(const std::string &str) {
return isToken(str);
}
bool isCookieValue(const std::string &str) {
for (char ch : str) {
if (ch <= 32 || ch == 0x22 || ch == 0x2C || ch == 0x3B || ch == 0x5C || ch >= 0x7F)
return false;
}
return true;
}
std::vector<std::pair<std::string, std::string>> parseCookieHeader(const std::string &hv) {
std::vector<std::pair<std::string, std::string>> result;
size_t pos = 0;
auto skip_ows = [&]() {
while (hv.size() > pos && isSPACE(hv[pos]))
pos++;
};
auto read_to_space_or_eq = [&]() -> std::string {
size_t S = pos;
while (hv.size() > pos && !isSPACE(hv[pos]) && hv[pos] != '=')
pos++;
return hv.substr(S, pos - S);
};
auto read_to_space_or_semc = [&]() -> std::string {
size_t S = pos;
while (hv.size() > pos && !isSPACE(hv[pos]) && hv[pos] != '"' && hv[pos] != ';')
pos++;
return hv.substr(S, pos - S);
};
auto isThis = [&](char ch) -> bool {
return pos < hv.size() && hv[pos] == ch;
};
skip_ows();
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();
// ASSERT(isCookieName(name_of_pechenye), "Incorrect Cookie name");
skip_ows();
if (!isThis('='))
THROW("Incorrect Cookie header line, missing =");
pos++;
skip_ows();
std::string value_of_pechenye = read_to_space_or_semc();
// ASSERT(isCookieValue(value_of_pechenye), "Incorrect Cookie value");
result.emplace_back(name_of_pechenye, value_of_pechenye);
skip_ows();
}
return result;
}
std::vector<std::pair<std::string, std::string>>
findAllClientCookies(const std::vector<std::pair<std::string, std::string>>& header) {
std::vector<std::pair<std::string, std::string>> result;
for (const std::pair<std::string, std::string>& line: header) {
try {
if (line.first == "Cookie") {
std::vector<std::pair<std::string, std::string>> new_cookies = parseCookieHeader(line.second);
result.reserve(result.size() + new_cookies.size());
result.insert(result.end(), new_cookies.begin(), new_cookies.end());
}
} catch (const std::exception& e) {}
}
return result;
}
void set_cookie(const std::vector<std::pair<std::string, std::string>>& new_cookies,
std::vector<std::pair<std::string, std::string>>& res_header_lines) {
for (const std::pair<std::string, std::string>& cookie : new_cookies) {
ASSERT_pl(isCookieName(cookie.first) && isCookieValue(cookie.second));
res_header_lines.emplace_back("Set-Cookie", cookie.first + "=" + cookie.second + ";SameSite=Strict;Path=/");
}
}
}

View File

@ -1,26 +0,0 @@
#ifndef HTTP_STRUCTURES_COOKIES_H
#define HTTP_STRUCTURES_COOKIES_H
#include <string>
#include <vector>
#include "../baza.h"
#include <map>
namespace een9 {
bool isCookieName(const std::string& str);
bool isCookieValue(const std::string& str);
/* Throws een9::ServerError on failure */
std::vector<std::pair<std::string, std::string>> parseCookieHeader(const std::string& hv);
/* Header is header. Throws een9::ServerError on failure. Concatenates output of een9::parseCookieHeader */
std::vector<std::pair<std::string, std::string>>
findAllClientCookies(const std::vector<std::pair<std::string, std::string>>& header);
/* Can throw een9::ServerError (if check for a value failed). res_header_lines is mutated accordingle */
void set_cookie(const std::vector<std::pair<std::string, std::string>>& new_cookies,
std::vector<std::pair<std::string, std::string>>& res_header_lines);
}
#endif

View File

@ -1,11 +0,0 @@
#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

View File

@ -5,8 +5,7 @@
namespace een9 { namespace een9 {
std::string form_http_server_response_header(const char* code, std::string form_http_server_response_header(const char* code, const std::map<std::string, std::string>& headers) {
const std::vector<std::pair<std::string, std::string>>& headers) {
assert(strlen(code) == 3); assert(strlen(code) == 3);
std::string result = std::string("HTTP/1.0 ") + code + " " + (code[0] < '4' ? "OK" : "ERROR") + "\r\n"; std::string result = std::string("HTTP/1.0 ") + code + " " + (code[0] < '4' ? "OK" : "ERROR") + "\r\n";
for (auto& p: headers) for (auto& p: headers)
@ -14,13 +13,12 @@ namespace een9 {
return result; return result;
} }
std::string form_http_server_response_header_only(const char* code, std::string form_http_server_reponse_header_only(const char* code, const std::map<std::string, std::string>& headers) {
const std::vector<std::pair<std::string, std::string>>& headers) {
return form_http_server_response_header(code, headers) + "\r\n"; return form_http_server_response_header(code, headers) + "\r\n";
} }
std::string form_http_server_response_with_body(const char* code, std::string form_http_server_response_with_body(const char* code,
const std::vector<std::pair<std::string, std::string>>& headers, const std::map<std::string, std::string>& headers,
const std::string& body) const std::string& body)
{ {
std::string result = form_http_server_response_header(code, headers) std::string result = form_http_server_response_header(code, headers)
@ -40,15 +38,4 @@ namespace een9 {
{"Content-Type", Content_Type} {"Content-Type", Content_Type}
}, body); }, body);
} }
std::string form_http_server_response_303(const std::string& Location) {
return form_http_server_response_header_only("303", {{"Location", Location}});
}
std::string form_http_server_response_303_spec_head(const std::string &Location,
const std::vector<std::pair<std::string, std::string>>& headers) {
std::vector<std::pair<std::string, std::string>> cp = headers;
cp.emplace_back("Location", Location);
return form_http_server_response_header_only("303", cp);
}
} }

View File

@ -1,29 +1,21 @@
#ifndef ENGINE_ENGINE_NUMBER_9_HTTP_STRUCTURES_RESPONSE_GEN_H #ifndef ENGINE_ENGINE_NUMBER_9_HTTP_STRUCTURES_RESPONSE_GEN_H
#define ENGINE_ENGINE_NUMBER_9_HTTP_STRUCTURES_RESPONSE_GEN_H #define ENGINE_ENGINE_NUMBER_9_HTTP_STRUCTURES_RESPONSE_GEN_H
#include <vector> #include <map>
#include <string> #include <string>
namespace een9 { namespace een9 {
std::string form_http_server_response_header(const char* code, std::string form_http_server_response_header(const char* code, const std::map<std::string, std::string>& headers);
const std::vector<std::pair<std::string, std::string>>& headers);
std::string form_http_server_reponse_header_only(const char* code, std::string form_http_server_reponse_header_only(const char* code, const std::map<std::string, std::string>& headers);
const std::vector<std::pair<std::string, std::string>>& headers);
std::string form_http_server_response_with_body(const char* code, std::string form_http_server_response_with_body(const char* code,
const std::vector<std::pair<std::string, std::string>>& headers, const std::map<std::string, std::string>& headers,
const std::string& body); const std::string& body);
std::string form_http_server_response_200(const std::string& Content_Type, const std::string& body); std::string form_http_server_response_200(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_404(const std::string& Content_Type, const std::string& body);
std::string form_http_server_response_303(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);
} }
#endif #endif

View File

@ -25,6 +25,7 @@ namespace een9 {
} }
UniqueFdWrapper::~UniqueFdWrapper() { UniqueFdWrapper::~UniqueFdWrapper() {
// printf("DEBUG!!! Closing fd = %d\n", fd);
if (fd >= 0) if (fd >= 0)
close(fd); close(fd);
} }
@ -54,7 +55,7 @@ namespace een9 {
while ((ret = (int)read(fd, buf, 2048)) > 0) { while ((ret = (int)read(fd, buf, 2048)) > 0) {
size_t oldN = result.size(); size_t oldN = result.size();
result.resize(oldN + ret); result.resize(oldN + ret);
memcpy(result.data() + oldN, buf, ret); memcpy((void*)&result.c_str()[oldN], buf, ret);
} }
ASSERT_on_iret(ret, "Reading from " + description); ASSERT_on_iret(ret, "Reading from " + description);
} }
@ -66,28 +67,6 @@ namespace een9 {
readFromFileDescriptor(fdw(), result, "file \"" + path + "\""); readFromFileDescriptor(fdw(), result, "file \"" + path + "\"");
} }
/* write(fd, text); close(fd); */
void writeToFileDescriptor(int fd, const std::string& text, const std::string& description = "") {
size_t n = text.size();
size_t i = 0;
while (i < n) {
size_t block = std::min(2048lu, n - i);
int ret = write(fd, &text[i], block);
ASSERT_on_iret(ret, "Writing to" + description);
i += ret;
}
close(fd);
}
/* Truncational */
void writeFile(const std::string& path, const std::string& text) {
int fd = open(path.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0755);
ASSERT_on_iret(fd, "Opening \"" + path + "\"");
UniqueFdWrapper fdw(fd);
writeToFileDescriptor(fdw(), text, "file \"" + path + "\n");
}
void configure_socket_rcvsndtimeo(int fd, timeval tv) { void configure_socket_rcvsndtimeo(int fd, timeval tv) {
int ret; int ret;
ret = setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(timeval)); ret = setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(timeval));

View File

@ -30,8 +30,6 @@ namespace een9 {
void readFile(const std::string& path, std::string& result); void readFile(const std::string& path, std::string& result);
void writeFile(const std::string& path, const std::string& text);
void configure_socket_rcvsndtimeo(int fd, timeval tv); void configure_socket_rcvsndtimeo(int fd, timeval tv);
} }

View File

@ -13,7 +13,6 @@
#include "http_structures/client_request_parse.h" #include "http_structures/client_request_parse.h"
#include "http_structures/response_gen.h" #include "http_structures/response_gen.h"
#include "baza_inter.h" #include "baza_inter.h"
#include "admin_control.h"
namespace een9 { namespace een9 {
struct QElementHttpConnections { struct QElementHttpConnections {
@ -41,6 +40,7 @@ namespace een9 {
} }
void push_back(SlaveTask task) { void push_back(SlaveTask task) {
/* Throws a goddamn execption. Because why not. Ofcourse everything has to throw an exception */
/* CLion says. Allocated memory is leaking. YOUR MOTHER IS LEAKING YOU FOOL!! MY CODE IS FINE!! */ /* CLion says. Allocated memory is leaking. YOUR MOTHER IS LEAKING YOU FOOL!! MY CODE IS FINE!! */
QElementHttpConnections* el = new QElementHttpConnections(std::move(task)); QElementHttpConnections* el = new QElementHttpConnections(std::move(task));
/* Exception does not leave queue in incorrect state */ /* Exception does not leave queue in incorrect state */
@ -65,15 +65,6 @@ namespace een9 {
sz--; sz--;
} }
} }
~WorkersTaskQueue() {
QElementHttpConnections* cur = first;
while (cur) {
QElementHttpConnections* nxt = cur->nxt;
delete cur;
cur = nxt;
}
}
}; };
struct WorkersEnvCommon { struct WorkersEnvCommon {
@ -82,13 +73,11 @@ namespace een9 {
WorkersTaskQueue queue; WorkersTaskQueue queue;
bool& termination; bool& termination;
guest_core_t guest_core; guest_core_t guest_core;
guest_core_admin_control_t guest_core_admin_control;
/* Parser programs */ /* Parser programs */
ClientRequestParser_CommonPrograms parser_programs; ClientRequestParser_CommonPrograms parser_programs;
WorkersEnvCommon(bool& term, const MainloopParameters& params): termination(term), WorkersEnvCommon(bool& term, guest_core_t g_c): termination(term), guest_core(std::move(g_c)){}
guest_core(params.guest_core), guest_core_admin_control(params.guest_core_admin_control){}
}; };
struct WorkersEnv { struct WorkersEnv {
@ -100,22 +89,25 @@ namespace een9 {
}; };
// todo: add timeout for multiple bytes, add more settings // todo: add timeout for multiple bytes, add more settings
ClientRequest process_http_connection_input(int fd, const EEN9_ServerTips& s_tips, WorkersEnv& wte) { ClientRequest process_connection_input(int fd, const EEN9_ServerTips& s_tips, WorkersEnv& wte) {
ClientRequest res; ClientRequest res;
ClientHttpRequestParser_Ctx parser(res, wte.personal_parser_buffer, wte.wtec.parser_programs); ClientHttpRequestParser_Ctx parser(res, wte.personal_parser_buffer, wte.wtec.parser_programs);
int ret; int ret;
char buf[2048]; char buf[2048];
assert(parser.status == 0); ASSERT_pl(parser.status == 0);
while ((ret = (int)recv(fd, buf, 2048, 0)) > 0) { while ((ret = (int)recv(fd, buf, 2048, 0)) > 0) {
for (size_t i = 0; i < ret; i++) { for (size_t i = 0; i < ret; i++) {
if (parser.feedCharacter(buf[i]) != 0) /* Throws ServerError on bad input */
if (parser.feedCharacter(buf[i]) > 0) {
break; break;
}
} }
if (parser.status != 0) if (parser.status > 0)
break; break;
} }
ASSERT(parser.status == 1, "Incorrect request"); // todo: do the same thing everywhere else
ASSERT_on_iret(ret, "recv"); ASSERT_on_iret(ret, "recv");
ASSERT_pl(parser.status == 1);
// printf("Log: worker received clients request\n%s\n", client_request.toString().c_str());
return res; return res;
} }
@ -131,41 +123,10 @@ namespace een9 {
printf("Log: worker: succesfully asnwered with response\n"); printf("Log: worker: succesfully asnwered with response\n");
} }
std::string process_admin_control_connection_input(int fd, const EEN9_ServerTips& s_tips, WorkersEnv& wte) {
AdminControlRequestRCtx pctx;
int ret;
char buf[2048];
assert(pctx.status == 0);
while ((ret = (int)recv(fd, buf, 2048, 0)) > 0) {
for (size_t i = 0; i < ret; i++) {
if (pctx.feedCharacter(buf[i]) != 0)
break;
}
if (pctx.status != 0)
break;
}
ASSERT(pctx.status == 1, "Incorrect request");
ASSERT_on_iret(ret, "recv");
return pctx.body;
}
void process_connection(const SlaveTask& task, WorkersEnv& wte) { void process_connection(const SlaveTask& task, WorkersEnv& wte) {
if (task.conn_info.type == 0) { ClientRequest client_request = process_connection_input(task.fd(), task.s_tips, wte);
printf("%d::Got http reuest\n", wte.id); std::string server_response = wte.wtec.guest_core(task, client_request, wte.id);
ClientRequest client_request = process_http_connection_input(task.fd(), task.s_tips, wte); process_connection_output(task.fd(), server_response);
printf("%d::Http request has been read\n", wte.id);
std::string server_response = wte.wtec.guest_core(task, client_request, wte.id);
process_connection_output(task.fd(), server_response);
printf("%d::Http response has been sent\n", wte.id);
} else if (task.conn_info.type == 1) {
printf("%d::Got admin-cmd request\n", wte.id);
std::string admin_request = process_admin_control_connection_input(task.fd(), task.s_tips, wte);
printf("%d::Admin-cmd request has been read\n", wte.id);
std::string server_response_content = wte.wtec.guest_core_admin_control(task, admin_request, wte.id);
std::string server_response = generate_admin_control_response(server_response_content);
process_connection_output(task.fd(), server_response);
printf("%d::Admin-cmd response has been sent\n", wte.id);
}
} }
void* worker_func(void* wte_ptr) { void* worker_func(void* wte_ptr) {
@ -195,13 +156,13 @@ namespace een9 {
return NULL; return NULL;
} }
// todo: retrieve address of connected client
void electric_boogaloo(const MainloopParameters& params, bool& termination_trigger) { void electric_boogaloo(const MainloopParameters& params, bool& termination_trigger) {
WorkersEnvCommon wtec(termination_trigger, params); WorkersEnvCommon wtec(termination_trigger, params.guest_core);
ASSERT(params.slave_number > 0, "No workers spawned"); ASSERT(params.slave_number > 0, "No workers spawned");
size_t CRL_Nip = params.client_regular_listened.size(); size_t Nip = params.ports_to_listen.size();
ASSERT(CRL_Nip > 0, "No open listeting addresses (http)"); ASSERT(Nip > 0, "No open listeting addresses");
size_t ACL_Nip = params.admin_control_listened.size();
size_t Nip = CRL_Nip + ACL_Nip;
std::vector<pthread_t> workers(params.slave_number); std::vector<pthread_t> workers(params.slave_number);
std::vector<uptr<WorkersEnv>> wtes(params.slave_number); std::vector<uptr<WorkersEnv>> wtes(params.slave_number);
@ -212,72 +173,66 @@ namespace een9 {
pthread_create(&workers[i], NULL, worker_func, wtes[i].get()); pthread_create(&workers[i], NULL, worker_func, wtes[i].get());
} }
// 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 { std::vector<UniqueFdWrapper> listening_socks(Nip);
/* A copy from params */
SocketAddress my_addr;
UniqueFdWrapper listening_sock;
/* type 0 is for http protocol
* type 1 is for admin-cmd protocol */
int type;
};
std::vector<Ear> ears(Nip);
for (size_t i = 0; i < CRL_Nip; i++) {
ears[i].my_addr = params.client_regular_listened[i];
ears[i].type = 0;
}
for (size_t i = 0; i < ACL_Nip; i++) {
ears[i + CRL_Nip].my_addr = params.admin_control_listened[i];
ears[i + CRL_Nip].type = 1;
}
for (size_t i = 0; i < Nip; i++) { for (size_t i = 0; i < Nip; i++) {
int listening_socket_fd = socket(ears[i].my_addr.v.gen.sa_family, SOCK_STREAM, 0); printf("Creating listening socket\n");
ASSERT_on_iret(listening_socket_fd, "'Listening socket' creation"); uint16_t port = params.ports_to_listen[i];
int listening_socket_fd = socket(AF_INET, SOCK_STREAM, 0);
ASSERT_on_iret(listening_socket_fd, "Listening socket creation");
UniqueFdWrapper listening_socket(listening_socket_fd); UniqueFdWrapper listening_socket(listening_socket_fd);
printf("Listening socket created\n");
sockaddr_in listening_address;
listening_address.sin_family = AF_INET;
listening_address.sin_port = htons(port);
uint32_t lca = (127u << 24) | 1;
listening_address.sin_addr.s_addr = htonl(lca);
int reuseaddr_nozero_option_value = 1; int reuseaddr_nozero_option_value = 1;
ret = setsockopt(listening_socket_fd, SOL_SOCKET, SO_REUSEADDR, &reuseaddr_nozero_option_value, sizeof(int)); ret = setsockopt(listening_socket_fd, SOL_SOCKET, SO_REUSEADDR, &reuseaddr_nozero_option_value, sizeof(int));
ASSERT_on_iret(ret, "Can't set SO_REUSEADDR"); ASSERT_on_iret(ret, "setting SO_REUSEADDR befire binding to address");
bind_to_socket_address(listening_socket_fd, ears[i].my_addr); ret = bind(listening_socket(), (const sockaddr*)&listening_address, sizeof(listening_address));
ASSERT_on_iret(ret, "binding to INADDR_ANY:" + std::to_string(port));
printf("Binded socket to address\n");
ret = listen(listening_socket(), 128); ret = listen(listening_socket(), 128);
ASSERT_on_iret(ret, "listen() listening for connections"); ASSERT_on_iret(ret, "listening for connections");
ears[i].listening_sock = std::move(listening_socket); printf("Listening socket succesfully started listening\n");
listening_socks[i] = std::move(listening_socket);
} }
std::vector<pollfd> pollfds(Nip); std::vector<pollfd> pollfds(Nip);
for (size_t i = 0; i < Nip; i++) { for (size_t i = 0; i < Nip; i++) {
pollfds[i].fd = ears[i].listening_sock(); pollfds[i].fd = listening_socks[i]();
pollfds[i].events = POLLRDNORM; pollfds[i].events = POLLRDNORM;
} }
printf("Entering mainloop\n");
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 (ret != 0 && errno != 0) { if (errno == EINTR)
printf("poll() error :> %s\n", een9::prettyprint_errno("").c_str()); break;
continue; ASSERT_on_iret(ret, "polling");
}
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 {
int session_sock = accept(pollfds[i].fd, NULL, NULL); sockaddr client_address;
socklen_t client_addr_len = sizeof(client_address);
int session_sock = accept(pollfds[i].fd, &client_address, &client_addr_len);
ASSERT_on_iret(session_sock, "Failed to accept incoming connection"); ASSERT_on_iret(session_sock, "Failed to accept incoming connection");
printf("Log: successful connection\n");
UniqueFdWrapper session_sock_fdw(session_sock); UniqueFdWrapper session_sock_fdw(session_sock);
configure_socket_rcvsndtimeo(session_sock_fdw(), params.s_conf.request_timeout); configure_socket_rcvsndtimeo(session_sock_fdw(), params.s_conf.request_timeout);
SocketAddress peer_addr;
get_peer_name_as_socket_address(session_sock, peer_addr);
{ MutexLockGuard lg2(wtec.corvee_bed, "poller adds connection"); { MutexLockGuard lg2(wtec.corvee_bed, "poller adds connection");
SlaveTask task{ SlaveTask task{ConnectionInfo{}, std::move(session_sock_fdw),
ConnectionInfo{ears[i].my_addr, peer_addr, ears[i].type},
std::move(session_sock_fdw),
EEN9_ServerTips{wtec.queue.size(), EEN9_ServerTips{wtec.queue.size(),
params.s_conf.critical_load_1, params.s_conf.request_timeout} params.s_conf.critical_load_1, params.s_conf.request_timeout}};
};
if (wtec.queue.size() < params.s_conf.critical_load_2) if (wtec.queue.size() < params.s_conf.critical_load_2)
wtec.queue.push_back(std::move(task)); wtec.queue.push_back(std::move(task));
} }
@ -292,7 +247,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-once-and-for-good-kind */ /* There is no need to tiptoe around this multi-access field. It is write-onle-and-for-good-kind */
wtec.termination = true; wtec.termination = true;
wtec.corvee_bed.wake_them_all(); wtec.corvee_bed.wake_them_all();
} }

View File

@ -7,14 +7,12 @@
#include "os_utils.h" #include "os_utils.h"
#include "http_structures/client_request.h" #include "http_structures/client_request.h"
#include <stdint.h> #include <stdint.h>
#include "socket_address.h"
namespace een9 { namespace een9 {
struct ConnectionInfo { struct ConnectionInfo {
SocketAddress server_name; // todo: add server address field
SocketAddress client_name; // todo: add client address field
/* 0 - http, 1 - 'een9::admin-control' protocol */ int type; // O_o why??
int type;
}; };
/* This structure is passed to guest function. It contains server info that might be or might be not used /* This structure is passed to guest function. It contains server info that might be or might be not used
@ -35,8 +33,6 @@ namespace een9 {
/* guest_core function must not throw anything that is not derived from std::exception */ /* guest_core function must not throw anything that is not derived from std::exception */
typedef std::function<std::string(const SlaveTask&, const ClientRequest&, worker_id_t worker_id)> guest_core_t; typedef std::function<std::string(const SlaveTask&, const ClientRequest&, worker_id_t worker_id)> guest_core_t;
/* same as gurst_core_t, but it used not for http, but for een9 specific "admin-cmd" protocol */
typedef std::function<std::string(const SlaveTask&, const std::string&, worker_id_t)> guest_core_admin_control_t;
struct ServersConfiguration { struct ServersConfiguration {
size_t critical_load_1 = 90; size_t critical_load_1 = 90;
@ -46,18 +42,14 @@ namespace een9 {
}; };
struct MainloopParameters { struct MainloopParameters {
/* On which addresses should I listed for incoming HTTP connections */
std::vector<SocketAddress> client_regular_listened;
/* On which addresses should I listen for incoming administrative commands */
std::vector<SocketAddress> admin_control_listened;
bool do_logging = true; bool do_logging = true;
size_t slave_number = 2; bool open_admin_listener = true;
int mainloop_recheck_interval_us = 100; // todo: add support for unix socket address
uint16_t admin_listener_port = 12345;
/* Takes parsed http request object. Should return fully-prepared http response */
guest_core_t guest_core; guest_core_t guest_core;
/* Takes admin input. Returns only desired output message (without protocol header) */ size_t slave_number = 2;
guest_core_admin_control_t guest_core_admin_control; std::vector<uint16_t> ports_to_listen;
int mainloop_recheck_interval_us = 100;
ServersConfiguration s_conf; ServersConfiguration s_conf;
}; };

View File

@ -1,277 +0,0 @@
#include "socket_address.h"
#include <stddef.h>
#include "baza.h"
#include "baza_inter.h"
#include <libregexis024tools/delayed_matching.h>
#include <libregexis024tools/stringmatching.h>
#include <libregexis024vm/libregexis024vm_interface.h>
#include <vector>
#include <assert.h>
#include <algorithm>
namespace een9 {
struct regexp_cmp_out{
std::vector<uint8_t> prg;
regexis024::track_var_list vars;
};
regexp_cmp_out compile_regexp(const char* expr) {
regexp_cmp_out res;
std::string problem;
int ret = regexis024::compile(expr, res.vars, res.prg, problem);
ASSERT(ret == 0, "Can't compile regexp");
return res;
}
struct SocketAddressParser_Inner {
regexp_cmp_out prg;
regexis024::VirtualMachine vm;
regexis024::tai_t tdiff_k;
std::pair<regexis024::tai_t, regexis024::tai_t> i4_k;
std::pair<regexis024::tai_t, regexis024::tai_t> i6_k;
std::pair<regexis024::tai_t, regexis024::tai_t> ip_k;
regexis024::tai_t skip_k;
#define reg_int4 "#4(0|[1-9][0-9]!r{0;2})"
#define reg_int6 "#6(0|[1-9a-fA-F][0-9a-fA-F]!r{0;3})"
#define reg_6RHT_rep(mt) "(:|(:" reg_int6 ")!r{1;" #mt "})"
#define reg_6LFT_rep(ea) "(" reg_int6 ":)!r{" #ea "}"
#define reg_6zs "#s:0;"
#define reg_intp "#p(0|[1-9][0-9]!r{0;4})"
#define reg_addr_in4 "(#t:1;" reg_int4 "." reg_int4 "." reg_int4 "." reg_int4 ":" reg_intp ")"
#define reg_addr_in6_core "(" reg_6LFT_rep(7) reg_int6 "|" ":" reg_6zs reg_6RHT_rep(6) "|" \
reg_6LFT_rep(1) reg_6zs reg_6RHT_rep(5) "|" \
reg_6LFT_rep(2) reg_6zs reg_6RHT_rep(4) "|" \
reg_6LFT_rep(3) reg_6zs reg_6RHT_rep(3) "|" \
reg_6LFT_rep(4) reg_6zs reg_6RHT_rep(2) "|" \
reg_6LFT_rep(5) reg_6zs reg_6RHT_rep(1) "|" \
reg_6LFT_rep(6) reg_6zs ":" ")"
#define reg_addr_in6 "(#t:2;\\[" reg_addr_in6_core "\\]:" reg_intp ")"
SocketAddressParser_Inner(): prg(compile_regexp(
reg_addr_in4 "|" reg_addr_in6
)), vm(prg.prg.size(), prg.prg.data(), UINT64_MAX, UINT16_MAX, UINT32_MAX, UINT32_MAX, UINT64_MAX)
{
ASSERT_pl(vm.initialize() == 0);
tdiff_k = prg.vars.at("t").colarr_first;
skip_k = prg.vars.at("s").colarr_first;
auto obtain_range = [&](const std::string& name) -> std::pair<regexis024::tai_t, regexis024::tai_t> {
const regexis024::TrackingVariableInfo& vi = prg.vars.at(name);
assert(vi.colarr_first > 0 && vi.colarr_second > 0);
return {(regexis024::tai_t)vi.colarr_first, (regexis024::tai_t)vi.colarr_second};
};
i4_k = obtain_range("4");
i6_k = obtain_range("6");
ip_k = obtain_range("p");
}
};
SocketAddressParser::SocketAddressParser() {
opaque = new SocketAddressParser_Inner();
}
SocketAddressParser::~SocketAddressParser() {
delete (SocketAddressParser_Inner*)opaque;
}
int parse_socket_address(const std::string& addr, SocketAddress& res, SocketAddressParser& pdata) {
#define reveal (*(SocketAddressParser_Inner*)pdata.opaque)
reveal.vm.wipeToInit();
ASSERT_pl(reveal.vm.addNewMatchingThread() == 0);
int ret;
for (size_t i = 0; i < addr.size(); i++) {
ret = reveal.vm.feedCharacter((uint64_t)addr[i], 1);
// if (!reveal.vm.haveSurvivors()) {
// printf("DEBUG: died on %s\n", addr.substr(0, i + 1).c_str());
// break;
// }
}
if (reveal.vm.isMatched()) {
std::vector<regexis024::CAEvent> ca = reveal.vm.getMatchedThreadCABranchReverse();
std::reverse(ca.begin(), ca.end());
size_t ci = 0;
#define curKey() ca[ci].key
#define curValue() ca[ci].value
auto extractRange = [&](std::pair<regexis024::tai_t, regexis024::tai_t> rk) -> std::string {
assert(ca[ci].key == rk.first && ca[ci + 1].key == rk.second);
size_t oci = ci;
ci += 2;
return getSubstring(addr, ca[oci].value, ca[oci + 1].value);
};
assert(curKey() == reveal.tdiff_k);
if (curValue() == 1) {
ci++;
uint32_t res_addr = 0;
for (int i = 0; i < 4; i++) {
std::string h = extractRange(reveal.i4_k);
uint32_t p = std::stoul(h);
if (p > 255)
return -1;
res_addr = ((res_addr << 8) | p);
}
uint32_t res_port = std::stoul(extractRange(reveal.ip_k));
if (res_port > 65535)
return -1;
res.v.gen.sa_family = AF_INET;
res.v.sin.sin_port = htons(res_port); // host to network short
res.v.sin.sin_addr.s_addr = htonl(res_addr); // host to network long
res.addrLen = sizeof(sockaddr_in);
} else if (curValue() == 2){
ci++;
int skipped = 8;
for (const regexis024::CAEvent& ev: ca) {
if (ev.key == reveal.i6_k.first)
skipped--;
}
assert(skipped == 0 || skipped >= 2);
uint16_t res_u6_addr16[8];
size_t bi = 0;
std::string h;
while (bi < 8) {
if (curKey() == reveal.i6_k.first) {
h = extractRange(reveal.i6_k);
assert(h.size() <= 4);
uint32_t v = 0;
for (char ch: h) {
v <<= 4;
if ('0' <= ch && ch <= '9') {
v |= (uint32_t)(ch - '0');
} else if ('a' <= ch && ch <= 'z') {
v |= (uint32_t)(ch - 'a' + 10);
} else if ('A' <= ch && ch <= 'Z') {
v |= (uint32_t)(ch - 'A' + 10);
} else
assert(false);
}
assert(v <= UINT16_MAX);
res_u6_addr16[bi++] = (uint16_t)v;
} else if (curKey() == reveal.skip_k) {
ci++;
for (int j = 0; j < skipped; j++)
res_u6_addr16[bi++] = 0;
} else
assert(false);
}
assert(bi == 8);
uint32_t res_port = std::stoul(extractRange(reveal.ip_k));
if (res_port > 65535)
return -1;
res.v.gen.sa_family = AF_INET6;
res.v.sin6.sin6_port = htons(res_port);
for (int i = 0; i < 8; i++)
res.v.sin6.sin6_addr.__in6_u.__u6_addr16[i] = htons(res_u6_addr16[i]);
res.addrLen = sizeof(sockaddr_in6);
} else
assert(false);
assert(ci == ca.size());
return 0;
}
const std::string& up = "unix:";
if (beginsWith(addr, up)) {
std::string path = addr.substr(up.size());
if (path.empty())
return -1;
if (path.back() == '/')
return -1;
for (char ch: path)
if (ch == 0)
return -1;
res.v.gen.sa_family = AF_UNIX;
if (sizeof(res.v.sun.sun_path) < path.size())
THROW("path is too big");
memcpy(res.v.sun.sun_path, path.c_str(), path.size());
res.addrLen = offsetof(sockaddr_un, sun_path) + path.size();
return 0;
}
return -1;
}
std::string short2hex(uint16_t v) {
if (v == 0)
return "0";
std::string result;
while (v) {
result += (char)((v & 0xf) > 9 ? (v & 0xf) - 10 + 'a' : (v & 0xf) + '0');
v >>= 4;
}
std::reverse(result.begin(), result.end());
return result;
}
std::string stringify_socket_address(const SocketAddress &addr) {
if (addr.v.gen.sa_family == AF_INET) {
char buf[22];
uint32_t IP = ntohl(addr.v.sin.sin_addr.s_addr);
uint16_t port = ntohs(addr.v.sin.sin_port);
snprintf(buf, 22, "%u.%u.%u.%u:%hu", (IP >> 24) & 0xff, (IP >> 16) & 0xff, (IP >> 8) & 0xff,
(IP >> 0) & 0xff, port);
return buf;
} else if (addr.v.gen.sa_family == AF_INET6) {
uint16_t IP[8];
for (int i = 0; i < 8; i++)
IP[i] = ntohs(addr.v.sin6.sin6_addr.__in6_u.__u6_addr16[i]);
uint16_t port = ntohs(addr.v.sin6.sin6_port);
int largest_sz = 0;
int largest_start = 0;
int cur_sz = 0;
for (int i = 0; i < 8; i++) {
if (IP[i] == 0) {
cur_sz++;
if (largest_sz < cur_sz) {
largest_sz = cur_sz;
largest_start = i + 1 - cur_sz;
}
} else {
cur_sz = 0;
}
}
std::string core;
for (int i = 0; i < 8;) {
if (largest_sz >= 2 && largest_start == i) {
i += largest_sz;
if (i == 8)
core += "::";
else
core += ":";
} else {
if (i > 0)
core += ":";
core += short2hex(IP[i]);
i++;
}
}
assert(core.size() <= 39);
char buf[48];
snprintf(buf, 48, "[%s]:%hu", core.c_str(), port);
return buf;
} else if (addr.v.gen.sa_family == AF_UNIX) {
assert(addr.addrLen > offsetof(sockaddr_un, sun_path));
size_t path_len = addr.addrLen - offsetof(sockaddr_un, sun_path);
assert(path_len <= sizeof(addr.v.sun.sun_path));
std::string path(path_len, ')');
memcpy(path.data(), addr.v.sun.sun_path, path_len);
return "unix:" + path;
} else
return "Socket address of unknown domain";
}
void bind_to_socket_address(int sockfd, const SocketAddress &addr) {
sa_family_t f = addr.v.gen.sa_family;
if (f == AF_INET || f == AF_INET6 || f == AF_UNIX) {
int ret = bind(sockfd, &addr.v.gen, addr.addrLen);
ASSERT_on_iret(ret, "binding socket");
} else
THROW("binding socket to address of unsupported domain");
}
void get_peer_name_as_socket_address(int sockfd, SocketAddress &res) {
socklen_t willbecome = sizeof(res.v);
int ret = getpeername(sockfd, &res.v.gen, &willbecome);
ASSERT_on_iret(ret, "getpeername");
assert(willbecome <= sizeof(res.v));
res.addrLen = willbecome;
}
void connect_to_socket_address(int sockfd, const SocketAddress& targ) {
int ret = connect(sockfd, &targ.v.gen, targ.addrLen);
ASSERT_on_iret(ret, "connect socket to addr");
}
}

View File

@ -1,53 +0,0 @@
#ifndef ENGINE_ENGINE_NUMBER_9_SOCKET_ADDRESS_H
#define ENGINE_ENGINE_NUMBER_9_SOCKET_ADDRESS_H
#if defined(SOLARIS)
#include <netinet/in.h>
#endif
#include <netdb.h>
#include <arpa/inet.h>
#if defined(BSD)
#include <sys/socket.h>
#include <netinet/in.h>
#endif
#include <sys/un.h>
#include <string>
namespace een9 {
/* Right now een9 supports only IP4, IP6, unix domain. System service querying is not implemented yet *
* */
struct SocketAddress {
union {
sockaddr gen;
sockaddr_in sin;
sockaddr_in6 sin6;
sockaddr_un sun;
} v;
size_t addrLen = sizeof(sockaddr_in);
};
/* Not thread-safe. Use only from one thread (at a time) */
struct SocketAddressParser {
void* opaque = NULL;
SocketAddressParser();
SocketAddressParser(const SocketAddressParser&) = delete;
SocketAddressParser& operator=(const SocketAddressParser&) = delete;
~SocketAddressParser();
};
int parse_socket_address(const std::string& addr, SocketAddress& res, SocketAddressParser& pdata);
std::string stringify_socket_address(const SocketAddress& addr);
/* Throws ServerError on error */
void bind_to_socket_address(int sockfd, const SocketAddress& addr);
/* Throws ServerError on error */
void get_peer_name_as_socket_address(int sockfd, SocketAddress& res);
void connect_to_socket_address(int sockfd, const SocketAddress& targ);
}
#endif

View File

@ -1,6 +0,0 @@
{% ELDEF main JSON userprofile %}
AAA
--> {% WRITE userprofile.name %}
AAA
{% ENDELDEF %}

View File

@ -1,35 +0,0 @@
#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;
}

View File

@ -1,28 +0,0 @@
#include <jsonincpp/string_representation.h>
#include <new_york_transit_line/templater.h>
#include <new_york_transit_line/core.h>
#include <engine_engine_number_9/os_utils.h>
int main(int argc, char** argv) {
if (argc < 3) {
fprintf(stderr, "Usage: test assets_dir config_file");
exit(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}});
templater.update();
json::JSON userprofile;
userprofile["uid"].asInteger() = json::Integer(0l);
userprofile["name"].asString() = "radasdasdasdadsdasd";
userprofile["nickname"].asString() = "root";
userprofile["bio"].asString() = "Your mother";
json::JSON errors;
errors = json::JSON(json::array);
std::string answer2 = templater.render("err-404", {});
printf("%s\n<a><f><t><e><r><><l><f>\n", answer2.c_str());
return 0;
}

View File

@ -1,73 +0,0 @@
#include <engine_engine_number_9/socket_address.h>
#include <engine_engine_number_9/baza.h>
using namespace een9;
void test(const std::string& test, bool is_correct, SocketAddressParser& parser) {
#define fup printf("Test failed\n"); fflush(stdout); abort(); return;
SocketAddress addr;
int ret = parse_socket_address(test, addr, parser);
if ((ret == 0) != is_correct) {
fup
}
if (is_correct) {
std::string back = stringify_socket_address(addr);
if (make_uppercase(test) != make_uppercase(back)){
fup
}
}
printf("Test passed\n");
}
void test_dcs(const std::string& test, const std::string& need, SocketAddressParser& parser) {
SocketAddress addr;
int ret = parse_socket_address(test, addr, parser);
if (ret != 0) {
fup
}
std::string right = stringify_socket_address(addr);
if (right != need) {
fup
}
printf("Test passed\n");
}
int main() {
SocketAddressParser parser;
test("127:0:0:1:1026", false, parser);
test("[12::12:0:0:0]:600", true, parser);
test("[12::12:0:FFFF:0]:600", true, parser);
test("[12::11:1]:600", true, parser);
test("[::a::]:600", false, parser);
test("[FFd:1:1:1:1:FFd:1:f]:65535", true, parser);
test("[f:f:f:f:f:0:1:f]:65535", true, parser);
test("[1:1:1:1:1:0:1:0]:11212", true, parser);
test("[1:1:1:1:1:1:1:1]:1", true, parser);
test("[1:1:1:1:1:0:1:0]:65536", false, parser);
test("[1:1:1:1:1:0:1:0]:65535", true, parser);
test("[1:H:1:1:1:FFd:1:f]:65535", false, parser);
test("[::]:1", true, parser);
test("12.11.11.123:312", true, parser);
test("0.1.111.123:31212", true, parser);
test("0.1.111.123:65536", false, parser);
test("0.1.111.123:65535", true, parser);
test("0.1.111.255:65535", true, parser);
test("255.0.255.255:65535", true, parser);
test("255.0.256.0:65535", false, parser);
test("255.0.1000.0:65535", false, parser);
test("255.0.01.0:65535", false, parser);
test("255.0.1.0:605535", false, parser);
test("2.0.1.0:05535", false, parser);
test("255.0.1.0::65535", false, parser);
test(".255.0.1.0::65535", false, parser);
test("..0.1.0::65535", false, parser);
test("[FFFF0::]:0", false, parser);
test("[0::01:]:0", false, parser);
test("[0:fa:ffff::]:0", true, parser);
test("[::a:a:a:a:a:a:b]:10", false, parser);
test_dcs("[0:0:0:0:0:0:0:0]:413", "[::]:413", parser);
test_dcs("[::0:0:0:0]:413", "[::]:413", parser);
test_dcs("[::0:0:0:0:0:0]:413", "[::]:413", parser);
test_dcs("[::a:a:0:0:0:0]:413", "[0:0:a:a::]:413", parser);
}

View File

@ -7,13 +7,8 @@ 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());
assert(p.second.when_element); const Element& el = p.second;
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;

View File

@ -55,16 +55,13 @@ namespace nytl {
} }
char skip(ParsingContext& ctx) { char skip(ParsingContext& ctx) {
if (ctx.pos >= ctx.text.size()) ASSERT(ctx.pos < ctx.text.size(), "Unexpected EOF");
THROW("Unexpected EOF");
return advance(ctx); return advance(ctx);
} }
void skip(ParsingContext& ctx, char ch) { void skip(ParsingContext& ctx, char ch) {
if (ctx.pos >= ctx.text.size()) ASSERT(ctx.pos < ctx.text.size(), "Unexpected EOF");
THROW("Unexpected EOF"); ASSERT(ctx.text[ctx.pos] == ch, "Unexpected character");
if (ctx.text[ctx.pos] != ch)
THROW("Unexpected character");
advance(ctx); advance(ctx);
} }
@ -150,41 +147,16 @@ namespace nytl {
return concatenateLines(lines); return concatenateLines(lines);
} }
Element& add_hidden_element(const std::string& new_el_name, global_elem_set_t& result) { void parse_bare_file(const std::string& filename, const std::string& content,
if (result.count(new_el_name) != 0) global_elem_set_t& result)
THROW("Repated element " + new_el_name); {
TemplaterRegPref& rp = result[new_el_name]; ASSERT(result.count(filename) == 0, "Repeated element " + filename);
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);
} }
@ -198,17 +170,15 @@ 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);
if (nm.empty()) ASSERT(!nm.empty(), "Type specification expected");
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;
} }
if (nm != "EL") ASSERT(nm == "EL", "Type of argument variable is either JSON or EL(...signature)")
THROW("Type of argument variable is either JSON or EL(...signature)");
skip(ctx, '('); skip(ctx, '(');
result.asArray(); result = json::JSON(json::array);
assert(result.isArray()); assert(result.isArray());
} }
skipWhitespace(ctx); skipWhitespace(ctx);
@ -247,21 +217,19 @@ 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);
if (first.empty()) ASSERT(!first.empty(), "Expression should start with 'root' name of global package or local variable");
THROW("Expression should start with 'root' name of global package or local variable"); ASSERT(first != "_", "_ ??? ARE YOU KIDDING???");
if (first == "_")
THROW("Expression root can't be _");
if (local_var_names.count(first) == 1) { if (local_var_names.count(first) == 1) {
result["V"].asInteger() = json::Integer((int64_t)local_var_names.at(first)); result["V"] = json::JSON(json::Integer((int64_t)local_var_names.at(first)));
} else { } else {
result["V"].asString() = first; result["V"] = json::JSON(first);
} }
result["C"].asArray(); result["C"] = json::JSON(json::array);
} else { } else {
skipWhitespace(ctx); skipWhitespace(ctx);
skip(ctx, ']'); skip(ctx, ']');
} }
std::vector<json::JSON>& chain = result["C"].asArray(); std::vector<json::JSON>& chain = result["C"].g().asArray();
while (true) { while (true) {
if (peep(ctx) == '.') { if (peep(ctx) == '.') {
skip(ctx, '.'); skip(ctx, '.');
@ -275,8 +243,7 @@ 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);
if (v >= INT64_MAX) ASSERT(v < INT64_MAX, "Index is too big");
THROW("Index is too big");
chain.back() = json::JSON((int64_t)v); chain.back() = json::JSON((int64_t)v);
continue; continue;
} }
@ -385,8 +352,7 @@ 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);
if (V1.empty()) ASSERT(!V1.empty(), "Expected variable name");
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;
@ -398,23 +364,21 @@ namespace nytl {
skipWhitespace(ctx); skipWhitespace(ctx);
} }
op = make_uppercase(readName(ctx)); op = make_uppercase(readName(ctx));
if (op != "IN") ASSERT(op == "IN", "Expected 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 = add_hidden_element(P.internal_element, elem_ns); Element& newborn = elem_ns[P.internal_element];
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 != "_") {
if (local_var_names_of_nxt.count(V1) != 0) ASSERT(local_var_names_of_nxt.count(V1) == 0, "Repeated local variable");
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 != "_") {
if (local_var_names_of_nxt.count(V2) != 0) ASSERT(local_var_names_of_nxt.count(V2) == 0, "Repeated local variable");
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;
@ -431,16 +395,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);
if (Vn.empty() || Vn == "_") ASSERT(!Vn.empty(), "Expected variable name");
THROW("REF: expected variable name"); ASSERT(Vn != "_", "Are you kidding???");
skipWhitespace(ctx); skipWhitespace(ctx);
op = make_uppercase(readName(ctx)); op = make_uppercase(readName(ctx));
if (op != "AS") ASSERT(op == "AS", "Expected 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 = add_hidden_element(P.internal_element, elem_ns); Element& newborn = elem_ns[P.internal_element];
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);
@ -449,7 +413,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" || op == "P") { if (op == "PUT") {
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;
@ -475,11 +439,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" || op == "W") { if (op == "WRITE") {
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" || op == "RI") { if (op == "ROUGHINSERT") {
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;;
} }
@ -503,15 +467,13 @@ namespace nytl {
} }
}; };
if (op == "ENDELDEF") { if (op == "ENDELDEF") {
if (myself != gone_for_nothing) ASSERT(myself == gone_for_nothing, "Unexpected end of element");
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") {
if (myself != gone_for_for) ASSERT(myself == gone_for_for, "Unexpected end of for cycle");
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
@ -529,8 +491,7 @@ namespace nytl {
return NULL; return NULL;
} }
if (op == "ENDREF") { if (op == "ENDREF") {
if (myself != gone_for_ref) assert(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;
@ -564,14 +525,13 @@ namespace nytl {
if (peep(ctx) == EOFVAL) if (peep(ctx) == EOFVAL)
break; break;
skip_magic_block_start(ctx, syntax); skip_magic_block_start(ctx, syntax);
if (make_uppercase(readName(ctx)) != "ELDEF") ASSERT(make_uppercase(readName(ctx)) == "ELDEF", "Expected ELDEF");
THROW("Expected ELDEF");
skipWhitespace(ctx); skipWhitespace(ctx);
std::string elname_postfix = readName(ctx); std::string elname_postfix = readName(ctx);
if (elname_postfix == "_") ASSERT(elname_postfix != "_", "please don't");
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;
Element& newborn = add_new_element(fullname, result); ASSERT(result.count(fullname) == 0, "Element " + fullname + " has been already defined");
Element& newborn = result[fullname];
arg_name_list_t arglist; arg_name_list_t arglist;
while (true) { while (true) {
skipWhitespace(ctx); skipWhitespace(ctx);
@ -580,11 +540,9 @@ 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);
if (argname.empty()) ASSERT(!argname.empty(), "Expected argument name");
THROW("Expected argument name");
if (argname != "_") { if (argname != "_") {
if (arglist.count(argname) != 0) ASSERT(arglist.count(argname) == 0, "Repeated argument (" + argname + ")");
THROW("Repeated argument (" + argname + ")");
size_t k = arglist.size(); size_t k = arglist.size();
arglist[argname] = k; arglist[argname] = k;
} }

View File

@ -23,64 +23,58 @@ namespace nytl {
result(result) { result(result) {
} }
void descend(const json::JSON& what, const global_elem_set_t& global_elems) { void descend(const json::JSON& what) {
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();
if (!(ind_w > 0 && ind_w < arr_p.size())) ASSERT(ind_w > 0 && ind_w < arr_p.size(), "Expression \"array[integer]\" caused out-of-bound situation");
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();
if (dict_p.count(key_w) != 1) ASSERT(dict_p.count(key_w) == 1, "No such key exception");
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 {
if (!what.isString()) ASSERT(what.isString(), "Expression \"element[X]\" allowed only if X is string (json object)");
THROW("Expression \"element[X]\" allowed only if X is string (json object)"); if (what.asString().empty())
if (!isUname(what.asString())) return;
THROW("Expression \"element[str]\" has incorrect str (" + what.asString() + ")"); if (!is_uname_dotted_sequence(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) {
if (!temp_ret.is_json) ASSERT(temp_ret.is_json, "Expression \"X[ element ]\" is not allowed");
THROW("Expression \"X[ element ]\" is not allowed");
assert(temp_ret.JSON_subval); assert(temp_ret.JSON_subval);
descend(*(temp_ret.JSON_subval), global_elems); descend(*(temp_ret.JSON_subval));
} else { } else {
assert(expr.isDictionary()); assert(expr.isDictionary());
const json::JSON& val = expr["V"]; const json::JSON& val = expr["V"].g();
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"].asString(); std::string cur_el_name_str = expr["V"].g().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"].asArray(); const std::vector<json::JSON>& chain = expr["C"].g().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, global_elems); descend(t);
} }
} }
}; };
@ -90,8 +84,6 @@ 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;
@ -120,9 +112,15 @@ namespace nytl {
for (size_t i = 0; i < m; i++) { for (size_t i = 0; i < m; i++) {
result += text[i]; result += text[i];
if (text[i] == '\n') { if (text[i] == '\n') {
// newlined_somewhere = true;
result.resize(result.size() + wsp_before_newlines, ' '); result.resize(result.size() + wsp_before_newlines, ' ');
cur_line_width = wsp_before_newlines; cur_line_width = wsp_before_newlines;
} else { } else {
// if (cur_line_width == 0 && newlined_somewhere) {
// result.resize(result.size() + wsp_before_newlines, ' ');
// cur_line_width = wsp_before_newlines;
// }
cur_line_width++; cur_line_width++;
} }
} }
@ -219,9 +217,8 @@ 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)
if ((elem_ns.count(name) != 1) || (!elem_ns.at(name).is_element)) ASSERT(elem_ns.count(name) == 1, "No such element");
THROW("Can't render. No such element (" + name + ")"); const Element& el = elem_ns.at(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 */
@ -230,17 +227,14 @@ 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) {
if (!passed_args[i].is_json) ASSERT(passed_args[i].is_json, "Expected json element argument, got element");
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());
if (passed_args[i].is_json) ASSERT(!passed_args[i].is_json, "Expected element element arguemnt, got json");
THROW("Expected element element arguemnt, got json"); ASSERT(elem_ns.count(passed_args[i].EL_name), "No such element, can't compare signatures of argument value");
const std::string& passed_el_as_arg = passed_args[i].EL_name; const Element& arg_element = elem_ns.at(passed_args[i].EL_name);
if ((elem_ns.count(passed_el_as_arg) != 1) || !elem_ns.at(passed_el_as_arg).is_element) // ASSERT(passed_args);
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");
} }
@ -252,11 +246,7 @@ namespace nytl {
assert(passed_args.size() == 1); assert(passed_args.size() == 1);
const json::JSON* X = passed_args[0].JSON_subval; const json::JSON* X = passed_args[0].JSON_subval;
assert(X); assert(X);
if (name == "jsinsert") { if (name == "jesc") {
std::string pure_json = json::generate_str(*X, json::print_pretty);
rstrip(pure_json);
append(pure_json, result);
} else if (name == "jesc") {
std::string escaped_json = escape(json::generate_str(*X, json::print_pretty)); std::string escaped_json = escape(json::generate_str(*X, json::print_pretty));
rstrip(escaped_json); rstrip(escaped_json);
append(escaped_json, result); append(escaped_json, result);

View File

@ -100,7 +100,7 @@ namespace nytl {
while ((ret = (int)read(fd, buf, 2048)) > 0) { while ((ret = (int)read(fd, buf, 2048)) > 0) {
size_t oldN = result.size(); size_t oldN = result.size();
result.resize(oldN + ret); result.resize(oldN + ret);
memcpy(result.data() + oldN, buf, ret); memcpy((void*)&result.c_str()[oldN], buf, ret);
} }
if (ret < 0) { if (ret < 0) {
close(fd); close(fd);
@ -109,17 +109,15 @@ 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[""] = TemplaterRegPref{0, NULL}; elements = {
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);
@ -133,8 +131,7 @@ 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 {
if (!is_uname_dotted_sequence(element)) ASSERT(is_uname_dotted_sequence(element), "Incorrect entry element name");
THROW("Incorrect entry element name");
return rendering_core(element, arguments, elements, settings.escape); return rendering_core(element, arguments, elements, settings.escape);
} }
} }

View File

@ -6,8 +6,6 @@
#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;
@ -63,12 +61,7 @@ namespace nytl {
std::function<std::string(std::string)> escape = html_case_espace_string; std::function<std::string(std::string)> escape = html_case_espace_string;
}; };
struct TemplaterRegPref { typedef std::map<std::string, Element> global_elem_set_t;
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;

View File

@ -0,0 +1,4 @@
AAA
{% FOR _:val IN cba %}
TUTUTUTUTUTUTUTUN {% PUT jesccomp val %}
{% ENDFOR %}

View File

@ -0,0 +1,8 @@
{% ELDEF main JSON cba %}
AAA
{% FOR val IN cba.arr %}
--> {% WRITE val %}
{% ENDFOR %}
AAA
{% ENDELDEF %}

9
src/web_chat/actions.h Normal file
View File

@ -0,0 +1,9 @@
#ifndef IU9_CA_WEB_CHAT_ACTIONS_H
#define IU9_CA_WEB_CHAT_ACTIONS_H
#include <jsonincpp/jsonobj.h>
void run_website(const json::JSON& config);
void initialize_website(const json::JSON& config, const std::string& root_pw);
#endif

14
src/web_chat/find_db.cpp Normal file
View File

@ -0,0 +1,14 @@
#include "find_db.h"
int find_db_sqlite_file_path(const json::JSON& config, std::string& res_path) {
const json::JSON& type = config["database"]["type"].g();
if (!type.isString() && type.asString() == "sqlite3")
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;
}

View File

@ -3,8 +3,6 @@
#include <jsonincpp/jsonobj.h> #include <jsonincpp/jsonobj.h>
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);
}
#endif #endif

View File

@ -0,0 +1,59 @@
#include "actions.h"
#include <engine_engine_number_9/baza_throw.h>
#include "str_fields_check.h"
#include <sqlite3.h>
#include <engine_engine_number_9/os_utils.h>
#include <find_db.h>
void sqlite_single_statement(sqlite3* db_hand, const std::string& req_statement) {
sqlite3_stmt* stmt_obj = NULL;
int ret = sqlite3_prepare16_v2(db_hand, req_statement.c_str(), -1, &stmt_obj, NULL);
een9_ASSERT(ret == 0, "Can't compile request expression");
struct Guard1{sqlite3_stmt*& r; ~Guard1(){if (sqlite3_finalize(r) != 0) {abort();}}} guard1{stmt_obj};
while (true) {
ret = sqlite3_step(stmt_obj);
if (ret == SQLITE_DONE)
break;
if (ret != SQLITE_ROW) {
printf("sqlite_row error!!!\n");
printf("%s\n", sqlite3_errmsg(db_hand));
break;
}
int cc = sqlite3_column_count(stmt_obj);
std::vector<int> types(cc);
for (int i = 0; i < cc; i++) {
types[i] = sqlite3_column_type(stmt_obj, i);
}
printf("Column: |");
for (int i = 0; i < cc; i++) {
switch (types[i]) {
#define ccase(tname) case SQLITE_ ## tname: printf(" " #tname " |"); break;
ccase(INTEGER)
ccase(FLOAT)
ccase(BLOB)
ccase(NULL)
case SQLITE3_TEXT:
printf(" TEXT |"); break;
}
}
printf("\n");
}
printf("Request steps are done\n");
}
void initialize_website(const json::JSON& config, const std::string& root_pw) {
printf("Initialization...\n");
een9_ASSERT(check_password(root_pw), "Bad root password");
std::string db_path;
int ret;
ret = find_db_sqlite_file_path(config, db_path);
een9_ASSERT(ret == 0, "Invalid settings[\"database\"] field");
een9_ASSERT(!een9::isRegularFile(db_path), "Database file exists prior to initialization. "
"Can't preceed withut harming existing data");
// sqlite3* db_hand = NULL;
// ret = sqlite3_open(db_path.c_str(), &db_hand);
// een9_ASSERT(ret == 0, "Can't open database");
// struct Guard1{sqlite3*& dhp; ~Guard1(){if (sqlite3_close(dhp) != 0) {abort();}}} guard1{db_hand};
// sqlite_single_statement(db_hand, "CREATE TABLE tb(a INT, b INT);");
// todo: actually write something
}

View File

@ -1,85 +0,0 @@
#include <engine_engine_number_9/admin_control.h>
#include <engine_engine_number_9/socket_address.h>
#include <engine_engine_number_9/baza_throw.h>
#include <assert.h>
#include <engine_engine_number_9/os_utils.h>
/* This so called 'een9::admin-control' protocol is very simple:
* Admin sends request to server:
* <Magic constant string> <8 byte field: size of body> <body (string of any characters)>
* Server reads it to the end and sents response to admin:
* <Magic constant string> <8 byte field: size of body> <body (string of any characters)>
* More can be found in src/http_server/engine_engine_number_9/admin_control.cpp
*/
void send_request_msg(int fd, const std::string& request_msg) {
std::string str = een9::generate_admin_control_request(request_msg);
size_t N = str.size(), i = 0;
while (i < N) {
int written = (int)send(fd, &str[i], std::min(2048lu, N - i), MSG_NOSIGNAL);
een9_ASSERT_on_iret(written, "sending");
een9_ASSERT_pl(written > 0);
i += written;
}
}
std::string receive_response_msg(int fd) {
een9::AdminControlResponseRCtx pctx;
int ret;
char buf[2048];
assert(pctx.status == 0);
while ((ret = (int)recv(fd, buf, 2048, 0)) > 0) {
for (size_t i = 0; i < ret; i++) {
if (pctx.feedCharacter(buf[i]) != 0)
break;
}
if (pctx.status != 0)
break;
}
een9_ASSERT(pctx.status == 1, "Received incorrect response");
een9_ASSERT_on_iret(ret, "recv");
return pctx.body;
}
void usage(char* za) {
printf("%s <address of servers admin cmd listener> <message> [<other parts of message> ...]\n", za);
exit(1);
}
void funny_log_print(const char* str) {
printf("===\\\\\n -=| %s\n===//\n", str);
}
int main(int argc, char** argv) {
if (argc < 1)
return 134;
if (argc < 3) {
usage(argv[0]);
}
try {
std::string conn_addr_targ = argv[1];
std::string msg;
for (int i = 2; i < argc; i++) {
if (!msg.empty())
msg += '\n';
msg += argv[i];
}
int ret;
een9::SocketAddressParser sap;
een9::SocketAddress addr;
ret = een9::parse_socket_address(conn_addr_targ, addr, sap);
een9_ASSERT(ret == 0, "Incorrect address");
int sock = socket(addr.v.gen.sa_family, SOCK_STREAM, 0);
een9_ASSERT_on_iret(sock, "creating socket to target server");
een9::UniqueFdWrapper sockGuard(sock);
een9::connect_to_socket_address(sock, addr);
funny_log_print("Ready to send request");
send_request_msg(sock, msg);
funny_log_print("Admin-cmd request has been sent");
std::string answer = receive_response_msg(sock);
funny_log_print("Admin-cmd response has been read");
printf("Output:\n%s", answer.c_str());
} catch (const std::exception& e) {
printf("%s\n", e.what());
}
}

View File

@ -1,11 +0,0 @@
#ifndef IU9_CA_WEB_CHAT_ACTIONS_H
#define IU9_CA_WEB_CHAT_ACTIONS_H
#include <jsonincpp/jsonobj.h>
namespace iu9cawebchat {
void run_website(const json::JSON& config);
void initialize_website(const json::JSON& config, const std::string& root_pw);
}
#endif

View File

@ -1,81 +0,0 @@
#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";
}
}

View File

@ -1,75 +0,0 @@
#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;
}
}

View File

@ -1,30 +0,0 @@
#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;
}
}

View File

@ -1,33 +0,0 @@
#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;
}
}

View File

@ -1,15 +0,0 @@
#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;
}
}

View File

@ -1,21 +0,0 @@
#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;
}
}

View File

@ -1,50 +0,0 @@
#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;
}
}

View File

@ -1,91 +0,0 @@
#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);
}
}

View File

@ -1,81 +0,0 @@
#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

View File

@ -1,198 +0,0 @@
#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;
}
}

View File

@ -1,246 +0,0 @@
#include "server_data_interact.h"
#include <engine_engine_number_9/baza_throw.h>
#include <engine_engine_number_9/http_structures/cookies.h>
#include "../str_fields.h"
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) {
if (role == user_chat_role_admin)
return "admin";
if (role == user_chat_role_regular)
return "regular";
if (role == user_chat_role_read_only)
return "read-only";
return "not-a-member";
}
int64_t find_user_by_credentials (SqliteConnection& conn, const std::string& nickname, const std::string& password) {
SqliteStatement sql_req(conn,
"SELECT `id` FROM `user` WHERE `nickname` = ?1 AND `password` = ?2",
{}, {{1, nickname}, {2, password}});
fsql_integer_or_null id_col;
int status = sqlite_stmt_step(sql_req, {{0, &id_col}}, {});
if (status == SQLITE_ROW) {
een9_ASSERT_pl(id_col.exist & id_col.value >= 0);
return id_col.value;
}
return -1;
}
std::string get_user_name (SqliteConnection& conn, int64_t uid) {
een9_ASSERT(uid >= 0, "Are you crazy?");
SqliteStatement sql_req(conn,
"SELECT `name` FROM `user` WHERE `id` = ?1",
{{1, uid}}, {});
fsql_text8_or_null name_col;
int status = sqlite_stmt_step(sql_req, {}, {{0, &name_col}});
if (status == SQLITE_ROW) {
een9_ASSERT_pl(name_col.exist);
return name_col.value;
}
een9_THROW("No such user");
}
RowUser_Content lookup_user_content(SqliteConnection &conn, int64_t uid) {
een9_ASSERT(uid >= 0, "Are you crazy?");
SqliteStatement sql_req(conn,
"SELECT `nickname`, `name` FROM `user` WHERE `id` = ?1",
{{1, uid}}, {});
fsql_text8_or_null nickname_col;
fsql_text8_or_null name_col;
int status = sqlite_stmt_step(sql_req, {}, {{0, &nickname_col}, {1, &name_col}});
if (status == SQLITE_ROW) {
return {uid, std::move(nickname_col.value), std::move(name_col.value)};
}
een9_THROW("No such user");
}
RowUser_Content lookup_user_content_by_nickname(SqliteConnection& conn, const std::string& nickname) {
SqliteStatement sql_req(conn,
"SELECT `id`, `name` FROM `user` WHERE `nickname` = ?1",
{}, {{1, nickname}});
fsql_integer_or_null id_col;
fsql_text8_or_null name_col;
int status = sqlite_stmt_step(sql_req, {{0, &id_col}}, {{1, &name_col}});
if (status == SQLITE_ROW) {
return {id_col.value, nickname, std::move(name_col.value)};
}
een9_THROW("No such user");
}
RowChat_Content lookup_chat_content(SqliteConnection &conn, int64_t chatId) {
een9_ASSERT(chatId >= 0, "Are you crazy?");
SqliteStatement sql_req(conn,
"SELECT `nickname`, `name`, `lastMsgId` FROM `chat` WHERE `id` = ?1",
{{1, chatId}}, {});
fsql_text8_or_null nickname_col;
fsql_text8_or_null name_col;
fsql_integer_or_null last_msg_id_col;
int status = sqlite_stmt_step(sql_req, {{2, &last_msg_id_col}}, {{0, &nickname_col}, {1, &name_col}});
if (status == SQLITE_ROW) {
return {chatId, std::move(nickname_col.value), std::move(name_col.value),
last_msg_id_col.exist ? last_msg_id_col.value : -1};
}
een9_THROW("No such chat");
}
RowChat_Content lookup_chat_content_by_nickname(SqliteConnection &conn, const std::string& nickname) {
SqliteStatement sql_req(conn,
"SELECT `id`, `name`, `lastMsgId` FROM `chat` WHERE `nickname` = ?1",
{}, {{1, nickname}});
fsql_integer_or_null id_col;
fsql_text8_or_null name_col;
fsql_integer_or_null last_msg_id_col;
int status = sqlite_stmt_step(sql_req, {{0, &id_col}, {2, &last_msg_id_col}}, {{1, &name_col}});
if (status == SQLITE_ROW) {
return {id_col.value, nickname, std::move(name_col.value),
last_msg_id_col.exist ? last_msg_id_col.value : -1};
}
een9_THROW("No such chat");
}
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) {
SqliteStatement req(conn,
"SELECT `role` FROM `user_chat_membership` WHERE `userId` = ?1 AND `chatId` = ?2",
{{1, userId}, {2, chatId}}, {});
fsql_integer_or_null role;
int status = sqlite_stmt_step(req, {{0, &role}}, {});
if (status == SQLITE_ROW) {
return role.exist ? (int)role.value : user_chat_role_deleted;
}
return user_chat_role_deleted;
}
int64_t get_lastMsgId_of_chat(SqliteConnection &conn, int64_t chatId) {
een9_ASSERT(chatId >= 0, "Are you crazy?");
SqliteStatement sql_req(conn,
"SELECT `lastMsgId` FROM `chat` WHERE `id` = ?1", {{1, chatId}}, {});
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}});
}
}

View File

@ -1,94 +0,0 @@
#ifndef IU9_CA_WEB_CHAT_LIB_BACKEND_LOGIC_SERVER_DATA_INTERACT_H
#define IU9_CA_WEB_CHAT_LIB_BACKEND_LOGIC_SERVER_DATA_INTERACT_H
/* This folder covers all code that helps to interact with database and templater,
* or dictates the logic of how this web chat functions */
#include "../sqlite3_wrapper.h"
#include <jsonincpp/string_representation.h>
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_regular = 2;
constexpr int64_t user_chat_role_read_only = 3;
constexpr int64_t user_chat_role_deleted = 4;
const char* stringify_user_chat_role(int64_t role);
int64_t find_user_by_credentials (SqliteConnection& conn, const std::string& nickname, const std::string& password);
std::string get_user_name (SqliteConnection& conn, int64_t uid);
struct RowUser_Content {
int64_t id;
std::string nickname;
std::string name;
};
struct RowChat_Content {
int64_t id;
std::string nickname;
std::string name;
int64_t lastMsgId; // Negative if it does not exist
};
RowUser_Content lookup_user_content(SqliteConnection& conn, int64_t uid);
RowUser_Content lookup_user_content_by_nickname(SqliteConnection& conn, const std::string& nickname);
/* Does not make authorization check */
RowChat_Content lookup_chat_content(SqliteConnection& conn, int64_t chatId);
RowChat_Content lookup_chat_content_by_nickname(SqliteConnection &conn, const std::string& nickname);
struct RowMessage_Content {
int64_t id;
int64_t senderUserId;
bool exists;
bool isSystem;
std::string text;
};
RowMessage_Content lookup_message_content(SqliteConnection& conn, int64_t chatId, int64_t msgId);
/* Does not make authorization check */
int64_t get_role_of_user_in_chat(SqliteConnection& conn, int64_t userId, int64_t chatId);
/* Does not make authorization check */
int64_t get_lastMsgId_of_chat(SqliteConnection& conn, int64_t chatId);
int64_t get_current_history_id_of_chat(SqliteConnection& conn, int64_t chatId);
int64_t get_current_history_id_of_user_chatList(SqliteConnection& conn, int64_t userId);
json::JSON poll_update_chat_list_resp(SqliteConnection& conn, int64_t userId, int64_t LocalHistoryId);
void poll_update_chat_list(SqliteConnection& conn, int64_t userId, const json::JSON& Sent, json::JSON& Recv);
json::JSON poll_update_chat_ONE_MSG_resp(SqliteConnection& conn, int64_t chatId, int64_t selectedMsg);
void poll_update_chat(SqliteConnection& conn, const json::JSON& Sent, json::JSON& Recv);
void 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);
bool is_nickname_taken(SqliteConnection& conn, const std::string& nickname);
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);
/* ============================= 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);
/**/
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

View File

@ -1,61 +0,0 @@
#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});
}
}

Some files were not shown because too many files have changed in this diff Show More