Compare commits

..

39 Commits

Author SHA1 Message Date
yakovlevanton
f6c649f9db Удалил лишние файлы chatSettings 2024-09-14 03:45:45 +03:00
yakovlevanton
9f0331ed62 Merge remote-tracking branch 'origin/anton_branch_2.0' 2024-09-14 03:43:33 +03:00
Fedor_Kitanin
16a6ee963a inaccuracies have been eliminated 2024-09-12 18:24:03 +03:00
Fedor_Kitanin
febf3af534 убраны два файла для мерджа 2024-09-12 17:00:45 +03:00
Fedor_Kitanin
61ef791c89 rooms are centralized 2024-09-12 16:52:18 +03:00
Fedor_Kitanin
14a8c1755e что-то 2024-09-12 15:46:17 +03:00
Fedor_Kitanin
1eb3f08b2c changed the rooms page 2024-09-12 14:43:53 +03:00
Fedor_Kitanin
5d4d1149d4 many folders have been updated, and html, js, and css extension files have also been changed 2024-09-12 10:47:29 +03:00
d1bfe44d0d Merge pull request 'Added registration' (#5) from fed into master
Reviewed-on: #5
2024-09-11 08:52:35 +00:00
Fedor Kitanin
bc2c7d31c9 Added registration 2024-09-11 11:46:35 +03:00
249f5e6b78 Fixed annoying bugs, added reassignment of anchor. FIRST RELEASE VERSION 2024-09-07 21:55:26 +03:00
6813c6249d Added cumping to chat widget 2024-09-07 10:48:15 +03:00
270549c21a Translated everything to ru-RU 2024-09-07 00:49:48 +03:00
0ed8150c4e Translated to english 2024-09-07 00:31:42 +03:00
b0d7a35eb2 First efforts to internationalize site 2024-09-06 01:06:36 +03:00
68094f904d minor fixes, added system messages 2024-09-05 11:30:40 +03:00
2219653f40 FIRST WORKING VERSION OF IU9 CA WEB CHAT. IT'S WORKINGgit add -A! YOU CAN SEND STUFF INTO CHAT AND IT WILL LOAD UP ON ANOTHER DEVICE EEEEEEEE 2024-09-05 00:17:33 +03:00
b9626aa860 Making /chat. Unstable save. Working message loading. Styles of messages are not perfect. Several iu9cachat bugs fixed 2024-09-04 12:39:39 +03:00
fc721d7f5c Fixed a lot of server-side bugs, /list-rooms page bugs. Added favicon, finished /chat-members page 2024-09-02 12:34:49 +03:00
3a8bdb99ec Fixed some problems with deletion buttons in /list-rooms, wrote a sketch for /chat-members 2024-09-02 01:11:28 +03:00
9283590122 fixed some backend bugs. Finished /list-rooms. Now we have these cool popup windows before serious actions 2024-09-01 22:24:55 +03:00
21a23be96e Doing frontend now. Added these cool popup windows in list-rooms.nytl.html 2024-09-01 13:03:52 +03:00
07711a93b0 Fixed some bugs in nytl, een9 ad chat, wrote a skeleton for list-rooms, chat pages, finished login, view-profile, edit-profile-pages. chat-members page still needs attention 2024-09-01 01:29:32 +03:00
Fedor_Kitanin
91d88dc06c добавлены новые файлы 2024-08-31 21:26:12 +03:00
c009b848b5 Merge branch 'master' into chat-wg-test
To pull all the images
2024-08-31 18:59:10 +03:00
cc8aa516bb New logo 2024-08-31 18:58:21 +03:00
yakovlevanton
29e06059d5 Добавлена реализация http-запросов на странице chatSettings.nytl.html 2024-08-31 17:53:38 +03:00
711e495f15 Added some cool svg icons 2024-08-30 20:45:03 +03:00
5062e1ab70 asdasdasdasdasdasdas 2024-08-30 16:27:33 +03:00
632d4069ac Fixed some bugs, added user registration, tweaked syntax of admin control commands 2024-08-30 00:21:14 +03:00
7de352ce1c Fixed buildscript 2024-08-29 20:58:50 +03:00
925229bbda Api updated. done everything except for system message sending, user registration and any testing 2024-08-29 20:46:29 +03:00
yakovlevanton
d7aaeccfa2 Добавлена страница настроек чата 2024-08-29 18:43:36 +03:00
Fedor_Kitanin
d328acfb93 сделан кривой преход на страницу после нажатия кнопки 2024-08-29 13:43:04 +03:00
Fedor_Kitanin
a04a791ff2 изменен login.css 2024-08-29 00:54:56 +03:00
807f0d0eab error messages after incorrect login. sendmessage. small refactoring 2024-08-26 18:04:03 +03:00
5a07cadd4a Skeleton for internalapi response completed 2024-08-26 10:38:38 +03:00
45a1662eae api/geetMessageNeighbours 2024-08-26 00:13:56 +03:00
Fedor_Kitanin
5ba48ff58f добавлены новые файлы 2024-08-25 17:58:18 +03:00
99 changed files with 4021 additions and 1948 deletions

1
.gitignore vendored
View File

@ -16,3 +16,4 @@ iu9-ca-web-chat.db
log/ log/
core core
config/example.json

View File

@ -38,12 +38,12 @@ regexis024_build_system.sh
Помимо самого бинарника нужен файл с настройками сервиса. Формат настроек: JSON. Помимо самого бинарника нужен файл с настройками сервиса. Формат настроек: JSON.
Комментарии не поддерживаются. Пример такого файла находится в example/config.json. Комментарии не поддерживаются. Пример такого файла находится в example/config.json.
Вместе с бинарным фалом так же распространяются ассеты, необъходимые для работы сайта. Вместе с бинарным фалом так же распространяются ассеты, необходимые для работы сайта.
Их можно найти в папке assets. В настроках (поле `["assets"]`) указывается путь до Их можно найти в папке assets. В настроках (поле `config.assets`) указывается путь до
папки с ассетами. Путь может быть как абсолютным, так и относительным к рабочей директории. папки с ассетами. Путь может быть как абсолютным, так и относительным к рабочей директории.
Поле настроек `["database"]` указывает как соединиться с базой данных. Поле настроек `config.database` указывает как соединиться с базой данных.
Поддерживается только база данных sqlite. Поддерживается только хранение в файле. Поддерживается только база данных sqlite3. Поддерживается только хранение в файле.
Поле `["database"]["file"]` указывает путь где хранится sqlite база данных. Поле `config.database.file` указывает путь где хранится sqlite база данных.
Перед тем как использовать сервис нужно его проинициализировать (а точнее проинициализировать Перед тем как использовать сервис нужно его проинициализировать (а точнее проинициализировать
базу данных): базу данных):
@ -60,6 +60,36 @@ regexis024_build_system.sh
Утилита `iu9-ca-web-chat-admin-cli` позволяет администратору сервиса контролировать его через сокет Утилита `iu9-ca-web-chat-admin-cli` позволяет администратору сервиса контролировать его через сокет
(адрес указан в `config["server"]["admin-command-listen"]`). (адрес указан в `config["server"]["admin-command-listen"]`).
По адресам `config.server.admin-command-listen` идёт прослушивание так называемых "команд администратора".
iu9cawebchat определяет свой простой протокол для передачи этих команд.
Утилита iu9-ca-web-chat-admin-cli может отправить текст с некой командой на сервер на этот адрес и получить
ответ от сервера.
```shell
iu9-ca-web-chat-admin-cli <server admin-control address> <command text> [<command text> ...]
```
Дополнительные параметры конкатенируются, разделяясь переводом строки.
Команды администратора:
`updateroopw <new root password>` - сменить пароль пользователя с номером 0
`adduser <user nickname> <user name> <user password> <user bio>` - зарегистрировать пользователя сайта
`8` - остановить сервис
Если нужно ввести пробел или символ `\ ` в любое из этих полей, перед ними нужно поставить `\ `;
Если указать меньше полей, чем нужно, незаполненные поля станут пустыми строками.
Параметры конфигурации `config.lang.whitelist` и `config.lang.force-order` определяют на
какие языки будет локализован сервер, и какие переводы приоритетнее каких.
На данный момент поддерживаются
- `ru-RU`
- `en-US`
Все переводы хранятся в папке `assets/lang`. Для добавления своего перевода нужно форкнуть репозиторий и
сделать копию файла `assets/lang/ru-RU.lang.json` в `assets/lang/XXXXX.lang.json`.
# Список участников # Список участников
1. [Китанин Фёдор](https://gitflic.ru/user/fed-kit) 1. [Китанин Фёдор](https://gitflic.ru/user/fed-kit)

View File

@ -0,0 +1,61 @@
{% ELDEF main JSON pres JSON userinfo JSON openedchat JSON initial_chatUpdResp %}
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" type="image/png" href="/assets/img/favicon.png">
<link rel="stylesheet" href="/assets/css/common.css">
<link rel="stylesheet" href="/assets/css/common-popup.css">
<link rel="stylesheet" href="/assets/css/chat-members.css">
<title>{%w pres.chat-members.members-of %} {%w openedchat.name %}</title>
</head>
<body>
{% PUT chat.pass pres userinfo openedchat initial_chatUpdResp %}
<div id="user-summoning-win" class="popup-window">
<h1 class="popup-window-msg">{%w pres.chat-members.summon-label-nickname %}</h1>
<input class="one-line-input" id="summoned-user-nickname">
<input type="checkbox" id="summoned-user-is-read-only">
<label>{%w pres.chat-members.summon-label-ro %}</label><br>
<button class="popup-window-btn-yes" id="user-summoning-yes">{%w pres.chat-members.yes-summon %}</button>
<button class="popup-window-btn-no" id="user-summoning-no">{%w pres.chat-members.no-summon %}</button>
</div>
<div id="user-deletion-win" class="popup-window">
<!-- header will actually be rewritten before showing the window to include user nickname -->
<h1 id="user-deletion-win-title" class="popup-window-msg"> ||||||||| </h1>
<button class="popup-window-btn-yes" id="user-deletion-yes">{%w pres.chat-members.yes-kick %}</button>
<button class="popup-window-btn-no" id="user-deletion-no">{%w pres.chat-members.no-kick %}</button>
</div>
<div class="document-container resp-container">
<div id="navigation-panel" class="panel">
<a href="/list-rooms" id="go-to-chat-list" class="panel-thing">
<img alt="Go to list of chats" src="/assets/img/list-rooms.svg" width="32px">
</a>
<a href="/user/{% W userinfo.nickname %}" id="go-to-my-profile" class="panel-thing">
<img alt="Go to my profile" src="/assets/img/user.svg" width="32px">
</a>
<p class="panel-thing panel-header-txt">
{%w pres.chat-members.members-list-of %} {% W openedchat.name %} ({% W openedchat.nickname %})
</p>
<a href="/chat/{% W openedchat.nickname %}" id="go-to-chat-settings" class="panel-thing">
<img alt="Back to chat" src="/assets/img/return.svg" width="32px">
</a>
</div>
<div class="dynamic-block-list">
<img id="CM-btn-add" class="button-add centered-block-el" alt="New chat" src="/assets/img/add.svg">
<div class="dynamic-block-list-el-container" id="CM-list">
</div>
</div>
</div>
<script src="/assets/js/common.js"></script>
<script src="/assets/js/common-popup.js"></script>
<script src="/assets/js/chat-members.js"></script>
</body>
</html>
{% ENDELDEF %}

View File

@ -1,40 +1,68 @@
{% ELDEF main JSON pres JSON userinfo %} {% ELDEF pass JSON pres JSON userinfo JSON openedchat JSON initial_chatUpdResp %}
<script>
let pres = {% PUT jsinsert pres %};
let userinfo = {% PUT jsinsert userinfo %};
let openedchat = {% PUT jsinsert openedchat %};
let initial_chatUpdResp = {% PUT jsinsert initial_chatUpdResp %};
</script>
{% ENDELDEF %}
{% ELDEF main JSON pres JSON userinfo JSON openedchat JSON initial_chatUpdResp %}
<!DOCTYPE html> <!DOCTYPE html>
<html lang="ru"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Веб-Чат</title> <link rel="icon" type="image/png" href="/assets/img/favicon.png">
<link rel="stylesheet" href="/assets/css/debug.css">
<link rel="stylesheet" href="/assets/css/common.css">
<link rel="stylesheet" href="/assets/css/common-popup.css">
<link rel="stylesheet" href="/assets/css/chat.css"> <link rel="stylesheet" href="/assets/css/chat.css">
<title>{%w pres.chat.header-chat %} {%w openedchat.name %}</title>
</head> </head>
<body> <body>
<div class="chat-container"> {% PUT chat.pass pres userinfo openedchat initial_chatUpdResp %}
<div class="chat-header">
<span class="room-name">Веб чат</span> <div id="msg-deletion-win" class="popup-window">
<button class="members" onclick="openMembersList()">Показать участников</button> <h1 class="popup-window-msg">{%w pres.chat.reask-delete-message %}</h1>
<!-- mesage preview will be actually rewritten before each window activation-->
<p class="message-in-popup-preview" id="win-deletion-msg-preview">|||||||||</p>
<button class="popup-window-btn-yes" id="msg-deletion-yes">{%w pres.chat.yes-delete %}</button>
<button class="popup-window-btn-no" id="msg-deletion-no">{%w pres.chat.no-delete %}</button>
</div> </div>
<div class="chat-messages" id="chat-messages">
<!-- Сообщения чата будут здесь --> <div class="fullscreen-container resp-container">
</div> <div class="panel" id="navigation-info-panel">
<div class="chat-footer"> <a href="/list-rooms" id="go-to-chat-list" class="panel-thing">
<input type="text" class="chat-input" id="chat-input" placeholder="Введите сообщение..."> <img alt="Go to list of chats" src="/assets/img/list-rooms.svg" width="32px">
<button class="chat-send-button" onclick="sendMessage()">Отправить</button> </a>
</div> <a href="/user/{% W userinfo.nickname %}" id="go-to-my-profile" class="panel-thing">
</div> <img alt="Go to my profile" src="/assets/img/user.svg" width="32px">
<div class="overlay" id="overlay"> </a>
<div class="members-list" id="members-list"> <p class="panel-thing panel-header-txt"> {% W openedchat.name %} ({% W openedchat.nickname %})</p>
<div class="members-list-header"> <a href="/chat-members/{% W openedchat.nickname %}" id="go-to-chat-settings" class="panel-thing">
<span class="close" onclick="closeMembersList()">&times;</span> <img alt="Settings of chat. List of members" src="/assets/img/settings-iron.svg" width="32px">
<h2 class="all-members">Все участники</h2> </a>
</div> </div>
<div class="members-list-body"> <div id="chat-widget">
<ul id="members-list-body"> <div class="chat-debug-rect" id="debug-line-highest" style="background-color: #8600d3"></div>
<!-- Список участников будет добавлен динамически --> <div class="chat-debug-rect" id="debug-line-top-padding" style="background-color: #ff00ae"></div>
</ul> <div class="chat-debug-rect" id="debug-line-bottom-padding" style="background-color: #ff0062"></div>
<div class="chat-debug-rect" id="debug-line-lowest" style="background-color: #ff2f00"></div>
<div id="top-loading" class="message-supercontainer">
<img class="loading-spinner" alt="Loading backward..." src="/assets/gif/loading.gif">
</div>
<div id="bottom-loading" class="message-supercontainer">
<img class="loading-spinner" alt="Loading forward..." src="/assets/gif/loading.gif">
</div>
</div> </div>
<div class="panel" id="input-panel">
<div contentEditable id="message-input" class="panel-thing"></div>
</div>
<script src="/assets/js/common.js"></script>
<script src="/assets/js/common-popup.js"></script>
<script src="/assets/js/chat.js"></script>
</div> </div>
</div>
<script src="/assets/js/chat.js"></script>
</body> </body>
</html> </html>
{% ENDELDEF %} {% ENDELDEF %}

View File

@ -0,0 +1,71 @@
{% ELDEF main JSON pres JSON userinfo JSON alienprofile JSON errors %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" type="image/png" href="/assets/img/favicon.png">
<link rel="stylesheet" href="/assets/css/common.css">
<link rel="stylesheet" href="/assets/css/edit-profile.css">
<title>{%w pres.edit-profile.header-profile-of %} {%w alienprofile.name %}</title>
</head>
<body>
<div class="document-container">
<div id="navigation-panel" class="panel">
<a href="/list-rooms" id="go-to-chat-list" class="panel-thing">
<img alt="Go to list of chats" src="/assets/img/list-rooms.svg" width="32px">
</a>
<a href="/user/{% W userinfo.nickname %}" id="go-to-my-profile" class="panel-thing">
<img alt="Go to my profile" src="/assets/img/user.svg" width="32px">
</a>
</div>
{% FOR error IN errors %}
<div class="server-notif-error-msg-box">
{% W error.text %}
</div>
{% ENDFOR %}
<div class="profile-container">
<h2 class="profile-name-text">{% W alienprofile.name %}</h2>
<h3 class="profile-nickname-text">{%w pres.edit-profile.directive-nickname %} {% W alienprofile.nickname %}</h3>
<p class="profile-bio-text">
{% W alienprofile.bio %}
</p>
</div>
<div class="profile-container">
<h1 class="wide-centered-header">{%w pres.edit-profile.change-user-attributes %}</h1>
<form action = "/user/{% W alienprofile.nickname %}" method="post" enctype="application/x-www-form-urlencoded">
<table class="logins-input-table">
<tr>
<td class="logins-input-td1">
<label for="new-name-input">{%w pres.edit-profile.directive-name %}</label>
</td>
<td class="logins-input-td2">
<input name="name" id="new-name-input" type="text"
placeholder="{%w pres.edit-profile.placeholder-name %}" class="one-line-input">
</td>
</tr>
<tr>
<td class="logins-input-td1">
<label for="new-password-input">{%w pres.edit-profile.directive-password %}</label>
</td>
<td class="logins-input-td2">
<input name="password" id="new-password-input" type="password"
placeholder="{%w pres.edit-profile.placeholder-password %}" class="one-line-input">
</td>
</tr>
</table>
<label for="input-change-bio">{%w pres.edit-profile.directive-bio %}</label>
<br>
<textarea name="bio" id="input-change-bio" class="multiline-input"></textarea>
<button class="action-button centered-block-el" type="submit">{%w pres.edit-profile.act-submit %}</button>
</form>
</div>
</div>
</body>
</html>
{% ENDELDEF%}

View File

@ -0,0 +1,11 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="icon" type="image/png" href="/assets/img/favicon.png">
<title>Not found</title>
</head>
<body>
<h1>Page not found</h1>
</body>
</html>

View File

@ -1,68 +1,74 @@
{% ELDEF main JSON pres JSON userinfo %} {% ELDEF main JSON pres JSON userinfo JSON initial_chatListUpdResp %}
<!DOCTYPE html> <!DOCTYPE html>
<html lang="{% WRITE pres.lang %}"> <html lang="{% W pres.lang %}">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Список Чат-Комнат</title> <title>{%w pres.list-rooms.header %}</title>
<link rel="icon" type="image/png" href="/assets/img/favicon.png">
<link rel="stylesheet" href="/assets/css/common.css">
<link rel="stylesheet" href="/assets/css/common-popup.css">
<link rel="stylesheet" href="/assets/css/list-rooms.css"> <link rel="stylesheet" href="/assets/css/list-rooms.css">
</head> </head>
<body> <body>
{% PUT pass-pres-userinfo pres userinfo %} <script>
<div class="container"> let pres = {% PUT jsinsert pres %};
<h1 style="color: white;">Выберите Чат-Комнату</h1> let userinfo = {% PUT jsinsert userinfo %};
<ul class="room-list"> let initial_chatListUpdResp = {% PUT jsinsert initial_chatListUpdResp %};
<!-- Здесь будет список комнат --> </script>
</ul> <div id="chat-creation-win" class="popup-window">
<button class="create-room-button" onclick="openCreateRoomModal()">Создать Комнату</button> <h1 class="popup-window-msg">{%w pres.list-rooms.new-chat-header %}</h1>
</div> <table class="id-str-input-table">
<tr>
<td class="id-str-input-td1">
<label for="chat-nickname-input">{%w pres.list-rooms.directive-nickname %}</label>
</td>
<td class="id-str-input-td2">
<input id="chat-nickname-input" type="text" class="one-line-input"
placeholder="{%w pres.list-rooms.placeholder-nickname %}" required>
</td>
</tr>
<tr>
<td class="id-str-input-td1">
<label for="chat-name-input">{%w pres.list-rooms.directive-name %}</label>
</td>
<td class="id-str-input-td2">
<input id="chat-name-input" type="text" class="one-line-input"
placeholder="{%w pres.list-rooms.placeholder-name %}" required>
</td>
</tr>
</table>
<h1 class="popup-window-msg">{%w pres.list-rooms.reask-create-new-chat %}</h1>
<button class="popup-window-btn-yes" id="chat-creation-win-yes">{%w pres.list-rooms.yes-create %}</button>
<button class="popup-window-btn-no" id="chat-creation-win-no">{%w pres.list-rooms.no-create %}</button>
</div>
<!-- Модальное окно для создания комнаты --> <div id="chat-renunciation-win" class="popup-window">
<!--<div id="createRoomModal" class="modal">--> <h1 id="chat-renunciation-win-title" class="popup-window-msg">||||||||||</h1>
<!-- <div class="modal-content">--> <button class="popup-window-btn-yes" id="chat-renunciation-win-yes">{%w pres.list-rooms.yes-leave %}</button>
<!-- <div class="modal-header">--> <button class="popup-window-btn-no" id="chat-renunciation-win-no">{%w pres.list-rooms.no-leave %}</button>
<!-- <span class="close" onclick="closeCreateRoomModal()">&times;</span>--> </div>
<!-- <h2>Создать Комнату</h2>-->
<!-- </div>-->
<!-- <div class="modal-body">-->
<!-- <input type="text" id="newRoomName" placeholder="Название комнаты">-->
<!-- <input type="password" id="newRoomNickname" placeholder="Никнейм комнаты">-->
<!-- </div>-->
<!-- <div id="error"></div>-->
<!-- <div class="modal-footer">-->
<!-- <button class="join-button" onclick="createRoom()">Создать</button>-->
<!-- </div>-->
<!-- </div>-->
<!--</div>-->
<!--&lt;!&ndash; Модальное окно для добавления участников &ndash;&gt;--> <div class="document-container resp-container">
<!--<div class="overlay" id="add_members">--> <div id="navigation-panel" class="panel">
<!-- <div class="add-members">--> <a href="/user/{%w userinfo.nickname %}" id="go-to-my-profile" class="panel-thing">
<!-- <div class="add-members-header">--> <img alt="Go to my profile" src="/assets/img/user.svg" width="32px">
<!-- <span class="close" onclick="closeAdd()">&times;</span>--> </a>
<!-- <h2>Добавить участников</h2>--> <p class="panel-thing panel-header-txt">
<!-- </div>--> {%w pres.list-rooms.page-description %}
<!-- <div class="add-members-body">--> </p>
<!-- <input type="text" id="newMemberLogin" placeholder="Логин пользователя">--> </div>
<!-- </div>-->
<!-- <div class="add-members-footer">--> <div class="dynamic-block-list">
<!-- <button class="add-member-button" onclick="addMember()">Добавить</button>--> <img id="CL-bacbe" class="button-add centered-block-el" alt="New chat" src="/assets/img/add.svg">
<!-- </div>--> <div class="dynamic-block-list-el-container" id="CL-dblec">
<!-- </div>-->
<!--</div>--> </div>
<!--<div class="overlay" id="delete-chat">--> </div>
<!-- <div class="delete-chat">--> </div>
<!-- <div class="delete-chat-header">--> <script src="/assets/js/common.js"></script>
<!-- <span class="delete-close" onclick="closeConfirm()">&times;</span>--> <script src="/assets/js/common-popup.js"></script>
<!-- <h2>Вы уверены, что хотите удалить чат?</h2>--> <script src="/assets/js/list-rooms.js"></script>
<!-- </div>-->
<!-- <div class="delete-chat-body">-->
<!-- <button class="confirm" onclick="deleteChat()">Да</button>-->
<!-- <button class="cancel" onclick="closeConfirm()">Нет</button>-->
<!-- </div>-->
<!-- </div>-->
<!--</div>-->
<script src="/assets/js/list-rooms.js"></script>
</body> </body>
</html> </html>
{% ENDELDEF %} {% ENDELDEF %}

View File

@ -1,26 +1,43 @@
{% ELDEF main JSON pres JSON userinfo %} {% ELDEF main JSON pres JSON userinfo JSON errors %}
<!DOCTYPE html> <!DOCTYPE html>
<html lang="{% WRITE pres.lang %}"> <html lang="{% W pres.lang %}">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% WRITE pres.phr.decl.page-login %}</title> <link rel="icon" type="image/png" href="/assets/img/favicon.png">
<link rel="stylesheet" href="/assets/css/common.css">
<link rel="stylesheet" href="/assets/css/login.css"> <link rel="stylesheet" href="/assets/css/login.css">
<title>{% W pres.login.header %}</title>
</head> </head>
<body> <body>
{% PUT pass-pres-userinfo pres userinfo %} {% FOR error IN errors %}
<div class="form-container"> <div class="server-notif-error-msg-box">
<h1 class="hide-cursor no-select">{% WRITE pres.phr.decl.enter %}</h1> {% W error.text %}
<form action="/login" method="post" enctype="application/x-www-form-urlencoded"> </div>
<label for="nickname">{% WRITE pres.phr.decl.nickname %}</label> {% ENDFOR %}
<input type="text" name="nickname" id="nickname"><br>
<label for="password">{% WRITE pres.phr.decl.password %}</label> <div class="form-container">
<input type="password" name="password" id="password"><br> <h1 class="wide-centered-header">{% W pres.login.header %}</h1>
<button type="submit" class="hide-cursor no-select">{% WRITE pres.phr.act.enter %}</button> <form action="/login" method="post" enctype="application/x-www-form-urlencoded">
</form> <table class="logins-input-table">
</div> <tr>
<td class="logins-input-td1"><label for="input-nickname">{% W pres.login.directive-nickname %}</label></td>
<td class="logins-input-td2">
<input type="text" name="nickname" id="input-nickname"
placeholder="{% W pres.login.placeholder-nickname %}" class="one-line-input" required>
</td>
</tr>
<tr>
<td class="logins-input-td1"><label for="input-password">{% W pres.login.directive-password %}</label></td>
<td class="logins-input-td2">
<input name="password" id="input-password" type="password"
placeholder="{% W pres.login.placeholder-password %}" class="one-line-input" required>
</td>
</tr>
</table>
<button class="action-button centered-block-el" type="submit">{% W pres.login.act %}</button>
</form>
</div>
</body> </body>
</html> </html>

View File

@ -1,6 +0,0 @@
{% ELDEF main JSON pres JSON userinfo %}
<script>
let pres = {% PUT jsinsert pres %};
let userinfo = {% PUT jsinsert userinfo %};
</script>
{% ENDELDEF %}

View File

@ -1,39 +0,0 @@
{% ELDEF main JSON pres JSON userinfo %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="/assets/css/profile.css">
<title>Профиль</title>
</head>
<body>
<div class="main-container">
<div class="profile-header">
<h1 style="color: white; text-align: center;">Профиль пользователя</h1>
<a class="return" href="chat.nytl.html">Назад</a>
</div>
<form>
<div class="columns">
<div class="column">
<img class="avatar" src="/assets/img/empty_avatar.png" id="avatar" height="200" width="200"><br>
<input type="file" id="fileInput" style="display:none">
<button class="add" type="button" onclick="document.getElementById('fileInput').click();">Изменить фото</button><br>
</div>
<div class="column">
<input type="text" name="username" placeholder = "Имя пользователя" value="Some Name" id="username"><br>
<input type="text" name="login" placeholder="Логин" value="some_login123" id="login" readonly><br>
</div>
</div>
<h3 style="color:#007bb5;">О себе</h3>
<div class="additional-info">
<textarea name="bio" placeholder="Напишите о себе..." id="bio"></textarea>
</div>
<button class="save" type="submit">Сохранить изменения</button>
</form>
</div>
<script src="/assets/js/list-rooms.js"> </script>
</body>
</html>
{% ENDELDEF%}

View File

@ -0,0 +1,51 @@
{% ELDEF main JSON pres JSON userinfo JSON messages %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" type="image/png" href="/assets/img/favicon.png">
<link rel="stylesheet" href="/assets/css/common.css">
<link rel="stylesheet" href="/assets/css/register.css">
<title>{% W pres.register.header %}</title>
</head>
<body>
{% FOR error IN messages %}
<div class="server-notif-error-msg-box">
{% W error.text %}
</div>
{% ENDFOR %}
<div class="form-container">
<h1 class="wide-centered-header">{% W pres.register.header %}</h1>
<form action="/register" method="post" enctype="application/x-www-form-urlencoded">
<table class="reg-input-table">
<tr>
<td class="reg-input-td1"><label for="input-nickname">{% W pres.register.directive-nickname %}</label></td>
<td class="reg-input-td2">
<input type="text" name="nickname" id="input-nickname"
placeholder="{% W pres.register.placeholder-nickname %}" class="one-line-input" required>
</td>
</tr>
<tr>
<td class="reg-input-td1"><label for="input-name">{% W pres.register.directive-name %}</label></td>
<td class="reg-input-td2">
<input type="text" name="name" id="input-name"
placeholder="{% W pres.register.placeholder-name %}" class="one-line-input" required>
</td>
</tr>
<tr>
<td class="reg-input-td1"><label for="input-password">{% W pres.register.directive-password %}</label></td>
<td class="reg-input-td2">
<input name="password" id="input-password" type="password"
placeholder="{% W pres.register.placeholder-password %}" class="one-line-input" required>
</td>
</tr>
</table>
<button class="action-button centered-block-el" type="submit">{% W pres.register.act %}</button>
</form>
</div>
</body>
</html>
{%ENDELDEF%}

View File

@ -1,27 +0,0 @@
{% ELDEF main JSON pres JSON userinfo %}
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Страница Регистрации</title>
<link rel="stylesheet" href="/assets/css/registration.css">
</head>
<body>
<div class="form-container">
<h1 class="hide-cursor no-select">Вход</h1>
<form action="assets/html/list-rooms.html" method="post">
<input type="text" name="username" placeholder="Имя пользователя" id="username"><br>
<input type="text" name="login" placeholder="Логин" id="login"><br>
<input type="password" name="password" placeholder="Пароль" id="password"><br>
<button type="submit" class="hide-cursor no-select">Зарегистрироваться</button>
<div id="error"></div>
</form>
</div>
<script src="assets/js/registration.js"></script>
</body>
</html>
{% ENDELDEF %}

View File

@ -0,0 +1,36 @@
{% ELDEF main JSON pres JSON userinfo JSON alienprofile %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" type="image/png" href="/assets/img/favicon.png">
<link rel="stylesheet" href="/assets/css/common.css">
<!-- This page is so simple, that it does not even have it's separate css file -->
<title>{%w pres.view-profile.header-profile-of %} {%w alienprofile.name %}</title>
</head>
<body>
<div class="document-container resp-container">
<div id="navigation-panel" class="panel">
<a href="/list-rooms" id="go-to-chat-list" class="panel-thing">
<img alt="Go to list of chats" src="/assets/img/list-rooms.svg" width="32px">
</a>
<a href="/user/{% W userinfo.nickname %}" id="go-to-my-profile" class="panel-thing">
<img alt="Go to my profile" src="/assets/img/user.svg" width="32px">
</a>
</div>
<div class="profile-container">
<h2 class="profile-name-text">{%w alienprofile.name %}</h2>
<h3 class="profile-nickname-text">{%w pres.view-profile.directive-nickname%} {%w alienprofile.nickname %}</h3>
<p class="profile-bio-text">
{%w alienprofile.bio %}
</p>
</div>
</div>
</body>
</html>
{% ENDELDEF%}

View File

@ -0,0 +1,33 @@
#CM-btn-add {
margin-top: 6px;
margin-bottom: 4px;
display: none;
}
.CM-member-box {
display: flex;
flex-direction: row;
}
.CL-member-box-nickname {
margin-left: 8px;
justify-self: flex-start;
}
.CM-member-box-name {
margin-left: 14px;
justify-self: flex-start;
}
.CM-member-box-role {
margin-left: auto;
justify-self: flex-end;
}
.CM-member-box-leave-btn {
margin-left: 10px;
margin-right: 8px;
justify-self: flex-end;
width: 16px;
cursor: pointer;
}

View File

@ -1,202 +1,143 @@
body { body, html {
font-family: Arial, sans-serif;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
background-color: #e5e5e5;
}
.chat-container {
width: 100%;
max-width: 800px;
height: 90vh;
background-color: white;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
display: flex;
flex-direction: column;
border-radius: 8px;
overflow: hidden;
}
.chat-header {
background-color: #007bb5;
color: white;
padding: 25px;
display: flex;
justify-content: center;
align-items: center;
position: relative;
}
.room-name {
position: absolute;
left: 50%;
font-size: 24px;
}
.members {
border: none;
position: absolute;
left: 80%;
border-radius: 10px;
cursor: pointer;
width: 150px;
background-color: #f7f7f7;
height: 25px;
transition: background-color 0.3s ease;
}
.members:hover {
background-color: #218838;
}
.chat-messages {
flex: 1;
padding: 15px;
overflow-y: auto;
background-color: #f7f7f7;
}
.chat-message {
display: flex;
align-items: flex-start;
margin-bottom: 15px;
}
.chat-message .avatar {
width: 40px;
height: 40px;
border-radius: 50%;
overflow: hidden;
margin-right: 10px;
}
.chat-message .avatar img {
width: 100%;
height: 100%; height: 100%;
object-fit: cover;
} }
.chat-message .message-content { #chat-widget {
max-width: 70%; position: relative;
flex: 1;
background-color: #f1f1f1;
overflow: hidden;
}
.message-supercontainer{
position: absolute;
width: 100%;
left: 0;
/*background-color: rgba(150, 0, 100, 50);*/
background-color: rgba(0, 0, 0, 0);
/*display: flex;*/
/*flex-direction: row;*/
/*justify-content: center;*/
}
.message-box{
/*display: inline-block;*/
padding: 5px;
}
.message-box-mine {
margin-right: 5px;
margin-left: auto;
max-width: 400px;
border: 2px solid #82a173;
padding: 5px;
background-color: #cdff9b;
color: black;
/*justify-self: flex-end;*/
}
.message-box-alien {
margin-left: 5px;
margin-right: auto;
max-width: 400px;
border: 2px solid dimgrey;
padding: 5px;
background-color: white; background-color: white;
padding: 10px; color: black;
border-radius: 8px; /*justify-self: flex-start;*/
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
} }
.chat-message .message-content .username { /* Only non-system messages can be deleted. Deleted messages do not have delete button
This class should be used with (and, ofcourse, after) class message-box-my/message-box-alien */
.message-box-deleted {
border: 2px solid #cb0005;
background-color: #ffc1bc;
}
.message-box-deleted .message-box-msg{
font-weight: bold; font-weight: bold;
margin-bottom: 5px;
} }
.chat-message .message-content .text { .message-box-system {
margin-left: auto;
margin-right: auto;
max-width: 500px;
padding: 4px;
background-color: #2d2d2d;
color: white;
font-weight: bold;
justify-self: center;
}
/* in #chat-widget .message-box */
.message-box-top{
/* You see, each message contains a 20+2+2 px high icon that HAS TO BE LOADED FIRST.
This happens after window.onload, so I added a crutch: loading won't update height in
unpredictable moment. cause it will be already high enough. BUGA-GA-GA!! */
min-height: 30px;
display: block;
}
.message-box-sender-name{
color: black;
text-decoration: none;
padding: 2px;
display: inline;
font-size: 0.8em;
}
/* Additional to message-box-sender-name */
.message-box-sender-shortname {
font-weight: bold;
padding-left: 3px;
font-size: 0.94em;
}
.message-box-sender-name:hover{
color: #1060ff
}
.message-box-button{
width: 20px;
padding: 2px;
cursor: pointer;
display: inline;
}
.message-box-msg{
word-wrap: break-word; word-wrap: break-word;
} }
.chat-footer { #input-panel {
display: flex; min-height: 20px;
}
#message-input {
padding: 15px; padding: 15px;
padding-left: 50px; height: auto;
border-top: 1px solid #ddd;
}
.chat-input {
flex: 1;
padding: 10px;
border: 1px solid #ddd;
border-radius: 20px;
margin-right: 10px;
outline: none;
}
.chat-send-button {
padding: 10px 20px;
border: none;
background-color: #0088cc;
color: white;
border-radius: 20px;
cursor: pointer;
outline: none;
}
.members-list {
display: none;
position: fixed;
background-color: #fff;
margin: 10% auto;
padding: 20px;
border: 1px solid #888;
width: 80%;
max-width: 400px;
border-radius: 10px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
}
.members-list-header {
display: flex;
}
.all-members {
position: absolute;
left: 32%;
top: 0%;
margin-bottom: 30px;
font-family: Arial, sans-serif;
}
.close {
position: absolute;
right: 5%;
font-size: 24px;
font-weight: bold;
}
.members-list span {
cursor: pointer;
}
.members-list-body ul {
list-style-type: none;
left: 0%;
}
.members-list-body img {
margin-top: 10px;
left: 0%;
height: 30px;
width: 30px;
border-radius: 50%;
}
.members-list-body a {
margin-left: 5px;
margin-top: 10px;
text-decoration: none;
color: black;
}
.members-list-body a:hover {
text-decoration: underline;
color: #0088cc;
}
.members-list-body button {
padding: 5px 10px;
border: none;
background-color: #dc2e45;
color: white;
border-radius: 20px;
position: absolute;
left: 300px;
margin-top: 20px;
cursor: pointer;
transition: background-color 0.3s ease;
}
.members-list-body button:hover {
background-color: #881527;
}
.overlay {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%; width: 100%;
height: 100%; display: inline-block;
background-color: rgba(0, 0, 0, 0.5); background-color: white;
justify-content: center; border: 1px solid #1000d0;
align-items: center; border-radius : 7px;
z-index: 1000; font-size: .9rem;
margin: 10px;
} }
.chat-send-button:hover {
background-color: #007bb5; .message-in-popup-preview{
border: 4px solid red;
width: 80%;
max-width: 200px;
margin-left: auto;
margin-right: auto;
max-height: 20%;
word-wrap: break-word;
}
.loading-spinner{
margin-left: auto;
margin-right: auto;
background-color: rgba(0, 0, 0, 0);
width: 25px;
display: block;
} }

