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
iu9-ca-web-chat.db
log/
core
config/example.json

View File

@ -38,12 +38,12 @@ regexis024_build_system.sh
Помимо самого бинарника нужен файл с настройками сервиса. Формат настроек: JSON.
Комментарии не поддерживаются. Пример такого файла находится в example/config.json.
Вместе с бинарным фалом так же распространяются ассеты, необходимые для работы сайта.
Их можно найти в папке assets. В настроках (поле `config.assets`) указывается путь до
Вместе с бинарным фалом так же распространяются ассеты, необъходимые для работы сайта.
Их можно найти в папке assets. В настроках (поле `["assets"]`) указывается путь до
папки с ассетами. Путь может быть как абсолютным, так и относительным к рабочей директории.
Поле настроек `config.database` указывает как соединиться с базой данных.
Поддерживается только база данных sqlite3. Поддерживается только хранение в файле.
Поле `config.database.file` указывает путь где хранится sqlite база данных.
Поле настроек `["database"]` указывает как соединиться с базой данных.
Поддерживается только база данных sqlite. Поддерживается только хранение в файле.
Поле `["database"]["file"]` указывает путь где хранится sqlite база данных.
Перед тем как использовать сервис нужно его проинициализировать (а точнее проинициализировать
базу данных):
@ -57,39 +57,6 @@ regexis024_build_system.sh
Для остановки сервиса киньте ему 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)
@ -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)
- [Доки 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>
<html lang="{% W pres.lang %}">
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{%w pres.list-rooms.header %}</title>
<link rel="icon" type="image/png" href="/assets/img/favicon.png">
<link rel="stylesheet" href="/assets/css/common.css">
<link rel="stylesheet" href="/assets/css/common-popup.css">
<title>{% WRITE pres.phr.decl.list-of-chat-rooms %}</title>
<link rel="stylesheet" href="/assets/css/list-rooms.css">
</head>
<body>
<script>
let pres = {% PUT jsinsert pres %};
let userinfo = {% PUT jsinsert userinfo %};
let initial_chatListUpdResp = {% PUT jsinsert initial_chatListUpdResp %};
</script>
<div id="chat-creation-win" class="popup-window">
<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 class="container">
<h1 style="color: white;">{% WRITE pres.phr.decl.select-chat-room %}</h1>
<ul class="room-list">
<!-- Здесь будет список комнат -->
</ul>
<button class="create-room-button" onclick="openCreateRoomModal()">{% WRITE pres.phr.act.create-room %}</button>
</div>
<div id="chat-renunciation-win" class="popup-window">
<h1 id="chat-renunciation-win-title" class="popup-window-msg">||||||||||</h1>
<button class="popup-window-btn-yes" id="chat-renunciation-win-yes">{%w pres.list-rooms.yes-leave %}</button>
<button class="popup-window-btn-no" id="chat-renunciation-win-no">{%w pres.list-rooms.no-leave %}</button>
</div>
<div class="document-container resp-container">
<div id="navigation-panel" class="panel">
<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.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 id="passwordModal" class="modal">
<div class="modal-content">
<div class="modal-header">
<span class="close" onclick="closeModal()">&times;</span>
<h2>VVedite parol</h2> <!-- Nam ne nuzhen parol ot komnat -->
</div>
<div class="modal-body">
<input type="password" id="roomPassword" placeholder="Пароль">
</div>
<div class="modal-footer">
<button class="join-button" onclick="validatePassword()">{% WRITE pres.phr.act.confirm %}</button>
</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>
</body>
</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 {
height: 100%;
body {
font-family: Arial, sans-serif;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
background-color: #e5e5e5;
}
#chat-widget {
position: relative;
flex: 1;
background-color: #f1f1f1;
.chat-container {
width: 100%;
max-width: 800px;
height: 90vh;
background-color: white;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
display: flex;
flex-direction: column;
border-radius: 8px;
overflow: hidden;
}
.message-supercontainer{
position: absolute;
width: 100%;
left: 0;
/*background-color: rgba(150, 0, 100, 50);*/
background-color: rgba(0, 0, 0, 0);
/*display: flex;*/
/*flex-direction: row;*/
/*justify-content: center;*/
}
.message-box{
/*display: inline-block;*/
padding: 5px;
}
.message-box-mine {
margin-right: 5px;
margin-left: auto;
max-width: 400px;
border: 2px solid #82a173;
padding: 5px;
background-color: #cdff9b;
color: black;
/*justify-self: flex-end;*/
}
.message-box-alien {
margin-left: 5px;
margin-right: auto;
max-width: 400px;
border: 2px solid dimgrey;
padding: 5px;
background-color: white;
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;
.chat-header {
background-color: #0088cc;
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;
height: auto;
width: 100%;
display: inline-block;
background-color: white;
border: 1px solid #1000d0;
border-radius : 7px;
font-size: .9rem;
margin: 10px;
text-align: center;
font-size: 20px;
}
.message-in-popup-preview{
border: 4px solid red;
width: 80%;
max-width: 200px;
margin-left: auto;
margin-right: auto;
max-height: 20%;
.chat-messages {
flex: 1;
padding: 15px;
overflow-y: auto;
background-color: #f7f7f7;
}
.chat-message {
display: flex;
align-items: flex-start;
margin-bottom: 15px;
}
.chat-message .avatar {
width: 40px;
height: 40px;
border-radius: 50%;
overflow: hidden;
margin-right: 10px;
}
.chat-message .avatar img {
width: 100%;
height: 100%;
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;
}
.loading-spinner{
margin-left: auto;
margin-right: auto;
background-color: rgba(0, 0, 0, 0);
width: 25px;
display: block;
.chat-footer {
display: flex;
padding: 15px;
padding-left: 50px;
border-top: 1px solid #ddd;
}
.chat-input {
flex: 1;
padding: 10px;
border: 1px solid #ddd;
border-radius: 20px;
margin-right: 10px;
outline: none;
}
.chat-send-button {
padding: 10px 20px;
border: none;
background-color: #0088cc;
color: white;
border-radius: 20px;
cursor: pointer;
outline: none;
}
.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 {
font-family: 'Roboto', sans-serif;
background-color: #f7f9fc;
color: #333;
font-family: Arial, sans-serif;
background-color: #f0f0f0;
margin: 0;
padding: 0;
box-sizing: border-box;
}
/* Панель навигации */
.panel {
.container {
max-width: 800px;
margin: 30px auto;
padding: 20px;
background-color: #007bff;
padding: 10px;
color: white;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
border-radius: 8px;
}
h1 {
text-align: center;
color: #fff;
}
.room-list {
list-style-type: none;
padding: 0;
}
.room-item {
display: flex;
justify-content: space-between;
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 {
margin-right: 20px;
text-decoration: none;
color: white;
.room-item:hover {
background-color: #eaeaea;
}
.panel-header-txt {
.room-name {
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;
}
/* Стили динамических блоков */
.dynamic-block-list {
display: flex;
flex-direction: column;
align-items: center;
padding: 20px;
.close:hover, .close:focus {
color: black;
text-decoration: none;
cursor: pointer;
}
.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%;
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;
}
/* Кнопка добавления */
.button-add {
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;
.create-room-button:hover {
background-color: #218838;
}

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
/*
container: EL, box: EL, offset: number (msgPres) */
let visibleMessages = new Map(); // HTMLElement 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';
avatarElement.appendChild(avatarImage);
let anchoredMsg = -1;
let visibleMsgSegStart = -1;
let visibleMsgSegEnd = -2;
let offsetOfAnchor = 500;
let highestPoint = null;
let lowestPoint = null;
const messageContentElement = document.createElement('div');
messageContentElement.classList.add('message-content');
let lastMsgId = -1;
let myRoleHere = null; // Dung local state updates should be updated first
const usernameElement = document.createElement('div');
usernameElement.classList.add('username');
usernameElement.textContent = 'Адель';
// Would start with true if opened `/chat/<>`
let bumpedAtBottom = false;
const textElement = document.createElement('div');
textElement.classList.add('text');
textElement.textContent = message;
// Hidden variable. When deletion window popup is active
// Persists from popup activation until popup deactivation
let storeHiddenMsgIdForDeletionWin = -1;
messageContentElement.appendChild(usernameElement);
messageContentElement.appendChild(textElement);
let debugMode = false;
messageElement.appendChild(avatarElement);
messageElement.appendChild(messageContentElement);
// Positive in production, negative for debug
let softZoneSz = debugMode ? -150 : 300;
let chatPadding = debugMode ? 300 : 5;
let msgGap = 5;
const msgErased = pres.chat.msgErased;
chatMessages.appendChild(messageElement);
function genSentBase(){
return {
'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))
chatInput.value = '';
chatMessages.scrollTop = chatMessages.scrollHeight;
}
}
function shouldShowDeleteMesgBtn(messageSt){
return !messageSt.isSystem && messageSt.exists && (myRoleHere !== userChatRoleReadOnly) &&(
myRoleHere === userChatRoleAdmin || messageSt.senderUserId === userinfo.uid);
}
function getMsgTypeClassSenderBased(messageSt){
if (messageSt.isSystem)
return "message-box-system"
if (messageSt.senderUserId === userinfo.uid)
return "message-box-mine"
return "message-box-alien";
}
function getMsgFullTypeClassName(messageSt){
return getMsgTypeClassSenderBased(messageSt) + (messageSt.exists ? "" : " message-box-deleted");
}
/* Two things can be updated: messages existance and delete button visibility
* Supercontainer.container is persistent, Supercontainer.box can change it's class */
function updateMessageSupercontainer(supercontainer, messageSt){
let box = supercontainer.box;
if (messageSt.isSystem)
return;
setElementVisibility(box.querySelector(".message-box-button-delete"), shouldShowDeleteMesgBtn(messageSt), "inline");
box.className = getMsgFullTypeClassName(messageSt);
// Notice, that no check of previous state is performed. Double loading is a rare event, I can afford to be slow
if (!messageSt.exists)
box.querySelector(".message-box-msg").innerText = msgErased;
}
function decodeSystemMessage(text){
let [subject, verb, object] = text.split(',');
let subjectId = Number(subject);
let objectId = Number(object);
let subjectRef = members.has(subjectId) ? members.get(subjectId).nickname : "???";
let objectRef = members.has(objectId) ? members.get(objectId).nickname : "???";
if (verb === "kicked"){
return subjectRef + " " + pres.chat.syslog.kicked + " " + objectRef;
} else if (verb === "summoned"){
return subjectRef + " " + pres.chat.syslog.summoned + " " + objectRef;
} else if (verb === "left"){
return subjectRef + " " + pres.chat.syslog.left;
} else if (verb === "created"){
return subjectRef + " " + pres.chat.syslog.created;
document.getElementById('chat-input').addEventListener('keydown', function (event) {
if (event.key === 'Enter') {
sendMessage();
}
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(){
return {
'chatListUpdReq': {
'LocalHistoryId': LocalHistoryId
}
};
function openModal(roomName) {
currentRoom = roomName;
document.getElementById('passwordModal').style.display = 'block';
}
let myChats = new Map();
let chatBoxes = new Map();
/* 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 closeModal() {
document.getElementById('passwordModal').style.display = 'none';
}
let chatRenunciationWinStoredId = -1;
function shouldShowDeleteButton(myMembershipSt){
return myMembershipSt.myRoleHere === userChatRoleDeleted;
}
/* 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);
}
function validatePassword() {
const enteredPassword = document.getElementById('roomPassword').value;
if (enteredPassword === rooms[currentRoom]) {
alert('Вы вошли в комнату: ' + currentRoom);
closeModal();
} else {
alert('Неверный пароль. Попробуйте снова.');
}
}
/* Use it ONLY if `Recv` reported success */
function updateLocalStateFromRecv(Recv){
updateLocalStateFromChatListUpdResp(Recv.chatListUpdResp);
function openCreateRoomModal() {
document.getElementById('createRoomModal').style.display = 'block';
}
function configureChatCreationInterface(){
document.getElementById("chat-creation-win-yes").onclick = function (ev) {
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 closeCreateRoomModal() {
document.getElementById('createRoomModal').style.display = 'none';
}
function configureChatRenunciationInterfaceWinPart(){
document.getElementById("chat-renunciation-win-yes").onclick = function (ev){
if (ev.button !== 0)
return;
deactivateActivePopup();
if (chatRenunciationWinStoredId < 0)
throw new Error("chatRenunciationWinStoredId < 0");
let chatId = chatRenunciationWinStoredId;
let Sent = genSentBase();
Sent.chatId = chatId;
apiRequest("leaveChat", Sent
).then((Recv) => {
updateLocalStateFromRecv(Recv);
}).catch((e) => {
alert(pres['list-rooms']["failed-create-chat"]);
console.log(e);
});
function createRoom() {
const roomName = document.getElementById('newRoomName').value.trim();
const roomPassword = document.getElementById('newRoomPassword').value.trim();
if (roomName === '' || roomPassword === '') {
alert('Пожалуйста, заполните все поля.');
return;
}
document.getElementById("chat-renunciation-win-no").onclick = function(ev) {
if (ev.button !== 0)
return;
deactivateActivePopup();
if (rooms[roomName]) {
alert('Комната с таким названием уже существует.');
return;
}
rooms[roomName] = roomPassword;
addRoomToList(roomName);
closeCreateRoomModal();
}
__mainloopDelayMs = 3000;
__guestMainloopPollerAction = function(){
let Sent = genSentBase();
apiRequest("chatListPollEvents", Sent
).then((Recv) => {
console.log("Got a response");
console.log(Recv);
updateLocalStateFromRecv(Recv);
function addRoomToList(roomName) {
const roomList = document.querySelector('.room-list');
const existingRoomItem = Array.from(roomList.children).find(item => item.querySelector('.room-name').textContent === roomName);
if (existingRoomItem) {
existingRoomItem.remove();
}
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 () {
console.log("Loading complete");
updateLocalStateFromChatListUpdResp(initial_chatListUpdResp);
configureChatCreationInterface();
configureChatRenunciationInterfaceWinPart();
mainloopPoller();
};
initializeRoomList();
window.onclick = function(event) {
if (event.target === document.getElementById('passwordModal')) {
closeModal();
}
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;
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",
"-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_debug = {"_GLIBCXX_DEBUG", "DEBUG_ALLOW_LOUD"};
std::vector<std::string> opt_flags_release = {"-g", "-O2"};
@ -49,11 +49,9 @@ struct CAWebChat {
return my_flag_collection;
}
explicit CAWebChat(const NormalCBuildSystemCommandMeaning& cmd){
const char* BSCRIPT_TYPE = getenv("BSCRIPT_TYPE");
const char* BSCRIPT_TESTS = getenv("BSCRIPT_TESTS");
build_type = BSCRIPT_TYPE ? BSCRIPT_TYPE : "release";
make_tests = (bool)BSCRIPT_TESTS;
CAWebChat(const std::string& _build_type, bool _build_tests, const NormalCBuildSystemCommandMeaning& cmd)
: build_type(_build_type), build_tests(_build_tests)
{
ASSERT(build_type == "release" || build_type == "debug", "Unknown build type");
std::vector<ExternalLibraryTarget> ext_targets = {
@ -77,13 +75,9 @@ struct CAWebChat {
"os_utils.cpp",
"http_structures/client_request_parse.cpp",
"http_structures/response_gen.cpp",
"http_structures/cookies.cpp",
"http_structures/accept_language.cpp",
"connecting_assets/static_asset_manager.cpp",
"running_mainloop.cpp",
"form_data_structure/urlencoded_query.cpp",
"socket_address.cpp",
"admin_control.cpp",
};
for (std::string& u: T.units)
u = "http_server/engine_engine_number_9/" + u;
@ -96,17 +90,14 @@ struct CAWebChat {
"os_utils.h",
"connecting_assets/static_asset_manager.h",
"http_structures/client_request.h",
"http_structures/cookies.h",
"http_structures/response_gen.h",
"http_structures/accept_language.h",
"running_mainloop.h",
"form_data_structure/urlencoded_query.h",
"socket_address.h",
"admin_control.h",
};
for (std::string& u: T.exported_headers)
u = "engine_engine_number_9/" + u;
T.installation_dir = "een9";
T.installation_dir = "";
my_targets.push_back(T);
}
{ CTarget T{"new_york_transit_line", "shared_library"};
@ -130,76 +121,28 @@ struct CAWebChat {
};
for (std::string& u: T.exported_headers)
u = "new_york_transit_line/" + u;
T.installation_dir = "nytl";
my_targets.push_back(T);
}
{ CTarget T{"iu9_ca_web_chat_lib", "shared_library"};
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"};
{ CTarget T{"iu9-ca-web-chat", "executable"};
T.additional_compilation_flags = getSomeRadFlags();
T.proj_deps = {
CTargetDependenceOnProjectsLibrary{"engine_engine_number_9"},
CTargetDependenceOnProjectsLibrary{"new_york_transit_line"},
};
T.external_deps = {
CTargetDependenceOnExternalLibrary{"sqlite3"}
};
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)
u = "web_chat/iu9_ca_web_chat_admin_cli/" + u;
u = "web_chat/" + u;
T.include_pr = "web_chat";
T.installation_dir = "";
my_targets.push_back(T);
}
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;
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)
complete_tasks_of_build_units(bs.runlevel_1);
umask(~0755);

View File

@ -1,13 +1,25 @@
{
"lang": {
"whitelist": ["*"],
"force-order": [
"ru"
]
"presentation": {
"instance-identity": {
"top-title": "Вэб чат от ИУ9"
},
"phr": {
"decl": {
"list-of-chat-rooms": "Список Чат-Комнат",
"select-chat-room": "Выберете чат комнату",
"name-of-room": "Название комнаты",
"create-room": "Создать комнату"
},
"act": {
"create-room": "Создать комнату",
"confirm": "Подтвердить",
"create": "Создать"
}
}
},
"assets": "./assets",
"database": {
"type": "sqlite3",
"type": "sqlite",
"file": "./iu9-ca-web-chat.db"
},
"limits": {
@ -17,8 +29,8 @@
"storage-size-limit": 100000000000
},
"server": {
"workers": 16,
"workers": 8,
"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;
}
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())
return false;
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) {
ASSERT(A <= B && B <= str.size(), "Incorrect substring segment");
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[]);
// b is postfix of a
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);
bool endsIn(const std::string& a, const std::string& b);
/* In case of error, throws een9::ServerError */
std::string getSubstring(const std::string& str, size_t A, size_t B);
std::string make_uppercase(const std::string &source);
template<typename 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);
for (const std::string& file: c_files) {
for (const StaticAssetManagerRulePostfixFilter& ext: dir_rule.postfix_rules_type_assign) {
if (endsWith(file, ext.required_postfix)) {
if (endsIn(file, ext.required_postfix)) {
/* Found it! */
StaticAsset etot{ext.assigned_type, };
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 <algorithm>
#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 {
ClientRequestParser_CommonPrograms::ClientRequestParser_CommonPrograms() {
@ -107,15 +99,9 @@ namespace een9 {
if (p.first == "Content-Length") {
collecting_body = res.has_body = true;
body_size = std::stoull(p.second);
if (body_size > 100000000) {
status = -1;
return status;
}
res.body.reserve(std::min(100000ul, body_size));
if (body_size == 0) {
status = 1;
}
break;
if (body_size > 100000000)
THROW("Message content is too big");
res.body.reserve(body_size);
}
}
if (!res.has_body) {
@ -123,11 +109,8 @@ namespace een9 {
}
/* We either finish now or we finish later */
} else if (!vm.haveSurvivors()) {
#ifdef DEBUG_ALLOW_LOUD
mkdir("log", 0750);
writeFile("log/req", header);
#endif
status = -1;
THROW("bad request");
}
}
return status;

View File

@ -37,6 +37,7 @@ namespace een9 {
explicit ClientRequestParser_WorkerBuffers(const ClientRequestParser_CommonPrograms& common_comp_program);
};
/* Ou yeah, baby, it's time for more OOP */
struct ClientHttpRequestParser_Ctx {
ClientRequest& res;
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 {
std::string form_http_server_response_header(const char* code,
const std::vector<std::pair<std::string, std::string>>& headers) {
std::string form_http_server_response_header(const char* code, const std::map<std::string, std::string>& headers) {
assert(strlen(code) == 3);
std::string result = std::string("HTTP/1.0 ") + code + " " + (code[0] < '4' ? "OK" : "ERROR") + "\r\n";
for (auto& p: headers)
@ -14,13 +13,12 @@ namespace een9 {
return result;
}
std::string form_http_server_response_header_only(const char* code,
const std::vector<std::pair<std::string, std::string>>& headers) {
std::string form_http_server_reponse_header_only(const char* code, const std::map<std::string, std::string>& headers) {
return form_http_server_response_header(code, headers) + "\r\n";
}
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)
{
std::string result = form_http_server_response_header(code, headers)
@ -40,15 +38,4 @@ namespace een9 {
{"Content-Type", Content_Type}
}, 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
#define ENGINE_ENGINE_NUMBER_9_HTTP_STRUCTURES_RESPONSE_GEN_H
#include <vector>
#include <map>
#include <string>
namespace een9 {
std::string form_http_server_response_header(const char* code,
const std::vector<std::pair<std::string, std::string>>& headers);
std::string form_http_server_response_header(const char* code, const std::map<std::string, std::string>& headers);
std::string form_http_server_reponse_header_only(const char* code,
const std::vector<std::pair<std::string, std::string>>& headers);
std::string form_http_server_reponse_header_only(const char* code, const std::map<std::string, std::string>& headers);
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);
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_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

View File

@ -25,6 +25,7 @@ namespace een9 {
}
UniqueFdWrapper::~UniqueFdWrapper() {
// printf("DEBUG!!! Closing fd = %d\n", fd);
if (fd >= 0)
close(fd);
}
@ -54,7 +55,7 @@ namespace een9 {
while ((ret = (int)read(fd, buf, 2048)) > 0) {
size_t oldN = result.size();
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);
}
@ -66,28 +67,6 @@ namespace een9 {
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) {
int ret;
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 writeFile(const std::string& path, const std::string& text);
void configure_socket_rcvsndtimeo(int fd, timeval tv);
}

View File

@ -13,7 +13,6 @@
#include "http_structures/client_request_parse.h"
#include "http_structures/response_gen.h"
#include "baza_inter.h"
#include "admin_control.h"
namespace een9 {
struct QElementHttpConnections {
@ -41,6 +40,7 @@ namespace een9 {
}
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!! */
QElementHttpConnections* el = new QElementHttpConnections(std::move(task));
/* Exception does not leave queue in incorrect state */
@ -65,15 +65,6 @@ namespace een9 {
sz--;
}
}
~WorkersTaskQueue() {
QElementHttpConnections* cur = first;
while (cur) {
QElementHttpConnections* nxt = cur->nxt;
delete cur;
cur = nxt;
}
}
};
struct WorkersEnvCommon {
@ -82,13 +73,11 @@ namespace een9 {
WorkersTaskQueue queue;
bool& termination;
guest_core_t guest_core;
guest_core_admin_control_t guest_core_admin_control;
/* Parser programs */
ClientRequestParser_CommonPrograms parser_programs;
WorkersEnvCommon(bool& term, const MainloopParameters& params): termination(term),
guest_core(params.guest_core), guest_core_admin_control(params.guest_core_admin_control){}
WorkersEnvCommon(bool& term, guest_core_t g_c): termination(term), guest_core(std::move(g_c)){}
};
struct WorkersEnv {
@ -100,22 +89,25 @@ namespace een9 {
};
// 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;
ClientHttpRequestParser_Ctx parser(res, wte.personal_parser_buffer, wte.wtec.parser_programs);
int ret;
char buf[2048];
assert(parser.status == 0);
ASSERT_pl(parser.status == 0);
while ((ret = (int)recv(fd, buf, 2048, 0)) > 0) {
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;
}
}
if (parser.status != 0)
if (parser.status > 0)
break;
}
ASSERT(parser.status == 1, "Incorrect request"); // todo: do the same thing everywhere else
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;
}
@ -131,41 +123,10 @@ namespace een9 {
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) {
if (task.conn_info.type == 0) {
printf("%d::Got http reuest\n", wte.id);
ClientRequest client_request = process_http_connection_input(task.fd(), task.s_tips, wte);
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);
}
ClientRequest client_request = process_connection_input(task.fd(), task.s_tips, wte);
std::string server_response = wte.wtec.guest_core(task, client_request, wte.id);
process_connection_output(task.fd(), server_response);
}
void* worker_func(void* wte_ptr) {
@ -195,13 +156,13 @@ namespace een9 {
return NULL;
}
// todo: retrieve address of connected client
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");
size_t CRL_Nip = params.client_regular_listened.size();
ASSERT(CRL_Nip > 0, "No open listeting addresses (http)");
size_t ACL_Nip = params.admin_control_listened.size();
size_t Nip = CRL_Nip + ACL_Nip;
size_t Nip = params.ports_to_listen.size();
ASSERT(Nip > 0, "No open listeting addresses");
std::vector<pthread_t> workers(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());
}
// todo: right now this try block protects threads. So I need to put pthreads in some kind of guarding object
try {
int ret;
struct Ear {
/* 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;
}
std::vector<UniqueFdWrapper> listening_socks(Nip);
for (size_t i = 0; i < Nip; i++) {
int listening_socket_fd = socket(ears[i].my_addr.v.gen.sa_family, SOCK_STREAM, 0);
ASSERT_on_iret(listening_socket_fd, "'Listening socket' creation");
printf("Creating listening socket\n");
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);
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;
ret = setsockopt(listening_socket_fd, SOL_SOCKET, SO_REUSEADDR, &reuseaddr_nozero_option_value, sizeof(int));
ASSERT_on_iret(ret, "Can't set SO_REUSEADDR");
bind_to_socket_address(listening_socket_fd, ears[i].my_addr);
ASSERT_on_iret(ret, "setting SO_REUSEADDR befire binding to address");
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);
ASSERT_on_iret(ret, "listen() listening for connections");
ears[i].listening_sock = std::move(listening_socket);
ASSERT_on_iret(ret, "listening for connections");
printf("Listening socket succesfully started listening\n");
listening_socks[i] = std::move(listening_socket);
}
std::vector<pollfd> pollfds(Nip);
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;
}
printf("Entering mainloop\n");
ASSERT(params.mainloop_recheck_interval_us > 0, "Incorrect poll timeout");
while (true) {
MutexLockGuard lg1(wtec.corvee_bed, "poller termination check");
if (wtec.termination)
break;
lg1.unlock();
for (size_t i = 0; i < Nip; i++) {
pollfds[i].revents = 0;
}
errno = 0;
ret = poll(pollfds.data(), Nip, params.mainloop_recheck_interval_us);
if (ret != 0 && errno != 0) {
printf("poll() error :> %s\n", een9::prettyprint_errno("").c_str());
continue;
}
if (errno == EINTR)
break;
ASSERT_on_iret(ret, "polling");
for (size_t i = 0; i < Nip; i++) {
if ((pollfds[i].revents & POLLRDNORM)) {
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");
printf("Log: successful connection\n");
UniqueFdWrapper session_sock_fdw(session_sock);
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");
SlaveTask task{
ConnectionInfo{ears[i].my_addr, peer_addr, ears[i].type},
std::move(session_sock_fdw),
SlaveTask task{ConnectionInfo{}, std::move(session_sock_fdw),
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)
wtec.queue.push_back(std::move(task));
}
@ -292,7 +247,7 @@ namespace een9 {
} catch (const std::exception& e) {
printf("System failure 2\n");
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.corvee_bed.wake_them_all();
}

View File

@ -7,14 +7,12 @@
#include "os_utils.h"
#include "http_structures/client_request.h"
#include <stdint.h>
#include "socket_address.h"
namespace een9 {
struct ConnectionInfo {
SocketAddress server_name;
SocketAddress client_name;
/* 0 - http, 1 - 'een9::admin-control' protocol */
int type;
// todo: add server address field
// todo: add client address field
int type; // O_o why??
};
/* 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 */
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 {
size_t critical_load_1 = 90;
@ -46,18 +42,14 @@ namespace een9 {
};
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;
size_t slave_number = 2;
int mainloop_recheck_interval_us = 100;
/* Takes parsed http request object. Should return fully-prepared http response */
bool open_admin_listener = true;
// todo: add support for unix socket address
uint16_t admin_listener_port = 12345;
guest_core_t guest_core;
/* Takes admin input. Returns only desired output message (without protocol header) */
guest_core_admin_control_t guest_core_admin_control;
size_t slave_number = 2;
std::vector<uint16_t> ports_to_listen;
int mainloop_recheck_interval_us = 100;
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) {
printf("===== TEMPLATER INTERNAL RESOURCES =====\n");
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());
assert(p.second.when_element);
const Element& el = *p.second.when_element;
const Element& el = p.second;
printf("%s, %s\n", el.base ? "BASE" : "NOT BASE", el.is_hidden ? "HIDDEN" : "NOT HIDDEN");
if (!el.is_hidden) {
std::string signature;

View File

@ -55,16 +55,13 @@ namespace nytl {
}
char skip(ParsingContext& ctx) {
if (ctx.pos >= ctx.text.size())
THROW("Unexpected EOF");
ASSERT(ctx.pos < ctx.text.size(), "Unexpected EOF");
return advance(ctx);
}
void skip(ParsingContext& ctx, char ch) {
if (ctx.pos >= ctx.text.size())
THROW("Unexpected EOF");
if (ctx.text[ctx.pos] != ch)
THROW("Unexpected character");
ASSERT(ctx.pos < ctx.text.size(), "Unexpected EOF");
ASSERT(ctx.text[ctx.pos] == ch, "Unexpected character");
advance(ctx);
}
@ -150,41 +147,16 @@ namespace nytl {
return concatenateLines(lines);
}
Element& add_hidden_element(const std::string& new_el_name, global_elem_set_t& result) {
if (result.count(new_el_name) != 0)
THROW("Repated element " + new_el_name);
TemplaterRegPref& rp = result[new_el_name];
rp.is_element = 1;
rp.when_element = std::make_unique<Element>();
rp.when_element->is_hidden = true;
return *rp.when_element;
}
Element& add_new_element(const std::string& new_el_name, global_elem_set_t& result) {
if (!is_uname_dotted_sequence(new_el_name))
THROW("Krabovaya oshibka");
if (result.count(new_el_name) != 0 && result.at(new_el_name).is_element)
THROW("Repated element " + new_el_name);
size_t n = new_el_name.size();
for (size_t i = 0; i < n; i++) {
if (new_el_name[i] == '.') {
std::string pref = new_el_name.substr(0, i);
result[pref];
}
}
TemplaterRegPref& rp = result[new_el_name];
rp.is_element = 1;
rp.when_element = std::make_unique<Element>();
return *rp.when_element;
}
void parse_bare_file(const std::string& filename, const std::string& content, global_elem_set_t& result) {
Element& el = add_new_element(filename, result);
void parse_bare_file(const std::string& filename, const std::string& content,
global_elem_set_t& result)
{
ASSERT(result.count(filename) == 0, "Repeated element " + filename);
std::string txt = clement_lstrip(content);
rstrip(txt);
size_t cut = 9999999999999;
one_part_update_min_start_wsp_non_empty(txt, 0, 1, cut);
txt = one_part_cut_excess_tab(txt, 0, 1, cut);
Element& el = result[filename];
el.parts = {ElementPart{}};
el.parts[0].when_code.lines = mv(txt);
}
@ -198,17 +170,15 @@ namespace nytl {
uptr<TPFrame> toMe(bool returned, ParsingContext& ctx) {
if (!returned) {
std::string nm = readName(ctx);
if (nm.empty())
THROW("Type specification expected");
ASSERT(!nm.empty(), "Type specification expected");
nm = make_uppercase(nm);
if (nm == "JSON") {
result = json::JSON(true);
return NULL;
}
if (nm != "EL")
THROW("Type of argument variable is either JSON or EL(...signature)");
ASSERT(nm == "EL", "Type of argument variable is either JSON or EL(...signature)")
skip(ctx, '(');
result.asArray();
result = json::JSON(json::array);
assert(result.isArray());
}
skipWhitespace(ctx);
@ -247,21 +217,19 @@ namespace nytl {
uptr<EPFrame> toMe(bool returned, ParsingContext& ctx, const arg_name_list_t& local_var_names) {
if (!returned) {
std::string first = readName(ctx);
if (first.empty())
THROW("Expression should start with 'root' name of global package or local variable");
if (first == "_")
THROW("Expression root can't be _");
ASSERT(!first.empty(), "Expression should start with 'root' name of global package or local variable");
ASSERT(first != "_", "_ ??? ARE YOU KIDDING???");
if (local_var_names.count(first) == 1) {
result["V"].asInteger() = json::Integer((int64_t)local_var_names.at(first));
result["V"] = json::JSON(json::Integer((int64_t)local_var_names.at(first)));
} else {
result["V"].asString() = first;
result["V"] = json::JSON(first);
}
result["C"].asArray();
result["C"] = json::JSON(json::array);
} else {
skipWhitespace(ctx);
skip(ctx, ']');
}
std::vector<json::JSON>& chain = result["C"].asArray();
std::vector<json::JSON>& chain = result["C"].g().asArray();
while (true) {
if (peep(ctx) == '.') {
skip(ctx, '.');
@ -275,8 +243,7 @@ namespace nytl {
t = readUint(ctx);
if (!t.empty()) {
size_t v = std::stoul(t);
if (v >= INT64_MAX)
THROW("Index is too big");
ASSERT(v < INT64_MAX, "Index is too big");
chain.back() = json::JSON((int64_t)v);
continue;
}
@ -385,8 +352,7 @@ namespace nytl {
ElementPart::when_for_put_S& P = result.parts.back().when_for_put;
skipWhitespace(ctx);
std::string V1 = readName(ctx);
if (V1.empty())
THROW("Expected variable name");
ASSERT(!V1.empty(), "Expected variable name");
skipWhitespace(ctx);
bool have_colon_and_2 = false;
std::string V2;
@ -398,23 +364,21 @@ namespace nytl {
skipWhitespace(ctx);
}
op = make_uppercase(readName(ctx));
if (op != "IN")
THROW("Expected IN");
ASSERT(op == "IN", "Expected IN");
skipWhitespace(ctx);
P.ref_over = parse_expression(ctx, local_var_names);
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;
if (V1 != "_") {
if (local_var_names_of_nxt.count(V1) != 0)
THROW("Repeated local variable");
ASSERT(local_var_names_of_nxt.count(V1) == 0, "Repeated local variable");
size_t k = local_var_names_of_nxt.size();
local_var_names_of_nxt.emplace(V1, k);
(have_colon_and_2 ? P.where_key_var : P.where_value_var) = (ssize_t)k;
}
if (have_colon_and_2 && V2 != "_") {
if (local_var_names_of_nxt.count(V2) != 0)
THROW("Repeated local variable");
ASSERT(local_var_names_of_nxt.count(V2) == 0, "Repeated local variable");
size_t k = local_var_names_of_nxt.size();
local_var_names_of_nxt.emplace(V2, 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;
skipWhitespace(ctx);
std::string Vn = readName(ctx);
if (Vn.empty() || Vn == "_")
THROW("REF: expected variable name");
ASSERT(!Vn.empty(), "Expected variable name");
ASSERT(Vn != "_", "Are you kidding???");
skipWhitespace(ctx);
op = make_uppercase(readName(ctx));
if (op != "AS")
THROW("Expected AS");
ASSERT(op == "AS", "Expected AS");
skipWhitespace(ctx);
P.ref_over = parse_expression(ctx, local_var_names);
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;
size_t k = local_var_names_of_nxt.size();
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,
ret_data_int, newborn);
}
if (op == "PUT" || op == "P") {
if (op == "PUT") {
result.parts.emplace_back();
result.parts.back().type = ElementPart::p_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)};
skip_magic_block_end(ctx, syntax);
};
if (op == "WRITE" || op == "W") {
if (op == "WRITE") {
mediocre_operator("str2text");
goto ya_e_ya_h_i_ya_g_d_o;;
}
if (op == "ROUGHINSERT" || op == "RI") {
if (op == "ROUGHINSERT") {
mediocre_operator("str2code");
goto ya_e_ya_h_i_ya_g_d_o;;
}
@ -503,15 +467,13 @@ namespace nytl {
}
};
if (op == "ENDELDEF") {
if (myself != gone_for_nothing)
THROW("Unexpected ENDELDEF");
ASSERT(myself == gone_for_nothing, "Unexpected end of element");
skip_magic_block_end(ctx, syntax);
prepare_to_depart_parts();
return NULL;
}
if (op == "ENDFOR") {
if (myself != gone_for_for)
THROW("Unexpected ENDFOR");
ASSERT(myself == gone_for_for, "Unexpected end of for cycle");
skipWhitespace(ctx);
/* 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
@ -529,8 +491,7 @@ namespace nytl {
return NULL;
}
if (op == "ENDREF") {
if (myself != gone_for_ref)
THROW("Unexpected ENDREF");
assert(myself == gone_for_ref);
skip_magic_block_end(ctx, syntax);
prepare_to_depart_parts();
return NULL;
@ -564,14 +525,13 @@ namespace nytl {
if (peep(ctx) == EOFVAL)
break;
skip_magic_block_start(ctx, syntax);
if (make_uppercase(readName(ctx)) != "ELDEF")
THROW("Expected ELDEF");
ASSERT(make_uppercase(readName(ctx)) == "ELDEF", "Expected ELDEF");
skipWhitespace(ctx);
std::string elname_postfix = readName(ctx);
if (elname_postfix == "_")
THROW("Can't use _ as element name");
ASSERT(elname_postfix != "_", "please don't");
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;
while (true) {
skipWhitespace(ctx);
@ -580,11 +540,9 @@ namespace nytl {
newborn.arguments.push_back(parse_type(ctx));
skipWhitespace(ctx);
std::string argname = readName(ctx);
if (argname.empty())
THROW("Expected argument name");
ASSERT(!argname.empty(), "Expected argument name");
if (argname != "_") {
if (arglist.count(argname) != 0)
THROW("Repeated argument (" + argname + ")");
ASSERT(arglist.count(argname) == 0, "Repeated argument (" + argname + ")");
size_t k = arglist.size();
arglist[argname] = k;
}

View File

@ -23,64 +23,58 @@ namespace nytl {
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) {
const json::JSON& P = *result.JSON_subval;
if (P.isArray() && what.isInteger()) {
const std::vector<json::JSON>& arr_p = P.asArray();
int64_t ind_w = what.asInteger().get_int();
if (!(ind_w > 0 && ind_w < arr_p.size()))
THROW("Expression \"array[integer]\" caused out-of-bound situation");
ASSERT(ind_w > 0 && ind_w < arr_p.size(), "Expression \"array[integer]\" caused out-of-bound situation");
result = LocalVarValue{true, "", &arr_p[ind_w]};
} else if (P.isDictionary() && what.isString()) {
const std::map<std::string, json::JSON>& dict_p = P.asDictionary();
const std::string& key_w = what.asString();
if (dict_p.count(key_w) != 1)
THROW("No such key exception (" + key_w + ")");
ASSERT(dict_p.count(key_w) == 1, "No such key exception");
result = LocalVarValue{true, "", &dict_p.at(key_w)};
} else
THROW("Incorrect type of \"json[json]\" expression. Unallowed signature of [] operator");
} else {
if (!what.isString())
THROW("Expression \"element[X]\" allowed only if X is string (json object)");
if (!isUname(what.asString()))
THROW("Expression \"element[str]\" has incorrect str (" + what.asString() + ")");
ASSERT(what.isString(), "Expression \"element[X]\" allowed only if X is string (json object)");
if (what.asString().empty())
return;
if (!is_uname_dotted_sequence(what.asString()))
THROW("Incorrect X in \"element[X]\"");
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,
const std::vector<LocalVarValue>& local_vars) {
if (returned) {
if (!temp_ret.is_json)
THROW("Expression \"X[ element ]\" is not allowed");
ASSERT(temp_ret.is_json, "Expression \"X[ element ]\" is not allowed");
assert(temp_ret.JSON_subval);
descend(*(temp_ret.JSON_subval), global_elems);
descend(*(temp_ret.JSON_subval));
} else {
assert(expr.isDictionary());
const json::JSON& val = expr["V"];
const json::JSON& val = expr["V"].g();
if (val.isInteger()) {
size_t lv_ind = val.asInteger().get_int();
assert(lv_ind < local_vars.size());
result = local_vars[lv_ind];
} else if (val.isString()) {
std::string cur_el_name_str = expr["V"].asString();
if (global_elems.count(cur_el_name_str) != 1)
THROW("Bad expression, no such element (" + cur_el_name_str + ")");
std::string cur_el_name_str = expr["V"].g().asString();
result = LocalVarValue{false, cur_el_name_str, NULL};
} else
assert(false);
}
const std::vector<json::JSON>& chain = expr["C"].asArray();
const std::vector<json::JSON>& chain = expr["C"].g().asArray();
while (true) {
if (chain_el >= chain.size())
return NULL;
const json::JSON& t = chain[chain_el++];
if (t.isDictionary())
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 */
LocalVarValue rendering_core_execute_expression(const global_elem_set_t& global_elems,
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;
std::vector<uptr<EEFrame>> stack;
LocalVarValue result;
@ -120,9 +112,15 @@ namespace nytl {
for (size_t i = 0; i < m; i++) {
result += text[i];
if (text[i] == '\n') {
// newlined_somewhere = true;
result.resize(result.size() + wsp_before_newlines, ' ');
cur_line_width = wsp_before_newlines;
} else {
// if (cur_line_width == 0 && newlined_somewhere) {
// result.resize(result.size() + wsp_before_newlines, ' ');
// cur_line_width = wsp_before_newlines;
// }
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,
const std::function<std::string(std::string)> &escape) {
if (!returned)
if ((elem_ns.count(name) != 1) || (!elem_ns.at(name).is_element))
THROW("Can't render. No such element (" + name + ")");
const Element& el = *elem_ns.at(name).when_element;
ASSERT(elem_ns.count(name) == 1, "No such element");
const Element& el = elem_ns.at(name);
if (!returned) {
/* Continue to do checks */
/* hidden elements (internal) do not need any check */
@ -230,17 +227,14 @@ namespace nytl {
ASSERT(n == passed_args.size(), "Argument count mismatch");
for (size_t i = 0; i < n; i++) {
if (el.arguments[i].type == json::true_symbol) {
if (!passed_args[i].is_json)
THROW("Expected json element argument, got element");
ASSERT(passed_args[i].is_json, "Expected json element argument, got element");
} else {
// If not json is expected, element must be expected
assert(el.arguments[i].isArray());
if (passed_args[i].is_json)
THROW("Expected element element arguemnt, got json");
const std::string& passed_el_as_arg = passed_args[i].EL_name;
if ((elem_ns.count(passed_el_as_arg) != 1) || !elem_ns.at(passed_el_as_arg).is_element)
THROW("No such element, can't compare signatures of argument value (" + passed_el_as_arg + ")");
const Element& arg_element = elem_ns.at(passed_el_as_arg).when_element.operator*();
ASSERT(!passed_args[i].is_json, "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 Element& arg_element = elem_ns.at(passed_args[i].EL_name);
// ASSERT(passed_args);
if(el.arguments[i].asArray() != arg_element.arguments)
THROW("Signature of argument " + std::to_string(i) + " does not match");
}
@ -252,11 +246,7 @@ namespace nytl {
assert(passed_args.size() == 1);
const json::JSON* X = passed_args[0].JSON_subval;
assert(X);
if (name == "jsinsert") {
std::string pure_json = json::generate_str(*X, json::print_pretty);
rstrip(pure_json);
append(pure_json, result);
} else if (name == "jesc") {
if (name == "jesc") {
std::string escaped_json = escape(json::generate_str(*X, json::print_pretty));
rstrip(escaped_json);
append(escaped_json, result);

View File

@ -100,7 +100,7 @@ namespace nytl {
while ((ret = (int)read(fd, buf, 2048)) > 0) {
size_t oldN = result.size();
result.resize(oldN + ret);
memcpy(result.data() + oldN, buf, ret);
memcpy((void*)&result.c_str()[oldN], buf, ret);
}
if (ret < 0) {
close(fd);
@ -109,17 +109,15 @@ namespace nytl {
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() {
elements[""] = TemplaterRegPref{0, NULL};
elements["jsinsert"] = gen_base_element();
elements["jesc"] = gen_base_element();
elements["str2text"] = gen_base_element();
elements["str2code"] = gen_base_element();
elements = {
{"jesc", Element{{json::JSON(true)}, true}},
{"jesccomp", Element{{json::JSON(true)}, true}},
/* str2text base element has a dedicated operator - WRITE */
{"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);
for (const InterestingFile& file: intersting_files) {
std::string content = readFile(file.path);
@ -133,8 +131,7 @@ namespace nytl {
/* 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 {
if (!is_uname_dotted_sequence(element))
THROW("Incorrect entry element name");
ASSERT(is_uname_dotted_sequence(element), "Incorrect entry element name");
return rendering_core(element, arguments, elements, settings.escape);
}
}

View File

@ -6,8 +6,6 @@
#include <jsonincpp/jsonobj.h>
#include <functional>
#include "html_case.h"
#include <memory>
#include <forward_list>
namespace nytl {
typedef json::JSON expression_t;
@ -63,12 +61,7 @@ namespace nytl {
std::function<std::string(std::string)> escape = html_case_espace_string;
};
struct TemplaterRegPref {
int is_element = 0;
std::unique_ptr<Element> when_element = NULL;
};
typedef std::map<std::string, TemplaterRegPref> global_elem_set_t;
typedef std::map<std::string, Element> global_elem_set_t;
struct Templater {
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>
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

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