View File

@ -0,0 +1,50 @@
.popup-overlay-veil {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.6);
z-index: 99;
display: none; /* Hidden by default */
}
.popup-window {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: white;
padding: 20px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
z-index: 100;
display: none;
}
.popup-btn {
display: inline;
padding: 5px;
border-bottom: 3px;
}
.popup-window-btn-yes {
background-color: #0c7f0e;
border-radius: 5px;
padding: 12px;
color: white;
}
.popup-window-btn-no {
background-color: #ff0005;
border-radius: 5px;
padding: 12px;
color: white;
}
.popup-window-msg {
padding-left: 20px;
font-weight: bold;
font-size: 1.3em;
}

206
assets/css/common.css Normal file
View File

@ -0,0 +1,206 @@
/* Profile view elements */
.profile-container {
background: white;
border-radius: 5px;
padding: 20px;
margin-top: 60px; /* Space below the fixed panel */
box-shadow: 0 10px 15px rgba(0, 0, 0, 0.3);
}
.profile-name-text {
color: black;
}
.profile-nickname-text{
color: #444;
text-align: left;
}
.profile-bio-text {
padding-top: 40px;
text-align: left;
line-height: 1.6;
color: black;
}
/* Panels */
.panel {
width: 100%;
border: 2px solid blue;
background-color: #54b3ff;
display: flex;
flex-direction: row;
align-items: center;
}
.panel-thing {
padding: 6px;
}
.panel-header-txt{
color: white;
font-size: 1.9em;
flex: 1;
text-align: center;
}
/* Containers for the whole document */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: Arial, sans-serif;
}
.document-container {
width: 80%; /* Full width of the viewport */
margin: 0 auto; /* Center the container horizontally */
}
.fullscreen-container {
width: 80%; /* Full width of the viewport */
height: 100vh; /* Full height of the viewport */
display: flex;
flex-direction: column; /* Stack children vertically */
margin: 0 auto; /* Center the container horizontally */
}
@media (orientation: landscape) {
.resp-container{
width: 80%;
}
}
@media (orientation: portrait){
.resp-container{
width: 100%;
}
}
body {
background-color: #f000f0;
background-image: url("/assets/img/clavicle-transparent.png"), url("/assets/img/broken-clavicle.png");
background-repeat: revert;
background-size: 10%, 25%;
}
/* Notifications, returned from server and embedded into html page at render-time */
.server-notif-error-msg-box{
font-size: 1.3em;
text-align: center;
padding: 10px;
border: 2px solid red;
border-radius: 30px;
background-color: #ff5050;
max-width: 40%;
margin: 15px auto;
}
/* Centered headers */
.wide-centered-header {
width: 100%;
text-align: center;
font-size: 1.4em;
}
/* Cool buttons with text */
.action-button {
padding: 10px 15px;
background-color: #007bff;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
transition: background-color 0.3s;
}
.action-button:hover {
background-color: #0056b3; /* Darker blue on hover */
}
/* This is for centering non-100%wide block */
.centered-block-el {
display: block;
margin-left: auto;
margin-right: auto;
}
/* Beautiful text input */
.one-line-input {
width: 100%;
padding: 8px;
margin: 8px 0;
border: 1px solid #ccc;
border-radius: 4px;
}
.multiline-input {
width: 100%;
/*max-width: 600px;*/
height: 200px;
padding: 10px;
font-size: 1.15em;
border: 2px solid #ccc;
border-radius: 5px;
box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.1); /* Subtle shadow */
outline: none; /* Remove default outline on focus */
resize: vertical; /* Allow resizing vertically */
transition: border-color 0.15s, box-shadow 0.15s; /* Smooth transition for border color and shadow */
}
.multiline-input:focus {
border-color: #007bff; /* Change border color on focus */
box-shadow: 0 0 5px rgba(0, 123, 255, 0.5); /* Shadow on focus */
}
/* Handles the case of list of elements with dickanme, name, role and delete button
For list of chats and list of users in chat */
.dynamic-block-list {
margin-top:12px;
display: flex;
flex-direction: column;
background-color: white;
border: 1px solid #c7c7c7;
align-items: stretch;
padding-left: 8px;
padding-right: 8px;
padding-bottom: 8px;
}
.dynamic-block-list-el {
margin-top: 8px;
background-color: white;
border: 1px solid #c7c7c7;
color: black;
padding: 5px;
}
.button-add{
width: 50px;
cursor: pointer;
}
.dynamic-block-list-el-container{
width: 100%;
}
.entity-nickname-txt {
font-weight: bold;
color: black;
text-decoration: none;
font-size: 1.5em;
}
.entity-reg-field-txt {
/* For name and role */
color: #242424;
text-decoration: none;
font-size: 1.5em;
}

8
assets/css/debug.css Normal file
View File

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

View File

@ -0,0 +1,23 @@
/* The morbid thing */
table.logins-input-table {
width: 100%;
border-collapse: collapse; /* Combine borders */
}
.logins-input-td1, .logins-input-td2 {
border: none;
}
.logins-input-td1 {
text-align: left;
padding-right: 5px;
white-space: nowrap; /* Prevent text wrap, keeping it in one line */
overflow: hidden; /* Hide overflow content */
text-overflow: ellipsis; /* Show ellipsis for overflowing text */
}
.logins-input-td2 {
width: 100%;
}
#input-change-bio{
margin-top: 5px;
margin-bottom: 5px;
}

View File

@ -1,253 +1,166 @@
/* Общие стили */
body { body {
font-family: Arial, sans-serif; font-family: 'Roboto', sans-serif;
background-color: #f0f0f0; background-color: #f7f9fc;
color: #333;
margin: 0; margin: 0;
padding: 0; padding: 0;
box-sizing: border-box;
} }
.container { /* Панель навигации */
max-width: 800px; .panel {
margin: 30px auto;
padding: 20px;
background-color: #007bff; background-color: #007bff;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); padding: 10px;
border-radius: 8px; color: white;
}
h1 {
text-align: center;
color: #fff;
}
.room-list {
list-style-type: none;
padding: 0;
}
.room-item {
display: flex; display: flex;
justify-content: space-between;
align-items: center; align-items: center;
padding: 15px;
margin: 10px 0;
background-color: #fafafa;
border: 1px solid #ddd;
border-radius: 5px;
transition: background-color 0.3s ease;
} }
.room-item:hover { .panel-thing {
background-color: #eaeaea; margin-right: 20px;
text-decoration: none;
color: white;
} }
.room-name { .panel-header-txt {
font-size: 18px; font-size: 18px;
color: #555; font-weight: bold;
} }
.join-button { /* Стили динамических блоков */
padding: 10px 15px; .dynamic-block-list {
font-size: 16px; display: flex;
color: white; flex-direction: column;
background-color: #007bff; align-items: center;
border: none;
border-radius: 5px;
cursor: pointer;
transition: background-color 0.3s ease;
}
.add-members-header {
text-align: center;
}
.add-members-footer {
text-align: right;
margin-top: 5px;
}
.add-members-button {
background-color: #218838;
padding: 10px 15px;
font-size: 16px;
color: white;
border: none;
border-radius: 5px;
position: absolute;
margin-left: 502px;
cursor: pointer;
transition: background-color 0.3s ease;
}
.add-members-button:hover {
background-color: #006509
}
.delete-chat-button {
background-color: #dc2e45;
border: none;
color: white;
font-size: 16px;
border-radius: 5px;
position: absolute;
cursor: pointer;
transition: background-color 0.3s ease;
padding: 10px 15px;
margin-left: 380px;
}
.delete-chat-button:hover {
background-color: #881527;
}
#newMemberLogin {
width: 93.5%;
padding: 10px;
margin: 10px 0;
border: 1px solid #ddd;
border-radius: 5px;
}
.add-member-button {
background-color: #218838;
padding: 10px 15px;
font-size: 16px;
color: white;
border: none;
border-radius: 5px;
position: absolute;
margin-left: -105px;
cursor: pointer;
transition: background-color 0.3s ease;
}
.join-button:hover {
background-color: #0056b3;
}
.modal {
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; 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 { .dynamic-block-list-el-container {
padding: 10px;
color: #333;
}
.modal-header {
text-align: center;
}
.modal-footer {
text-align: right;
}
.modal input {
width: 93.5%;
padding: 10px;
margin: 10px 0;
border: 1px solid #ddd;
border-radius: 5px;
}
.create-room-button {
display: block;
width: 100%; width: 100%;
padding: 10px; max-width: 600px;
font-size: 16px;
color: white;
background-color: #1609ab;
border: none;
border-radius: 5px;
cursor: pointer;
transition: background-color 0.3s ease;
margin-top: 20px; margin-top: 20px;
} }
.create-room-button:hover { /* Кнопка добавления */
background-color: #218838; .button-add {
width: 50px;
height: 50px;
cursor: pointer;
transition: transform 0.2s;
} }
.overlay { .button-add:hover {
display: none; transform: scale(1.1);
position: fixed; }
top: 0;
left: 0; /* Всплывающие окна */
.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%; width: 100%;
height: 100%; border-collapse: collapse;
background-color: rgba(0, 0, 0, 0.5); margin-bottom: 20px;
justify-content: center; }
.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; align-items: center;
z-index: 1000; min-height: 40px;
width: 100%;
} }
.overlay .add-members { /* Текст внутри блока комнаты */
background-color: white; .CL-my-chat-box-nickname, .CL-my-chat-box-name, .CL-my-chat-box-my-role {
padding: 30px; margin-left: 8px;
border-radius: 10px; justify-self: flex-start;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
max-width: 400px;
width: 100%;
height: 18%;
position: fixed;
} }
.overlay .delete-chat {
background-color: white; /* Крестик в правом верхнем углу */
padding: 30px; .CL-my-chat-box-leave-btn {
border-radius: 10px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
max-width: 400px;
width: 100%;
height: 18%;
position: fixed;
}
.delete-close {
color: #aaa;
float: right;
font-size: 28px;
font-weight: bold;
cursor: pointer;
}
.delete-chat-header {
text-align: center;
}
.confirm {
background-color: #1609ab;
padding: 20px 70px;
font-size: 16px;
color: white;
border: none;
border-radius: 5px;
position: absolute; position: absolute;
margin-left: 20px; top: 8px;
right: 8px;
width: 16px;
height: 16px;
background: url('/assets/img/close.svg') no-repeat center;
background-size: cover;
cursor: pointer; cursor: pointer;
transition: background-color 0.3s ease;
}
.cancel {
background-color: #1609ab;
padding: 20px 70px;
font-size: 16px;
color: white;
border: none; border: none;
border-radius: 5px; transition: transform 0.2s;
position: absolute; }
margin-left: 220px;
cursor: pointer; .CL-my-chat-box-leave-btn:hover {
transition: background-color 0.3s ease; transform: scale(1.2);
}
/* Дизайн списка комнат остается таким же */
#CL-bacbe {
margin-top: 6px;
margin-bottom: 4px;
} }
.close {
color: #aaa;
float: right;
font-size: 28px;
font-weight: bold;
cursor: pointer;
}

View File

@ -1,77 +1,46 @@
dy { body {
font-family: Arial, sans-serif;
display: flex; display: flex;
flex-direction: column;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
height: 100vh; height: 100vh; /* Full viewport height */
margin: 0; margin: 0;
background-color: #e5e5e5;
} }
.form-container { .form-container {
width: 100%; background-color: #ffffff;
max-width: 400px; padding: 20px;
background-color: white; border-radius: 10px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
display: flex;
flex-direction: column;
border-radius: 8px;
padding: 40px;
text-align: center;
} }
h1 { @media (orientation: landscape) {
margin-bottom: 20px; .form-container{
color: #2F4F4F; width: 50%;
}
} }
input { @media (orientation: portrait){
width: 100%; .form-container{
background: #f7f7f7; width: 85%;
font-size: 16px; }
padding: 10px;
border: 1px solid #ddd;
border-radius: 20px;
margin-bottom: 15px;
outline: none;
} }
button {
/* The morbid thing */
table.logins-input-table {
width: 100%; width: 100%;
padding: 15px; border-collapse: collapse; /* Combine borders */
}
.logins-input-td1, .logins-input-td2 {
border: none; 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;
} }
.logins-input-td1 {
button:hover, padding-right: 5px;
button:focus-visible { white-space: nowrap; /* Prevent text wrap, keeping it in one line */
background-color: #007bb5; overflow: hidden; /* Hide overflow content */
text-overflow: ellipsis; /* Show ellipsis for overflowing text */
} }
.logins-input-td2 {
.hide-cursor::placeholder { width: 100%;
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;
} }

View File

@ -1,129 +0,0 @@
body {
display: flex;
justify-content: center;
align-items: center;
height: 90vh;
background-color: #e5e5e5;
font-family: Arial, sans-serif;
}
.main-container {
width: 700px;
height: 700px;
border-color: antiquewhite;
background-color: white;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
border-radius: 10px;
}
.profile-header {
width: 700px;
height: 160px;
border-color: antiquewhite;
background-color: #0088cc;
border-radius: 10px;
position: relative;
}
.return {
background-color: #f0f0f0;
cursor: pointer;
width: 100px;
text-decoration: none;
color: black;
display: flex;
justify-content: center;
align-items: center;
height: 30px;
border-radius: 10px;
position: absolute;
left: 20px;
top: 25px;
border: none;
}
.return:hover{
text-decoration: underline;
color: #0088cc;
}
form {
display: flex;
flex-direction: column;
align-items: center;
}
.columns {
display: flex;
justify-content: center;
align-items: flex-start;
gap: 20px;
margin-bottom: 20px;
}
.column {
display: flex;
flex-direction: column;
align-items: center;
}
.add {
width: 100px;
height: 40px;
border-width: 2px;
cursor: pointer;
font-size: 16px;
border-radius: 10px;
}
.add:hover {
background-color: #007bb5;
}
.image-button:hover {
opacity: 0.8;
}
.image-button:active {
transform: scale(0.95);
}
#login {
font-family: Arial, sans-serif;
font-size:16px;
width: 150px;
height: 20px;
border-radius: 10px;
border-color: #2F4F4F;
}
#username {
font-family: Arial, sans-serif;
font-size:16px;
width: 150px;
height: 20px;
margin-bottom: 1px;
margin-top: 50px;
border-radius: 10px;
border-color: #2F4F4F;
}
#bio {
height: 150px;
width: 500px;
padding: 10px;
box-sizing: border-box;
font-family: Arial, sans-serif;
font-size:14px;
text-align: left;
vertical-align: top;
margin-bottom: 5px;
}
.save {
cursor:pointer;
font-size: 16px;
border-radius: 15px;
border-color: #2F4F4F;
height: 40px;
width: 150px;
}
.save:hover {
background-color: #007bb5;
}
.avatar {
border-radius: 50%;
object-fit: cover;
}

45
assets/css/register.css Normal file
View File

@ -0,0 +1,45 @@
body {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
height: 100vh; /* Full viewport height */
margin: 0;
}
.form-container {
background-color: #ffffff;
padding: 20px;
border-radius: 10px;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
}
@media (orientation: landscape) {
.form-container{
width: 60%;
}
}
@media (orientation: portrait){
.form-container{
width: 90%;
}
}
/* The morbid thing */
table.reg-input-table {
width: 100%;
border-collapse: collapse; /* Combine borders */
}
.reg-input-td1, .reg-input-td2 {
border: none;
}
.reg-input-td1 {
padding-right: 5px;
white-space: nowrap; /* Prevent text wrap, keeping it in one line */
overflow: hidden; /* Hide overflow content */
text-overflow: ellipsis; /* Show ellipsis for overflowing text */
}
.reg-input-td2 {
width: 100%;
}

View File

@ -1,77 +0,0 @@
dy {
font-family: Arial, sans-serif;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
background-color: #e5e5e5;
}
.form-container {
width: 100%;
max-width: 400px;
background-color: white;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
display: flex;
flex-direction: column;
border-radius: 8px;
padding: 40px;
text-align: center;
}
h1 {
margin-bottom: 20px;
color: #2F4F4F;
}
input {
width: 100%;
background: #f7f7f7;
font-size: 16px;
padding: 10px;
border: 1px solid #ddd;
border-radius: 20px;
margin-bottom: 15px;
outline: none;
}
button {
width: 100%;
padding: 15px;
border: none;
background-color: #0088cc;
color: white;
border-radius: 20px;
cursor: pointer;
outline: none;
font-size: 16px;
font-weight: bold;
transition: background-color 0.3s;
}
button:hover,
button:focus-visible {
background-color: #007bb5;
}
.hide-cursor::placeholder {
color: #000;
}
.hide-cursor {
caret-color: transparent;
}
.no-select {
-webkit-user-select: none; /* Для Safari */
-moz-user-select: none; /* Для Firefox */
user-select: none; /* Для всех остальных браузеров */
}
div {
color: red;
font-size: 15px;
margin-top: 10px;
display: none;
}

BIN
assets/gif/loading.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

20
assets/img/add.svg Normal file
View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="128"
height="128"
viewBox="0 0 3.84 3.84"
fill="none"
version="1.1"
id="svg1"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="m 1.9142674,3.7842088 c -0.8814997,0 -1.32224859,0 -1.59609402,-0.2738529 C 0.04432607,3.2365216 0.04432607,2.7957577 0.04432607,1.9142674 c 0,-0.8814997 0,-1.32224859 0.27384731,-1.59609402 C 0.59201881,0.04432607 1.0327677,0.04432607 1.9142674,0.04432607 c 0.8814903,0 1.3222542,0 1.5960885,0.27384731 0.2738529,0.27384543 0.2738529,0.71459432 0.2738529,1.59609402 0,0.8814903 0,1.3222542 -0.2738529,1.5960885 C 3.2365216,3.7842088 2.7957577,3.7842088 1.9142674,3.7842088 Z m 0,-2.5711694 c 0.077453,0 0.1402457,0.062791 0.1402457,0.1402456 v 0.4207368 h 0.4207367 c 0.077453,0 0.1402456,0.062793 0.1402456,0.1402456 0,0.077453 -0.062793,0.1402457 -0.1402456,0.1402457 H 2.0545131 v 0.4207367 c 0,0.077453 -0.062793,0.1402456 -0.1402457,0.1402456 -0.077453,0 -0.1402456,-0.062793 -0.1402456,-0.1402456 V 2.0545131 H 1.353285 c -0.077455,0 -0.1402456,-0.062793 -0.1402456,-0.1402457 0,-0.077453 0.062791,-0.1402456 0.1402456,-0.1402456 H 1.7740218 V 1.353285 c 0,-0.077455 0.062793,-0.1402456 0.1402456,-0.1402456 z"
fill="#1c274c"
id="path1"
style="stroke-width:0.186994" />
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 258 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 288 KiB

18
assets/img/delete.svg Normal file
View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
fill="#000000"
width="128"
height="128"
viewBox="-1.7 0 3.264 3.264"
class="cf-icon-svg"
version="1.1"
id="svg1"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<path
d="M 1.4376583,1.6137431 A 1.5211864,1.5211864 0 1 1 -0.08352807,0.09255663 1.5209944,1.5209944 0 0 1 1.4376583,1.6137431 Z m -1.30733258,0.00192 0.58257383,-0.582766 A 0.15217629,0.15217629 0 0 0 0.49770077,0.81769971 L -0.08468094,1.4004657 -0.66763909,0.81769971 A 0.15217629,0.15217629 0 0 0 -0.88283792,1.0328985 l 0.5829582,0.5827659 -0.58276597,0.5827661 a 0.15217629,0.15217629 0 0 0 0.21519874,0.2150066 l 0.58257388,-0.582766 0.58276608,0.582766 A 0.15217629,0.15217629 0 0 0 0.71309179,2.1982382 Z"
id="path1"
style="display:inline;fill:#ff0000;fill-opacity:1;stroke-width:0.192143" />
</svg>

After

Width:  |  Height:  |  Size: 929 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

BIN
assets/img/exit.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

BIN
assets/img/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 824 B

30
assets/img/link.svg Normal file
View File

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
fill="#000000"
version="1.1"
id="Capa_1"
width="128"
height="128"
viewBox="0 0 70.75936 70.75936"
xml:space="preserve"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><defs
id="defs3" />
<g
id="g3"
style="fill:#c8f5ff;fill-opacity:1;stroke:#6adeef;stroke-width:8.62702;stroke-dasharray:none;stroke-opacity:1"
transform="matrix(0.1490509,0,0,0.14948634,3.4471925,2.5065977)">
<g
id="g2"
style="fill:#c8f5ff;fill-opacity:1;stroke:#6adeef;stroke-width:8.62702;stroke-dasharray:none;stroke-opacity:1">
<path
d="m 409.657,32.474 c -43.146,-43.146 -113.832,-43.146 -156.978,0 l -84.763,84.762 c 29.07,-8.262 60.589,-6.12 88.129,6.732 l 44.063,-44.064 c 17.136,-17.136 44.982,-17.136 62.118,0 17.136,17.136 17.136,44.982 0,62.118 l -55.386,55.386 -36.414,36.414 c -17.136,17.136 -44.982,17.136 -62.119,0 l -47.43,47.43 c 11.016,11.017 23.868,19.278 37.332,24.48 36.415,14.382 78.643,8.874 110.467,-16.219 3.06,-2.447 6.426,-5.201 9.18,-8.262 l 57.222,-57.222 34.578,-34.578 c 43.453,-43.145 43.453,-113.525 10e-4,-156.977 z"
id="path1"
style="fill:#c8f5ff;fill-opacity:1;stroke:#6adeef;stroke-width:8.62702;stroke-dasharray:none;stroke-opacity:1" />
<path
d="m 184.135,320.114 -42.228,42.228 c -17.136,17.137 -44.982,17.137 -62.118,0 -17.136,-17.136 -17.136,-44.981 0,-62.118 l 91.8,-91.799 c 17.136,-17.136 44.982,-17.136 62.119,0 l 47.43,-47.43 c -11.016,-11.016 -23.868,-19.278 -37.332,-24.48 -38.25,-15.3 -83.232,-8.262 -115.362,20.502 -1.53,1.224 -3.06,2.754 -4.284,3.978 l -91.8,91.799 c -43.146,43.146 -43.146,113.832 0,156.979 43.146,43.146 113.832,43.146 156.978,0 l 82.927,-83.845 c -42.23,9.791 -52.022,8.568 -88.13,-5.814 z"
id="path2"
style="fill:#c8f5ff;fill-opacity:1;stroke:#6adeef;stroke-width:8.62702;stroke-dasharray:none;stroke-opacity:1" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

28
assets/img/list-rooms.svg Normal file
View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="127.99999"
height="127.99999"
viewBox="0 0 33.866664 33.866664"
version="1.1"
id="svg1"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<g
id="layer1">
<path
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="M 6.2911505,3.4844006 V 6.6404405 H 3.0362141 V 28.776832 h 4.7418555 v 2.929792 H 0.58956039 V 3.438992 Z"
id="path63" />
<path
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="m 27.557741,31.386555 v -3.15604 h 3.254937 V 6.0941238 h -4.741856 v -2.929792 h 7.188509 V 31.431963 Z"
id="path63-2" />
<path
id="rect63"
style="fill:#fdffff;fill-opacity:1;fill-rule:nonzero;stroke:#e1e1e1;stroke-width:0.529167;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="m 8.7183307,8.3901854 c -2.467802,0 -4.4545084,1.9867066 -4.4545084,4.4545086 v 5.357812 c 0,2.467802 1.9867064,4.454509 4.4545084,4.454509 0,0 6.6668103,0 10.2365843,0 1.184667,0 1.8523,1.573101 2.973462,1.946651 1.827305,0.608823 5.758305,0.474906 5.758305,0.474906 0,0 -1.204106,-0.984163 -2.336589,-2.042039 -0.307888,-0.287603 -0.182877,-1.488944 0.134028,-1.896921 0.674916,-0.868872 0.918918,-1.680982 0.918918,-2.937106 v -5.357812 c 0,-2.467802 -1.986706,-4.4545086 -4.454508,-4.4545086 z" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

20
assets/img/return.svg Normal file
View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="128"
height="127.99999"
viewBox="0 0 33.866667 33.866664"
version="1.1"
id="svg1"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<g
id="layer1">
<path
style="fill:#000000;fill-opacity:1;stroke:#ffffff;stroke-width:0.294159;stroke-linejoin:round;stroke-opacity:1"
d="m 0.65443061,23.377766 10.20536539,-7.351693 -0.06427,3.749871 c 0,0 6.384296,0.115304 9.607291,0.115304 1.824632,0 4.010317,0.117998 5.361398,-1.065432 0.797635,-0.698657 1.041209,-1.943308 0.954461,-2.97946 -0.118465,-1.414973 -0.417733,-2.830021 -1.492491,-3.799298 -0.964892,-0.870193 -2.338758,-1.449022 -3.659301,-1.465666 -1.347581,-0.01698 -4.311633,-0.01476 -4.311633,-0.01476 l 0.02678,-6.0468658 c 0,0 5.209174,-0.1269759 7.836958,-0.1269759 2.090574,0 3.730335,0.2813604 5.374446,1.5274776 1.165995,0.8837399 2.874757,2.1212447 2.874757,3.8971142 0,3.5737799 -0.03341,6.8479459 -0.03341,10.6502529 0,3.155641 -2.159421,4.126978 -3.036427,4.888332 -1.783594,1.548393 -4.069763,1.553329 -6.468859,1.553329 -4.709142,0 -13.295569,0 -13.295569,0 v 3.244296 z"
id="path1" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="127.99999"
height="127.99999"
viewBox="0 0 33.866664 33.866664"
version="1.1"
id="svg1"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<g
id="layer1">
<path
id="path11"
style="fill:#e6e6e6;fill-opacity:1;stroke:#b3b3b3;stroke-width:0.792427;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="m 9.8376422,3.4995279 c -0.413454,0.04422 -0.9249931,0.2733392 -1.5859497,0.7720459 -2.15303,1.6245101 -1.0645387,2.7213368 -0.2780192,3.6576578 0.9643744,1.1480481 0.5272069,2.4297214 -0.2444295,3.5413854 -1.0846469,1.562605 -1.8094411,2.689497 -3.1812011,2.618445 -1.7185528,-0.08902 -3.2061983,0.950419 -3.1817179,2.618962 0.020151,1.373449 0.960489,2.763844 2.9987833,2.826184 1.3729571,0.04199 2.35275,1.613748 2.9993001,2.825667 0.6465497,1.211919 1.3490594,3.417259 0.6769613,4.064868 -0.989134,0.953094 -1.312826,2.263816 0.6769612,4.064868 1.2829023,1.161218 2.6693573,0.639536 3.7666953,-0.946195 1.179214,-1.70405 2.337505,-1.523233 4.127396,-1.422652 1.371435,0.07707 2.886284,-0.325958 3.858679,1.446423 0.660704,1.204262 1.722209,2.911554 3.85868,1.445906 1.132687,-0.77704 2.468135,-2.085182 0.947745,-4.010091 -0.610564,-0.773014 -0.532201,-2.186383 0.948263,-4.010607 1.255317,-1.546798 1.98091,-2.751433 3.181201,-2.618962 1.365308,0.150686 2.84107,-0.224934 3.181718,-2.618445 0.366273,-2.57357 -1.941907,-2.695322 -3.312976,-2.778641 C 28.1324,14.906856 28.128014,13.488893 26.590624,12.103137 25.053235,10.717381 25.023658,9.341954 26.190649,8.2930337 27.35764,7.2441129 27.633021,6.3291961 25.719877,4.5222045 24.596532,3.461188 23.254965,3.1719841 21.630204,4.9759236 20.911774,5.7735833 19.336054,7.1894888 17.196366,7.1086099 15.002057,7.0256665 13.838949,7.4611865 12.285555,5.8053303 11.434493,4.8981315 11.078004,3.3668691 9.8376422,3.4995279 Z m 6.7892498,6.5215661 a 7.4172139,7.0961218 0 0 1 7.417118,7.096207 7.4172139,7.0961218 0 0 1 -7.417118,7.096208 7.4172139,7.0961218 0 0 1 -7.4171184,-7.096208 7.4172139,7.0961218 0 0 1 7.4171184,-7.096207 z" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

24
assets/img/user.svg Normal file
View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="127.99999"
height="127.99999"
viewBox="0 0 33.866664 33.866664"
version="1.1"
id="svg1"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<g
id="layer1">
<path
style="fill:#000080;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.465;stroke-linejoin:round;stroke-dasharray:none"
d="M 9.3745975,32.81176 H 25.632941 c 0,0 0.08996,-6.044498 -0.03778,-10.988966 0.0321,-3.524476 -1.327193,-6.290541 -8.363226,-6.155275 -7.036034,0.135265 -7.7455423,2.805955 -7.7693511,5.331649 -0.034594,3.669826 -0.087981,11.812592 -0.087981,11.812592 z"
id="path7" />
<path
id="path8"
style="fill:#000080;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.965;stroke-linejoin:round;stroke-dasharray:none"
d="M 17.963761,1.3260172 A 11.612947,6.192831 0 0 0 6.3510334,7.5189208 11.612947,6.192831 0 0 0 17.963761,13.711824 11.612947,6.192831 0 0 0 29.576489,7.5189208 11.612947,6.192831 0 0 0 17.963761,1.3260172 Z m -3.947046,2.876827 a 1.5186517,3.4351659 0 0 1 1.518771,3.4349323 1.5186517,3.4351659 0 0 1 -1.518771,3.4354495 1.5186517,3.4351659 0 0 1 -1.518253,-3.4354495 1.5186517,3.4351659 0 0 1 1.518253,-3.4349323 z m 6.47299,0.2160074 a 1.5186517,3.4351659 0 0 1 1.518254,3.4349324 1.5186517,3.4351659 0 0 1 -1.518254,3.435449 1.5186517,3.4351659 0 0 1 -1.51877,-3.435449 1.5186517,3.4351659 0 0 1 1.51877,-3.4349324 z" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -1,171 +0,0 @@
async function getChatEvents(chatId, localHistoryId) {
const response = await fetch('/api/chatPollEvents', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
chatUpdReq: {
chatId: chatId,
LocalHistoryId: localHistoryId
}
})
});
const data = await response.json();
return data.chatUpdResp;
}
async function getChatListEvents(localHistoryId) {
const response = await fetch('/api/chatListPollEvents', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
chatListUpdReq: {
LocalHistoryId: localHistoryId
}
})
});
const data = await response.json();
return data.chatListUpdResp;
}
async function getMessageNeighbours(chatId, msgId, direction, amount, localHistoryId) {
const response = await fetch('/api/getMessageNeighbours', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
chatUpdReq: {
chatId: chatId,
LocalHistoryId: localHistoryId
},
msgId: msgId,
direction: direction,
amount: amount
})
});
const data = await response.json();
return data.chatUpdResp;
}
async function sendMessage(chatId, localHistoryId, text) {
const response = await fetch('/api/sendMessage', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
chatUpdReq: {
chatId: chatId,
LocalHistoryId: localHistoryId
},
content: {
text: text
}
})
});
const data = await response.json();
return data.chatUpdResp;
}
async function deleteMessage(chatId, localHistoryId, messageId) {
const response = await fetch('/api/deleteMessage', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
chatUpdReq: {
chatId: chatId,
LocalHistoryId: localHistoryId
},
id: messageId
})
});
const data = await response.json();
return data.chatUpdResp;
}
async function addMemberToChat(chatId, localHistoryId, nickname) {
const response = await fetch('/api/addMemberToChat', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
chatUpdReq: {
chatId: chatId,
LocalHistoryId: localHistoryId
},
nickname: nickname
})
});
const data = await response.json();
return data.chatUpdResp;
}
async function removeMemberFromChat(chatId, localHistoryId, userId) {
const response = await fetch('/api/removeMemberFromChat', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
chatUpdReq: {
chatId: chatId,
LocalHistoryId: localHistoryId
},
userId: userId
})
});
const data = await response.json();
return data.chatUpdResp;
}
async function createChat(localHistoryId, name, nickname) {
const response = await fetch('/api/createChat', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
chatListUpdReq: {
LocalHistoryId: localHistoryId
},
content: {
name: name,
nickname: nickname
}
})
});
const data = await response.json();
return data.chatListUpdResp;
}
async function leaveChat(localHistoryId, chatId) {
const response = await fetch('/api/leaveChat', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
chatListUpdReq: {
LocalHistoryId: localHistoryId
},
chatId: chatId
})
});
const data = await response.json();
return data.chatListUpdResp;
}

188
assets/js/chat-members.js Normal file
View File

@ -0,0 +1,188 @@
let LocalHistoryId = 0;
function genSentBase(){
return {
'chatUpdReq': {
'LocalHistoryId': LocalHistoryId,
'chatId': openedchat.id
}
};
}
let members = new Map();
let memberBoxes = new Map();
let myRoleHere = null; // Dung local state updates should be updated first
let userDeletionWinStoredUId = -1;
function shouldShowDeleteButton(memberSt){
return userinfo.uid !== memberSt.userId && myRoleHere === userChatRoleAdmin && memberSt.roleHere !== userChatRoleDeleted;
}
function updateBoxWithSt(box, memberSt){
let ID = memberSt.userId;
let roleP = box.querySelector(".CM-member-box-role");
roleP.innerText = memberSt.roleHere;
box.style.backgroundColor = roleToColor(memberSt.roleHere);
box.querySelector(".CM-member-box-leave-btn").style.display =
(shouldShowDeleteButton(memberSt) ? "block" : "none");
}
function convertMemberStToBox(memberSt){
let ID = memberSt.userId;
let userProfileURI = "/user/" + memberSt.nickname;
let box = document.createElement("div");
box.className = "dynamic-block-list-el CM-member-box";
box.style.backgroundColor = roleToColor(memberSt.roleHere);
let inBoxNickname = document.createElement("a");
box.appendChild(inBoxNickname);
inBoxNickname.className = "entity-nickname-txt CM-member-box-nickname";
inBoxNickname.innerText = memberSt.nickname;
inBoxNickname.href = userProfileURI;
let inBoxName = document.createElement("a");
box.appendChild(inBoxName);
inBoxName.className = "entity-reg-field-txt CM-member-box-name";
inBoxName.innerText = memberSt.name;
inBoxName.href = userProfileURI;
let inBoxUserRoleHere = document.createElement("p");
box.appendChild(inBoxUserRoleHere);
inBoxUserRoleHere.className = "entity-reg-field-txt CM-member-box-role";
inBoxUserRoleHere.innerText = memberSt.roleHere;
let inBoxLeaveBtn = document.createElement("img");
box.appendChild(inBoxLeaveBtn);
inBoxLeaveBtn.className = "CM-member-box-leave-btn";
inBoxLeaveBtn.src = "/assets/img/delete.svg";
inBoxLeaveBtn.onclick = function (ev) {
if (ev.button !== 0)
return;
userDeletionWinStoredUId = ID;
document.getElementById("user-deletion-win-title").innerText =
pres['chat-members']['reask-kick-user-X'] + " " + memberSt.nickname + "?";
activatePopupWindowById("user-deletion-win");
};
box.querySelector(".CM-member-box-leave-btn").style.display =
(shouldShowDeleteButton(memberSt) ? "block" : "none");
return box;
}
function updateLocalStateFromChatUpdResp(chatUpdResp){
LocalHistoryId = chatUpdResp.HistoryId;
// If my role is updated, we need to update all the boes of already set users (kick button can appear and disappear)
let literalMemberList = document.getElementById("CM-list");
// We ignore messages and everything related to them. Dang, I really should add an argument to disable message lookup here
for (let memberSt of chatUpdResp.members){
console.log([memberSt, userinfo.uid, myRoleHere]);
if (memberSt.userId === userinfo.uid && myRoleHere !== memberSt.roleHere){
myRoleHere = memberSt.roleHere;
for (let [id, memberSt] of members){
let box = memberBoxes.get(id);
updateBoxWithSt(box, memberSt);
}
document.getElementById("CM-btn-add").style.display =
(memberSt.roleHere === userChatRoleAdmin ? "block" : "none");
console.log("DEBUG " + (memberSt.roleHere === userChatRoleAdmin ? "block" : "none"));
break;
}
}
for (let memberSt of chatUpdResp.members){
let id = memberSt.userId;
if (members.has(id)){
updateBoxWithSt(memberBoxes.get(id), memberSt);
} else {
if (memberSt.roleHere !== userChatRoleDeleted){
members.set(id, memberSt);
let box = convertMemberStToBox(memberSt);
memberBoxes.set(id, box);
literalMemberList.appendChild(box);
}
}
}
}
function updateLocalStateFromRecv(Recv){
updateLocalStateFromChatUpdResp(Recv.chatUpdResp);
}
function configureSummonUserInterface(){
document.getElementById("user-summoning-yes").onclick = function(ev ){
if (ev.button !==0)
return;
let nickname = String(document.getElementById("summoned-user-nickname").value);
let isReadOnly = document.getElementById("summoned-user-is-read-only").checked;
deactivateActivePopup();
let Sent = genSentBase();
Sent.nickname = nickname;
Sent.makeReadOnly = Boolean(isReadOnly);
apiRequest("addMemberToChat", Sent).
then((Recv) => {
updateLocalStateFromRecv(Recv);
}).catch((e) => {
console.log(e);
alert(pres['chat-members']["failed-summon-member"]);
});
};
document.getElementById("user-summoning-no").onclick = function (ev) {
if (ev.button !== 0)
return;
deactivateActivePopup();
};
document.getElementById("CM-btn-add").onclick = function(ev) {
if (ev.button !== 0)
return;
document.getElementById("summoned-user-nickname").value = "";
// read-only flag persists throughout user summoning sessions, and IT IS NOT A BUG
activatePopupWindowById("user-summoning-win");
};
}
/* Popup activation button is configured for each box separately */
function configureKickUserInterfaceWinPart(){
document.getElementById("user-deletion-yes").onclick = function (ev){
if (ev.button !== 0)
return;
deactivateActivePopup();
if (userDeletionWinStoredUId < 0)
throw new Error("Karaul");
let Sent = genSentBase();
Sent.userId = userDeletionWinStoredUId;
apiRequest("removeMemberFromChat", Sent).
then((Recv) => {
updateLocalStateFromRecv(Recv);
}).catch((e) => {
console.log(e);
alert(pres['chat-members']["failed-kick-member"]);
});
}
document.getElementById("user-deletion-no").onclick = function (ev) {
if (ev.button !== 0)
return;
deactivateActivePopup();
};
}
__mainloopDelayMS = 5000;
__guestMainloopPollerAction = function (){
console.log("Hello, world");
apiRequest("chatPollEvents", genSentBase()).
then((Recv) => {
console.log(Recv);
updateLocalStateFromRecv(Recv);
});
}
window.onload = function(){
console.log("Page loaded");
configureSummonUserInterface();
configureKickUserInterfaceWinPart();
updateLocalStateFromChatUpdResp(initial_chatUpdResp);
mainloopPoller();
}

View File

@ -1,162 +1,477 @@
let members = [ let LocalHistoryId = 0;
{ username: 'Адель', nickname: 'cold_siemens52', avatar: 'https://sun9-59.userapi.com/impg/t8GhZ7FkynVifY1FQCnaf31tGprbV_rfauZzgg/fSq4lyc6V0U.jpg?size=1280x1280&quality=96&sign=e3c309a125cb570d2e18465eba65f940&type=album' },
{ username: 'Антон', nickname: 'antyak_01', avatar: 'https://cdn.pixabay.com/photo/2015/10/05/22/37/blank-profile-picture-973460_1280.png' },
{ username: 'Владимир', nickname: 'kkrkk2006', avatar: 'https://cdn.pixabay.com/photo/2015/10/05/22/37/blank-profile-picture-973460_1280.png' }
];
let currentHistoryId = 0;
let currentChatID = null;
function renderMembersList() {
const membersListBody = document.getElementById('members-list-body');
membersListBody.innerHTML = '';
members.forEach((member, index) => { let members = new Map();
const memberItem = document.createElement('li');
memberItem.innerHTML = `
<img src="${member.avatar}" alt="${member.username}">
<a href="profile.html">${member.username}</a>
<button class="delete-member" onclick="deleteMember(${index})">Удалить из чата</button>
`;
membersListBody.appendChild(memberItem);
});
}
function deleteMember(index) { let loadedMessages = new Map(); // messageSt objects
members.splice(index, 1); /*
renderMembersList(); container: EL, box: EL, offset: number (msgPres) */
} let visibleMessages = new Map(); // HTMLElement objects
async function sendMessage() { let anchoredMsg = -1;
const chatMessages = document.getElementById('chat-messages'); let visibleMsgSegStart = -1;
const chatInput = document.getElementById('chat-input'); let visibleMsgSegEnd = -2;
const message = chatInput.value; let offsetOfAnchor = 500;
let highestPoint = null;
let lowestPoint = null;
if (message.trim() !== '') { let lastMsgId = -1;
const request = { let myRoleHere = null; // Dung local state updates should be updated first
'chatId': currentChatID,
'LocalHistoryId': currentHistoryId,
'content': {
'text': message
}
};
const response = await fetch("/internalapi/sendMessage", { // Would start with true if opened `/chat/<>`
method: 'POST', let bumpedAtBottom = false;
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(request)
});
const res = await response.json(); // Hidden variable. When deletion window popup is active
// Persists from popup activation until popup deactivation
let storeHiddenMsgIdForDeletionWin = -1;
if (res.update) { let debugMode = false;
const update = res.update[0];
currentHistoryId = update.HistoryId;
const messageElement = document.createElement('div'); // Positive in production, negative for debug
messageElement.classList.add('chat-message'); let softZoneSz = debugMode ? -150 : 300;
let chatPadding = debugMode ? 300 : 5;
let msgGap = 5;
const msgErased = pres.chat.msgErased;
const avatarElement = document.createElement('div'); function genSentBase(){
avatarElement.classList.add('avatar'); return {
'chatUpdReq': {
const avatarImage = document.createElement('img'); 'LocalHistoryId': LocalHistoryId,
avatarImage.src = 'https://sun9-59.userapi.com/impg/t8GhZ7FkynVifY1FQCnaf31tGprbV_rfauZzgg/fSq4lyc6V0U.jpg?size=1280x1280&quality=96&sign=e3c309a125cb570d2e18465eba65f940&type=album'; 'chatId': openedchat.id
avatarElement.appendChild(avatarImage);
const messageContentElement = document.createElement('div');
messageContentElement.classList.add('message-content');
const usernameElement = document.createElement('div');
usernameElement.classList.add('username');
usernameElement.textContent = await getUserName();
const textElement = document.createElement('div');
textElement.classList.add('text');
textElement.textContent = message;
messageContentElement.appendChild(usernameElement);
messageContentElement.appendChild(textElement);
messageElement.appendChild(avatarElement);
messageElement.appendChild(messageContentElement);
chatMessages.appendChild(messageElement);
chatInput.value = '';
chatMessages.scrollTop = chatMessages.scrollHeight;
}
}
}
function openMembersList() {
renderMembersList();
document.getElementById("members-list").style.display = "block";
document.getElementById("overlay").style.display = "flex";
}
function closeMembersList() {
document.getElementById("members-list").style.display = "none";
document.getElementById("overlay").style.display = "none";
}
document.getElementById('chat-input').addEventListener('keydown', function (event) {
if (event.key === 'Enter') {
sendMessage();
}
});
async function getUserID() {
const response = await fetch('/internalapi/mirror', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({})
});
const res = await response.json();
return res.id;
}
async function getChatID() {
const chatNickname = window.location.pathname.split('/').pop();
const response = await fetch('/internalapi/getChatList', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({})
});
const res = await response.json();
for (const chat of res.chats) {
if (chat.content.nickname === chatNickname) {
return chat.id;
}
}
return -1;
}
async function editMessage(new_message) {
const req = {
'chatId': currentChatID,
'LocalHistoryId': currentHistoryId,
'id': getUserID(),
'content': {
'text': new_message
} }
}; };
const res = await fetch('/internalapi/editMessage', { }
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(req)
});
const response = await res.json(); function genSentBaseGMN(){
if (response.update) { let Sent = genSentBase();
currentHistoryId = response.update[0].HistoryId; Sent.amount = debugMode ? 2 : 14;
return Sent;
}
function getChatWgSz(){
let chatWg = document.getElementById("chat-widget");
return [chatWg.offsetWidth, chatWg.offsetHeight];
}
function elSetOffsetInChat(el, offset){
el.style.bottom = String(offset) + "px";
}
function isMissingPrimaryMsgHeap(){
return lastMsgId >= 0 && anchoredMsg < 0;
}
function isMissingTopMsgHeap(){
let [W, H] = getChatWgSz();
return anchoredMsg >= 0 && (highestPoint < H + softZoneSz && visibleMsgSegStart > 0);
}
function isMissingBottomMsgHeap(){
return anchoredMsg >= 0 && (lowestPoint > - softZoneSz && visibleMsgSegEnd < lastMsgId);
}
function updateOffsetOfVisibleMsg(msgId, offset){
visibleMessages.get(msgId).container.style.bottom = String(offset) + "px";
}
function updateOffsetsUpToTop(){
let offset = offsetOfAnchor;
for (let curMsg = anchoredMsg; curMsg >= visibleMsgSegStart; curMsg--){
updateOffsetOfVisibleMsg(curMsg, offset);
let height = visibleMessages.get(curMsg).container.offsetHeight;
offset += height + msgGap;
}
return offset - msgGap;
}
function updateOffsetsDown(){
let offset = offsetOfAnchor;
for (let curMsg = anchoredMsg + 1; curMsg <= visibleMsgSegEnd; curMsg++){
let height = visibleMessages.get(curMsg).container.offsetHeight;
offset -= (height + msgGap);
updateOffsetOfVisibleMsg(curMsg, offset);
}
return offset;
}
function updateOffsetsSane(){
if (anchoredMsg < 0)
return;
highestPoint = updateOffsetsUpToTop();
lowestPoint = updateOffsetsDown();
}
function heightOfPreloadGhost(){
let [W, H] = getChatWgSz();
return Math.min(H * 0.9, Math.max(H * 0.69, 30));
}
function updateOffsets(){
let spinnerTop = document.getElementById("top-loading");
let spinnerBottom = document.getElementById("bottom-loading");
let SbH = spinnerBottom.offsetHeight;
if (anchoredMsg < 0){
hideHTMLElement(spinnerBottom);
elSetOffsetInChat(spinnerTop, chatPadding);
setElementVisibility(spinnerTop, isMissingPrimaryMsgHeap());
} else {
let [W, H] = getChatWgSz();
updateOffsetsSane();
let lowestLowestPoint = isMissingBottomMsgHeap() ? lowestPoint - heightOfPreloadGhost(): lowestPoint;
let highestHighestPoint = isMissingTopMsgHeap() ? highestPoint + heightOfPreloadGhost() : highestPoint;
if (lowestLowestPoint > chatPadding || (highestHighestPoint - lowestLowestPoint) <= H - chatPadding * 2 ||
(!isMissingBottomMsgHeap() && bumpedAtBottom)) {
offsetOfAnchor += (-lowestLowestPoint + chatPadding);
updateOffsetsSane();
} else if (highestHighestPoint < H - chatPadding) {
offsetOfAnchor += (-highestHighestPoint + (H - chatPadding));
updateOffsetsSane();
}
/* Messages weere updated (and only them). They were talking with ghosts.
Now we are trying to show spinners of ghosts */
elSetOffsetInChat(spinnerTop, highestPoint);
setElementVisibility(spinnerTop, isMissingTopMsgHeap());
elSetOffsetInChat(spinnerBottom, lowestPoint - SbH);
setElementVisibility(spinnerBottom, isMissingBottomMsgHeap());
/* Fix anchor */
let oldAnchor = anchoredMsg;
while (true){
let h = visibleMessages.get(anchoredMsg).container.offsetHeight;
if (!(offsetOfAnchor + h < chatPadding && visibleMsgSegStart < anchoredMsg))
break
offsetOfAnchor += (msgGap + h);
anchoredMsg--;
}
while (offsetOfAnchor > H - chatPadding && anchoredMsg < visibleMsgSegEnd){
anchoredMsg++;
let h = visibleMessages.get(anchoredMsg).container.offsetHeight;
offsetOfAnchor -= (msgGap + h);
}
if (oldAnchor !== anchoredMsg)
console.log("anchoredMsg: " + String(oldAnchor) + " -> " + String(anchoredMsg))
} }
} }
document.addEventListener("DOMContentLoaded", async function() {
currentChatID = await getChatID(); function shouldShowDeleteMesgBtn(messageSt){
}); return !messageSt.isSystem && messageSt.exists && (myRoleHere !== userChatRoleReadOnly) &&(
myRoleHere === userChatRoleAdmin || messageSt.senderUserId === userinfo.uid);
}
function getMsgTypeClassSenderBased(messageSt){
if (messageSt.isSystem)
return "message-box-system"
if (messageSt.senderUserId === userinfo.uid)
return "message-box-mine"
return "message-box-alien";
}
function getMsgFullTypeClassName(messageSt){
return getMsgTypeClassSenderBased(messageSt) + (messageSt.exists ? "" : " message-box-deleted");
}
/* Two things can be updated: messages existance and delete button visibility
* Supercontainer.container is persistent, Supercontainer.box can change it's class */
function updateMessageSupercontainer(supercontainer, messageSt){
let box = supercontainer.box;
if (messageSt.isSystem)
return;
setElementVisibility(box.querySelector(".message-box-button-delete"), shouldShowDeleteMesgBtn(messageSt), "inline");
box.className = getMsgFullTypeClassName(messageSt);
// Notice, that no check of previous state is performed. Double loading is a rare event, I can afford to be slow
if (!messageSt.exists)
box.querySelector(".message-box-msg").innerText = msgErased;
}
function decodeSystemMessage(text){
let [subject, verb, object] = text.split(',');
let subjectId = Number(subject);
let objectId = Number(object);
let subjectRef = members.has(subjectId) ? members.get(subjectId).nickname : "???";
let objectRef = members.has(objectId) ? members.get(objectId).nickname : "???";
if (verb === "kicked"){
return subjectRef + " " + pres.chat.syslog.kicked + " " + objectRef;
} else if (verb === "summoned"){
return subjectRef + " " + pres.chat.syslog.summoned + " " + objectRef;
} else if (verb === "left"){
return subjectRef + " " + pres.chat.syslog.left;
} else if (verb === "created"){
return subjectRef + " " + pres.chat.syslog.created;
}
return "... Bad log ...";
}
function convertMessageStToSupercontainer(messageSt){
let container = document.createElement("div");
container.className = "message-supercontainer";
let box = document.createElement("div");
container.appendChild(box);
box.className = getMsgFullTypeClassName(messageSt);
let ID = messageSt.id;
if (messageSt.isSystem){
} else {
let topPart = document.createElement("div");
box.appendChild(topPart);
topPart.className = "message-box-top";
if (!members.has(messageSt.senderUserId))
throw new Error("First - update members");
let senderMemberSt = members.get(messageSt.senderUserId);
let senderProfileURI = "/user/" + senderMemberSt.nickname;
let inTopPartSenderName = document.createElement("a");
topPart.appendChild(inTopPartSenderName);
inTopPartSenderName.className = "message-box-sender-name";
inTopPartSenderName.innerText = senderMemberSt.name;
inTopPartSenderName.href = senderProfileURI;
let inTopPartSenderNickname = document.createElement("a");
topPart.appendChild(inTopPartSenderNickname);
inTopPartSenderNickname.className = "message-box-sender-name message-box-sender-shortname"
inTopPartSenderNickname.innerText = senderMemberSt.nickname;
inTopPartSenderNickname.href = senderProfileURI;
let inTopPartButtonDelete = document.createElement("img");
topPart.appendChild(inTopPartButtonDelete);
inTopPartButtonDelete.className = "message-box-button message-box-button-delete";
inTopPartButtonDelete.src = "/assets/img/delete.svg";
inTopPartButtonDelete.onclick = (ev) => {
if (ev.button !== 0)
return;
let msgText = box.querySelector(".message-box-msg").innerText;
let previewText = senderMemberSt.nickname + ":\n" + msgText;
if (previewText.length > 1000)
previewText = previewText.substring(0, 1000 - 3);
document.getElementById("win-deletion-msg-preview").innerText = previewText;
storeHiddenMsgIdForDeletionWin = ID;
activatePopupWindowById("msg-deletion-win");
};
setElementVisibility(inTopPartButtonDelete, shouldShowDeleteMesgBtn(messageSt), "inline");
let inTopPartButtonGetLink = document.createElement("img");
topPart.appendChild(inTopPartButtonGetLink);
inTopPartButtonGetLink.className = "message-box-button";
inTopPartButtonGetLink.src = "/assets/img/link.svg";
inTopPartButtonGetLink.onclick = (ev) => {
if (ev.button !== 0)
return;
let URI = window.location.host + "/chat/" + openedchat.nickname + "/m/" + String(ID);
document.getElementById("message-input").innerText += (" " + URI + "");
};
}
let msgPart = document.createElement("p");
box.appendChild(msgPart);
msgPart.className = "message-box-msg";
if (messageSt.exists){
if (messageSt.isSystem)
msgPart.innerText = decodeSystemMessage(messageSt.text);
else
msgPart.innerText = messageSt.text;
} else
msgPart.innerText = msgErased;
return {'container': container, 'box': box};
}
function makeVisible(msgId){
let supercontainer = convertMessageStToSupercontainer(loadedMessages.get(msgId));
const chatWin = document.getElementById("chat-widget");
chatWin.appendChild(supercontainer.container);
visibleMessages.set(msgId, supercontainer);
}
function opaNewMessageSt(messageSt){
let msgId = messageSt.id;
if (loadedMessages.has(msgId)){
loadedMessages.set(msgId, messageSt);
if (visibleMessages.has(msgId)){
updateMessageSupercontainer(visibleMessages.get(msgId), messageSt);
}
} else {
loadedMessages.set(msgId, messageSt);
if (anchoredMsg < 0){
anchoredMsg = msgId;
visibleMsgSegStart = msgId;
visibleMsgSegEnd = msgId;
makeVisible(msgId);
} else if (msgId + 1 === visibleMsgSegStart) {
visibleMsgSegStart--;
makeVisible(msgId);
while (loadedMessages.has(visibleMsgSegStart - 1)){
visibleMsgSegStart--;
makeVisible(visibleMsgSegStart);
}
} else if (msgId - 1 === visibleMsgSegEnd){
visibleMsgSegEnd++;
makeVisible(msgId);
while (loadedMessages.has(visibleMsgSegEnd + 1)){
visibleMsgSegEnd++;
makeVisible(visibleMsgSegEnd);
}
}
}
}
function canISendMessages(){
return myRoleHere === userChatRoleRegular || myRoleHere === userChatRoleAdmin;
}
function updateLocalStateFromChatUpdRespBlind(chatUpdResp){
LocalHistoryId = chatUpdResp.HistoryId;
for (let memberSt of chatUpdResp.members){
let id = memberSt.userId;
if (id === userinfo.uid && myRoleHere !== memberSt.roleHere) {
myRoleHere = memberSt.roleHere;
for (let [msgId, sc] of visibleMessages){
updateMessageSupercontainer(sc, loadedMessages.get(msgId));
}
setElementVisibility(document.getElementById("message-input"), canISendMessages());
}
}
for (let memberSt of chatUpdResp.members){
let id = memberSt.userId;
members.set(id, memberSt);
}
lastMsgId = chatUpdResp.lastMsgId;
for (let messageSt of chatUpdResp.messages){
opaNewMessageSt(messageSt);
}
updateOffsets();
}
function updateLocalStateFromRecvBlind(Recv){
updateLocalStateFromChatUpdRespBlind(Recv.chatUpdResp);
}
async function requestMessageNeighbours(fromMsg, direction){
let Sent = genSentBaseGMN();
Sent.msgId = fromMsg;
Sent.direction = direction;
let Recv = await apiRequest("getMessageNeighbours", Sent);
updateLocalStateFromRecvBlind(Recv); // Blind to non-loaded whitespaces
}
function needToLoadWhitespace(){
return isMissingPrimaryMsgHeap() || isMissingTopMsgHeap() || isMissingBottomMsgHeap();
}
async function tryLoadWhitespaceSingle(){
if (isMissingPrimaryMsgHeap()){
await requestMessageNeighbours(-1, "backward");
} else if (isMissingTopMsgHeap()){
await requestMessageNeighbours(visibleMsgSegStart, "backward");
} else if (isMissingBottomMsgHeap()){
await requestMessageNeighbours(visibleMsgSegEnd, "forward");
}
}
async function loadWhitespaceMultitry(){
if (needToLoadWhitespace()){
cancelMainloopTimeout();
do {
try {
await tryLoadWhitespaceSingle();
if (debugMode)
await sleep(900);
} catch (e) {
console.error(e);
await sleep(1500);
}
} while (needToLoadWhitespace());
setMainloopTimeout();
}
}
async function updateLocalStateFromRecv(Recv){
updateLocalStateFromRecvBlind(Recv);
await loadWhitespaceMultitry();
}
async function safeApiRequestWithLocalStUpdate(type, Sent, errMsg){
try {
let Recv = await apiRequest(type, Sent)
await updateLocalStateFromRecv(Recv);
} catch(e) {
console.error(e);
alert(errMsg);
}
}
function configureMsgDeletionPopupButtons(){
document.getElementById("msg-deletion-yes").onclick = function(ev){
if (ev.button !== 0)
return;
deactivateActivePopup();
let Sent = genSentBase();
Sent.id = storeHiddenMsgIdForDeletionWin;
safeApiRequestWithLocalStUpdate("deleteMessage", Sent, pres.chat['failed-delete-message']);
};
document.getElementById("msg-deletion-no").onclick = function (ev){
if (ev.button !== 0)
return;
deactivateActivePopup();
}
}
__mainloopDelayMs = 1000;
async function UPDATE(){
let Recv = await apiRequest("chatPollEvents", genSentBase());
await updateLocalStateFromRecv(Recv);
}
__guestMainloopPollerAction = UPDATE;
window.onload = function (){
console.log("Page was loaded");
document.body.addEventListener("wheel", function (event) {
let offset = event.deltaY / 3;
if (offset < 0){
bumpedAtBottom = false;
} else if (offset > 0 && !isMissingBottomMsgHeap() && lowestPoint + offset > chatPadding){
bumpedAtBottom = true;
}
offsetOfAnchor += offset;
updateOffsets();
loadWhitespaceMultitry().then(dopDopYesYes);
});
document.getElementById("message-input").addEventListener("keyup", function (event) {
if (event.ctrlKey && event.key === 'Enter'){
let textarea = document.getElementById("message-input");
let text = String(textarea.innerText);
textarea.innerText = "";
let Sent = genSentBase();
Sent.content = {};
Sent.content.text = text;
safeApiRequestWithLocalStUpdate("sendMessage", Sent, pres.chat['failed-send-message']);
}
});
bumpedAtBottom = (openedchat.selectedMessageId < 0);
let chatWg = document.getElementById("chat-widget");
let chatWgDebugLinesFnc = function (){
let H = chatWg.offsetHeight;
elSetOffsetInChat(document.getElementById("debug-line-lowest"), -softZoneSz);
elSetOffsetInChat(document.getElementById("debug-line-highest"), H + softZoneSz);
elSetOffsetInChat(document.getElementById("debug-line-top-padding"), H - chatPadding);
elSetOffsetInChat(document.getElementById("debug-line-bottom-padding"), chatPadding)
};
if (debugMode){
window.addEventListener("resize", chatWgDebugLinesFnc);
chatWgDebugLinesFnc();
}
configureMsgDeletionPopupButtons();
updateLocalStateFromChatUpdRespBlind(initial_chatUpdResp);
setMainloopTimeout();
loadWhitespaceMultitry();
}

26
assets/js/common-popup.js Normal file
View File

@ -0,0 +1,26 @@
let activePopupWinId = "";
function activatePopupWindow__(el){
let veil = document.createElement("div");
veil.id = "popup-overlay-veil-OBJ"
veil.className = "popup-overlay-veil";
veil.style.display = "block";
document.body.appendChild(veil);
el.style.display = "block";
}
function activatePopupWindowById(id){
if (activePopupWinId !== "")
return;
/* Lmao, this thing is just... SO unsafe */
activePopupWinId = id;
activatePopupWindow__(document.getElementById(id))
}
function deactivateActivePopup(){
if (activePopupWinId === "")
return
document.getElementById("popup-overlay-veil-OBJ").remove();
document.getElementById(activePopupWinId).style.display = "none";
activePopupWinId = "";
}

77
assets/js/common.js Normal file
View File

@ -0,0 +1,77 @@
let dopDopYesYes = (ign) => {};
function sleep(ms){
return new Promise(res => setTimeout(res, ms));
}
async function apiRequest(type, req){
let A = await fetch("/api/" + type,
{method: 'POST', body: JSON.stringify(req)});
let B = await A.json();
if (B.status !== 0)
throw Error("Server returned non-zero status");
return B;
}
/* Framework for pages with mainloop (it can be npt only polling, but also literally anything else */
let __mainloopDelayMs = 3000;
let mainloopTimeout = null;
let __guestMainloopPollerAction = null;
function setMainloopTimeout(){
if (mainloopTimeout !== null)
return;
mainloopTimeout = setTimeout(mainloopPoller, __mainloopDelayMs);
}
function cancelMainloopTimeout(){
if (mainloopTimeout === null){
console.log("cancelling nothing")
return;
}
clearTimeout(mainloopTimeout);
mainloopTimeout = null;
}
function mainloopPoller(){
mainloopTimeout = null;
try {
if (__guestMainloopPollerAction)
__guestMainloopPollerAction();
} catch (error){
console.log(error)
}
setMainloopTimeout();
}
// 1
const userChatRoleAdmin = "admin";
// 2
const userChatRoleRegular = "regular";
// 3
const userChatRoleReadOnly = "read-only";
// 4
const userChatRoleDeleted = "not-a-member";
function roleToColor(role) {
if (role === userChatRoleAdmin) {
return "#aafff3";
} else if (role === userChatRoleRegular){
return "#ffffff";
} else if (role === userChatRoleReadOnly){
return "#bfb2b2";
} else if (role === userChatRoleDeleted) {
return "#fb4a4a";
}
return "#286500" // Bug
}
function hideHTMLElement(el){
el.style.display = "none";
}
function showHTMLElement(el){
el.style.display = "block";
}
function setElementVisibility(el, isVisible, howVisible = "block"){
el.style.display = isVisible ? howVisible : "none";
}

View File

@ -1,186 +1,188 @@
let rooms = {}; let LocalHistoryId = 0;
let roomToDelete = null;
let currentRoom = null;
let currentHistoryId = 0;
function openRoom(currentRoom) { function genSentBase(){
alert('Вы вошли в комнату: ' + currentRoom); return {
} 'chatListUpdReq': {
'LocalHistoryId': LocalHistoryId
function closeAdd() {
document.getElementById('add_members').style.display = 'none';
}
function openAdd() {
document.getElementById('add_members').style.display = 'flex';
}
function openConfirm(roomNickname) {
roomToDelete = roomNickname;
document.getElementById("delete-chat").style.display = "flex";
}
function closeConfirm() {
roomToDelete = null;
document.getElementById("delete-chat").style.display = "none";
}
function deleteChat() {
if (roomToDelete && rooms[roomToDelete]) {
delete rooms[roomToDelete];
removeRoomFromList(roomToDelete);
closeConfirm();
} else {
alert("Не удалось найти выбранную комнату.");
}
}
function addMember() {
const login = document.getElementById('newMemberLogin').value;
if (login) {
alert(`Участник с никнеймом '${login}' добавлен`);
closeAdd();
} else {
alert('Пожалуйста, введите логин участника');
}
}
function openCreateRoomModal() {
document.getElementById('createRoomModal').style.display = 'block';
}
function closeCreateRoomModal() {
document.getElementById('createRoomModal').style.display = 'none';
}
async function createRoom() {
const errorElement = document.getElementById('error');
const roomName = document.getElementById('newRoomName').value.trim();
const roomNickname = document.getElementById('newRoomNickname').value.trim();
errorElement.style.display = 'none';
errorElement.textContent = '';
if (roomName === '' || roomNickname === '') {
errorElement.textContent = 'Пожалуйста, заполните все поля';
errorElement.style.display = 'block';
return;
}
const request = {
LocalHistoryId: currentHistoryId,
content: {
name: roomName,
nickname: roomNickname
} }
}; };
}
try { let myChats = new Map();
const response = await fetch('/internalapi/createChat', { let chatBoxes = new Map();
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(request)
});
const res = await response.json(); /* Generate text that is displayed on the right side of chat intro box */
function youAreXHere(myRoleHere){
// todo: TRANSLATE IT
return pres['list-rooms']['you-are-X-here'][0] + " " + myRoleHere + " " + pres['list-rooms']['you-are-X-here'][1];
}
if (res.status === 0) {
addRoomToList(roomName, roomNickname); let chatRenunciationWinStoredId = -1;
rooms[roomNickname] = true;
closeCreateRoomModal(); function shouldShowDeleteButton(myMembershipSt){
currentHistoryId = res.update.LocalHistoryId; return myMembershipSt.myRoleHere === userChatRoleDeleted;
window.location.href = '/chat/' + roomNickname; }
/* Updating chat html box after myMembershipSt in it was updated */
function updateBoxWithNewSt(box, myMembershipSt){
let ID = myMembershipSt.chatId;
let roleP = box.querySelector(".CL-my-chat-box-my-role");
roleP.innerText = youAreXHere(myMembershipSt.myRoleHere);
box.style.backgroundColor = roleToColor(myMembershipSt.myRoleHere);
box.querySelector(".CL-my-chat-box-leave-btn").style.display =
(shouldShowDeleteButton(myMembershipSt) ? "none" : "block");
}
function convertMyMembershipStToBox(myMembershipSt){
let chatURI = "/chat/" + myMembershipSt.chatNickname;
let ID = myMembershipSt.chatId;
let box = document.createElement("div");
box.className = "dynamic-block-list-el CL-my-chat-box";
box.style.backgroundColor = roleToColor(myMembershipSt.myRoleHere);
let inBoxNickname = document.createElement("a");
box.appendChild(inBoxNickname);
inBoxNickname.className = "entity-nickname-txt CL-my-chat-box-nickname";
inBoxNickname.innerText = myMembershipSt.chatNickname;
inBoxNickname.href = chatURI;
let inBoxName = document.createElement("a");
box.appendChild(inBoxName);
inBoxName.className = "entity-reg-field-txt CL-my-chat-box-name";
inBoxName.innerText = myMembershipSt.chatName;
inBoxName.href = chatURI;
let inBoxMyRoleHere = document.createElement("p");
box.appendChild(inBoxMyRoleHere);
inBoxMyRoleHere.className = "entity-reg-field-txt CL-my-chat-box-my-role";
inBoxMyRoleHere.innerText = youAreXHere(myMembershipSt.myRoleHere);
let inBoxLeaveBtn = document.createElement("img");
box.appendChild(inBoxLeaveBtn);
inBoxLeaveBtn.className = "CL-my-chat-box-leave-btn";
inBoxLeaveBtn.src = "/assets/img/delete.svg";
inBoxLeaveBtn.onclick = function (ev) {
if (ev.button !== 0)
return;
chatRenunciationWinStoredId = ID;
document.getElementById("chat-renunciation-win-title").innerText =
pres['list-rooms']['reask-leave-chat-X'] + " " + myMembershipSt.chatNickname + "?";
activatePopupWindowById("chat-renunciation-win");
};
box.querySelector(".CL-my-chat-box-leave-btn").style.display =
(shouldShowDeleteButton(myMembershipSt) ? "none" : "block");
return box;
}
function updateLocalStateFromChatListUpdResp(chatListUpdResp){
LocalHistoryId = chatListUpdResp.HistoryId;
let literalChatList = document.getElementById("CL-dblec");
for (let myMembershipSt of chatListUpdResp.myChats){
let chatId = myMembershipSt.chatId;
console.log(myMembershipSt);
if (myChats.has(chatId)){
myChats.set(chatId, myMembershipSt);
updateBoxWithNewSt(chatBoxes.get(chatId), myMembershipSt);
} else { } else {
throw new Error(res.error || 'Ошибка'); if (myMembershipSt.myRoleHere === userChatRoleDeleted)
continue;
myChats.set(chatId, myMembershipSt);
let box = convertMyMembershipStToBox(myMembershipSt)
chatBoxes.set(chatId, box);
literalChatList.appendChild(box);
} }
} catch (error) {
alert('Ошибка создания чата: ' + error.message);
} }
} }
function addRoomToList(roomName) { /* Use it ONLY if `Recv` reported success */
const roomList = document.querySelector('.room-list'); function updateLocalStateFromRecv(Recv){
const existingRoomItem = Array.from(roomList.children).find(item => item.querySelector('.room-name').textContent === roomName); updateLocalStateFromChatListUpdResp(Recv.chatListUpdResp);
if (existingRoomItem) {
existingRoomItem.remove();
}
const roomItem = document.createElement('li');
roomItem.classList.add('room-item');
roomItem.innerHTML = `
<span class="room-name">${roomName}</span>
<button class="delete-chat-button" onclick="openConfirm('${roomNickname}')">Удалить чат</button>
<button class="add-members-button" onclick="openAdd()">Добавить участников</button>
<button class="join-button" onclick="window.location.href = '/chat/${roomNickname}'">Войти</button>
`;
roomList.appendChild(roomItem);
} }
function removeRoomFromList(roomName, roomNickname) { function configureChatCreationInterface(){
const roomList = document.querySelector('.room-list'); document.getElementById("chat-creation-win-yes").onclick = function (ev) {
const roomItem = Array.from(roomList.children).find(item => item.querySelector('.room-name').textContent === roomName); if (ev.button !== 0)
if (roomItem) { return;
roomList.removeChild(roomItem); let chatNicknameInput = document.getElementById("chat-nickname-input");
} let chatNameInput = document.getElementById("chat-name-input");
} let nickname = String(chatNicknameInput.value);
let name = String(chatNameInput.value);
async function initializeRoomList() { deactivateActivePopup();
try { let Sent = genSentBase();
const response = await fetch('/internalapi/getChatList', { Sent.content = {};
method: 'POST', Sent.content.nickname = nickname;
headers: { Sent.content.name = name;
'Content-Type': 'application/json' apiRequest("createChat", Sent
}, ).then((Recv) => {
body: JSON.stringify({}) updateLocalStateFromRecv(Recv);
}).catch((e) => {
alert(pres['list-rooms']["failed-create-chat"]);
console.log(e);
}); });
};
const res = await response.json(); document.getElementById("chat-creation-win-no").onclick = function (ev) {
if (ev.button !== 0)
return;
deactivateActivePopup();
}
if (res.status === 0) { document.getElementById("CL-bacbe").onclick = function (ev){
res.chats.forEach(chat => { if (ev.button !== 0)
addRoomToList(chat.content.name, chat.content.nickname); return;
}); let chatNicknameInput = document.getElementById("chat-nickname-input");
} else { let chatNameInput = document.getElementById("chat-name-input");
throw new Error(res.error || 'Неизвестная ошибка'); chatNicknameInput.value = "";
} chatNameInput.value = "";
} catch (error) { activatePopupWindowById("chat-creation-win");
alert('Ошибка загрузки списка чатов: ' + error.message); };
}
function configureChatRenunciationInterfaceWinPart(){
document.getElementById("chat-renunciation-win-yes").onclick = function (ev){
if (ev.button !== 0)
return;
deactivateActivePopup();
if (chatRenunciationWinStoredId < 0)
throw new Error("chatRenunciationWinStoredId < 0");
let chatId = chatRenunciationWinStoredId;
let Sent = genSentBase();
Sent.chatId = chatId;
apiRequest("leaveChat", Sent
).then((Recv) => {
updateLocalStateFromRecv(Recv);
}).catch((e) => {
alert(pres['list-rooms']["failed-create-chat"]);
console.log(e);
});
}
document.getElementById("chat-renunciation-win-no").onclick = function(ev) {
if (ev.button !== 0)
return;
deactivateActivePopup();
} }
} }
__mainloopDelayMs = 3000;
async function getChatID() { __guestMainloopPollerAction = function(){
const chatNickname = window.location.pathname.split('/').pop(); let Sent = genSentBase();
const response = await fetch('/internalapi/getChatList', { apiRequest("chatListPollEvents", Sent
method: 'POST', ).then((Recv) => {
headers: { console.log("Got a response");
'Content-Type': 'application/json' console.log(Recv);
}, updateLocalStateFromRecv(Recv);
body: JSON.stringify({})
}); });
const res = await response.json();
for (const chat of res.chats) {
if (chat.content.nickname === chatNickname) {
return chat.id;
}
}
return -1;
}
window.onclick = function(event) {
if (event.target === document.getElementById('createRoomModal')) {
closeCreateRoomModal();
}
} }
document.getElementById('newRoomName').addEventListener('keydown', function(event) { window.onload = function () {
if (event.key === 'Enter') { console.log("Loading complete");
createRoom(); updateLocalStateFromChatListUpdResp(initial_chatListUpdResp);
} configureChatCreationInterface();
}); configureChatRenunciationInterfaceWinPart();
document.addEventListener('DOMContentLoaded', initializeRoomList); mainloopPoller();
};

View File

@ -1,44 +0,0 @@
document.addEventListener('DOMContentLoaded', function() {
let nickname, password;
function handleSubmit(event) {
event.preventDefault();
nickname = document.getElementById('nickname').value;
password = document.getElementById('password').value;
pageStatusUpdate();
}
function pageStatusUpdate() {
const req = {
nickname,
password
};
const apiUrl = 'http://127.0.0.1:1025/login';
fetch(apiUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(req)
})
.then(response => {
if (response.ok) {
return response.json();
}
})
.then(data => {
if (data && data.status === 0) {
window.location.href = '/list-rooms';
}
});
}
const form = document.querySelector('form');
form.addEventListener('submit', handleSubmit);
setInterval(pageStatusUpdate, 1000);
});

View File

@ -1,10 +0,0 @@
document.getElementById('fileInput').addEventListener('change', function(event) {
const file = event.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = function(e) {
document.getElementById('avatar').src = e.target.result;
};
reader.readAsDataURL(file);
}
});

View File

@ -0,0 +1,91 @@
{
"lang": "en",
"login": {
"header": "Login",
"directive-nickname": "Enter your nickname:",
"placeholder-nickname": "Nickname",
"directive-password": "Enter password:",
"placeholder-password": "Password",
"act": "Login",
"incorrect-nickname-or-password": "Incorrect nickname or password"
},
"view-profile": {
"header-profile-of": "Profile of",
"directive-nickname": "Nickname:"
},
"edit-profile": {
"header-profile-of": "Profile of",
"change-user-attributes": "Change user attributes",
"directive-nickname": "Nickname:",
"directive-name": "Enter new name:",
"placeholder-name": "New name",
"directive-password": "Enter new password:",
"placeholder-password": "New password",
"directive-bio": "Change description:",
"act-submit": "Submit changes",
"incorrect-profile-data": "Incorrec profile data"
},
"list-rooms": {
"header": "List of chat rooms",
"new-chat-header": "Input identifying information for your new chat",
"directive-nickname": "Enter nickname for new chat:",
"placeholder-nickname": "Take a nickname",
"directive-name": "Enter name for new chat:",
"placeholder-name": "Come up with name",
"reask-create-new-chat": "Create new chat?",
"yes-create": "Yes, create",
"no-create": "No, cancel",
"reask-leave-chat-X": "Do you really want to leave chat",
"yes-leave": "Yes, leave",
"no-leave": "No, cancel",
"page-description": "List of available rooms",
"you-are-X-here": ["You are", "here"],
"failed-create-chat": "Failed to create chat",
"failed-to-leave-chat": "Failed to leave chat"
},
"chat-members": {
"members-of": "Members of",
"summon-label-nickname": "Nickname for summoned user",
"summon-label-ro": "Make read only",
"yes-summon": "Yes, summon",
"no-summon": "No, cancel",
"yes-kick": "Yes, delete",
"no-kick": "No, cancel",
"members-list-of": "Members list of",
"reask-kick-user-X" : "Do you really want to kick user",
"failed-summon-member": "Failed to add user to chat",
"failed-kick-member": "Failed to kick user from chat"
},
"chat": {
"header-chat": "Chat",
"reask-delete-message": "Are you sure you want to delete this message?",
"yes-delete": "Yes, delete",
"no-delete": "No, cancel",
"msgErased": "[ ERASED ]",
"syslog": {
"kicked": "kicked",
"summoned": "summoned",
"left": "left chat",
"created": "created this chat"
},
"failed-delete-message": "Failed to delete message",
"failed-send-message": "Failed to send message"
},
"register": {
"header": "Admin control - Registration",
"directive-nickname": "Nickname for new user",
"placeholder-nickname": "Nickname",
"directive-name": "Name for new user:",
"placeholder-name": "Name",
"directive-password": "Temporary password:",
"placeholder-password": "Password",
"act": "Register him",
"incorrect-nickname": "Incorrect nickname",
"incorrect-name": "Incorrect name",
"incorrect-password": "Incorrect password",
"nickname-taken": "Nickname already taken",
"add_user_error": "add_user failed"
}
}

View File

@ -0,0 +1,91 @@
{
"lang": "ru",
"login": {
"header": "Вход",
"directive-nickname": "Введите свой никнейм:",
"placeholder-nickname": "Никнейм",
"directive-password": "Введите пароль:",
"placeholder-password": "Пароль",
"act": "Войти",
"incorrect-nickname-or-password": "Неверный логин или пароль"
},
"view-profile": {
"header-profile-of": "Профиль",
"directive-nickname": "Никнейм:"
},
"edit-profile": {
"header-profile-of": "Профиль",
"change-user-attributes": "Изменить аттрибуты пользователя",
"directive-nickname": "Никнейм:",
"directive-name": "Введите новое имя:",
"placeholder-name": "Новое имя",
"directive-password": "Введите новый пароль",
"placeholder-password": "Новый пароль",
"directive-bio": "Изменить 'о себе':",
"act-submit": "Применить",
"incorrect-profile-data": "Недопустимые данные профиля"
},
"list-rooms": {
"header": "Список чат-комнат",
"new-chat-header": "Введите идентификационные данные для вашего нового чата",
"directive-nickname": "Введите никнейм для нового чата:",
"placeholder-nickname": "Займите никнейм",
"directive-name": "Введите имя для нового чата:",
"placeholder-name": "Придумайте имя",
"reask-create-new-chat": "Создать новый чат?",
"yes-create": "Да, создай",
"no-create": "Нет, отмена",
"reask-leave-chat-X": "Вы действительно хотите покинуть чат",
"yes-leave": "Да, покидаю",
"no-leave": "Нет, отмена",
"page-description": "Список доступных чат-комнат",
"you-are-X-here": ["Вы", "здесь"],
"failed-create-chat": "Не смог создать чат",
"failed-to-leave-chat": "Не смог покинуть чат"
},
"chat-members": {
"members-of": "Участники",
"summon-label-nickname": "Никнейм для призываемого пользователя",
"summon-label-ro": "Сделать 'лишь читающим'",
"yes-summon": "Да, призваю",
"no-summon": "Нет, отмена",
"yes-kick": "Да, выкидываю",
"no-kick": "Нет, отмена",
"members-list-of": "Список участников",
"reask-kick-user-X" : "Вы действительно хотите выкинуть участника",
"failed-summon-member": "Не смог добавить участника",
"failed-kick-member": "Не смог выкинуть участника"
},
"chat": {
"header-chat": "Чат",
"reask-delete-message": "Удалить это сообщение?",
"yes-delete": "Да, удаляю",
"no-delete": "Нет, отмена",
"msgErased": "[ СТЁРТО ]",
"syslog": {
"kicked": "выкинул",
"summoned": "призвал",
"left": "покинул чат",
"created": "создал этот чат"
},
"failed-delete-message": "Не смог удалить сообщение",
"failed-send-message": "Не смог отправить сообщение"
},
"register": {
"header": "Admin control - Регистрация",
"directive-nickname": "Никнейм для нового пользователя:",
"placeholder-nickname": "Никнейм",
"directive-name": "Имя для нового пользователя:",
"placeholder-name": "Имя",
"directive-password": "Временный пароль:",
"placeholder-password": "Пароль",
"act": "Зарегистрируй его",
"incorrect-nickname": "Плохой никнейм",
"incorrect-name": "Плохое имя",
"incorrect-password": "Плохой пароль",
"nickname-taken": "Никнейм уже занят",
"add_user_error": "add_user failed"
}
}

View File

@ -78,6 +78,7 @@ struct CAWebChat {
"http_structures/client_request_parse.cpp", "http_structures/client_request_parse.cpp",
"http_structures/response_gen.cpp", "http_structures/response_gen.cpp",
"http_structures/cookies.cpp", "http_structures/cookies.cpp",
"http_structures/accept_language.cpp",
"connecting_assets/static_asset_manager.cpp", "connecting_assets/static_asset_manager.cpp",
"running_mainloop.cpp", "running_mainloop.cpp",
"form_data_structure/urlencoded_query.cpp", "form_data_structure/urlencoded_query.cpp",
@ -97,6 +98,7 @@ struct CAWebChat {
"http_structures/client_request.h", "http_structures/client_request.h",
"http_structures/cookies.h", "http_structures/cookies.h",
"http_structures/response_gen.h", "http_structures/response_gen.h",
"http_structures/accept_language.h",
"running_mainloop.h", "running_mainloop.h",
"form_data_structure/urlencoded_query.h", "form_data_structure/urlencoded_query.h",
"socket_address.h", "socket_address.h",
@ -141,6 +143,7 @@ struct CAWebChat {
CTargetDependenceOnExternalLibrary{"sqlite3", {true, true}} CTargetDependenceOnExternalLibrary{"sqlite3", {true, true}}
}; };
T.units = { T.units = {
"localizator.cpp",
"initialize.cpp", "initialize.cpp",
"run.cpp", "run.cpp",
"str_fields.cpp", "str_fields.cpp",
@ -149,16 +152,20 @@ struct CAWebChat {
"login_cookie.cpp", "login_cookie.cpp",
"backend_logic/server_data_interact.cpp", "backend_logic/server_data_interact.cpp",
"backend_logic/client_server_interact.cpp", "backend_logic/client_server_interact.cpp",
"backend_logic/when_list_rooms.cpp",
"backend_logic/when_login.cpp", "backend_logic/when_login.cpp",
"backend_logic/when_list_rooms.cpp",
"backend_logic/when_chat.cpp", "backend_logic/when_chat.cpp",
"backend_logic/when_user.cpp", "backend_logic/when_user.cpp",
"backend_logic/when_api_pollevents.cpp", "backend_logic/when_register.cpp",
"backend_logic/when_api_getchatlist.cpp", "backend_logic/polling.cpp",
"backend_logic/when_api_getchatinfo.cpp", "backend_logic/api_sendmessage.cpp",
"backend_logic/when_api_getchatmemberlist.cpp", "backend_logic/api_deletemessage.cpp",
"backend_logic/when_api_getuserinfo.cpp", "backend_logic/api_addmembertochat.cpp",
"backend_logic/when_api_getmessageinfo.cpp", "backend_logic/api_removememberfromchat.cpp",
"backend_logic/api_createchat.cpp",
"backend_logic/api_leavechat.cpp",
"backend_logic/admin_control_procedure.cpp",
}; };
for (std::string& u: T.units) for (std::string& u: T.units)
u = "web_chat/iu9_ca_web_chat_lib/" + u; u = "web_chat/iu9_ca_web_chat_lib/" + u;

View File

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

View File

@ -0,0 +1,72 @@
#include "accept_language.h"
#include <algorithm>
#include "grammar.h"
#include "../baza_inter.h"
namespace een9 {
bool AcceptLanguageSpec(char ch) {
return ch == ',' || ch == ';' || ch == '=';
}
/* todo: This is one of many places in een9, where bad alloc does not interrupt request,
* todo: completely changing response instead. (see cookies and login cookies lol)
* todo: I have to do something about it. Maybe add more exception types */
std::vector<std::string> parse_header_Accept_Language(const std::string &AcceptLanguage) {
size_t n = AcceptLanguage.size();
struct LR {
std::string lr;
float q = 1;
};
size_t i = 0;
auto skipOWS = [&]() {
while (i < n && isSPACE(AcceptLanguage[i]))
i++;
};
auto isThis = [&](char ch) {
skipOWS();
return i >= n ? false : AcceptLanguage[i] == ch;
};
auto readTkn = [&]() -> std::string {
skipOWS();
if (i >= n)
return "";
size_t bg = i;
while (i < n && !AcceptLanguageSpec(AcceptLanguage[i]) && !isSPACE(AcceptLanguage[i]))
i++;
return AcceptLanguage.substr(bg, i - bg);
};
std::vector<LR> lrs;
#define myMsg "Bad Accept-Language"
while (i < n) {
skipOWS();
if (i >= n)
break;
if (!lrs.empty()) {
if (isThis(','))
i++;
else
break;
}
lrs.emplace_back();
lrs.back().lr = readTkn();
LR lr{readTkn(), 0};
if (isThis(';')) {
i++;
if (readTkn() != "q")
THROW(myMsg);
if (!isThis('='))
THROW(myMsg);
i++;
lrs.back().q = std::stof(readTkn());
}
}
std::sort(lrs.begin(), lrs.end(), [](const LR& A, const LR& B) {
return A.q > B.q;
});
std::vector<std::string> result;
result.reserve(lrs.size());
for (const LR& lr: lrs)
result.push_back(lr.lr == "*" ? "" : lr.lr);
return result;
}
}

View File

@ -0,0 +1,13 @@
#ifndef ENGINE_ENGINE_NUMBER_9_HTTP_STRUCTURES_ACCEPT_LANGUAGE_H
#define ENGINE_ENGINE_NUMBER_9_HTTP_STRUCTURES_ACCEPT_LANGUAGE_H
#include <vector>
#include <string>
namespace een9 {
/* Returns language ranges, sorted by priority (reverse)
* throws std::exception if header is incorrect! But it is not guaranteed. Maybe it won't */
std::vector<std::string> parse_header_Accept_Language(const std::string& AcceptLanguage);
}
#endif

View File

@ -111,7 +111,11 @@ namespace een9 {
status = -1; status = -1;
return status; return status;
} }
res.body.reserve(body_size); res.body.reserve(std::min(100000ul, body_size));
if (body_size == 0) {
status = 1;
}
break;
} }
} }
if (!res.has_body) { if (!res.has_body) {

View File

@ -1,14 +1,20 @@
#include "cookies.h" #include "cookies.h"
#include "../baza_inter.h" #include "../baza_inter.h"
#include "grammar.h"
namespace een9 { namespace een9 {
bool isSPACE(char ch) { bool isSPACE(char ch) {
return ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n'; return ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n';
} }
bool isALPHANUM(char ch) {
return ('a' <= ch && ch <= 'z') || ('A' <= ch && ch <= 'Z') || ('0' <= ch && ch <= '9');
}
bool isToken(const std::string &str) { bool isToken(const std::string &str) {
for (char ch : str) { for (char ch : str) {
if (!(('a' <= ch && ch <= 'z') || ('A' <= ch && ch <= 'Z') || ('0' <= ch && ch <= '9') if (!(isALPHANUM(ch)
|| ch == '!' || ch == '#' || ch == '$' || ch == '%' || ch == '&' || ch == '\'' || ch == '*' || ch == '!' || ch == '#' || ch == '$' || ch == '%' || ch == '&' || ch == '\'' || ch == '*'
|| ch == '+' || ch == '-' || ch == '.' || ch == '^' || ch == '_' || ch == '`' || ch == '|' || ch == '+' || ch == '-' || ch == '.' || ch == '^' || ch == '_' || ch == '`' || ch == '|'
|| ch == '~' )) || ch == '~' ))
@ -43,7 +49,7 @@ namespace een9 {
pos++; pos++;
return hv.substr(S, pos - S); return hv.substr(S, pos - S);
}; };
auto read_to_space_or_dq_or_semc = [&]() -> std::string { auto read_to_space_or_semc = [&]() -> std::string {
size_t S = pos; size_t S = pos;
while (hv.size() > pos && !isSPACE(hv[pos]) && hv[pos] != '"' && hv[pos] != ';') while (hv.size() > pos && !isSPACE(hv[pos]) && hv[pos] != '"' && hv[pos] != ';')
pos++; pos++;
@ -68,16 +74,7 @@ namespace een9 {
THROW("Incorrect Cookie header line, missing ="); THROW("Incorrect Cookie header line, missing =");
pos++; pos++;
skip_ows(); skip_ows();
std::string value_of_pechenye; std::string value_of_pechenye = read_to_space_or_semc();
if (isThis('"')) {
pos++;
value_of_pechenye = read_to_space_or_dq_or_semc();
if (!isThis('"'))
THROW("Incorrect Cookie header line, missing \"");
pos++;
} else {
value_of_pechenye = read_to_space_or_dq_or_semc();
}
// ASSERT(isCookieValue(value_of_pechenye), "Incorrect Cookie value"); // ASSERT(isCookieValue(value_of_pechenye), "Incorrect Cookie value");
result.emplace_back(name_of_pechenye, value_of_pechenye); result.emplace_back(name_of_pechenye, value_of_pechenye);
skip_ows(); skip_ows();

View File

@ -0,0 +1,11 @@
#ifndef ENGINE_ENGINE_NUMBER_9_HTTP_STRUCTURES_GRAMMAR_H
#define ENGINE_ENGINE_NUMBER_9_HTTP_STRUCTURES_GRAMMAR_H
#include <string>
namespace een9 {
bool isSPACE(char ch);
bool isALPHANUM(char ch);
}
#endif

View File

@ -258,10 +258,10 @@ namespace een9 {
} }
errno = 0; errno = 0;
ret = poll(pollfds.data(), Nip, params.mainloop_recheck_interval_us); ret = poll(pollfds.data(), Nip, params.mainloop_recheck_interval_us);
if (ret != 0) { if (ret != 0 && errno != 0) {
printf("poll() error :> %d\n", errno); printf("poll() error :> %s\n", een9::prettyprint_errno("").c_str());
continue;
} }
ASSERT_on_iret(ret, "poll()");
for (size_t i = 0; i < Nip; i++) { for (size_t i = 0; i < Nip; i++) {
if ((pollfds[i].revents & POLLRDNORM)) { if ((pollfds[i].revents & POLLRDNORM)) {
try { try {

View File

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

View File

@ -0,0 +1,35 @@
#include <engine_engine_number_9/baza_inter.h>
#include <engine_engine_number_9/http_structures/accept_language.h>
using namespace een9;
void test(const std::string& al, const std::vector<std::string>& rls) {
std::vector<std::string> got = parse_header_Accept_Language(al);
if (got != rls) {
printf("Test failed: wrong answer\n");
abort();
}
printf("Test passed\n");
}
void btest(const std::string& al) {
try {
parse_header_Accept_Language(al);
} catch (std::exception& e) {}
printf("...\n");
}
int main() {
test("RU-RU, uk-EN; q = 12.22", {"uk-EN", "RU-RU"});
test(" RU-RU ,uk-EN; q = 12.22 ", {"uk-EN", "RU-RU"});
test(" AAA; q=0.1, BBB-bb ; q=3, *; q=3", {"BBB-bb", "", "AAA"});
test(" AAA; q=0.1, BBB-bb ; q=2.5, *; q=4.5", {"", "BBB-bb", "AAA"});
test("ABB, AAA; q=0.1,AAB, BBB-bb ; q=2.5, *; q=4.5", {"", "BBB-bb", "ABB", "AAB", "AAA"});
test("", {});
test(" ", {});
btest(";;;;");
btest(";;==;;");
btest("-;");
btest("-==");
return 0;
}

View File

@ -9,16 +9,19 @@ int main(int argc, char** argv) {
exit(1); exit(1);
} }
std::string dir_path = argv[1]; // std::string dir_path = "./src/http_server/misc_tests/HypertextPages";
std::string dir_path = "/home/gregory/cpp_projects/iu9-ca-web-chat/assets/HypertextPages";
nytl::Templater templater(nytl::TemplaterSettings{nytl::TemplaterDetourRules{dir_path}}); nytl::Templater templater(nytl::TemplaterSettings{nytl::TemplaterDetourRules{dir_path}});
templater.update(); templater.update();
std::string config_file = argv[2]; json::JSON userprofile;
std::string config_text; userprofile["uid"].asInteger() = json::Integer(0l);
een9::readFile(config_file, config_text); userprofile["name"].asString() = "radasdasdasdadsdasd";
const json::JSON config = json::parse_str_flawless(config_text); userprofile["nickname"].asString() = "root";
userprofile["bio"].asString() = "Your mother";
std::string answer2 = templater.render("login", {&config["presentation"].g()}); json::JSON errors;
errors = json::JSON(json::array);
std::string answer2 = templater.render("err-404", {});
printf("%s\n<a><f><t><e><r><><l><f>\n", answer2.c_str()); printf("%s\n<a><f><t><e><r><><l><f>\n", answer2.c_str());
return 0; return 0;

View File

@ -7,8 +7,13 @@ namespace nytl {
void debug_print_templater(const Templater& T) { void debug_print_templater(const Templater& T) {
printf("===== TEMPLATER INTERNAL RESOURCES =====\n"); printf("===== TEMPLATER INTERNAL RESOURCES =====\n");
for (auto& p: T.elements) { for (auto& p: T.elements) {
if (!p.second.is_element) {
printf("=== %s is empty =====\n", p.first.c_str());
continue;
}
printf("=== %s element =====\n", p.first.c_str()); printf("=== %s element =====\n", p.first.c_str());
const Element& el = p.second; assert(p.second.when_element);
const Element& el = *p.second.when_element;
printf("%s, %s\n", el.base ? "BASE" : "NOT BASE", el.is_hidden ? "HIDDEN" : "NOT HIDDEN"); printf("%s, %s\n", el.base ? "BASE" : "NOT BASE", el.is_hidden ? "HIDDEN" : "NOT HIDDEN");
if (!el.is_hidden) { if (!el.is_hidden) {
std::string signature; std::string signature;

View File

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

View File

@ -23,58 +23,64 @@ namespace nytl {
result(result) { result(result) {
} }
void descend(const json::JSON& what) { void descend(const json::JSON& what, const global_elem_set_t& global_elems) {
if (result.is_json) { if (result.is_json) {
const json::JSON& P = *result.JSON_subval; const json::JSON& P = *result.JSON_subval;
if (P.isArray() && what.isInteger()) { if (P.isArray() && what.isInteger()) {
const std::vector<json::JSON>& arr_p = P.asArray(); const std::vector<json::JSON>& arr_p = P.asArray();
int64_t ind_w = what.asInteger().get_int(); int64_t ind_w = what.asInteger().get_int();
ASSERT(ind_w > 0 && ind_w < arr_p.size(), "Expression \"array[integer]\" caused out-of-bound situation"); if (!(ind_w > 0 && ind_w < arr_p.size()))
THROW("Expression \"array[integer]\" caused out-of-bound situation");
result = LocalVarValue{true, "", &arr_p[ind_w]}; result = LocalVarValue{true, "", &arr_p[ind_w]};
} else if (P.isDictionary() && what.isString()) { } else if (P.isDictionary() && what.isString()) {
const std::map<std::string, json::JSON>& dict_p = P.asDictionary(); const std::map<std::string, json::JSON>& dict_p = P.asDictionary();
const std::string& key_w = what.asString(); const std::string& key_w = what.asString();
ASSERT(dict_p.count(key_w) == 1, "No such key exception (" + key_w + ")"); if (dict_p.count(key_w) != 1)
THROW("No such key exception (" + key_w + ")");
result = LocalVarValue{true, "", &dict_p.at(key_w)}; result = LocalVarValue{true, "", &dict_p.at(key_w)};
} else } else
THROW("Incorrect type of \"json[json]\" expression. Unallowed signature of [] operator"); THROW("Incorrect type of \"json[json]\" expression. Unallowed signature of [] operator");
} else { } else {
ASSERT(what.isString(), "Expression \"element[X]\" allowed only if X is string (json object)"); if (!what.isString())
if (what.asString().empty()) THROW("Expression \"element[X]\" allowed only if X is string (json object)");
return; if (!isUname(what.asString()))
if (!is_uname_dotted_sequence(what.asString())) THROW("Expression \"element[str]\" has incorrect str (" + what.asString() + ")");
THROW("Incorrect X in \"element[X]\"");
result.EL_name += ("." + what.asString()); result.EL_name += ("." + what.asString());
if (global_elems.count(result.EL_name) != 1)
THROW("Can't descend. No such element (" + result.EL_name + ")");
} }
} }
uptr<EEFrame> toMe(bool returned, const global_elem_set_t& global_elems, uptr<EEFrame> toMe(bool returned, const global_elem_set_t& global_elems,
const std::vector<LocalVarValue>& local_vars) { const std::vector<LocalVarValue>& local_vars) {
if (returned) { if (returned) {
ASSERT(temp_ret.is_json, "Expression \"X[ element ]\" is not allowed"); if (!temp_ret.is_json)
THROW("Expression \"X[ element ]\" is not allowed");
assert(temp_ret.JSON_subval); assert(temp_ret.JSON_subval);
descend(*(temp_ret.JSON_subval)); descend(*(temp_ret.JSON_subval), global_elems);
} else { } else {
assert(expr.isDictionary()); assert(expr.isDictionary());
const json::JSON& val = expr["V"].g(); const json::JSON& val = expr["V"];
if (val.isInteger()) { if (val.isInteger()) {
size_t lv_ind = val.asInteger().get_int(); size_t lv_ind = val.asInteger().get_int();
assert(lv_ind < local_vars.size()); assert(lv_ind < local_vars.size());
result = local_vars[lv_ind]; result = local_vars[lv_ind];
} else if (val.isString()) { } else if (val.isString()) {
std::string cur_el_name_str = expr["V"].g().asString(); std::string cur_el_name_str = expr["V"].asString();
if (global_elems.count(cur_el_name_str) != 1)
THROW("Bad expression, no such element (" + cur_el_name_str + ")");
result = LocalVarValue{false, cur_el_name_str, NULL}; result = LocalVarValue{false, cur_el_name_str, NULL};
} else } else
assert(false); assert(false);
} }
const std::vector<json::JSON>& chain = expr["C"].g().asArray(); const std::vector<json::JSON>& chain = expr["C"].asArray();
while (true) { while (true) {
if (chain_el >= chain.size()) if (chain_el >= chain.size())
return NULL; return NULL;
const json::JSON& t = chain[chain_el++]; const json::JSON& t = chain[chain_el++];
if (t.isDictionary()) if (t.isDictionary())
return std::make_unique<EEFrame>(t, temp_ret); return std::make_unique<EEFrame>(t, temp_ret);
descend(t); descend(t, global_elems);
} }
} }
}; };
@ -84,6 +90,8 @@ namespace nytl {
* and dictionaries. They are stored in json in rendering stack */ * and dictionaries. They are stored in json in rendering stack */
LocalVarValue rendering_core_execute_expression(const global_elem_set_t& global_elems, LocalVarValue rendering_core_execute_expression(const global_elem_set_t& global_elems,
const std::vector<LocalVarValue>& local_vars, const json::JSON& expr) { const std::vector<LocalVarValue>& local_vars, const json::JSON& expr) {
// todo: check if root element exists (if root value is not local variable, then it is element of package)
bool returned = false; bool returned = false;
std::vector<uptr<EEFrame>> stack; std::vector<uptr<EEFrame>> stack;
LocalVarValue result; LocalVarValue result;
@ -211,8 +219,9 @@ namespace nytl {
uptr<RFrame> RFrame_OverParts::toMe(bool returned, const global_elem_set_t &elem_ns, Ditch &result, uptr<RFrame> RFrame_OverParts::toMe(bool returned, const global_elem_set_t &elem_ns, Ditch &result,
const std::function<std::string(std::string)> &escape) { const std::function<std::string(std::string)> &escape) {
if (!returned) if (!returned)
ASSERT(elem_ns.count(name) == 1, "No such element"); if ((elem_ns.count(name) != 1) || (!elem_ns.at(name).is_element))
const Element& el = elem_ns.at(name); THROW("Can't render. No such element (" + name + ")");
const Element& el = *elem_ns.at(name).when_element;
if (!returned) { if (!returned) {
/* Continue to do checks */ /* Continue to do checks */
/* hidden elements (internal) do not need any check */ /* hidden elements (internal) do not need any check */
@ -221,14 +230,17 @@ namespace nytl {
ASSERT(n == passed_args.size(), "Argument count mismatch"); ASSERT(n == passed_args.size(), "Argument count mismatch");
for (size_t i = 0; i < n; i++) { for (size_t i = 0; i < n; i++) {
if (el.arguments[i].type == json::true_symbol) { if (el.arguments[i].type == json::true_symbol) {
ASSERT(passed_args[i].is_json, "Expected json element argument, got element"); if (!passed_args[i].is_json)
THROW("Expected json element argument, got element");
} else { } else {
// If not json is expected, element must be expected // If not json is expected, element must be expected
assert(el.arguments[i].isArray()); assert(el.arguments[i].isArray());
ASSERT(!passed_args[i].is_json, "Expected element element arguemnt, got json"); if (passed_args[i].is_json)
ASSERT(elem_ns.count(passed_args[i].EL_name), "No such element, can't compare signatures of argument value"); THROW("Expected element element arguemnt, got json");
const Element& arg_element = elem_ns.at(passed_args[i].EL_name); const std::string& passed_el_as_arg = passed_args[i].EL_name;
// ASSERT(passed_args); if ((elem_ns.count(passed_el_as_arg) != 1) || !elem_ns.at(passed_el_as_arg).is_element)
THROW("No such element, can't compare signatures of argument value (" + passed_el_as_arg + ")");
const Element& arg_element = elem_ns.at(passed_el_as_arg).when_element.operator*();
if(el.arguments[i].asArray() != arg_element.arguments) if(el.arguments[i].asArray() != arg_element.arguments)
THROW("Signature of argument " + std::to_string(i) + " does not match"); THROW("Signature of argument " + std::to_string(i) + " does not match");
} }

View File

@ -109,16 +109,17 @@ namespace nytl {
return result; return result;
} }
TemplaterRegPref gen_base_element() {
Element* e = new Element{{json::JSON(true)}, true, false, {}};
return {1, std::unique_ptr<Element>(e)};
}
void Templater::update() { void Templater::update() {
elements = { elements[""] = TemplaterRegPref{0, NULL};
{"jsinsert", Element{{json::JSON(true)}, true}}, elements["jsinsert"] = gen_base_element();
{"jesc", Element{{json::JSON(true)}, true}}, elements["jesc"] = gen_base_element();
{"jesccomp", Element{{json::JSON(true)}, true}}, elements["str2text"] = gen_base_element();
/* str2text base element has a dedicated operator - WRITE */ elements["str2code"] = gen_base_element();
{"str2text", Element{{json::JSON(true)}, true}},
/* str2code base element has a dedicated operator - ROUGHINSERT */
{"str2code", Element{{json::JSON(true)}, true}},
};
std::vector<InterestingFile> intersting_files = indexing_detour(settings.det); std::vector<InterestingFile> intersting_files = indexing_detour(settings.det);
for (const InterestingFile& file: intersting_files) { for (const InterestingFile& file: intersting_files) {
std::string content = readFile(file.path); std::string content = readFile(file.path);
@ -132,7 +133,8 @@ namespace nytl {
/* Still can throw some stuff derived from std::exception (like bad alloc) */ /* Still can throw some stuff derived from std::exception (like bad alloc) */
std::string Templater::render(const std::string& element, const std::vector<const json::JSON*> &arguments) const { std::string Templater::render(const std::string& element, const std::vector<const json::JSON*> &arguments) const {
ASSERT(is_uname_dotted_sequence(element), "Incorrect entry element name"); if (!is_uname_dotted_sequence(element))
THROW("Incorrect entry element name");
return rendering_core(element, arguments, elements, settings.escape); return rendering_core(element, arguments, elements, settings.escape);
} }
} }

View File

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

View File

@ -0,0 +1,81 @@
#include "server_data_interact.h"
#include <engine_engine_number_9/baza_throw.h>
#include "../str_fields.h"
#include <string.h>
namespace iu9cawebchat {
/* nagative `forced_id` means id isn't forced */
void add_user(SqliteConnection& conn, const std::string& nickname, const std::string& name,
const std::string& password, const std::string& bio, int64_t forced_id) {
if (!check_nickname(nickname))
een9_THROW("Bad user nickname " + nickname + ". Can't reg");
if (!check_name(name))
een9_THROW("Bad user name " + name + ". Can't reg");
if (!check_strong_password(password))
een9_THROW("Bad user password. Can't reg");
if (!is_orthodox_string(bio))
een9_THROW("Bad user bio. Can't reg");
if (is_nickname_taken(conn, nickname))
een9_THROW("Nickname taken already. Can't reg");
reserve_nickname(conn, nickname);
SqliteStatement req(conn,
"INSERT INTO `user` (`id`, `nickname`, `name`, `chatList_HistoryId`, `password`, `bio`) "
"VALUES (?1, ?2, ?3, 0, ?4, ?5)", {}, {{2, nickname}, {3, name}, {4, password}, {5, bio}});
if (forced_id >= 0)
sqlite_stmt_bind_int64(req, 1, forced_id);
int must_be_done = sqlite_stmt_step(req, {}, {});
if (must_be_done != SQLITE_DONE)
een9_THROW("sqlite error");
}
std::string admin_control_procedure(SqliteConnection& conn, const std::string& req, bool& termination) {
size_t nid = 0;
auto read_thing = [&]() -> std::string {
while (nid < req.size() && isSPACE(req[nid]))
nid++;
std::string result;
bool esc = false;
while (nid < req.size() && (esc || !isSPACE(req[nid]))) {
if (esc) {
result += req[nid];
esc = false;
} else if (req[nid] == '\\') {
esc = true;
} else {
result += req[nid];
}
nid++;
}
return result;
};
std::string cmd = read_thing();
if (cmd == "hello") {
return ":0 omg! hiii!! Hewwou :3 !!!!\n";
}
if (cmd == "8") {
termination = true;
return "Bye\n";
}
const char* adduser_pref = "adduser";
if (cmd == "updaterootpw") {
std::string new_password = read_thing();
if (!check_strong_password(new_password))
return "Bad password. Can't update";
sqlite_nooutput(conn,
"UPDATE `user` SET `password` = ?1 WHERE `id` = 0", {}, {{1, new_password}});
return "Successul update\n";
}
/* adduser <nickname> <name> <password> <bio> */
if (cmd == "adduser") {
std::string nickname = read_thing();
std::string name = read_thing();
std::string password = read_thing();
std::string bio = read_thing();
add_user(conn, nickname, name, password, bio);
return "User " + nickname + " successfully registered";
}
return "Incorrect command\n";
}
}

View File

@ -0,0 +1,75 @@
#include "server_data_interact.h"
#include <engine_engine_number_9/baza_throw.h>
#include <assert.h>
namespace iu9cawebchat {
bool is_membership_row_present(SqliteConnection& conn, int64_t chatId, int64_t alienUserId) {
SqliteStatement req(conn,
"SELECT EXISTS(SELECT 1 FROM `user_chat_membership` WHERE `chatId` = ?1 AND `userId` = ?2)",
{{1, chatId}, {2, alienUserId}}, {});
fsql_integer_or_null r{true, 0};
int status = sqlite_stmt_step(req, {{0, &r}}, {});
return (bool)r.value;
}
void alter_user_chat_role(SqliteConnection& conn, int64_t chatId, int64_t alienUserId, int64_t role) {
int64_t chat_HistoryId_BEFORE_EV = get_current_history_id_of_chat(conn, chatId);
int64_t alien_chatlist_HistoryId_BEFORE_EV = get_current_history_id_of_user_chatList(conn, alienUserId);
if (!is_membership_row_present(conn, chatId, alienUserId)) {
sqlite_nooutput(conn,
"INSERT INTO `user_chat_membership` (`userId`, `chatId`, `user_chatList_IncHistoryId`,"
"`chat_IncHistoryId`, `role`) VALUES (?1, ?2, ?3, ?4, ?5)",
{{1, alienUserId}, {2, chatId}, {3, alien_chatlist_HistoryId_BEFORE_EV + 1},
{4, chat_HistoryId_BEFORE_EV + 1}, {5, role}}, {});
} else {
sqlite_nooutput(conn,
"UPDATE `user_chat_membership` SET `user_chatList_IncHistoryId` = ?3,`chat_IncHistoryId` = ?4,"
"`role` = ?5 WHERE `userId` = ?1 AND `chatId` = ?2",
{{1, alienUserId}, {2, chatId}, {3, alien_chatlist_HistoryId_BEFORE_EV + 1},
{4, chat_HistoryId_BEFORE_EV + 1}, {5, role}}, {});
}
sqlite_nooutput(conn,
"UPDATE `chat` SET `it_HistoryId` = ?1 WHERE `id` = ?2", {{1, chat_HistoryId_BEFORE_EV + 1},
{2, chatId}}, {});
sqlite_nooutput(conn,
"UPDATE `user` SET `chatList_HistoryId` = ?1 WHERE `id` = ?2",
{{1, alien_chatlist_HistoryId_BEFORE_EV + 1}, {2, alienUserId}}, {});
}
void make_her_a_member_of_the_midnight_crew(SqliteConnection& conn, int64_t chatId, int64_t alienUserId, int64_t role) {
assert(role != user_chat_role_deleted);
alter_user_chat_role(conn, chatId, alienUserId, role);
}
json::JSON internalapi_addMemberToChat(SqliteConnection& conn, int64_t uid, const json::JSON& Sent) {
int64_t chatId = Sent["chatUpdReq"]["chatId"].asInteger().get_int();
int64_t my_role_here = get_role_of_user_in_chat(conn, uid, chatId);
if (my_role_here != user_chat_role_admin)
een9_THROW("Non-admin user tries to access internalapi_getChatInfo");
std::string alien_nickname = Sent["nickname"].asString();
RowUser_Content alien;
try {
alien = lookup_user_content_by_nickname(conn, alien_nickname);
} catch (std::exception& e) {
return at_api_error_gen_bad_recv(-1l);
}
bool makeReadOnly = Sent["makeReadOnly"].toBool();
int64_t aliens_old_role = get_role_of_user_in_chat(conn, alien.id, chatId);
if (aliens_old_role == user_chat_role_deleted) {
make_her_a_member_of_the_midnight_crew(conn, chatId, alien.id,
makeReadOnly ? user_chat_role_read_only : user_chat_role_regular);
} else {
return at_api_error_gen_bad_recv(-2l);
}
insert_system_message_svo(conn, chatId, uid, "summoned", alien.id);
json::JSON Recv;
poll_update_chat(conn, Sent, Recv);
return Recv;
}
}

View File

@ -0,0 +1,30 @@
#include "server_data_interact.h"
#include <engine_engine_number_9/baza_throw.h>
#include "../str_fields.h"
namespace iu9cawebchat {
json::JSON internalapi_createChat(SqliteConnection& conn, int64_t uid, const json::JSON& Sent) {
std::string new_chat_name = Sent["content"]["name"].asString();
std::string new_chat_nickname = Sent["content"]["nickname"].asString();
if (!check_nickname(new_chat_nickname) || !check_name(new_chat_name))
return at_api_error_gen_bad_recv(-1l);
if (is_nickname_taken(conn, new_chat_nickname))
return at_api_error_gen_bad_recv(-2l);
if (is_nickname_taken(conn, new_chat_nickname))
return at_api_error_gen_bad_recv(-3l);
reserve_nickname(conn, new_chat_nickname);
sqlite_nooutput(conn,
"INSERT INTO `chat` (`nickname`, `name`, `it_HistoryId`, `lastMsgId`) VALUES (?1, ?2, 0, -1)",
{}, {{1, new_chat_nickname}, {2, new_chat_name}});
int64_t CHAT_ID = sqlite_trsess_last_insert_rowid(conn);
make_her_a_member_of_the_midnight_crew(conn, CHAT_ID, uid, user_chat_role_admin);
// todo: send a message into chat
json::JSON Recv;
poll_update_chat_list(conn, uid, Sent, Recv);
return Recv;
}
}

View File

@ -0,0 +1,33 @@
#include "server_data_interact.h"
#include <engine_engine_number_9/baza_throw.h>
#include "../debug.h"
namespace iu9cawebchat {
json::JSON internalapi_deleteMessage(SqliteConnection& conn, int64_t uid, const json::JSON& Sent) {
int64_t chatId = Sent["chatUpdReq"]["chatId"].asInteger().get_int();
int64_t my_role_here = get_role_of_user_in_chat(conn, uid, chatId);
if (my_role_here == user_chat_role_deleted)
een9_THROW("Unauthorized user tries to access internalapi_getChatInfo");
if (my_role_here == user_chat_role_read_only)
een9_THROW("read-only user can't send messages");
int64_t LocalHistoryId = Sent["chatUpdReq"]["LocalHistoryId"].asInteger().get_int();
int64_t msgId = Sent["id"].asInteger().get_int();
RowMessage_Content msgInQuestion = lookup_message_content(conn, chatId, msgId);
if (!(!msgInQuestion.isSystem && (msgInQuestion.senderUserId == uid || my_role_here == user_chat_role_admin) ))
een9_THROW("Can't delete: permission denied");
int64_t chat_HistoryId_BEFORE_EV = get_current_history_id_of_chat(conn, chatId);
sqlite_nooutput(conn,
"UPDATE `message` SET `exists` = 0, `text` = NULL, `chat_IncHistoryId` = ?1 WHERE `id` = ?2",
{{1, chat_HistoryId_BEFORE_EV + 1}, {2, msgId}});
sqlite_nooutput(conn, "UPDATE `chat` SET `it_HistoryId` = ?1 WHERE `id` = ?2",
{{1, chat_HistoryId_BEFORE_EV + 1}, {2, chatId}}, {});
json::JSON Recv;
poll_update_chat(conn, Sent, Recv);
return Recv;
}
}

View File

@ -0,0 +1,15 @@
#include "server_data_interact.h"
#include <engine_engine_number_9/baza_throw.h>
namespace iu9cawebchat {
json::JSON internalapi_leaveChat(SqliteConnection& conn, int64_t uid, const json::JSON& Sent) {
int64_t chatId = Sent["chatId"].asInteger().get_int();
if (get_role_of_user_in_chat(conn, uid, chatId) == user_chat_role_deleted)
een9_THROW("Not a member");
kick_from_chat(conn, chatId, uid);
insert_system_message_svo(conn, chatId, uid, "left", -1);
json::JSON Recv;
poll_update_chat_list(conn, uid, Sent, Recv);
return Recv;
}
}

View File

@ -0,0 +1,21 @@
#include "server_data_interact.h"
#include <engine_engine_number_9/baza_throw.h>
namespace iu9cawebchat {
void kick_from_chat(SqliteConnection& conn, int64_t chatId, int64_t alienUserId) {
alter_user_chat_role(conn, chatId, alienUserId, user_chat_role_deleted);
}
json::JSON internalapi_removeMemberFromChat(SqliteConnection& conn, int64_t uid, const json::JSON& Sent) {
int64_t chatId = Sent["chatUpdReq"]["chatId"].asInteger().get_int();
int64_t my_role_here = get_role_of_user_in_chat(conn, uid, chatId);
if (my_role_here != user_chat_role_admin)
een9_THROW("Only admin can delete members of chat");
int64_t badAlienId = Sent["userId"].asInteger().get_int();
kick_from_chat(conn, chatId, badAlienId);
insert_system_message_svo(conn, chatId, uid, "kicked", badAlienId);
json::JSON Recv;
poll_update_chat(conn, Sent, Recv);
return Recv;
}
}

View File

@ -0,0 +1,50 @@
#include "server_data_interact.h"
#include <engine_engine_number_9/baza_throw.h>
#include "../str_fields.h"
#include "../debug.h"
namespace iu9cawebchat {
/* No authorization check is performed
* Chat's HistoryId will increment after this operation
* if adding system message, uid is ignored
*/
void insert_new_message(SqliteConnection& conn, int64_t uid, int64_t chatId, const std::string& text, bool isSystem) {
int64_t chat_HistoryId_BEFORE_MSG = get_current_history_id_of_chat(conn, chatId);
int64_t chat_lastMsgId = get_lastMsgId_of_chat(conn, chatId);
SqliteStatement req(conn,
"INSERT INTO `message` (`chatId`, `id`, `senderUserId`, `exists`, `isSystem`, `chat_IncHistoryId`, "
"`text`) VALUES (?1, ?2, ?3, 1, ?4, ?5, ?6)",
{{1, chatId}, {2, chat_lastMsgId + 1}, {4, (int64_t)isSystem}, {5, chat_HistoryId_BEFORE_MSG + 1}}, {{6, text}});
if (!isSystem)
sqlite_stmt_bind_int64(req, 3, uid);
if (sqlite_stmt_step(req, {}, {}) != SQLITE_DONE)
een9_THROW("There must be something wrong");
sqlite_nooutput(conn, "UPDATE `chat` SET `lastMsgId` = ?1, `it_HistoryId` = ?2 WHERE `id` = ?3",
{{1, chat_lastMsgId + 1}, {2, chat_HistoryId_BEFORE_MSG + 1}, {3, chatId}}, {});
}
void insert_system_message_svo(SqliteConnection& conn, int64_t chatId,
int64_t subject, const std::string& verb, int64_t object) {
insert_new_message(conn, -1, chatId,
std::to_string(subject) + "," + verb + "," + std::to_string(object), true);
}
json::JSON internalapi_sendMessage(SqliteConnection& conn, int64_t uid, const json::JSON& Sent) {
int64_t chatId = Sent["chatUpdReq"]["chatId"].asInteger().get_int();
int64_t my_role_here = get_role_of_user_in_chat(conn, uid, chatId);
if (my_role_here == user_chat_role_deleted)
een9_THROW("Unauthorized user tries to access internalapi_getChatInfo");
if (my_role_here == user_chat_role_read_only)
een9_THROW("read-only user can't send messages");
std::string text = Sent["content"]["text"].asString();
if (!is_orthodox_string(text) || text.empty())
een9_THROW("Bad input text");
insert_new_message(conn, uid, chatId, text, false);
json::JSON Recv;
poll_update_chat(conn, Sent, Recv);
return Recv;
}
}

View File

@ -1,5 +1,6 @@
#include "client_server_interact.h" #include "client_server_interact.h"
#include <engine_engine_number_9/http_structures/cookies.h> #include <engine_engine_number_9/http_structures/cookies.h>
#include <functional>
namespace iu9cawebchat { namespace iu9cawebchat {
void initial_extraction_of_all_the_useful_info_from_cookies( void initial_extraction_of_all_the_useful_info_from_cookies(
@ -19,60 +20,72 @@ namespace iu9cawebchat {
if (ret_logged_in_user >= 0) { if (ret_logged_in_user >= 0) {
ret_userinfo["uid"] = json::JSON(ret_logged_in_user); ret_userinfo["uid"] = json::JSON(ret_logged_in_user);
ret_userinfo["nickname"] = json::JSON(tried.nickname); ret_userinfo["nickname"] = json::JSON(tried.nickname);
ret_userinfo["name"] = json::JSON(find_user_name(conn, ret_logged_in_user)); ret_userinfo["name"] = json::JSON(get_user_name(conn, ret_logged_in_user));
} }
} }
} }
std::string RTEE(const std::string& el_name, std::string http_R200(const std::string &el_name, WorkerGuestData &wgd,
const json::JSON& config_presentation, WorkerGuestData& wgd, const std::vector<const json::JSON *> &args) {
const json::JSON& userinfo) { std::string page = wgd.templater->render(el_name, args);
std::string page = wgd.templater->render(el_name, {&config_presentation, &userinfo});
return een9::form_http_server_response_200("text/html", page); 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 =========================*/ /* ========================= 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) {
std::string when_internalapi_pollevents(WorkerGuestData& wgd,
const een9::ClientRequest& req, int64_t uid) {
const json::JSON& Sent = json::parse_str_flawless(req.body); const json::JSON& Sent = json::parse_str_flawless(req.body);
std::string result = json::generate_str(internalapi_pollEvents(*wgd.db, uid, Sent), json::print_pretty); std::string result = json::generate_str(F(*wgd.db, uid, Sent), json::print_pretty);
return een9::form_http_server_response_200("text/json", result); return een9::form_http_server_response_200("text/json", result);
} }
std::string when_internalapi_getchatlist(WorkerGuestData& wgd, std::string when_internalapi_chatpollevents(WorkerGuestData& wgd, const een9::ClientRequest& req, int64_t uid) {
const een9::ClientRequest& req, int64_t uid) { return when_internalapi(wgd, req, uid, internalapi_chatPollEvents);
const json::JSON& Sent = json::parse_str_flawless(req.body);
std::string result = json::generate_str(internalapi_getChatList(*wgd.db, uid), json::print_pretty);
return een9::form_http_server_response_200("text/json", result);
} }
std::string when_internalapi_getchatinfo(WorkerGuestData& wgd, std::string when_internalapi_chatlistpollevents(WorkerGuestData &wgd, const een9::ClientRequest &req, int64_t uid) {
const een9::ClientRequest& req, int64_t uid) { return when_internalapi(wgd, req, uid, internalapi_chatListPollEvents);
const json::JSON& Sent = json::parse_str_flawless(req.body);
std::string result = json::generate_str(internalapi_getChatInfo(*wgd.db, uid, Sent), json::print_pretty);
return een9::form_http_server_response_200("text/json", result);
} }
std::string when_internalapi_getchatmemberlist(WorkerGuestData& wgd, std::string when_internalapi_getmessageneighbours(WorkerGuestData &wgd, const een9::ClientRequest &req, int64_t uid) {
const een9::ClientRequest& req, int64_t uid) { return when_internalapi(wgd, req, uid, internalapi_getMessageNeighbours);
const json::JSON& Sent = json::parse_str_flawless(req.body);
std::string result = json::generate_str(internalapi_getChatMemberList(*wgd.db, uid, Sent), json::print_pretty);
return een9::form_http_server_response_200("text/json", result);
} }
std::string when_internalapi_getuserinfo(WorkerGuestData& wgd, std::string when_internalapi_sendmessage(WorkerGuestData &wgd, const een9::ClientRequest &req, int64_t uid) {
const een9::ClientRequest& req, int64_t uid) { return when_internalapi(wgd, req, uid, internalapi_sendMessage);
const json::JSON& Sent = json::parse_str_flawless(req.body);
std::string result = json::generate_str(internalapi_getUserInfo(*wgd.db, uid, Sent), json::print_pretty);
return een9::form_http_server_response_200("text/json", result);
} }
std::string when_internalapi_getmessageinfo(WorkerGuestData& wgd, std::string when_internalapi_deletemessage(WorkerGuestData &wgd, const een9::ClientRequest &req, int64_t uid) {
const een9::ClientRequest& req, int64_t uid) { return when_internalapi(wgd, req, uid, internalapi_deleteMessage);
const json::JSON& Sent = json::parse_str_flawless(req.body);
std::string result = json::generate_str(internalapi_getMessageInfo(*wgd.db, uid, Sent), json::print_pretty);
return een9::form_http_server_response_200("text/json", result);
} }
}
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

@ -11,12 +11,14 @@
#include <jsonincpp/jsonobj.h> #include <jsonincpp/jsonobj.h>
#include <new_york_transit_line/templater.h> #include <new_york_transit_line/templater.h>
#include <memory> #include <memory>
#include "../localizator.h"
namespace iu9cawebchat { namespace iu9cawebchat {
struct WorkerGuestData { struct WorkerGuestData {
/* Because templaters use libjsonincpp, they can't be READ by two thread simultaneously */ /* Because templaters use libjsonincpp, they can't be READ by two thread simultaneously */
std::unique_ptr<nytl::Templater> templater; std::unique_ptr<nytl::Templater> templater;
std::unique_ptr<SqliteConnection> db; std::unique_ptr<SqliteConnection> db;
std::unique_ptr<Localizator> locales;
}; };
void initial_extraction_of_all_the_useful_info_from_cookies( void initial_extraction_of_all_the_useful_info_from_cookies(
@ -27,9 +29,17 @@ namespace iu9cawebchat {
int64_t& ret_logged_in_user int64_t& ret_logged_in_user
); );
std::string RTEE(const std::string& el_name, std::string http_R200(const std::string& el_name, WorkerGuestData& wgd,
const json::JSON& config_presentation, WorkerGuestData& wgd, const std::vector<const json::JSON*>& args);
const json::JSON& userinfo);
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 ================================== */ /* ========================== PAGES ================================== */
@ -43,28 +53,29 @@ namespace iu9cawebchat {
const een9::ClientRequest& req, const json::JSON& userinfo); const een9::ClientRequest& req, const json::JSON& userinfo);
std::string when_page_user(WorkerGuestData& wgd, const json::JSON& config_presentation, std::string when_page_user(WorkerGuestData& wgd, const json::JSON& config_presentation,
const een9::ClientRequest& req, const json::JSON& userinfo); const een9::ClientRequest& req, const std::vector<LoginCookie>& login_cookies, const json::JSON& userinfo);
std::string when_page_register(WorkerGuestData& wgd, const json::JSON& config_presentation,
const een9::ClientRequest& req, const json::JSON& userinfo);
/* ======================== API ============================== */ /* ======================== API ============================== */
std::string when_internalapi_chatpollevents(WorkerGuestData& wgd, const een9::ClientRequest& req, int64_t uid);
std::string when_internalapi_pollevents(WorkerGuestData& wgd, std::string when_internalapi_chatlistpollevents(WorkerGuestData& wgd, const een9::ClientRequest& req, int64_t uid);
const een9::ClientRequest& req, int64_t uid);
std::string when_internalapi_getchatlist(WorkerGuestData& wgd, std::string when_internalapi_getmessageneighbours(WorkerGuestData& wgd, const een9::ClientRequest& req, int64_t uid);
const een9::ClientRequest& req, int64_t uid);
std::string when_internalapi_getchatinfo(WorkerGuestData& wgd, std::string when_internalapi_sendmessage(WorkerGuestData &wgd, const een9::ClientRequest &req, int64_t uid);
const een9::ClientRequest& req, int64_t uid);
std::string when_internalapi_getchatmemberlist(WorkerGuestData& wgd, std::string when_internalapi_deletemessage(WorkerGuestData &wgd, const een9::ClientRequest &req, int64_t uid);
const een9::ClientRequest& req, int64_t uid);
std::string when_internalapi_getuserinfo(WorkerGuestData& wgd, std::string when_internalapi_addmembertochat(WorkerGuestData &wgd, const een9::ClientRequest &req, int64_t uid);
const een9::ClientRequest& req, int64_t uid);
std::string when_internalapi_getmessageinfo(WorkerGuestData& wgd, std::string when_internalapi_removememberfromchat(WorkerGuestData &wgd, const een9::ClientRequest &req, int64_t uid);
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 #endif

View File

@ -0,0 +1,198 @@
#include "server_data_interact.h"
#include <assert.h>
#include <engine_engine_number_9/baza_throw.h>
#include "../debug.h"
namespace iu9cawebchat {
json::JSON poll_update_chat_list_resp(SqliteConnection& conn, int64_t userId, int64_t LocalHistoryId) {
printf("Userid: %ld\n", userId);
json::JSON chatListUpdResp;
SqliteStatement my_membership_changes(conn,
"SELECT `chatId`, `role` FROM `user_chat_membership` WHERE `userId` = ?1 "
"AND `user_chatList_IncHistoryId` > ?2", {{1, userId}, {2, LocalHistoryId}});
json::jarr& myChats = chatListUpdResp["myChats"].asArray();
while (true) {
fsql_integer_or_null ev_chatId, usersRoleHere;
int status = sqlite_stmt_step(my_membership_changes, {{0, &ev_chatId}, {1, &usersRoleHere}}, {});
if (status != SQLITE_ROW)
break;
myChats.emplace_back();
json::JSON& myMembershipSt = myChats.back();
myMembershipSt["chatId"].asInteger() = json::Integer(ev_chatId.value);
myMembershipSt["myRoleHere"].asString() = stringify_user_chat_role(usersRoleHere.value);
if (usersRoleHere.value != user_chat_role_deleted) {
RowChat_Content CHAT = lookup_chat_content(conn, ev_chatId.value);
myMembershipSt["chatName"].asString() = CHAT.name;
myMembershipSt["chatNickname"].asString() = CHAT.nickname;
}
}
int64_t HistoryId = get_current_history_id_of_user_chatList(conn, userId);
chatListUpdResp["HistoryId"].asInteger() = json::Integer(HistoryId);
return chatListUpdResp;
}
void poll_update_chat_list(SqliteConnection& conn, int64_t userId, const json::JSON& Sent, json::JSON& Recv) {
Recv["status"].asInteger() = json::Integer(0l);
// todo: in libjsonincpp: get rid of Integer
Recv["chatListUpdResp"] = poll_update_chat_list_resp(conn, userId, Sent["chatListUpdReq"]["LocalHistoryId"].asInteger().get_int());
}
json::JSON make_messageSt_obj(int64_t id, int64_t senderUserId, bool exists, bool isSystem, const std::string& text) {
json::JSON messageSt;
messageSt["id"].asInteger() = json::Integer(id);
if (!isSystem)
messageSt["senderUserId"].asInteger() = json::Integer(senderUserId);
messageSt["exists"] = json::JSON(exists);
messageSt["isSystem"] = json::JSON(isSystem);
if (exists)
messageSt["text"].asString() = text;
return messageSt;
}
json::jarr poll_update_chat_resp_messages(SqliteConnection& conn, int64_t chatId, int64_t LocalHistoryId,
int64_t QSEG_A, int64_t QSEG_B) {
json::jarr messages;
SqliteStatement messages_changes(conn,
"SELECT `id`, `senderUserId`, `exists`, `isSystem`, `text` FROM `message` "
"WHERE `chatId` = ?1 AND ( `chat_IncHistoryId` > ?2 OR ( ?3 <= `id` AND `id` <= ?4 ) )",
{{1, chatId}, {2, LocalHistoryId}, {3, QSEG_A}, {4, QSEG_B}}, {});
while (true) {
fsql_integer_or_null msgId, msgSenderUserId, msgExists, msgIsSystem;
fsql_text8_or_null msgText;
int status = sqlite_stmt_step(messages_changes,
{{0, &msgId}, {1, &msgSenderUserId}, {2, &msgExists}, {3, &msgIsSystem}},
{{4, &msgText}});
if (status != SQLITE_ROW)
break;
messages.push_back(make_messageSt_obj(msgId.value, msgSenderUserId.value, msgExists.value,
msgIsSystem.value, msgText.value));
}
return messages;
}
json::jarr poll_update_chat_resp_members(SqliteConnection& conn, int64_t chatId, int64_t LocalHistoryId) {
json::jarr members;
SqliteStatement membership_changes(conn,
"SELECT `userId`, `role` FROM `user_chat_membership` WHERE `chatId` = ?1 "
"AND `chat_IncHistoryId` > ?2", {{1, chatId}, {2, LocalHistoryId}}, {});
while (true) {
fsql_integer_or_null alienUserId;
fsql_integer_or_null alienRoleHere;
int status = sqlite_stmt_step(membership_changes,
{{0, &alienUserId}, {1, &alienRoleHere}}, {});
if (status != SQLITE_ROW)
break;
members.emplace_back();
json::JSON& memberSt = members.back();
memberSt["userId"].asInteger() = json::Integer(alienUserId.value);
memberSt["roleHere"].asString() = stringify_user_chat_role(alienRoleHere.value);
if (alienRoleHere.value != user_chat_role_deleted) {
RowUser_Content alien = lookup_user_content(conn, alienUserId.value);
memberSt["name"].asString() = alien.name;
memberSt["nickname"].asString() = alien.nickname;
}
}
return members;
}
json::JSON poll_update_chat_ONE_MSG_resp(SqliteConnection& conn, int64_t chatId, int64_t selectedMsg) {
json::JSON chatUpdResp;
chatUpdResp["members"].asArray() = poll_update_chat_resp_members(conn, chatId, 0);
json::jarr& messages = chatUpdResp["messages"].asArray();
if (selectedMsg >= 0) {
RowMessage_Content msg = lookup_message_content(conn, chatId, selectedMsg);
messages.push_back(make_messageSt_obj(msg.id, msg.senderUserId, msg.exists, msg.isSystem, msg.text));
}
int64_t lastMsgId = get_lastMsgId_of_chat(conn, chatId);
chatUpdResp["lastMsgId"].asInteger() = json::Integer(lastMsgId);
int64_t HistoryId = get_current_history_id_of_chat(conn, chatId);
chatUpdResp["HistoryId"].asInteger() = json::Integer(HistoryId);
return chatUpdResp;
}
json::JSON poll_update_chat_important_segment_resp(SqliteConnection& conn, int64_t chatId, int64_t LocalHistoryId,
int64_t QSEG_A, int64_t QSEG_B) {
json::JSON chatUpdResp;
chatUpdResp["members"].asArray() = poll_update_chat_resp_members(conn, chatId, LocalHistoryId);
chatUpdResp["messages"].asArray() = poll_update_chat_resp_messages(conn, chatId, LocalHistoryId, QSEG_A, QSEG_B);
int64_t lastMsgId = get_lastMsgId_of_chat(conn, chatId);
chatUpdResp["lastMsgId"].asInteger() = json::Integer(lastMsgId);
int64_t HistoryId = get_current_history_id_of_chat(conn, chatId);
chatUpdResp["HistoryId"].asInteger() = json::Integer(HistoryId);
return chatUpdResp;
}
/* chat polling function MUST have one queer feature: it accepts a range of msgId, which are guaranteed to be
* lookud up. */
void poll_update_chat_important_segment(SqliteConnection& conn, const json::JSON& Sent, json::JSON& Recv,
int64_t QSEG_A, int64_t QSEG_B) {
Recv["status"].asInteger() = json::Integer(0l);
Recv["chatUpdResp"] = poll_update_chat_important_segment_resp(conn,
Sent["chatUpdReq"]["chatId"].asInteger().get_int(),
Sent["chatUpdReq"]["LocalHistoryId"].asInteger().get_int(), QSEG_A, QSEG_B);
}
void poll_update_chat(SqliteConnection& conn, const json::JSON& Sent, json::JSON& Recv) {
poll_update_chat_important_segment(conn, Sent, Recv, -1, -2);
}
json::JSON internalapi_chatPollEvents(SqliteConnection& conn, int64_t uid, const json::JSON& Sent) {
int64_t chatId = Sent["chatUpdReq"]["chatId"].asInteger().get_int();
if (get_role_of_user_in_chat(conn, uid, chatId) == user_chat_role_deleted)
een9_THROW("chatPollEvents: trying to access chat that user does not belong to");
json::JSON Recv;
poll_update_chat(conn, Sent, Recv);
return Recv;
}
json::JSON internalapi_chatListPollEvents(SqliteConnection& conn, int64_t uid, const json::JSON& Sent) {
json::JSON Recv;
poll_update_chat_list(conn, uid, Sent, Recv);
return Recv;
}
/* Reznya */
json::JSON internalapi_getMessageNeighbours(SqliteConnection& conn, int64_t uid, const json::JSON& Sent) {
int64_t chatId = Sent["chatUpdReq"]["chatId"].asInteger().get_int();
if (get_role_of_user_in_chat(conn, uid, chatId) == user_chat_role_deleted)
een9_THROW("Authentication failure");
int64_t lastMsgId = get_lastMsgId_of_chat(conn, chatId);
bool dir_forward = Sent["direction"].asString() == "forward";
int64_t amount = Sent["amount"].asInteger().get_int();
int64_t K = Sent["msgId"].asInteger().get_int();
if (amount <= 0 || amount > 15)
een9_THROW("Incorrect amount");
json::JSON Recv;
int64_t qBeg = -1;
int64_t qEnd = -2;
if (lastMsgId >= 0) {
if (K < 0) {
if (dir_forward)
een9_THROW("Can't go from the top of chat");
qBeg = std::max(0l, lastMsgId - amount + 1);
qEnd = lastMsgId;
} else if (dir_forward) {
qBeg = K + 1;
qEnd = K + amount;
} else {
qBeg = K - amount;
qEnd = K - 1;
}
}
poll_update_chat_important_segment(conn, Sent, Recv, qBeg, qEnd);
return Recv;
}
}

View File

@ -2,8 +2,13 @@
#include <engine_engine_number_9/baza_throw.h> #include <engine_engine_number_9/baza_throw.h>
#include <engine_engine_number_9/http_structures/cookies.h> #include <engine_engine_number_9/http_structures/cookies.h>
#include "../str_fields.h"
namespace iu9cawebchat { namespace iu9cawebchat {
json::JSON at_api_error_gen_bad_recv(int64_t code) {
return json::JSON(json::jdict{{"status", json::JSON(code)}});
}
const char* stringify_user_chat_role(int64_t role) { const char* stringify_user_chat_role(int64_t role) {
if (role == user_chat_role_admin) if (role == user_chat_role_admin)
return "admin"; return "admin";
@ -27,7 +32,7 @@ namespace iu9cawebchat {
return -1; return -1;
} }
std::string find_user_name (SqliteConnection& conn, int64_t uid) { std::string get_user_name (SqliteConnection& conn, int64_t uid) {
een9_ASSERT(uid >= 0, "Are you crazy?"); een9_ASSERT(uid >= 0, "Are you crazy?");
SqliteStatement sql_req(conn, SqliteStatement sql_req(conn,
"SELECT `name` FROM `user` WHERE `id` = ?1", "SELECT `name` FROM `user` WHERE `id` = ?1",
@ -50,7 +55,20 @@ namespace iu9cawebchat {
fsql_text8_or_null name_col; fsql_text8_or_null name_col;
int status = sqlite_stmt_step(sql_req, {}, {{0, &nickname_col}, {1, &name_col}}); int status = sqlite_stmt_step(sql_req, {}, {{0, &nickname_col}, {1, &name_col}});
if (status == SQLITE_ROW) { if (status == SQLITE_ROW) {
return {std::move(nickname_col.value), std::move(name_col.value)}; return {uid, std::move(nickname_col.value), std::move(name_col.value)};
}
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"); een9_THROW("No such user");
} }
@ -65,7 +83,22 @@ namespace iu9cawebchat {
fsql_integer_or_null last_msg_id_col; fsql_integer_or_null last_msg_id_col;
int status = sqlite_stmt_step(sql_req, {{2, &last_msg_id_col}}, {{0, &nickname_col}, {1, &name_col}}); int status = sqlite_stmt_step(sql_req, {{2, &last_msg_id_col}}, {{0, &nickname_col}, {1, &name_col}});
if (status == SQLITE_ROW) { if (status == SQLITE_ROW) {
return {std::move(nickname_col.value), std::move(name_col.value), return {chatId, std::move(nickname_col.value), std::move(name_col.value),
last_msg_id_col.exist ? last_msg_id_col.value : -1};
}
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}; last_msg_id_col.exist ? last_msg_id_col.value : -1};
} }
een9_THROW("No such chat"); een9_THROW("No such chat");
@ -73,15 +106,15 @@ namespace iu9cawebchat {
RowMessage_Content lookup_message_content(SqliteConnection& conn, int64_t chatId, int64_t msgId) { RowMessage_Content lookup_message_content(SqliteConnection& conn, int64_t chatId, int64_t msgId) {
SqliteStatement req(conn, SqliteStatement req(conn,
"SELECT `previousId`, `senderUserId`, `exists`, `isSystem`, `text` FROM `message` WHERE " "SELECT `senderUserId`, `exists`, `isSystem`, `text` FROM `message` WHERE "
"`chatId` = ?1 AND `id` = ?2", {{1, chatId}, {2, msgId}}, {}); "`chatId` = ?1 AND `id` = ?2", {{1, chatId}, {2, msgId}}, {});
fsql_integer_or_null previousId, senderUserId, exists, isSystem; fsql_integer_or_null senderUserId, exists, isSystem;
fsql_text8_or_null msg_text; fsql_text8_or_null msg_text;
int status = sqlite_stmt_step(req, {{0, &previousId}, {1, &senderUserId}, {2, &exists}, {3, &isSystem}}, int status = sqlite_stmt_step(req, {{0, &senderUserId}, {1, &exists}, {2, &isSystem}},
{{4, &msg_text}}); {{3, &msg_text}});
if (status == SQLITE_ROW) { if (status == SQLITE_ROW) {
return {(bool)isSystem.value, msg_text.value, senderUserId.exist ? senderUserId.value : -1, return {msgId, senderUserId.exist ? senderUserId.value : -1, (bool)exists.value,
previousId.exist ? previousId.value : -1}; (bool)isSystem.value, msg_text.value};
} }
een9_THROW("No such message"); een9_THROW("No such message");
} }
@ -98,5 +131,116 @@ namespace iu9cawebchat {
return user_chat_role_deleted; return user_chat_role_deleted;
} }
/* All the api calls processing is done in dedicated files */ 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

@ -8,6 +8,8 @@
#include <jsonincpp/string_representation.h> #include <jsonincpp/string_representation.h>
namespace iu9cawebchat { namespace iu9cawebchat {
json::JSON at_api_error_gen_bad_recv(int64_t code = -1);
constexpr int64_t user_chat_role_admin = 1; constexpr int64_t user_chat_role_admin = 1;
constexpr int64_t user_chat_role_regular = 2; constexpr int64_t user_chat_role_regular = 2;
constexpr int64_t user_chat_role_read_only = 3; constexpr int64_t user_chat_role_read_only = 3;
@ -16,41 +18,77 @@ namespace iu9cawebchat {
const char* stringify_user_chat_role(int64_t role); 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); int64_t find_user_by_credentials (SqliteConnection& conn, const std::string& nickname, const std::string& password);
std::string find_user_name (SqliteConnection& conn, int64_t uid); std::string get_user_name (SqliteConnection& conn, int64_t uid);
struct RowUser_Content { struct RowUser_Content {
int64_t id;
std::string nickname; std::string nickname;
std::string name; std::string name;
}; };
struct RowChat_Content { struct RowChat_Content {
int64_t id;
std::string nickname; std::string nickname;
std::string name; std::string name;
int64_t lastMsgId; // Negative if it does not exist int64_t lastMsgId; // Negative if it does not exist
}; };
RowUser_Content lookup_user_content(SqliteConnection& conn, int64_t uid); RowUser_Content lookup_user_content(SqliteConnection& conn, int64_t uid);
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(SqliteConnection& conn, int64_t chatId);
RowChat_Content lookup_chat_content_by_nickname(SqliteConnection &conn, const std::string& nickname);
struct RowMessage_Content { struct RowMessage_Content {
int64_t id;
int64_t senderUserId;
bool exists;
bool isSystem; bool isSystem;
std::string text; std::string text;
int64_t senderUserId;
int64_t previous = -1;
}; };
RowMessage_Content lookup_message_content(SqliteConnection& conn, int64_t chatId, int64_t msgId); 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); int64_t get_role_of_user_in_chat(SqliteConnection& conn, int64_t userId, int64_t chatId);
/* Does not make authorization check */
int64_t get_lastMsgId_of_chat(SqliteConnection& conn, int64_t chatId);
/* ============================= API ====================================*/ int64_t get_current_history_id_of_chat(SqliteConnection& conn, int64_t chatId);
json::JSON internalapi_pollEvents(SqliteConnection& conn, int64_t uid, const json::JSON& Sent); int64_t get_current_history_id_of_user_chatList(SqliteConnection& conn, int64_t userId);
json::JSON internalapi_getChatList(SqliteConnection& conn, int64_t uid);
json::JSON internalapi_getChatInfo(SqliteConnection& conn, int64_t uid, const json::JSON& Sent); json::JSON poll_update_chat_list_resp(SqliteConnection& conn, int64_t userId, int64_t LocalHistoryId);
json::JSON internalapi_getChatMemberList(SqliteConnection& conn, int64_t uid, const json::JSON& Sent); void poll_update_chat_list(SqliteConnection& conn, int64_t userId, const json::JSON& Sent, json::JSON& Recv);
json::JSON internalapi_getUserInfo(SqliteConnection& conn, int64_t uid, const json::JSON& Sent);
json::JSON internalapi_getMessageInfo(SqliteConnection& conn, int64_t uid, const json::JSON& Sent); json::JSON poll_update_chat_ONE_MSG_resp(SqliteConnection& conn, int64_t chatId, int64_t selectedMsg);
// todo: complete the list 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 #endif

View File

@ -1,19 +0,0 @@
#include "server_data_interact.h"
#include <engine_engine_number_9/baza_throw.h>
namespace iu9cawebchat {
json::JSON internalapi_getChatInfo(SqliteConnection& conn, int64_t uid, const json::JSON& Sent) {
int64_t chatId = Sent["id"].g().asInteger().get_int();
int64_t my_role_here = get_role_of_user_in_chat(conn, uid, chatId);
if (my_role_here == user_chat_role_deleted)
een9_THROW("Unauthorized user tries to access internalapi_getChatInfo");
json::JSON Recv;
Recv["status"] = json::JSON(0l);
RowChat_Content content = lookup_chat_content(conn, chatId);
Recv["name"] = json::JSON(content.name);
Recv["nickname"] = json::JSON(content.nickname);
Recv["lastMsgId"] = json::JSON(content.lastMsgId);
Recv["roleHere"] = json::JSON(stringify_user_chat_role(my_role_here));
return Recv;
}
}

View File

@ -1,33 +0,0 @@
#include "server_data_interact.h"
#include <engine_engine_number_9/baza_throw.h>
namespace iu9cawebchat {
json::JSON internalapi_getChatList(SqliteConnection& conn, int64_t uid) {
json::JSON Recv;
Recv["status"] = json::JSON(0l);
Recv["chats"] = json::JSON(json::array);
std::vector<json::JSON>& chats = Recv["chats"].g().asArray();
SqliteStatement req(conn,
"SELECT `chat`.`id`, `chat`.`nickname`, `chat`.`name`, `chat`.`lastMsgId`, "
"`user_chat_membership`.`role` FROM `chat` "
"RIGHT JOIN `user_chat_membership` ON `chat`.`id` = `user_chat_membership`.`chatId` "
"WHERE `user_chat_membership`.`userId` = ?1 ", {{1, uid}}, {});
while (true) {
fsql_integer_or_null chat_id;
fsql_text8_or_null chat_nickname, chat_name;
fsql_integer_or_null chat_lastMsgId, role_here;
int status = sqlite_stmt_step(req, {{0, &chat_id}, {3, &chat_lastMsgId}, {4, &role_here}},
{{1, &chat_nickname}, {2, &chat_name}});
if (status != SQLITE_ROW)
break;
chats.emplace_back();
json::JSON& chat = chats.back();
chat["id"] = json::JSON(chat_id.value);
chat["content"]["nickname"] = json::JSON(chat_nickname.value);
chat["content"]["name"] = json::JSON(chat_name.value);
chat["content"]["lastMsgId"] = json::JSON(chat_lastMsgId.exist ? chat_lastMsgId.value : -1);
chat["content"]["roleHere"] = json::JSON(stringify_user_chat_role(role_here.value));
}
return Recv;
}
}

View File

@ -1,36 +0,0 @@
#include "server_data_interact.h"
#include <engine_engine_number_9/baza_throw.h>
namespace iu9cawebchat {
json::JSON internalapi_getChatMemberList(SqliteConnection& conn, int64_t uid, const json::JSON& Sent) {
int64_t chatId = Sent["id"].g().asInteger().get_int();
int64_t my_role_here = get_role_of_user_in_chat(conn, uid, chatId);
if (my_role_here == user_chat_role_deleted)
een9_THROW("Unauthorized user tries to access internalapi_getChatInfo");
json::JSON Recv;
Recv["status"] = json::JSON(0l);
Recv["members"] = json::JSON(json::array);
std::vector<json::JSON>& members = Recv["members"].g().asArray();
SqliteStatement req(conn,
"SELECT `user`.`id`, `user`.`nickname`, `user`.`name`, `user_chat_membership`.`role` FROM "
"`user` RIGHT JOIN `user_chat_membership` ON `user`.`id` = `user_chat_membership`.`userId` "
"WHERE `user_chat_membership`.`chatId` = ?1",
{{1, chatId}}, {});
while (true) {
fsql_integer_or_null this_user_id;
fsql_text8_or_null this_user_nickname, this_user_name;
fsql_integer_or_null this_users_role;
int status = sqlite_stmt_step(req, {{0, &this_user_id}, {3, &this_users_role}},
{{1, &this_user_nickname}, {2, &this_user_name}});
if (status != SQLITE_ROW)
break;
members.emplace_back();
json::JSON& member = members.back();
member["id"] = json::JSON(this_user_id.value);
member["content"]["nickname"] = json::JSON(this_user_nickname.value);
member["content"]["name"] = json::JSON(this_user_name.value);
member["content"]["role"] = json::JSON(this_users_role.value);
}
return Recv;
}
}

View File

@ -1,21 +0,0 @@
#include "server_data_interact.h"
#include <engine_engine_number_9/baza_throw.h>
namespace iu9cawebchat {
/* This is literally the most dumb and useless query */
json::JSON internalapi_getMessageInfo(SqliteConnection& conn, int64_t uid, const json::JSON& Sent) {
int64_t chatId = Sent["chatId"].g().asInteger().get_int();
int64_t msgId = Sent["id"].g().asInteger().get_int();
if (get_role_of_user_in_chat(conn, uid, chatId) == user_chat_role_deleted)
een9_THROW("Authentication failure");
json::JSON Recv;
Recv["status"] = json::JSON(0l);
RowMessage_Content content = lookup_message_content(conn, chatId, msgId);
Recv["text"] = json::JSON(content.text);
Recv["isSystem"] = json::JSON(content.isSystem);
Recv["sender"] = json::JSON(content.senderUserId);
// todo: sync that addition with api documentation
Recv["previous"] = json::JSON(content.previous);
return Recv;
}
}

View File

@ -1,13 +0,0 @@
#include "server_data_interact.h"
namespace iu9cawebchat {
json::JSON internalapi_getUserInfo(SqliteConnection& conn, int64_t uid, const json::JSON& Sent) {
int64_t otherUserId = Sent["id"].g().asInteger().get_int();
json::JSON Recv;
Recv["status"] = json::JSON(0l);
RowUser_Content content = lookup_user_content(conn, otherUserId);
Recv["name"] = json::JSON(content.name);
Recv["nickname"] = json::JSON(content.nickname);
return Recv;
}
}

View File

@ -1,132 +0,0 @@
#include "server_data_interact.h"
#include <engine_engine_number_9/baza_throw.h>
namespace iu9cawebchat {
int64_t get_current_history_id_of_chat(SqliteConnection& conn, int64_t chatId) {
SqliteStatement req(conn, "SELECT `it_HistoryId` FROM `chat` WHERE `id` = ?1", {{1, chatId}}, {});
fsql_integer_or_null HistoryId;
int status = sqlite_stmt_step(req, {{0, &HistoryId}}, {});
een9_ASSERT_pl(status == SQLITE_ROW);
return HistoryId.value;
}
int64_t get_current_history_id_of_user_chatList(SqliteConnection& conn, int64_t userId) {
SqliteStatement req(conn, "SELECT `chatList_HistoryId` FROM `user` WHERE `id` = ?1", {{1, userId}}, {});
fsql_integer_or_null HistoryId;
int status = sqlite_stmt_step(req, {{0, &HistoryId}}, {});
een9_ASSERT_pl(status == SQLITE_ROW);
return HistoryId.value;
}
void internalapi_pollEvents_in_chat_collect_membership_events(SqliteConnection& conn,
std::vector<json::JSON>& events, int64_t chatId, int64_t LocalHistoryId) {
SqliteStatement membership_changes(conn,
"SELECT `userId`, `role`, FROM `user_chat_membership` WHERE `chatId` = ?1 "
"AND `chat_IncHistoryId` > ?2", {{1, chatId}, {2, LocalHistoryId}}, {});
fsql_integer_or_null ev_userId;
fsql_integer_or_null ev_user_role;
while (true) {
int status = sqlite_stmt_step(membership_changes,
{{0, &ev_userId}, {1, &ev_user_role}}, {});
if (status != SQLITE_ROW)
break;
events.emplace_back();
json::JSON& event = events.back();
event["member"] = json::JSON(ev_userId.value);
if (ev_user_role.value == user_chat_role_deleted) {
event["type"] = json::JSON("removedMember");
} else {
event["type"] = json::JSON("addedMember");
RowUser_Content USER = lookup_user_content(conn, ev_userId.value);
event["content"]["name"] = json::JSON(USER.name);
event["content"]["nickname"] = json::JSON(USER.nickname);
event["content"]["role"] = json::JSON(stringify_user_chat_role(ev_user_role.value));
}
events.push_back(event);
}
}
void internalapi_pollEvents_in_chat_collect_messages_events(SqliteConnection& conn,
std::vector<json::JSON>& events, int64_t chatId, int64_t LocalHistoryId) {
SqliteStatement messages_changes(conn,
"SELECT `id`, `previous`, `senderUserId`, `exists`, `isSystem`, `text` FROM `messages` WHERE "
"WHERE `chatId` = ?1 AND `chat_IncHistoryId` > ?2", {{1, chatId}, {2, LocalHistoryId}}, {});
fsql_integer_or_null ev_msgId, ev_previousMsgId, msgSenderUserId, msgExists, msgIsSystem;
fsql_text8_or_null msgText;
while (true) {
int status = sqlite_stmt_step(messages_changes,
{{0, &ev_msgId}, {1, &ev_previousMsgId}, {2, &msgSenderUserId}, {3, &msgExists}, {4, &msgIsSystem}},
{{5, &msgText}});
if (status != SQLITE_ROW)
break;
events.emplace_back();
json::JSON& event = events.back();
event["type"] = json::JSON("newMessage");
event["id"] = json::JSON(ev_msgId.value);
event["previous"] = json::JSON(ev_previousMsgId.value);
event["content"]["sender"] = json::JSON(msgSenderUserId.value);
event["content"]["isSystem"] = json::JSON((bool)msgIsSystem.value);
event["content"]["text"] = json::JSON(msgText.value);
events.push_back(event);
}
}
void internalapi_pollEvents_in_user_chatList_collect_events(SqliteConnection& conn,
std::vector<json::JSON>& events, int64_t userId, int64_t LocalHistoryId) {
SqliteStatement membership_changes(conn,
"SELECT `chatId`, `role` FROM `user_chat_membership` WHERE `userId` = ?1 "
"AND `user_chatList_IncHistoryId` > ?2", {{1, userId}, {2, LocalHistoryId}});
fsql_integer_or_null ev_chatId, usersRoleHere;
while (true) {
int status = sqlite_stmt_step(membership_changes, {{0, &ev_chatId}, {1, &usersRoleHere}}, {});
if (status != SQLITE_ROW)
break;
events.emplace_back();
json::JSON& event = events.back();
event["id"] = json::JSON(ev_chatId.value);
if (usersRoleHere.value == user_chat_role_deleted) {
event["type"] = json::JSON("removedChat");
} else {
event["type"] = json::JSON("addedChat");
RowChat_Content CHAT = lookup_chat_content(conn, ev_chatId.value);
event["content"]["name"] = json::JSON(CHAT.name);
event["content"]["nickname"] = json::JSON(CHAT.nickname);
event["content"]["lastMsgId"] = json::JSON(CHAT.lastMsgId);
event["content"]["roleHere"] = json::JSON(stringify_user_chat_role(usersRoleHere.value));
}
}
}
json::JSON internalapi_pollEvents(SqliteConnection& conn, int64_t uid, const json::JSON& Sent) {
json::JSON Recv;
Recv["status"] = json::JSON(0l);
Recv["update"] = json::JSON(json::array);
const std::vector<json::JSON>& req_scope = Sent["scope"].g().asArray();
std::vector<json::JSON>& updated = Recv["update"].g().asArray();
for (const json::JSON& hist_entity_request: req_scope) {
updated.emplace_back();
json::JSON& hist_entity_response = updated.back();
hist_entity_response["type"] = hist_entity_request["type"].g();
hist_entity_response["events"] = json::JSON(json::array);
std::vector<json::JSON>& events = hist_entity_response["events"].g().asArray();
const int64_t LocalHistoryId = hist_entity_request["LocalHistoryId"].g().asInteger().get_int();
if (hist_entity_request["type"].g().asString() == "chat") {
int64_t chatId = hist_entity_request["chatId"].g().asInteger().get_int();
if (get_role_of_user_in_chat(conn, uid, chatId) == user_chat_role_deleted)
een9_THROW("internalapi/pollEvents: trying to access chat that user does not belong to");
hist_entity_response["chatId"] = json::JSON(chatId);
hist_entity_response["HistoryId"] = json::JSON(get_current_history_id_of_chat(conn, chatId));
/* Two classes of 'real events' can happen to chat: membership table change, message table change */
/* Here, I collect membership changes (related to this chat) */
internalapi_pollEvents_in_chat_collect_membership_events(conn, events, chatId, LocalHistoryId);
/* Here, I collect message changes (related to this chat) */
internalapi_pollEvents_in_chat_collect_messages_events(conn, events, chatId, LocalHistoryId);
} else if (hist_entity_request["type"].g().asString() == "chatlist") {
hist_entity_response["HistotyId"] = json::JSON(get_current_history_id_of_user_chatList(conn, uid));
internalapi_pollEvents_in_user_chatList_collect_events(conn, events, uid, LocalHistoryId);
} else
een9_THROW("Bad request");
}
return Recv;
}
}

View File

@ -1,8 +1,61 @@
#include "client_server_interact.h" #include "client_server_interact.h"
#include <engine_engine_number_9/baza_throw.h>
#include "../str_fields.h"
namespace iu9cawebchat { namespace iu9cawebchat {
std::string when_page_chat(WorkerGuestData& wgd, const json::JSON& config_presentation, std::string when_page_chat(WorkerGuestData& wgd, const json::JSON& config_presentation,
const een9::ClientRequest& req, const json::JSON& userinfo) { const een9::ClientRequest& req, const json::JSON& userinfo) {
return RTEE("chat", config_presentation, wgd, 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});
} }
} }

View File

@ -6,6 +6,9 @@ namespace iu9cawebchat {
if (userinfo.isNull()) { if (userinfo.isNull()) {
return een9::form_http_server_response_303("/login"); return een9::form_http_server_response_303("/login");
} }
return RTEE("list-rooms", config_presentation, wgd, userinfo); json::JSON initial_chatListUpdResp = poll_update_chat_list_resp(*wgd.db,
userinfo["uid"].asInteger().get_int(), 0);
printf("%s\n", json::generate_str(initial_chatListUpdResp, json::print_pretty).c_str());
return http_R200("list-rooms", wgd, {&config_presentation, &userinfo, &initial_chatListUpdResp});
} }
} }

View File

@ -18,21 +18,27 @@ namespace iu9cawebchat {
if (cmp.first == "password") if (cmp.first == "password")
password = cmp.second; password = cmp.second;
} }
een9_ASSERT(check_nickname(nickname), "/login/accpet-data rejected impossible nickname"); if (!check_nickname(nickname))
een9_ASSERT(check_password(password), "/login/accpet-data rejected impossible password"); een9_THROW("/login/accpet-data rejected impossible nickname");
if (!check_password(password))
een9_THROW("/login/accpet-data rejected impossible password");
uid = find_user_by_credentials(*wgd.db, nickname, password); uid = find_user_by_credentials(*wgd.db, nickname, password);
} catch(const std::exception& e){} } catch(const std::exception& e){}
if (uid < 0) { if (uid < 0) {
printf("Redirecting back to /login because of incorrect credentials\n"); printf("Redirecting back to /login because of incorrect credentials\n");
/* todo: Here I need to tell somehow to user (through fancy red box, maybe), that login was incorrect */ json::JSON msg_list = jsonify_html_message_list({{"",
return RTEE("login", config_presentation, wgd, userinfo); config_presentation["login"]["incorrect-nickname-or-password"].asString()}});
return http_R200("login", wgd, {&config_presentation, &userinfo, &msg_list});
} }
std::vector<std::pair<std::string, std::string>> response_hlines; std::vector<std::pair<std::string, std::string>> response_hlines;
LoginCookie new_login_cookie = create_login_cookie(nickname, password); LoginCookie new_login_cookie = create_login_cookie(nickname, password);
add_set_cookie_headers_to_login(login_cookies, response_hlines, new_login_cookie); add_set_cookie_headers_to_login(login_cookies, response_hlines, new_login_cookie);
return een9::form_http_server_response_303_spec_head("/", response_hlines); return een9::form_http_server_response_303_spec_head("/", response_hlines);
} }
return RTEE("login", config_presentation, wgd, userinfo); if (req.method != "GET")
een9_THROW("Bad method");
json::JSON empty_msg_list = json::JSON(json::array);
return http_R200("login", wgd, {&config_presentation, &userinfo, &empty_msg_list});
} }
} }

View File

@ -0,0 +1,56 @@
#include "client_server_interact.h"
#include <engine_engine_number_9/form_data_structure/urlencoded_query.h>
#include <engine_engine_number_9/baza_throw.h>
#include <string.h>
#include "../str_fields.h"
namespace iu9cawebchat {
std::string when_page_register(WorkerGuestData& wgd, const json::JSON& config_presentation,
const een9::ClientRequest& req, const json::JSON& userinfo) {
const json::JSON& reg_pres = config_presentation["register"];
json::JSON msg_list = json::JSON(json::array);
if (req.method == "POST") {
if (userinfo.isNull() || userinfo["uid"].asInteger().get_int() != 0)
een9_THROW("Unauthorized access");
// Kod dlya dobaldal lkslkfjgk
std::vector<std::pair<std::string, std::string>> query = een9::split_html_query(req.body);
std::string nickname;
std::string name;
std::string password;
std::vector<HtmlMsgBox> problems; // We explain problem to root
for (const std::pair<std::string, std::string>& cmp: query) {
if (cmp.first == "nickname")
nickname = cmp.second;
if (cmp.first == "name")
name = cmp.second;
if (cmp.first == "password")
password = cmp.second;
}
if (!check_nickname(nickname)) {
problems.push_back({"", reg_pres["incorrect-nickname"].asString()});
}
if (!check_name(name)) {
problems.push_back({"", reg_pres["incorrect-name"].asString()});
}
if (!check_strong_password(password)) {
problems.push_back({"", reg_pres["incorrect-password"].asString()});
}
if (is_nickname_taken(*wgd.db, nickname)) {
problems.push_back({"", reg_pres["nickname-taken"].asString()});
}
if (problems.empty()) {
try {
add_user(*wgd.db, nickname, name, password, "");
} catch (std::exception& err) {
problems.push_back({"", reg_pres["add_user_error"].asString()});
}
}
msg_list = jsonify_html_message_list(problems);
return http_R200("register", wgd, {&config_presentation, &userinfo, &msg_list});
}
if (userinfo.isNull() || userinfo["uid"].asInteger().get_int() != 0)
return page_E404(wgd);
return http_R200("register", wgd, {&config_presentation, &userinfo, &msg_list});
}
}

View File

@ -1,8 +1,123 @@
#include "client_server_interact.h" #include "client_server_interact.h"
#include <engine_engine_number_9/form_data_structure/urlencoded_query.h>
#include <engine_engine_number_9/baza_throw.h>
#include <string.h>
#include "../str_fields.h"
#include "../login_cookie.h"
namespace iu9cawebchat { namespace iu9cawebchat {
std::string get_user_bio(SqliteConnection& conn, int64_t userId) {
een9_ASSERT(userId >= 0, "Are you crazy?");
SqliteStatement sql_req(conn, "SELECT `bio` FROM `user` WHERE `id` = ?1", {{1, userId}}, {});
fsql_text8_or_null bio_col;
int status = sqlite_stmt_step(sql_req, {}, {{0, &bio_col}});
if (status == SQLITE_ROW)
return bio_col.value;
een9_THROW("No such user");
}
static const char* pr_path = "/user/";
bool is_page_of_certain_user(const std::string& path) {
if (!een9::beginsWith(path, pr_path))
return false;
std::string r = path.substr(strlen(pr_path));
return check_nickname(r);
}
std::string obtain_nickname_in_user_page_path(const std::string& path) {
return path.substr(strlen(pr_path));
}
json::JSON user_row_to_userprofile_obj(SqliteConnection& conn, const RowUser_Content& alien) {
return json::JSON(json::jdict{
{"name", json::JSON(alien.name)},
{"nickname", json::JSON(alien.nickname)},
{"bio", json::JSON(get_user_bio(conn, alien.id))}
});
}
std::string when_page_user(WorkerGuestData& wgd, const json::JSON& config_presentation, std::string when_page_user(WorkerGuestData& wgd, const json::JSON& config_presentation,
const een9::ClientRequest& req, const json::JSON& userinfo) { const een9::ClientRequest& req, const std::vector<LoginCookie>& login_cookies, const json::JSON& userinfo) {
return RTEE("profile", config_presentation, wgd, userinfo); if (userinfo.isNull())
return een9::form_http_server_response_303("/");
SqliteConnection& conn = *wgd.db;
if (!is_page_of_certain_user(req.uri_path))
return page_E404(wgd);
std::string alien_nickname = obtain_nickname_in_user_page_path(req.uri_path);
RowUser_Content alien;
try {
alien = lookup_user_content_by_nickname(conn, alien_nickname);
} catch (const std::exception& e) {
return page_E404(wgd);
}
// todo: in libjsonincpp: fix '999999 problem'
bool can_edit = false;
int64_t myuid = -1;
if (userinfo.isDictionary()) {
myuid = userinfo["uid"].asInteger().get_int();
can_edit = (alien.id == myuid && myuid >= 0);
}
if (req.method == "POST") {
std::vector<std::pair<std::string, std::string>> response_hlines;
try {
if (!can_edit)
een9_THROW("Unauthorized access");
std::vector<std::pair<std::string, std::string>> query = een9::split_html_query(req.body);
// Profile update processing
std::string bio;
std::string name;
std::string password;
for (const std::pair<std::string, std::string>& p: query) {
if (p.first == "bio")
bio = p.second;
else if (p.first == "name")
name = p.second;
else if (p.first == "password")
password = p.second;
}
if (!bio.empty()) {
if (!is_orthodox_string(bio) || bio.size() > 100000)
een9_THROW("Incorrect `bio`");
sqlite_nooutput(conn,
"UPDATE `user` SET `bio` = ?1 WHERE `id` = ?2",
{{2, alien.id}}, {{1, bio}});
}
if (!name.empty()) {
if (!check_name(name))
een9_THROW("Incorrect `name`");
sqlite_nooutput(conn,
"UPDATE `user` SET `name` = ?1 WHERE `id` = ?2",
{{2, alien.id}}, {{1, name}});
}
if (!password.empty()) {
if (!check_strong_password(password))
een9_THROW("Incorrect `password`");
sqlite_nooutput(conn,
"UPDATE `user` SET `password` = ?1 WHERE `id` = ?2",
{{2, alien.id}}, {{1, password}});
if (alien.id == myuid) {
LoginCookie new_login_cookie = create_login_cookie(userinfo["nickname"].asString(), password);
add_set_cookie_headers_to_login(login_cookies, response_hlines, new_login_cookie);
}
}
} catch (const std::exception& e) {
printf("Redirecting back to /user/... because of incorrect credentials\n");
json::JSON msg_list = jsonify_html_message_list({{"",
config_presentation["edit-profile"]["incorrect-profile-data"].asString()}});
json::JSON alien_userprofile = user_row_to_userprofile_obj(conn, alien);
return http_R200("edit-profile", wgd, {&config_presentation, &userinfo, &alien_userprofile, &msg_list});
}
return een9::form_http_server_response_303_spec_head("/user/" + alien_nickname, response_hlines);
}
if (req.method == "GET") {
json::JSON alien_userprofile = user_row_to_userprofile_obj(conn, alien);
if (can_edit) {
json::JSON empty_msg_list = jsonify_html_message_list({});
return http_R200("edit-profile", wgd, {&config_presentation, &userinfo, &alien_userprofile, &empty_msg_list});
}
return http_R200("view-profile", wgd, {&config_presentation, &userinfo, &alien_userprofile});
}
een9_THROW("Bad method");
} }
} }

View File

@ -0,0 +1,6 @@
#ifndef IU9_CA_WEB_CHAT_LIB_DEBUG_H
#define IU9_CA_WEB_CHAT_LIB_DEBUG_H
#define debug_print_json(x) printf("%s\n", json::generate_str(x, json::print_pretty).c_str())
#endif

View File

@ -2,15 +2,17 @@
namespace iu9cawebchat{ namespace iu9cawebchat{
int find_db_sqlite_file_path(const json::JSON& config, std::string& res_path) { int find_db_sqlite_file_path(const json::JSON& config, std::string& res_path) {
const json::JSON& type = config["database"]["type"].g(); try {
if (!type.isString() && type.asString() == "sqlite3") const std::string& type = config["database"]["type"].asString();
if (type != "sqlite3")
return -1;
const std::string& path = config["database"]["file"].asString();
if (path.empty() || path[0] == ':')
return -1;
res_path = path;
} catch (const json::misuse& e) {
return -1; return -1;
const json::JSON& path = config["database"]["file"].g(); }
if (!path.isString())
return -1;
if (path.asString().empty() || path.asString()[0] == ':')
return -1;
res_path = path.asString();
return 0; return 0;
} }
} }

View File

@ -7,24 +7,27 @@
#include <unistd.h> #include <unistd.h>
#include <assert.h> #include <assert.h>
#include "sqlite3_wrapper.h" #include "sqlite3_wrapper.h"
#include "backend_logic/server_data_interact.h"
namespace iu9cawebchat { namespace iu9cawebchat {
void initialize_website(const json::JSON& config, const std::string& root_pw) { void initialize_website(const json::JSON& config, const std::string& root_pw) {
printf("Initialization...\n"); printf("Initialization...\n");
een9_ASSERT(check_password(root_pw), "Bad root password"); if (!check_strong_password(root_pw))
een9_THROW("Bad root password");
std::string db_path; std::string db_path;
int ret; int ret;
ret = find_db_sqlite_file_path(config, db_path); ret = find_db_sqlite_file_path(config, db_path);
een9_ASSERT(ret == 0, "Invalid settings[\"database\"] field"); if (ret != 0)
een9_THROW("Invalid settings[\"database\"] field");
if (een9::isRegularFile(db_path)) { if (een9::isRegularFile(db_path)) {
// todo: plaese, don't do this // todo: plaese, don't do this
ret = unlink(db_path.c_str()); ret = unlink(db_path.c_str());
een9_ASSERT_pl(ret == 0); if (ret != 0)
een9_THROW("unlink");
} }
een9_ASSERT(!een9::isRegularFile(db_path), "Database file exists prior to initialization. " if (een9::isRegularFile(db_path))
"Can't preceed withut harming existing data"); een9_THROW("Database file exists prior to initialization. Can't preceed withut harming existing data");
SqliteConnection conn(db_path.c_str()); SqliteConnection conn(db_path.c_str());
assert(sqlite3_errcode(conn.hand) == SQLITE_OK);
/* Role of memeber of chat: /* Role of memeber of chat:
* 1 - admin * 1 - admin
* 2 - regular * 2 - regular
@ -44,36 +47,38 @@ namespace iu9cawebchat {
"`nickname` TEXT UNIQUE REFERENCES `nickname` NOT NULL," "`nickname` TEXT UNIQUE REFERENCES `nickname` NOT NULL,"
"`name` TEXT NOT NULL," "`name` TEXT NOT NULL,"
"`chatList_HistoryId` INTEGER NOT NULL," "`chatList_HistoryId` INTEGER NOT NULL,"
"`password` TEXT NOT NULL" "`password` TEXT NOT NULL,"
"`bio` TEXT NOT NULL"
")"); ")");
sqlite_nooutput(conn, "CREATE TABLE `chat` (" sqlite_nooutput(conn, "CREATE TABLE `chat` ("
"`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL," "`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,"
"`nickname` TEXT UNIQUE REFERENCES `nickname` NOT NULL," "`nickname` TEXT UNIQUE REFERENCES `nickname` NOT NULL,"
"`name` TEXT NOT NULL," "`name` TEXT NOT NULL,"
"`it_HistoryId` INTEGER NOT NULL," "`it_HistoryId` INTEGER NOT NULL,"
"`lastMsgId` INTEGER REFERENCES `message`" "`lastMsgId` INTEGER NOT NULL"
")"); ")");
sqlite_nooutput(conn, "CREATE TABLE `user_chat_membership` (" sqlite_nooutput(conn, "CREATE TABLE `user_chat_membership` ("
"`userId` INTEGER REFERENCES `user` NOT NULL," "`userId` INTEGER REFERENCES `user` NOT NULL,"
"`chatId` INTEGER REFERENCES `chat` NOT NULL," "`chatId` INTEGER REFERENCES `chat` NOT NULL,"
"`user_chatList_IncHistoryId` INTEGER NOT NULL," "`user_chatList_IncHistoryId` INTEGER NOT NULL,"
"`chat_IncHistoryId` INTEGER NOT NULL," "`chat_IncHistoryId` INTEGER NOT NULL,"
"`role` INTEGER NOT NULL" "`role` INTEGER NOT NULL,"
"UNIQUE (`userId`, `chatId`)"
")"); ")");
sqlite_nooutput(conn, "CREATE TABLE `message` (" sqlite_nooutput(conn, "CREATE TABLE `message` ("
"`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,"
"`chatId` INTEGER REFERENCES `chat` NOT NULL," "`chatId` INTEGER REFERENCES `chat` NOT NULL,"
"`previous` INTEGER REFERENCES `message`," "`id` INTEGER NOT NULL,"
"`senderUserId` INTEGER REFERENCES `user` NOT NULL," "`senderUserId` INTEGER REFERENCES `user`,"
"`exists` BOOLEAN NOT NULL," "`exists` BOOLEAN NOT NULL,"
"`isSystem` BOOLEAN NOT NULL," "`isSystem` BOOLEAN NOT NULL,"
"`text` TEXT NOT NULL," "`text` TEXT,"
"`chat_IncHistoryId` INTEGER NOT NULL" "`chat_IncHistoryId` INTEGER NOT NULL,"
"PRIMARY KEY (`chatId`, `id`)"
")"); ")");
sqlite_nooutput(conn, "INSERT INTO `nickname` VALUES (?1)", {}, {{1, "root"}}); std::vector<std::string> sus = {"unknown", "undefined", "null", "none", "None", "NaN"};
sqlite_nooutput(conn, "INSERT INTO `user` (`id`, `nickname`, `name`, `chatList_HistoryId`, `password`) VALUES " for (auto& s: sus)
"(0, ?1, ?2, 0, ?3)", {}, reserve_nickname(conn, s);
{{1, "root"}, {2, "Rootov Root Rootovich"}, {3, root_pw}}); add_user(conn, "root", "Rootov Root Rootovich", root_pw, "One admin to rule them all", 0);
sqlite_nooutput(conn, "END"); sqlite_nooutput(conn, "END");
} catch (const std::exception& e) { } catch (const std::exception& e) {
sqlite_nooutput(conn, "ROLLBACK", {}, {}); sqlite_nooutput(conn, "ROLLBACK", {}, {});

View File

@ -0,0 +1,154 @@
#include "localizator.h"
#include <jsonincpp/string_representation.h>
#include <sys/stat.h>
#include <dirent.h>
#include <engine_engine_number_9/os_utils.h>
#include <engine_engine_number_9/baza_throw.h>
#include <assert.h>
namespace iu9cawebchat {
std::string languageRangeSimpler(const std::string& a) {
return a == "*" ? "" : a;
}
// I won't use iterators. c plus plus IS a scripting language and I do not want to mess with iterators
std::vector<std::string> languageRangeGetPrefixes(const std::string& lr) {
if (lr.empty())
return {""};
std::vector<std::string> result = {"", ""};
for (size_t i = 0; i < lr.size(); i++) {
if (lr[i] == '-')
result.push_back(result.back());
result.back() += lr[i];
}
return result;
}
bool isInWhitelist(const std::string& lr, const std::vector<std::string>& whitelist) {
for (const std::string& prefix : languageRangeGetPrefixes(lr))
for (const std::string& nicePrefix: whitelist)
if (prefix == nicePrefix)
return true;
return false;
}
std::vector<LanguageFile> collect_lang_dir_content(const std::string& lang_dir,
const std::vector<std::string>& whitelist) {
std::vector<LanguageFile> result;
errno = 0;
DIR* D = opendir(lang_dir.c_str());
struct Guard1{ DIR*& D; ~Guard1(){ closedir(D); } } g1{D};
if (!D)
een9_THROW_on_errno("opendir (" + lang_dir + ")");
while (true) {
errno = 0;
struct dirent* Dent = readdir(D);
if (Dent == NULL) {
if (errno == 0)
break;
een9_THROW_on_errno("dirent");
}
std::string entry = Dent->d_name;
if (entry == "." || entry == "..")
continue;
std::string filename = lang_dir + "/" + entry;
struct stat info;
int ret = stat(filename.c_str(), &info);
een9_ASSERT_on_iret(ret, "stat(" + filename + ")");
if (!S_ISREG(info.st_mode))
continue;
const std::string postfix = ".lang.json";
if (!een9::endsWith(entry, postfix))
continue;
std::string lang_antirange = entry.substr(0, entry.size() - postfix.size());
if (!isInWhitelist(lang_antirange, whitelist))
continue;
std::string content;
een9::readFile(filename, content);
result.emplace_back();
result.back().languagerange = languageRangeSimpler(lang_antirange);
result.back().content = json::parse_str_flawless(content);
}
return result;
}
Localizator::Localizator(const LocalizatorSettings &settings) : settings(settings) {
/* First - length of the longest prefix that was found so far (in force_order)
* Second - index in force_order that was assigned to this thingy
*/
files = collect_lang_dir_content(settings.lang_dir, settings.whitelist);
size_t n = files.size();
#define redundantFileMsg "Redundant localization file"
for (size_t i = 0; i < n; i++) {
for (size_t j = i + 1; j < n; j++) {
std::string A = files[i].languagerange;
std::string B = files[j].languagerange;
for (std::string& pa: languageRangeGetPrefixes(A))
if (pa == B)
een9_THROW(redundantFileMsg);
for (std::string& pb: languageRangeGetPrefixes(B))
if (pb == A)
een9_THROW(redundantFileMsg);
}
}
std::map<std::string, std::vector<std::size_t>> pref_to_files;
for (size_t k = 0; k < n; k++) {
for (const std::string& prefix: languageRangeGetPrefixes(files[k].languagerange)) {
pref_to_files[prefix].push_back(k);
}
}
std::vector<std::pair<size_t, size_t>> assignment;
constexpr size_t inf_bad_order = 999999999;
assignment.assign(n, {0, inf_bad_order});
if (settings.force_order.size() >= inf_bad_order - 2)
een9_THROW("o_O");
for (ssize_t i = 0; i < settings.force_order.size(); i++) {
const std::string& ip = settings.force_order[i];
if (pref_to_files.count(ip) != 1)
een9_THROW("force-order list contains entries that match no files (" + ip + ")");
for (size_t k: pref_to_files.at(ip)) {
if (assignment[k].first <= ip.size()) {
assignment[k].first = ip.size();
assignment[k].second = i;
}
}
}
for (auto& p: pref_to_files) {
const std::vector<size_t>& candidates = p.second;
assert(!candidates.empty());
size_t bestSoFar = candidates[0];
size_t f = inf_bad_order;
for (size_t k: candidates) {
if (assignment[k].second <= f) {
f = assignment[k].second;
bestSoFar = k;
}
}
prefix_to_file[p.first] = bestSoFar;
}
if (prefix_to_file.count("") != 1)
een9_THROW("No locales were provided");
// todo: remove DEBUG
// for (size_t k = 0; k < n; k++) {
// printf("%s has priority %lu\n", files[k].languagerange.c_str(), assignment[k].second);
// }
// printf("==============\n");
// for (const auto& p : prefix_to_file) {
// printf("%s -> %s\n", p.first.c_str(), files[p.second].languagerange.c_str());
// }
}
const LanguageFile& Localizator::get_right_locale(const std::vector<std::string> &preferred_langs) {
for (const std::string& lr: preferred_langs) {
if (prefix_to_file.count(lr) == 1)
return files[prefix_to_file.at(lr)];
}
return files[prefix_to_file.at("")];
}
}

View File

@ -0,0 +1,36 @@
#ifndef IU9_CA_WEB_CHAT_LIB_LOCALIZATOR_H
#define IU9_CA_WEB_CHAT_LIB_LOCALIZATOR_H
#include <jsonincpp/jsonobj.h>
namespace iu9cawebchat {
/* '*' -> ''; X -> X */
std::string languageRangeSimpler(const std::string& a);
struct LocalizatorSettings {
std::string lang_dir;
std::vector<std::string> whitelist;
std::vector<std::string> force_order;
};
/* There is no need to put http Content-Language response value into json file. When is is in the name */
struct LanguageFile {
std::string languagerange;
json::JSON content;
};
/* Localizator uses libjsonincpp internally, and thus can't be read by two treads simultaneously */
struct Localizator {
LocalizatorSettings settings;
std::vector<LanguageFile> files;
std::map<std::string, size_t> prefix_to_file;
/* Throws std::exception if something goes wrong */
explicit Localizator(const LocalizatorSettings& settings);
/* Returns a reference to object inside Localizator */
const LanguageFile& get_right_locale(const std::vector<std::string>& preferred_langs);
};
}
#endif

View File

@ -23,8 +23,8 @@ namespace iu9cawebchat {
uint64_t nsec = std::stoull(ft.substr(s_ + 1, ft.size() - s_ - 1)); uint64_t nsec = std::stoull(ft.substr(s_ + 1, ft.size() - s_ - 1));
een9_ASSERT_pl(nsec < 1000000000); een9_ASSERT_pl(nsec < 1000000000);
const json::JSON cnt = json::parse_str_flawless(base64_decode(login_cookie_encoded.second)); const json::JSON cnt = json::parse_str_flawless(base64_decode(login_cookie_encoded.second));
std::string nickname = cnt[0].g().asString(); std::string nickname = cnt[0].asString();
std::string password = cnt[1].g().asString(); std::string password = cnt[1].asString();
return LoginCookie{{(time_t)sec, (time_t)nsec}, nickname, password}; return LoginCookie{{(time_t)sec, (time_t)nsec}, nickname, password};
} }
@ -37,8 +37,8 @@ namespace iu9cawebchat {
std::pair<std::string, std::string> encode_login_cookie(const LoginCookie& cookie) { std::pair<std::string, std::string> encode_login_cookie(const LoginCookie& cookie) {
json::JSON cnt; json::JSON cnt;
cnt[1].g() = cookie.password; cnt[1].asString() = cookie.password;
cnt[0].g() = cookie.nickname; cnt[0].asString() = cookie.nickname;
return {"login_" + std::to_string(cookie.login_time.tv_sec) + "_" + std::to_string(cookie.login_time.tv_nsec), return {"login_" + std::to_string(cookie.login_time.tv_sec) + "_" + std::to_string(cookie.login_time.tv_nsec),
base64_encode(json::generate_str(cnt, json::print_compact))}; base64_encode(json::generate_str(cnt, json::print_compact))};
} }

View File

@ -5,6 +5,7 @@
#include <engine_engine_number_9/connecting_assets/static_asset_manager.h> #include <engine_engine_number_9/connecting_assets/static_asset_manager.h>
#include "find_db.h" #include "find_db.h"
#include <engine_engine_number_9/running_mainloop.h> #include <engine_engine_number_9/running_mainloop.h>
#include <engine_engine_number_9/http_structures/accept_language.h>
#include <signal.h> #include <signal.h>
#include "str_fields.h" #include "str_fields.h"
#include "backend_logic/client_server_interact.h" #include "backend_logic/client_server_interact.h"
@ -16,22 +17,50 @@ namespace iu9cawebchat {
termination = true; termination = true;
} }
struct ONE_SQLITE_TRANSACTION_GUARD {
SqliteConnection& conn;
bool rollback = false;
explicit ONE_SQLITE_TRANSACTION_GUARD(SqliteConnection& conn_) : conn(conn_) {
sqlite_nooutput(conn, "BEGIN", {}, {});
}
~ONE_SQLITE_TRANSACTION_GUARD() {
if (rollback)
sqlite_nooutput(conn, "ROLLBACK", {}, {});
else
sqlite_nooutput(conn, "END", {}, {});
}
};
LocalizatorSettings make_localizator_settings(const std::string& assets_dir, const json::JSON& config) {
std::vector<std::string> whitelist;
for (const json::JSON& entry: config["lang"]["whitelist"].asArray())
whitelist.push_back(languageRangeSimpler(entry.asString()));
std::vector<std::string> force_order;
for (const json::JSON& entry: config["lang"]["force-order"].asArray())
force_order.push_back(languageRangeSimpler(entry.asString()));
return LocalizatorSettings{assets_dir + "/lang", whitelist, force_order};
}
void run_website(const json::JSON& config) { void run_website(const json::JSON& config) {
een9_ASSERT(config["assets"].g().isString(), "config[\"assets\"] is not string"); een9_ASSERT(config["assets"].isString(), "config[\"assets\"] is not string");
std::string assets_dir = config["assets"].g().asString(); const std::string& assets_dir = config["assets"].asString();
een9_ASSERT(een9::isDirectory(assets_dir), "\"" + assets_dir + "\" is not a directory"); een9_ASSERT(een9::isDirectory(assets_dir), "\"" + assets_dir + "\" is not a directory");
LocalizatorSettings localizator_settings = make_localizator_settings(assets_dir, config);
een9::StaticAssetManagerSlaveModule samI; een9::StaticAssetManagerSlaveModule samI;
samI.update({ samI.update({
een9::StaticAssetManagerRule{assets_dir + "/css", "/assets/css", {{".css", "text/css"}} }, een9::StaticAssetManagerRule{assets_dir + "/css", "/assets/css", {{".css", "text/css"}} },
een9::StaticAssetManagerRule{assets_dir + "/js", "/assets/js", {{".js", "text/javascript"}} }, een9::StaticAssetManagerRule{assets_dir + "/js", "/assets/js", {{".js", "text/javascript"}} },
een9::StaticAssetManagerRule{assets_dir + "/gif", "/assets/gif", {{".gif", "image/gif"}} },
een9::StaticAssetManagerRule{assets_dir + "/img", "/assets/img", { een9::StaticAssetManagerRule{assets_dir + "/img", "/assets/img", {
{".jpg", "image/jpg"}, {".png", "image/png"}, {".svg", "image/svg+xml"} {".jpg", "image/jpg"}, {".png", "image/png"}, {".svg", "image/svg+xml"}
} }, } },
}); });
const json::JSON& config_presentation = config["presentation"].g(); int64_t slave_number = config["server"]["workers"].asInteger().get_int();
int64_t slave_number = config["server"]["workers"].g().asInteger().get_int();
een9_ASSERT(slave_number > 0 && slave_number <= 200, "E"); een9_ASSERT(slave_number > 0 && slave_number <= 200, "E");
std::string sqlite_db_path; std::string sqlite_db_path;
@ -44,21 +73,24 @@ namespace iu9cawebchat {
nytl::TemplaterSettings{nytl::TemplaterDetourRules{assets_dir + "/HypertextPages"}}); nytl::TemplaterSettings{nytl::TemplaterDetourRules{assets_dir + "/HypertextPages"}});
worker_guest_data[i].templater->update(); worker_guest_data[i].templater->update();
worker_guest_data[i].db = std::make_unique<SqliteConnection>(sqlite_db_path); worker_guest_data[i].db = std::make_unique<SqliteConnection>(sqlite_db_path);
worker_guest_data[i].locales = std::make_unique<Localizator>(localizator_settings);
} }
een9::MainloopParameters params; een9::MainloopParameters params;
params.guest_core = [&samI, &worker_guest_data, config_presentation] params.guest_core = [&samI, &worker_guest_data]
(const een9::SlaveTask& task, const een9::ClientRequest& req, een9::worker_id_t worker_id) -> std::string { (const een9::SlaveTask& task, const een9::ClientRequest& req, een9::worker_id_t worker_id) -> std::string {
een9_ASSERT_pl(0 <= worker_id && worker_id < worker_guest_data.size()); een9_ASSERT_pl(0 <= worker_id && worker_id < worker_guest_data.size());
WorkerGuestData& wgd = worker_guest_data[worker_id]; WorkerGuestData& wgd = worker_guest_data[worker_id];
een9::StaticAsset sa; een9::StaticAsset sa;
sqlite_nooutput(*wgd.db, "BEGIN", {}, {}); ONE_SQLITE_TRANSACTION_GUARD conn_guard(*wgd.db);
struct guard {SqliteConnection& conn; bool rollback = false; ~guard() { std::string AcceptLanguage;
if (rollback) for (const std::pair<std::string, std::string>& p: req.headers) {
sqlite_nooutput(conn, "ROLLBACK", {}, {}); if (p.first == "Accept-Language")
else AcceptLanguage = p.second;
sqlite_nooutput(conn, "END", {}, {}); }
}} guard_{*wgd.db}; std::vector<std::string> AcceptLanguageB = een9::parse_header_Accept_Language(AcceptLanguage);
const LanguageFile& locale = wgd.locales->get_right_locale(AcceptLanguageB);
const json::JSON& pres = locale.content;
try { try {
std::vector<std::pair<std::string, std::string>> cookies; std::vector<std::pair<std::string, std::string>> cookies;
std::vector<LoginCookie> login_cookies; std::vector<LoginCookie> login_cookies;
@ -67,38 +99,50 @@ namespace iu9cawebchat {
initial_extraction_of_all_the_useful_info_from_cookies(*wgd.db, req, cookies, login_cookies, userinfo, logged_in_user); initial_extraction_of_all_the_useful_info_from_cookies(*wgd.db, req, cookies, login_cookies, userinfo, logged_in_user);
if (req.uri_path == "/" || req.uri_path == "/list-rooms") { if (req.uri_path == "/" || req.uri_path == "/list-rooms") {
return when_page_list_rooms(wgd, config_presentation, req, userinfo); return when_page_list_rooms(wgd, pres, req, userinfo);
} }
if (req.uri_path == "/login") { if (req.uri_path == "/login") {
return when_page_login(wgd, config_presentation, req, login_cookies, userinfo); return when_page_login(wgd, pres, req, login_cookies, userinfo);
} }
if (een9::beginsWith(req.uri_path, "/chat")) { // todo: split
return when_page_chat(wgd, config_presentation, req, userinfo); if (een9::beginsWith(req.uri_path, "/chat/") || een9::beginsWith(req.uri_path, "/chat-members/")) {
return when_page_chat(wgd, pres, req, userinfo);
} }
if (req.uri_path == "/user") { if (een9::beginsWith(req.uri_path, "/user/")) {
return when_page_user(wgd, config_presentation, req, userinfo); return when_page_user(wgd, pres, req, login_cookies, userinfo);
} }
if (req.uri_path == "/internalapi/pollEvents") { if (req.uri_path == "/register") {
return when_internalapi_pollevents(wgd, req, logged_in_user); return when_page_register(wgd, pres, req, userinfo);
} }
if (req.uri_path == "/internalapi/getChatList") { if (req.uri_path == "/api/chatPollEvents") {
return when_internalapi_getchatlist(wgd, req, logged_in_user); return when_internalapi_chatpollevents(wgd, req, logged_in_user);
} }
if (req.uri_path == "/internalapi/getChatInfo") { if (req.uri_path == "/api/chatListPollEvents") {
return when_internalapi_getchatinfo(wgd, req, logged_in_user); return when_internalapi_chatlistpollevents(wgd, req, logged_in_user);
} }
if (req.uri_path == "/internalapi/getChatMemberList") { if (req.uri_path == "/api/getMessageNeighbours") {
return when_internalapi_getchatmemberlist(wgd, req, logged_in_user); return when_internalapi_getmessageneighbours(wgd, req, logged_in_user);
} }
if (req.uri_path == "/internalapi/getUserInfo") { if (req.uri_path == "/api/sendMessage") {
return when_internalapi_getuserinfo(wgd, req, logged_in_user); return when_internalapi_sendmessage(wgd, req, logged_in_user);
} }
if (req.uri_path == "/internalapi/getMessageInfo") { if (req.uri_path == "/api/deleteMessage") {
return when_internalapi_getmessageinfo(wgd, req, logged_in_user); return when_internalapi_deletemessage(wgd, req, logged_in_user);
}
if (req.uri_path == "/api/addMemberToChat") {
return when_internalapi_addmembertochat(wgd, req, logged_in_user);
}
if (req.uri_path == "/api/removeMemberFromChat") {
return when_internalapi_removememberfromchat(wgd, req, logged_in_user);
}
if (req.uri_path == "/api/createChat") {
return when_internalapi_createchat(wgd, req, logged_in_user);
}
if (req.uri_path == "/api/leaveChat") {
return when_internalapi_leavechat(wgd, req, logged_in_user);
} }
// todo: write all the other interfaces
} catch (const std::exception& e) { } catch (const std::exception& e) {
guard_.rollback = true; conn_guard.rollback = true;
throw; throw;
} }
/* Trying to interpret request as asset lookup */ /* Trying to interpret request as asset lookup */
@ -113,29 +157,11 @@ namespace iu9cawebchat {
(const een9::SlaveTask& task, const std::string& req, een9::worker_id_t worker_id) -> std::string { (const een9::SlaveTask& task, const std::string& req, een9::worker_id_t worker_id) -> std::string {
een9_ASSERT_pl(0 <= worker_id && worker_id < worker_guest_data.size()); een9_ASSERT_pl(0 <= worker_id && worker_id < worker_guest_data.size());
WorkerGuestData& wgd = worker_guest_data[worker_id]; WorkerGuestData& wgd = worker_guest_data[worker_id];
ONE_SQLITE_TRANSACTION_GUARD conn_guad(*wgd.db);
try { try {
if (req == "hello") { return admin_control_procedure(*wgd.db, req, termination);
return ":0 omg! hiii!! Hewwou :3 !!!!\n";
}
if (req == "8") {
termination = true;
return "Bye\n";
}
std::string updaterootpw_pref = "updaterootpw";
if (een9::beginsWith(req, "updaterootpw")) {
size_t nid = updaterootpw_pref.size();
if (nid >= req.size() || !isSPACE(req[nid]))
return "Bad command syntax. Missing whitespace\n";
std::string new_password = req.substr(nid + 1);
if (!check_password(new_password))
een9_THROW("Bad password");
sqlite_nooutput(*wgd.db,
"UPDATE `user` SET `password` = ?1 WHERE `id` = 0 ",
{}, {{1, new_password}});
return "Successul update\n";
}
return "Incorrect command\n";
} catch (std::exception& e) { } catch (std::exception& e) {
conn_guad.rollback = true;
return std::string("Server error\n") + e.what(); return std::string("Server error\n") + e.what();
} }
}; };
@ -151,8 +177,8 @@ namespace iu9cawebchat {
een9_ASSERT(ret == 0, "Incorrect ear address: " + source[i].asString()); een9_ASSERT(ret == 0, "Incorrect ear address: " + source[i].asString());
} }
}; };
translate_addr_list_conf(params.client_regular_listened, config["server"]["http-listen"].g().asArray()); translate_addr_list_conf(params.client_regular_listened, config["server"]["http-listen"].asArray());
translate_addr_list_conf(params.admin_control_listened, config["server"]["admin-command-listen"].g().asArray()); translate_addr_list_conf(params.admin_control_listened, config["server"]["admin-command-listen"].asArray());
signal(SIGINT, sigterm_action); signal(SIGINT, sigterm_action);
signal(SIGTERM, sigterm_action); signal(SIGTERM, sigterm_action);

View File

@ -76,7 +76,7 @@ namespace iu9cawebchat {
int ret = sqlite3_prepare_v2(connection.hand, req_statement.c_str(), -1, &stmt_obj, NULL); int ret = sqlite3_prepare_v2(connection.hand, req_statement.c_str(), -1, &stmt_obj, NULL);
if (ret != 0) { if (ret != 0) {
int err_pos = sqlite3_error_offset(connection.hand); int err_pos = -1;
een9_THROW("Compilation of request\n" + req_statement + "\nfailed" + een9_THROW("Compilation of request\n" + req_statement + "\nfailed" +
((err_pos >= 0) ? " with offset " + std::to_string(err_pos) : "")); ((err_pos >= 0) ? " with offset " + std::to_string(err_pos) : ""));
} }
@ -101,9 +101,14 @@ namespace iu9cawebchat {
sqlite3_finalize(stmt_obj); sqlite3_finalize(stmt_obj);
} }
void sqlite_stmt_bind_int64(SqliteStatement &stmt, int paramId, int64_t value) {
int ret = sqlite3_bind_int64(stmt.stmt_obj, paramId, value);
een9_ASSERT(ret == 0, "sqlite3_bind_int64");
}
int sqlite_stmt_step(SqliteStatement &stmt, int sqlite_stmt_step(SqliteStatement &stmt,
const std::vector<std::pair<int, fsql_integer_or_null *>> &ret_of_integer_or_null, const std::vector<std::pair<int, fsql_integer_or_null *>> &ret_of_integer_or_null,
const std::vector<std::pair<int, fsql_text8_or_null *>> &ret_of_text8_or_null) { const std::vector<std::pair<int, fsql_text8_or_null *>> &ret_of_text8_or_null) {
int ret = sqlite3_step(stmt.stmt_obj); int ret = sqlite3_step(stmt.stmt_obj);
if (ret == SQLITE_DONE) if (ret == SQLITE_DONE)
return ret; return ret;
@ -137,4 +142,9 @@ namespace iu9cawebchat {
} }
return SQLITE_ROW; return SQLITE_ROW;
} }
int64_t sqlite_trsess_last_insert_rowid(SqliteConnection& conn) {
int64_t res = sqlite3_last_insert_rowid(conn.hand);
return res;
}
} }

View File

@ -41,9 +41,13 @@ namespace iu9cawebchat {
~SqliteStatement(); ~SqliteStatement();
}; };
void sqlite_stmt_bind_int64(SqliteStatement& stmt, int paramId, int64_t value);
int sqlite_stmt_step(SqliteStatement& stmt, int sqlite_stmt_step(SqliteStatement& stmt,
const std::vector<std::pair<int, fsql_integer_or_null*>>& ret_of_integer_or_null, const std::vector<std::pair<int, fsql_integer_or_null*>>& ret_of_integer_or_null,
const std::vector<std::pair<int, fsql_text8_or_null*>>& ret_of_text8_or_null); const std::vector<std::pair<int, fsql_text8_or_null*>>& ret_of_text8_or_null);
int64_t sqlite_trsess_last_insert_rowid(SqliteConnection& conn);
} }
#endif #endif

View File

@ -28,11 +28,15 @@ namespace iu9cawebchat {
} }
bool check_password(const std::string &pwd) { bool check_password(const std::string &pwd) {
return is_orthodox_string(pwd) && pwd.size() >= 8; return is_orthodox_string(pwd) && pwd.size() <= 150;
}
bool check_strong_password(const std::string& pwd) {
return check_password(pwd) && pwd.size() >= 8;
} }
bool check_name(const std::string &name) { bool check_name(const std::string &name) {
return is_orthodox_string(name); return is_orthodox_string(name) && name.size() <= 150 && !name.empty();
} }
bool check_nickname(const std::string &nickname) { bool check_nickname(const std::string &nickname) {
@ -42,7 +46,7 @@ namespace iu9cawebchat {
if (!isUNCHAR(ch)) if (!isUNCHAR(ch))
return false; return false;
} }
return true; return nickname.size() <= 150;
} }
/* Yeah baby, it's base64 time!!! */ /* Yeah baby, it's base64 time!!! */

View File

@ -13,6 +13,7 @@ namespace iu9cawebchat {
bool is_orthodox_string(const std::string& str); bool is_orthodox_string(const std::string& str);
bool check_password(const std::string& pwd); bool check_password(const std::string& pwd);
bool check_strong_password(const std::string& pwd);
bool check_name(const std::string& name); bool check_name(const std::string& name);
bool check_nickname(const std::string& nickname); bool check_nickname(const std::string& nickname);

View File

@ -34,6 +34,8 @@ int main(int argc, char** argv){
iu9cawebchat::initialize_website(config, root_pw); iu9cawebchat::initialize_website(config, root_pw);
} else if (cmd == "run") { } else if (cmd == "run") {
iu9cawebchat::run_website(config); iu9cawebchat::run_website(config);
} else if (cmd == "version") {
printf("IU9 Collarbone Annihilation Web Chat (service) V 1.0\n");
} else } else
een9_THROW("unknown action (known are 'run', 'initialize')"); een9_THROW("unknown action (known are 'run', 'initialize')");
} catch (std::exception& e) { } catch (std::exception& e) {