Compare commits

..

73 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
2ad4f5b52d Poll error is not longer breaking everything 2024-08-25 21:37:04 +03:00
785cbff2a7 now redirections use 303, small refactoring, added skeleton for response logic, API responses up to getMessageInfo 2024-08-25 21:20:01 +03:00
Fedor_Kitanin
5ba48ff58f добавлены новые файлы 2024-08-25 17:58:18 +03:00
90f8289bcd Hot fix: now bad cookies do not ruin everything 2024-08-25 15:27:48 +03:00
799e156f88 Fiexs bugs caused by misuse if sqlite3, fixed very old memory leak, added separate .cpp files for each request type, added raw login page, did some refactoring, added functions for http redirection (307), /internalapi/pollEvents is done, /internalapi/getChatList is done 2024-08-25 13:06:26 +03:00
a6f4bd6c88 Fixed some Cookie bugs, rewrote everything to nytl, added session system, added ugly login page 2024-08-24 01:43:07 +03:00
3632ade86d Hot fix: added templater for future login page, fixed some critical bugs with binding socket to unix domain address 2024-08-23 18:29:21 +03:00
5314ffef1f Merged with anton_branch 2024-08-23 16:54:52 +03:00
69d054ed04 Added base64 encoding/decoding, added cookie creation and parsing, divided main service into library (entry = actions.h, module: iu9cawechat::) and small executable 2024-08-23 15:37:29 +03:00
0df109643a ... 2024-08-21 15:44:20 +03:00
925c072039 Initialization of chat service (all tables), admin command to update root password without erasing the whole database. Each een9 worker now has it's own sqlite3 connectin handler 2024-08-21 14:29:53 +03:00
6be78c1510 fixed some bugs, made an utility to send admin commands to server, added both http listeners and admin-cmd listeners. Command "8" to shutdown the server. And the most important: IU9 CA Web Chat can now talk like a mentally ill teenager (command "hello" (gonna delete this later)) 2024-08-20 00:07:17 +03:00
4d18d13a93 Added server support for 'admin-control' protocol, added parsing of IP4/6/unix addresses, fixed some bugs, moved to C++17, config["server"] now actually does something 2024-08-19 19:25:21 +03:00
1aa95ad87e uuups 2024-08-17 16:09:29 +03:00
38d3a2ea78 Printing bad requests to log/req 2024-08-17 15:49:28 +03:00
06d5a33495 Moved from pkg-config 2024-08-16 22:10:04 +03:00
ef4a6dec24 Daily update, added .cpp files for two commands: initialize.cpp and run.cpp 2024-08-16 00:06:35 +03:00
ef3af2ec45 UF CHANGED README UUUUFH UH UH AAAAAAAAA I- I am COOOOMMITTING YEEEAAH! YEAH ANOTHER README CHANGE THIS IS THE UF- UH -AH I'M DOING SO MUCH JOB 2024-08-15 13:39:42 +03:00
7a1bfd81f3 nytl: quick fix 2024-08-15 11:53:44 +03:00
0f84548f78 nytl: hot fix 2024-08-15 09:50:14 +03:00
yakovlevanton
0e2ed000f9 Добавлены кнопки удаления чатов, участников чата, изменен дизайн некоторых элементов, добавлено api 2024-08-15 01:07:59 +03:00
da2cb4261c nytl: bug fix 2024-08-14 22:32:30 +03:00
ea7d50a183 nytl: merged files rendering.cpp and execute_expression.cpp 2024-08-14 21:24:27 +03:00
dd806a5633 New York Transit Line is finally usable at least a little. Integrated it with frontend 2024-08-14 21:18:00 +03:00
393703b0b2 Fixed one of those "Cs convenient ways to show error" (sigsegv caused by ivalid pointer). It were 2 hours of pure frustration 2024-08-14 21:18:00 +03:00
e03794410b FIXED SOME TABULATION BUGS, DO NOT LOOK AT MY PREVIOUS SYSTEM 2024-08-14 21:18:00 +03:00
076a941a6e Mhm, I wrote something, for some reason nothing works 2024-08-14 21:18:00 +03:00
83c17c22e6 Daily update 2024-08-14 21:18:00 +03:00
01176052d7 Лмао, Адель, харе carriage return ставить в конце каждой строки((( 2024-08-14 21:18:00 +03:00
91aaf51410 I hate my life so much 2024-08-14 21:18:00 +03:00
be37e4f5ab Very raw version of nytl, no parsing yet 2024-08-14 21:18:00 +03:00
5fab1131ca Daily update 2024-08-14 21:18:00 +03:00
c264e3802b Daily saving of progress 2024-08-14 21:18:00 +03:00
vladimir
40f637a1cf added antons code 2024-08-11 15:30:38 +03:00
vladimir
9dfe013cc6 api update 2024-08-11 15:17:53 +03:00
124 changed files with 7683 additions and 1208 deletions

6
.gitignore vendored
View File

@ -11,3 +11,9 @@ building/*.svg
.idea/
compile_commands.json
local.sh
iu9-ca-web-chat.db
log/
core
config/example.json

100
README.md
View File

@ -1,6 +1,94 @@
# ИУ9-21Б Вэб-чат C.A
# IU9 C.A. WEB CHAT
Сделан на летней практике 5-ю первокурсниками ИУ9
C.A. stands for Collarbone Annihilation.
# About
Сделан на летней практике 5-ю первокурсниками из ИУ9-21Б.
# Dependencies
iu9-ca-web-chat использует
- GCC
- [regexis024-build-system](
https://gitlab.yyyi.ru/collarbone-annihilation/regexis024-build-system
)
- [libregexis024](
https://gitlab.yyyi.ru/kme-devline/libregexis024
)
- [libjsonincpp](
https://gitlab.yyyi.ru/collarbone-annihilation/libjsonincpp
)
- [sqlite3](
https://www.sqlite.org
)
Сервис так же использует библиотеки engine_engine_number_9 и new_york_transit_line,
размещённые прямо в репозитории.
Работает только на unix системах.
# Compilation
```sh
regexis024_build_system.sh
./building/main bi ./ "absolute/path/to/installation/root"
```
# Usage
Помимо самого бинарника нужен файл с настройками сервиса. Формат настроек: JSON.
Комментарии не поддерживаются. Пример такого файла находится в example/config.json.
Вместе с бинарным фалом так же распространяются ассеты, необходимые для работы сайта.
Их можно найти в папке assets. В настроках (поле `config.assets`) указывается путь до
папки с ассетами. Путь может быть как абсолютным, так и относительным к рабочей директории.
Поле настроек `config.database` указывает как соединиться с базой данных.
Поддерживается только база данных sqlite3. Поддерживается только хранение в файле.
Поле `config.database.file` указывает путь где хранится sqlite база данных.
Перед тем как использовать сервис нужно его проинициализировать (а точнее проинициализировать
базу данных):
`ROOT_PW="<your desired root password>" iu9-ca-web-chat /path/to/config.json initialize`
Переменная окружения `ROOT_PW` читается для устаановки пароля root пользователю раз и навсегда.
Далее можно запускать сервис:
`iu9-ca-web-chat /path/to/config.json run`
Для остановки сервиса киньте ему SIGTERM или SIGINT.
Утилита `iu9-ca-web-chat-admin-cli` позволяет администратору сервиса контролировать его через сокет
(адрес указан в `config["server"]["admin-command-listen"]`).
По адресам `config.server.admin-command-listen` идёт прослушивание так называемых "команд администратора".
iu9cawebchat определяет свой простой протокол для передачи этих команд.
Утилита iu9-ca-web-chat-admin-cli может отправить текст с некой командой на сервер на этот адрес и получить
ответ от сервера.
```shell
iu9-ca-web-chat-admin-cli <server admin-control address> <command text> [<command text> ...]
```
Дополнительные параметры конкатенируются, разделяясь переводом строки.
Команды администратора:
`updateroopw <new root password>` - сменить пароль пользователя с номером 0
`adduser <user nickname> <user name> <user password> <user bio>` - зарегистрировать пользователя сайта
`8` - остановить сервис
Если нужно ввести пробел или символ `\ ` в любое из этих полей, перед ними нужно поставить `\ `;
Если указать меньше полей, чем нужно, незаполненные поля станут пустыми строками.
Параметры конфигурации `config.lang.whitelist` и `config.lang.force-order` определяют на
какие языки будет локализован сервер, и какие переводы приоритетнее каких.
На данный момент поддерживаются
- `ru-RU`
- `en-US`
Все переводы хранятся в папке `assets/lang`. Для добавления своего перевода нужно форкнуть репозиторий и
сделать копию файла `assets/lang/ru-RU.lang.json` в `assets/lang/XXXXX.lang.json`.
# Список участников
@ -10,3 +98,11 @@
4. [Каримов Адель](https://gitflic.ru/user/ra1n)
5. [Яковлев Антон](https://gitflic.ru/user/yakovlevanton)
# Комментарии (для разработчиков)
Зачем писать комментарии в коде, если можно их вынести в отдельные пдф-ки?
- [Документация для разработчиков](
https://gitlab.yyyi.ru/collarbone-annihilation/iu9-ca-chat-api)
О том как работает всё остальное можно только догадываться.

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

@ -0,0 +1,68 @@
{% ELDEF pass JSON pres JSON userinfo JSON openedchat JSON initial_chatUpdResp %}
<script>
let pres = {% PUT jsinsert pres %};
let userinfo = {% PUT jsinsert userinfo %};
let openedchat = {% PUT jsinsert openedchat %};
let initial_chatUpdResp = {% PUT jsinsert initial_chatUpdResp %};
</script>
{% ENDELDEF %}
{% ELDEF main JSON pres JSON userinfo JSON openedchat JSON initial_chatUpdResp %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" type="image/png" href="/assets/img/favicon.png">
<link rel="stylesheet" href="/assets/css/debug.css">
<link rel="stylesheet" href="/assets/css/common.css">
<link rel="stylesheet" href="/assets/css/common-popup.css">
<link rel="stylesheet" href="/assets/css/chat.css">
<title>{%w pres.chat.header-chat %} {%w openedchat.name %}</title>
</head>
<body>
{% PUT chat.pass pres userinfo openedchat initial_chatUpdResp %}
<div id="msg-deletion-win" class="popup-window">
<h1 class="popup-window-msg">{%w pres.chat.reask-delete-message %}</h1>
<!-- mesage preview will be actually rewritten before each window activation-->
<p class="message-in-popup-preview" id="win-deletion-msg-preview">|||||||||</p>
<button class="popup-window-btn-yes" id="msg-deletion-yes">{%w pres.chat.yes-delete %}</button>
<button class="popup-window-btn-no" id="msg-deletion-no">{%w pres.chat.no-delete %}</button>
</div>
<div class="fullscreen-container resp-container">
<div class="panel" id="navigation-info-panel">
<a href="/list-rooms" id="go-to-chat-list" class="panel-thing">
<img alt="Go to list of chats" src="/assets/img/list-rooms.svg" width="32px">
</a>
<a href="/user/{% W userinfo.nickname %}" id="go-to-my-profile" class="panel-thing">
<img alt="Go to my profile" src="/assets/img/user.svg" width="32px">
</a>
<p class="panel-thing panel-header-txt"> {% W openedchat.name %} ({% W openedchat.nickname %})</p>
<a href="/chat-members/{% W openedchat.nickname %}" id="go-to-chat-settings" class="panel-thing">
<img alt="Settings of chat. List of members" src="/assets/img/settings-iron.svg" width="32px">
</a>
</div>
<div id="chat-widget">
<div class="chat-debug-rect" id="debug-line-highest" style="background-color: #8600d3"></div>
<div class="chat-debug-rect" id="debug-line-top-padding" style="background-color: #ff00ae"></div>
<div class="chat-debug-rect" id="debug-line-bottom-padding" style="background-color: #ff0062"></div>
<div class="chat-debug-rect" id="debug-line-lowest" style="background-color: #ff2f00"></div>
<div id="top-loading" class="message-supercontainer">
<img class="loading-spinner" alt="Loading backward..." src="/assets/gif/loading.gif">
</div>
<div id="bottom-loading" class="message-supercontainer">
<img class="loading-spinner" alt="Loading forward..." src="/assets/gif/loading.gif">
</div>
</div>
<div class="panel" id="input-panel">
<div contentEditable id="message-input" class="panel-thing"></div>
</div>
<script src="/assets/js/common.js"></script>
<script src="/assets/js/common-popup.js"></script>
<script src="/assets/js/chat.js"></script>
</div>
</body>
</html>
{% ENDELDEF %}

View File

@ -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

@ -0,0 +1,74 @@
{% ELDEF main JSON pres JSON userinfo JSON initial_chatListUpdResp %}
<!DOCTYPE html>
<html lang="{% W pres.lang %}">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{%w pres.list-rooms.header %}</title>
<link rel="icon" type="image/png" href="/assets/img/favicon.png">
<link rel="stylesheet" href="/assets/css/common.css">
<link rel="stylesheet" href="/assets/css/common-popup.css">
<link rel="stylesheet" href="/assets/css/list-rooms.css">
</head>
<body>
<script>
let pres = {% PUT jsinsert pres %};
let userinfo = {% PUT jsinsert userinfo %};
let initial_chatListUpdResp = {% PUT jsinsert initial_chatListUpdResp %};
</script>
<div id="chat-creation-win" class="popup-window">
<h1 class="popup-window-msg">{%w pres.list-rooms.new-chat-header %}</h1>
<table class="id-str-input-table">
<tr>
<td class="id-str-input-td1">
<label for="chat-nickname-input">{%w pres.list-rooms.directive-nickname %}</label>
</td>
<td class="id-str-input-td2">
<input id="chat-nickname-input" type="text" class="one-line-input"
placeholder="{%w pres.list-rooms.placeholder-nickname %}" required>
</td>
</tr>
<tr>
<td class="id-str-input-td1">
<label for="chat-name-input">{%w pres.list-rooms.directive-name %}</label>
</td>
<td class="id-str-input-td2">
<input id="chat-name-input" type="text" class="one-line-input"
placeholder="{%w pres.list-rooms.placeholder-name %}" required>
</td>
</tr>
</table>
<h1 class="popup-window-msg">{%w pres.list-rooms.reask-create-new-chat %}</h1>
<button class="popup-window-btn-yes" id="chat-creation-win-yes">{%w pres.list-rooms.yes-create %}</button>
<button class="popup-window-btn-no" id="chat-creation-win-no">{%w pres.list-rooms.no-create %}</button>
</div>
<div id="chat-renunciation-win" class="popup-window">
<h1 id="chat-renunciation-win-title" class="popup-window-msg">||||||||||</h1>
<button class="popup-window-btn-yes" id="chat-renunciation-win-yes">{%w pres.list-rooms.yes-leave %}</button>
<button class="popup-window-btn-no" id="chat-renunciation-win-no">{%w pres.list-rooms.no-leave %}</button>
</div>
<div class="document-container resp-container">
<div id="navigation-panel" class="panel">
<a href="/user/{%w userinfo.nickname %}" id="go-to-my-profile" class="panel-thing">
<img alt="Go to my profile" src="/assets/img/user.svg" width="32px">
</a>
<p class="panel-thing panel-header-txt">
{%w pres.list-rooms.page-description %}
</p>
</div>
<div class="dynamic-block-list">
<img id="CL-bacbe" class="button-add centered-block-el" alt="New chat" src="/assets/img/add.svg">
<div class="dynamic-block-list-el-container" id="CL-dblec">
</div>
</div>
</div>
<script src="/assets/js/common.js"></script>
<script src="/assets/js/common-popup.js"></script>
<script src="/assets/js/list-rooms.js"></script>
</body>
</html>
{% ENDELDEF %}

View File

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

View File

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

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

46
assets/css/login.css Normal file
View File

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

View File

@ -1,118 +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-image: url("/assets/img/return.svg");
background-repeat: no-repeat;
cursor: pointer;
width: 30px;
height: 30px;
border-radius: 10px;
position: absolute;
left: 20px;
top: 30px;
border: none;
}
form {
display: flex;
flex-direction: column;
align-items: center;
}
.columns {
display: flex;
justify-content: center;
align-items: flex-start;
gap: 20px;
margin-bottom: 20px;
}
.column {
display: flex;
flex-direction: column;
align-items: center;
}
.add {
background-image: url("/assets/img/add_photo.svg");
background-size: cover;
width: 30px;
height: 30px;
border: none;
cursor: pointer;
border-radius: 10px;
}
.image-button:hover {
opacity: 0.8;
}
.image-button:active {
transform: scale(0.95);
}
#login {
font-family: Arial, sans-serif;
font-size:16px;
width: 150px;
height: 20px;
border-radius: 10px;
border-color: #2F4F4F;
}
#username {
font-family: Arial, sans-serif;
font-size:16px;
width: 150px;
height: 20px;
margin-bottom: 1px;
margin-top: 50px;
border-radius: 10px;
border-color: #2F4F4F;
}
#bio {
height: 150px;
width: 500px;
padding: 10px;
box-sizing: border-box;
font-family: Arial, sans-serif;
font-size:14px;
text-align: left;
vertical-align: top;
margin-bottom: 5px;
}
.save {
cursor:pointer;
font-size: 16px;
border-radius: 15px;
border-color: #2F4F4F;
height: 40px;
color: white;
background-color: #0088cc;
width: 150px;
}
.avatar {
border-radius: 50%;
object-fit: cover;
}

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

BIN
assets/gif/loading.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

View File

@ -1,39 +0,0 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Веб-Чат</title>
<link rel="stylesheet" href="/assets/css/chat.css">
</head>
<body>
<div class="chat-container">
<div class="chat-header">
<span class="room-name">Веб чат</span>
<button class="members" onclick="openMembersList()">Показать участников</button>
</div>
<div class="chat-messages" id="chat-messages">
<!-- Сообщения чата будут здесь -->
</div>
<div class="chat-footer">
<input type="text" class="chat-input" id="chat-input" placeholder="Введите сообщение...">
<button class="chat-send-button" onclick="sendMessage()">Отправить</button>
</div>
</div>
<div class="overlay" id="overlay">
<div class="members-list" id="members-list">
<div class="members-list-header">
<span class="close" onclick="closeMembersList()">&times;</span>
<h2 class="all-members">Все участники</h2>
</div>
<div class="members-list-body">
<ul>
<li><img src="https://sun9-59.userapi.com/impg/t8GhZ7FkynVifY1FQCnaf31tGprbV_rfauZzgg/fSq4lyc6V0U.jpg?size=1280x1280&quality=96&sign=e3c309a125cb570d2e18465eba65f940&type=album">
<a href="profile.html">Адель</a></li>
</ul>
</div>
</div>
</div>
<script src="/assets/js/chat.js"></script>
</body>
</html>

View File

@ -1,63 +0,0 @@
<!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/list-rooms.css">
</head>
<body>
<div class="container">
<h1 style="color: white;">Выберите Чат-Комнату</h1>
<ul class="room-list">
<!-- Здесь будет список комнат -->
</ul>
<button class="create-room-button" onclick="openCreateRoomModal()">Создать Комнату</button>
</div>
<!-- Модальное окно для создания комнаты -->
<div id="createRoomModal" class="modal">
<div class="modal-content">
<div class="modal-header">
<span class="close" onclick="closeCreateRoomModal()">&times;</span>
<h2>Создать Комнату</h2>
</div>
<div class="modal-body">
<input type="text" id="newRoomName" placeholder="Название комнаты">
</div>
<div class="modal-footer">
<button class="join-button" onclick="createRoom()">Создать</button>
</div>
</div>
</div>
<!-- Модальное окно для добавления участников -->
<div class="overlay" id="add_members">
<div class="add-members">
<div class="add-members-header">
<span class="close" onclick="closeAdd()">&times;</span>
<h2>Добавить участников</h2>
</div>
<div class="add-members-body">
<input type="text" id="newMemberLogin" placeholder="Логин пользователя">
</div>
<div class="add-members-footer">
<button class="add-member-button" onclick="addMember()">Добавить</button>
</div>
</div>
</div>
<div class="overlay" id="delete-chat">
<div class="delete-chat">
<div class="delete-chat-header">
<span class="delete-close" onclick="closeConfirm()">&times;</span>
<h2>Вы уверены, что хотите удалить чат?</h2>
</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>
</html>

View File

@ -1,37 +0,0 @@
<!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>
<button class="return" type="button"></button>
</div>
<form>
<div class="columns">
<div class="column">
<img class="avatar" src="/assets/img/empty_avatar.png" id="avatar" height="200" width="200"><br>
<input type="file" id="fileInput" style="display:none">
<button class="add" type="button" onclick="document.getElementById('fileInput').click();"></button><br>
</div>
<div class="column">
<input type="text" name="username" placeholder = "Имя пользователя" value="Some Name" id="username"><br>
<input type="text" name="login" placeholder="Логин" value="some_login123" id="login" readonly><br>
</div>
</div>
<h3 style="color:#007bb5;">О себе</h3>
<div class="additional-info">
<textarea name="bio" placeholder="Напишите о себе..." id="bio"></textarea>
</div>
<button class="save" type="submit">Сохранить изменения</button>
</form>
</div>
<script src="/assets/js/list-rooms.js"> </script>
</body>
</html>

View File

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

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

View File

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

Before

Width:  |  Height:  |  Size: 1.0 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

View File

@ -1,15 +1,20 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg fill="#000000" version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
width="800px" height="800px" viewBox="0 0 489.394 489.394"
xml:space="preserve">
<g>
<path d="M375.789,92.867H166.864l17.507-42.795c3.724-9.132,1-19.574-6.691-25.744c-7.701-6.166-18.538-6.508-26.639-0.879
L9.574,121.71c-6.197,4.304-9.795,11.457-9.563,18.995c0.231,7.533,4.261,14.446,10.71,18.359l147.925,89.823
c8.417,5.108,19.18,4.093,26.481-2.499c7.312-6.591,9.427-17.312,5.219-26.202l-19.443-41.132h204.886
c15.119,0,27.418,12.536,27.418,27.654v149.852c0,15.118-12.299,27.19-27.418,27.19h-226.74c-20.226,0-36.623,16.396-36.623,36.622
v12.942c0,20.228,16.397,36.624,36.623,36.624h226.74c62.642,0,113.604-50.732,113.604-113.379V206.709
C489.395,144.062,438.431,92.867,375.789,92.867z"/>
</g>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="128"
height="127.99999"
viewBox="0 0 33.866667 33.866664"
version="1.1"
id="svg1"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<g
id="layer1">
<path
style="fill:#000000;fill-opacity:1;stroke:#ffffff;stroke-width:0.294159;stroke-linejoin:round;stroke-opacity:1"
d="m 0.65443061,23.377766 10.20536539,-7.351693 -0.06427,3.749871 c 0,0 6.384296,0.115304 9.607291,0.115304 1.824632,0 4.010317,0.117998 5.361398,-1.065432 0.797635,-0.698657 1.041209,-1.943308 0.954461,-2.97946 -0.118465,-1.414973 -0.417733,-2.830021 -1.492491,-3.799298 -0.964892,-0.870193 -2.338758,-1.449022 -3.659301,-1.465666 -1.347581,-0.01698 -4.311633,-0.01476 -4.311633,-0.01476 l 0.02678,-6.0468658 c 0,0 5.209174,-0.1269759 7.836958,-0.1269759 2.090574,0 3.730335,0.2813604 5.374446,1.5274776 1.165995,0.8837399 2.874757,2.1212447 2.874757,3.8971142 0,3.5737799 -0.03341,6.8479459 -0.03341,10.6502529 0,3.155641 -2.159421,4.126978 -3.036427,4.888332 -1.783594,1.548393 -4.069763,1.553329 -6.468859,1.553329 -4.709142,0 -13.295569,0 -13.295569,0 v 3.244296 z"
id="path1" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

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

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,52 +1,477 @@
function sendMessage() {
const chatMessages = document.getElementById('chat-messages');
const chatInput = document.getElementById('chat-input');
const message = chatInput.value;
if (message.trim() !== '') {
const messageElement = document.createElement('div');
messageElement.classList.add('chat-message');
let LocalHistoryId = 0;
const avatarElement = document.createElement('div');
avatarElement.classList.add('avatar');
let members = new Map();
const avatarImage = document.createElement('img');
avatarImage.src = 'https://sun9-59.userapi.com/impg/t8GhZ7FkynVifY1FQCnaf31tGprbV_rfauZzgg/fSq4lyc6V0U.jpg?size=1280x1280&quality=96&sign=e3c309a125cb570d2e18465eba65f940&type=album';
avatarElement.appendChild(avatarImage);
let loadedMessages = new Map(); // messageSt objects
/*
container: EL, box: EL, offset: number (msgPres) */
let visibleMessages = new Map(); // HTMLElement objects
const messageContentElement = document.createElement('div');
messageContentElement.classList.add('message-content');
let anchoredMsg = -1;
let visibleMsgSegStart = -1;
let visibleMsgSegEnd = -2;
let offsetOfAnchor = 500;
let highestPoint = null;
let lowestPoint = null;
const usernameElement = document.createElement('div');
usernameElement.classList.add('username');
usernameElement.textContent = 'Адель';
let lastMsgId = -1;
let myRoleHere = null; // Dung local state updates should be updated first
const textElement = document.createElement('div');
textElement.classList.add('text');
textElement.textContent = message;
// Would start with true if opened `/chat/<>`
let bumpedAtBottom = false;
messageContentElement.appendChild(usernameElement);
messageContentElement.appendChild(textElement);
// Hidden variable. When deletion window popup is active
// Persists from popup activation until popup deactivation
let storeHiddenMsgIdForDeletionWin = -1;
messageElement.appendChild(avatarElement);
messageElement.appendChild(messageContentElement);
let debugMode = false;
chatMessages.appendChild(messageElement);
// Positive in production, negative for debug
let softZoneSz = debugMode ? -150 : 300;
let chatPadding = debugMode ? 300 : 5;
let msgGap = 5;
const msgErased = pres.chat.msgErased;
chatInput.value = '';
chatMessages.scrollTop = chatMessages.scrollHeight;
function genSentBase(){
return {
'chatUpdReq': {
'LocalHistoryId': LocalHistoryId,
'chatId': openedchat.id
}
};
}
function genSentBaseGMN(){
let Sent = genSentBase();
Sent.amount = debugMode ? 2 : 14;
return Sent;
}
function getChatWgSz(){
let chatWg = document.getElementById("chat-widget");
return [chatWg.offsetWidth, chatWg.offsetHeight];
}
function elSetOffsetInChat(el, offset){
el.style.bottom = String(offset) + "px";
}
function isMissingPrimaryMsgHeap(){
return lastMsgId >= 0 && anchoredMsg < 0;
}
function isMissingTopMsgHeap(){
let [W, H] = getChatWgSz();
return anchoredMsg >= 0 && (highestPoint < H + softZoneSz && visibleMsgSegStart > 0);
}
function isMissingBottomMsgHeap(){
return anchoredMsg >= 0 && (lowestPoint > - softZoneSz && visibleMsgSegEnd < lastMsgId);
}
function updateOffsetOfVisibleMsg(msgId, offset){
visibleMessages.get(msgId).container.style.bottom = String(offset) + "px";
}
function updateOffsetsUpToTop(){
let offset = offsetOfAnchor;
for (let curMsg = anchoredMsg; curMsg >= visibleMsgSegStart; curMsg--){
updateOffsetOfVisibleMsg(curMsg, offset);
let height = visibleMessages.get(curMsg).container.offsetHeight;
offset += height + msgGap;
}
return offset - msgGap;
}
function updateOffsetsDown(){
let offset = offsetOfAnchor;
for (let curMsg = anchoredMsg + 1; curMsg <= visibleMsgSegEnd; curMsg++){
let height = visibleMessages.get(curMsg).container.offsetHeight;
offset -= (height + msgGap);
updateOffsetOfVisibleMsg(curMsg, offset);
}
return offset;
}
function updateOffsetsSane(){
if (anchoredMsg < 0)
return;
highestPoint = updateOffsetsUpToTop();
lowestPoint = updateOffsetsDown();
}
function heightOfPreloadGhost(){
let [W, H] = getChatWgSz();
return Math.min(H * 0.9, Math.max(H * 0.69, 30));
}
function updateOffsets(){
let spinnerTop = document.getElementById("top-loading");
let spinnerBottom = document.getElementById("bottom-loading");
let SbH = spinnerBottom.offsetHeight;
if (anchoredMsg < 0){
hideHTMLElement(spinnerBottom);
elSetOffsetInChat(spinnerTop, chatPadding);
setElementVisibility(spinnerTop, isMissingPrimaryMsgHeap());
} else {
let [W, H] = getChatWgSz();
updateOffsetsSane();
let lowestLowestPoint = isMissingBottomMsgHeap() ? lowestPoint - heightOfPreloadGhost(): lowestPoint;
let highestHighestPoint = isMissingTopMsgHeap() ? highestPoint + heightOfPreloadGhost() : highestPoint;
if (lowestLowestPoint > chatPadding || (highestHighestPoint - lowestLowestPoint) <= H - chatPadding * 2 ||
(!isMissingBottomMsgHeap() && bumpedAtBottom)) {
offsetOfAnchor += (-lowestLowestPoint + chatPadding);
updateOffsetsSane();
} else if (highestHighestPoint < H - chatPadding) {
offsetOfAnchor += (-highestHighestPoint + (H - chatPadding));
updateOffsetsSane();
}
/* Messages weere updated (and only them). They were talking with ghosts.
Now we are trying to show spinners of ghosts */
elSetOffsetInChat(spinnerTop, highestPoint);
setElementVisibility(spinnerTop, isMissingTopMsgHeap());
elSetOffsetInChat(spinnerBottom, lowestPoint - SbH);
setElementVisibility(spinnerBottom, isMissingBottomMsgHeap());
/* Fix anchor */
let oldAnchor = anchoredMsg;
while (true){
let h = visibleMessages.get(anchoredMsg).container.offsetHeight;
if (!(offsetOfAnchor + h < chatPadding && visibleMsgSegStart < anchoredMsg))
break
offsetOfAnchor += (msgGap + h);
anchoredMsg--;
}
while (offsetOfAnchor > H - chatPadding && anchoredMsg < visibleMsgSegEnd){
anchoredMsg++;
let h = visibleMessages.get(anchoredMsg).container.offsetHeight;
offsetOfAnchor -= (msgGap + h);
}
if (oldAnchor !== anchoredMsg)
console.log("anchoredMsg: " + String(oldAnchor) + " -> " + String(anchoredMsg))
}
}
function openMembersList() {
document.getElementById("members-list").style.display = "block";
document.getElementById("overlay").style.display = "flex";
function shouldShowDeleteMesgBtn(messageSt){
return !messageSt.isSystem && messageSt.exists && (myRoleHere !== userChatRoleReadOnly) &&(
myRoleHere === userChatRoleAdmin || messageSt.senderUserId === userinfo.uid);
}
function closeMembersList() {
document.getElementById("members-list").style.display = "none";
document.getElementById("overlay").style.display = "none";
function getMsgTypeClassSenderBased(messageSt){
if (messageSt.isSystem)
return "message-box-system"
if (messageSt.senderUserId === userinfo.uid)
return "message-box-mine"
return "message-box-alien";
}
document.getElementById('chat-input').addEventListener('keydown', function (event) {
if (event.key === 'Enter') {
sendMessage();
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,115 +1,188 @@
let rooms = {};
let roomToDelete = null;
let LocalHistoryId = 0;
function openRoom(currentRoom) {
alert('Вы вошли в комнату: ' + currentRoom);
function genSentBase(){
return {
'chatListUpdReq': {
'LocalHistoryId': LocalHistoryId
}
};
}
function closeAdd() {
document.getElementById('add_members').style.display = 'none';
let myChats = new Map();
let chatBoxes = new Map();
/* Generate text that is displayed on the right side of chat intro box */
function youAreXHere(myRoleHere){
// todo: TRANSLATE IT
return pres['list-rooms']['you-are-X-here'][0] + " " + myRoleHere + " " + pres['list-rooms']['you-are-X-here'][1];
}
function openAdd() {
document.getElementById('add_members').style.display = 'flex';
let chatRenunciationWinStoredId = -1;
function shouldShowDeleteButton(myMembershipSt){
return myMembershipSt.myRoleHere === userChatRoleDeleted;
}
function openConfirm(roomName) {
roomToDelete = roomName;
document.getElementById("delete-chat").style.display = "flex";
/* 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 closeConfirm() {
roomToDelete = null;
document.getElementById("delete-chat").style.display = "none";
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 deleteChat() {
if (roomToDelete && rooms[roomToDelete]) {
delete rooms[roomToDelete];
removeRoomFromList(roomToDelete);
closeConfirm();
} else {
alert("Не удалось найти выбранную комнату.");
function updateLocalStateFromChatListUpdResp(chatListUpdResp){
LocalHistoryId = chatListUpdResp.HistoryId;
let literalChatList = document.getElementById("CL-dblec");
for (let myMembershipSt of chatListUpdResp.myChats){
let chatId = myMembershipSt.chatId;
console.log(myMembershipSt);
if (myChats.has(chatId)){
myChats.set(chatId, myMembershipSt);
updateBoxWithNewSt(chatBoxes.get(chatId), myMembershipSt);
} else {
if (myMembershipSt.myRoleHere === userChatRoleDeleted)
continue;
myChats.set(chatId, myMembershipSt);
let box = convertMyMembershipStToBox(myMembershipSt)
chatBoxes.set(chatId, box);
literalChatList.appendChild(box);
}
}
}
function addMember() {
const login = document.getElementById('newMemberLogin').value;
if (login) {
alert(`Участник с никнеймом '${login}' добавлен`);
closeAdd();
} else {
alert('Пожалуйста, введите логин участника');
/* Use it ONLY if `Recv` reported success */
function updateLocalStateFromRecv(Recv){
updateLocalStateFromChatListUpdResp(Recv.chatListUpdResp);
}
function configureChatCreationInterface(){
document.getElementById("chat-creation-win-yes").onclick = function (ev) {
if (ev.button !== 0)
return;
let chatNicknameInput = document.getElementById("chat-nickname-input");
let chatNameInput = document.getElementById("chat-name-input");
let nickname = String(chatNicknameInput.value);
let name = String(chatNameInput.value);
deactivateActivePopup();
let Sent = genSentBase();
Sent.content = {};
Sent.content.nickname = nickname;
Sent.content.name = name;
apiRequest("createChat", Sent
).then((Recv) => {
updateLocalStateFromRecv(Recv);
}).catch((e) => {
alert(pres['list-rooms']["failed-create-chat"]);
console.log(e);
});
};
document.getElementById("chat-creation-win-no").onclick = function (ev) {
if (ev.button !== 0)
return;
deactivateActivePopup();
}
document.getElementById("CL-bacbe").onclick = function (ev){
if (ev.button !== 0)
return;
let chatNicknameInput = document.getElementById("chat-nickname-input");
let chatNameInput = document.getElementById("chat-name-input");
chatNicknameInput.value = "";
chatNameInput.value = "";
activatePopupWindowById("chat-creation-win");
};
}
function 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();
}
}
function openCreateRoomModal() {
document.getElementById('createRoomModal').style.display = 'block';
}
function closeCreateRoomModal() {
document.getElementById('createRoomModal').style.display = 'none';
}
function createRoom() {
const roomName = document.getElementById('newRoomName').value.trim();
if (roomName === '') {
alert('Пожалуйста, заполните все поля.');
return;
}
if (rooms[roomName]) {
alert('Комната с таким названием уже существует.');
return;
}
rooms[roomName] = true;
addRoomToList(roomName);
closeCreateRoomModal();
}
function addRoomToList(roomName) {
const roomList = document.querySelector('.room-list');
const existingRoomItem = Array.from(roomList.children).find(item => item.querySelector('.room-name').textContent === roomName);
if (existingRoomItem) {
existingRoomItem.remove();
}
const roomItem = document.createElement('li');
roomItem.classList.add('room-item');
roomItem.innerHTML = `
<span class="room-name">${roomName}</span>
<button class="delete-chat-button" onclick="openConfirm('${roomName}')">Удалить чат</button>
<button class="add-members-button" onclick="openAdd()">Добавить участников</button>
<button class="join-button" onclick="openRoom('${roomName}')">Войти</button>
`;
roomList.appendChild(roomItem);
}
function removeRoomFromList(roomName) {
const roomList = document.querySelector('.room-list');
const roomItem = Array.from(roomList.children).find(item => item.querySelector('.room-name').textContent === roomName);
if (roomItem) {
roomList.removeChild(roomItem);
}
}
function initializeRoomList() {
Object.keys(rooms).forEach(roomName => {
addRoomToList(roomName);
__mainloopDelayMs = 3000;
__guestMainloopPollerAction = function(){
let Sent = genSentBase();
apiRequest("chatListPollEvents", Sent
).then((Recv) => {
console.log("Got a response");
console.log(Recv);
updateLocalStateFromRecv(Recv);
});
}
initializeRoomList();
window.onclick = function(event) {
if (event.target === document.getElementById('createRoomModal')) {
closeCreateRoomModal();
}
}
document.getElementById('newRoomName').addEventListener('keydown', function(event) {
if (event.key === 'Enter') {
createRoom();
}
});
window.onload = function () {
console.log("Loading complete");
updateLocalStateFromChatListUpdResp(initial_chatListUpdResp);
configureChatCreationInterface();
configureChatRenunciationInterfaceWinPart();
mainloopPoller();
};

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

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

View File

@ -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

@ -1,10 +0,0 @@
#!/usr/bin/env sh
BUILDING_DIR="./building"
[ -d "$BUILDING_DIR" ] || exit 1
MAIN_FILE="$BUILDING_DIR/main.cpp"
[ -f "$MAIN_FILE" ] || exit 1
COOL_FLAGS="$(pkg-config --cflags regexis024-build-system)"
g++ $COOL_FLAGS -o "$BUILDING_DIR/main" "$MAIN_FILE" || exit 1

View File

@ -1,30 +1,21 @@
#include "regexis024_build_system.h"
#include <utility>
#include "regexis024_build_system.h"
std::vector<std::string> getFromPkgConfig(const std::string& req, const std::string& name){
std::string pc_stdout, pc_stderr;
CommandReturnCode rc = executeCommand_and_save_output({"pkg-config", "--" + req, name}, pc_stdout, pc_stderr);
ASSERT(rc.isOk(), "failed to use pkg-config beacause of:\n" + pc_stderr);
// todo: learn how pkg-config actually stores these options
std::vector<std::string> result;
for (char ch: pc_stdout) {
if (result.empty())
result.emplace_back();
if (ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r') {
if (!result.back().empty())
result.emplace_back();
} else {
result.back() += ch;
}
std::string make_uppercase(const std::string &source) {
std::string result(source);
for (size_t i = 0; i < source.size(); i++) {
char ch = source[i];
if ('a' <= ch && ch <= 'z')
result[i] = (char)(ch - 'a' + 'A');
}
if (!result.empty() && result.back().empty())
result.pop_back();
return result;
}
ExternalLibraryTarget formExternalLibraryTargetWithNativeName(const std::string& name) {
return {name, {getFromPkgConfig("cflags", name), getFromPkgConfig("libs", name)}};
ExternalLibraryTarget formExternalLibraryTargetWithNonNativeName(const std::string& name) {
std::string ev_name = "BSCRIPT_DEP_" + make_uppercase(name);
const char* ev = getenv(ev_name.c_str());
ASSERT(ev, "No environmaent variable " + ev_name);
return {name, parse_passed_forward_str(ev)};
}
struct CAWebChat {
@ -34,10 +25,11 @@ struct CAWebChat {
BuildUnitsArray runlevel_2;
std::string build_type;
bool make_tests = false;
std::vector<std::string> warning_flags = {"-Wall", "-Wno-unused-variable", "-Werror=return-type","-pedantic",
"-Wno-unused-but-set-variable", "-Wno-reorder"};
std::vector<std::string> version_flags = {"--std", "c++14", "-D", "_POSIX_C_SOURCE=200809L"};
std::vector<std::string> version_flags = {"--std", "c++17", "-D", "_POSIX_C_SOURCE=200809L"};
std::vector<std::string> debug_defines_release = {"_GLIBCXX_DEBUG"};
std::vector<std::string> debug_defines_debug = {"_GLIBCXX_DEBUG", "DEBUG_ALLOW_LOUD"};
std::vector<std::string> opt_flags_release = {"-g", "-O2"};
@ -57,15 +49,17 @@ struct CAWebChat {
return my_flag_collection;
}
CAWebChat(std::string _build_type, const NormalCBuildSystemCommandMeaning& cmd)
: build_type(std::move(_build_type))
{
explicit CAWebChat(const NormalCBuildSystemCommandMeaning& cmd){
const char* BSCRIPT_TYPE = getenv("BSCRIPT_TYPE");
const char* BSCRIPT_TESTS = getenv("BSCRIPT_TESTS");
build_type = BSCRIPT_TYPE ? BSCRIPT_TYPE : "release";
make_tests = (bool)BSCRIPT_TESTS;
ASSERT(build_type == "release" || build_type == "debug", "Unknown build type");
std::vector<ExternalLibraryTarget> ext_targets = {
formExternalLibraryTargetWithNativeName("libjsonincpp"),
formExternalLibraryTargetWithNativeName("sqlite3"),
formExternalLibraryTargetWithNativeName("libregexis024"),
formExternalLibraryTargetWithNonNativeName("libjsonincpp"),
formExternalLibraryTargetWithNonNativeName("sqlite3"),
formExternalLibraryTargetWithNonNativeName("libregexis024"),
};
std::vector<CTarget> my_targets;
@ -83,9 +77,13 @@ struct CAWebChat {
"os_utils.cpp",
"http_structures/client_request_parse.cpp",
"http_structures/response_gen.cpp",
"http_structures/cookies.cpp",
"http_structures/accept_language.cpp",
"connecting_assets/static_asset_manager.cpp",
"running_mainloop.cpp",
"form_data_structure/urlencoded_query.cpp",
"socket_address.cpp",
"admin_control.cpp",
};
for (std::string& u: T.units)
u = "http_server/engine_engine_number_9/" + u;
@ -98,25 +96,110 @@ struct CAWebChat {
"os_utils.h",
"connecting_assets/static_asset_manager.h",
"http_structures/client_request.h",
"http_structures/cookies.h",
"http_structures/response_gen.h",
"http_structures/accept_language.h",
"running_mainloop.h",
"form_data_structure/urlencoded_query.h",
"socket_address.h",
"admin_control.h",
};
for (std::string& u: T.exported_headers)
u = "engine_engine_number_9/" + u;
T.installation_dir = "";
T.installation_dir = "een9";
my_targets.push_back(T);
}
{ CTarget T{"iu9-ca-web-chat", "executable"};
{ CTarget T{"new_york_transit_line", "shared_library"};
T.additional_compilation_flags = getSomeRadFlags();
T.proj_deps = {CTargetDependenceOnProjectsLibrary{"engine_engine_number_9"}};
T.external_deps = {CTargetDependenceOnExternalLibrary{"sqlite3"}};
T.units = {"main.cpp"};
T.external_deps = {
CTargetDependenceOnExternalLibrary{"libjsonincpp", {true, true}},
};
T.units = {
"alotalot.cpp",
"html_case.cpp",
"parser.cpp",
"rendering.cpp",
"templater.cpp",
};
for (std::string& u: T.units)
u = "web_chat/" + u;
u = "http_server/new_york_transit_line/" + u;
T.include_pr = "http_server";
T.exported_headers = {
"templater.h",
"html_case.h",
};
for (std::string& u: T.exported_headers)
u = "new_york_transit_line/" + u;
T.installation_dir = "nytl";
my_targets.push_back(T);
}
{ CTarget T{"iu9_ca_web_chat_lib", "shared_library"};
T.additional_compilation_flags = getSomeRadFlags();
T.proj_deps = {
CTargetDependenceOnProjectsLibrary{"engine_engine_number_9", {true, true}},
CTargetDependenceOnProjectsLibrary{"new_york_transit_line", {true, true}},
};
T.external_deps = {
CTargetDependenceOnExternalLibrary{"sqlite3", {true, true}}
};
T.units = {
"localizator.cpp",
"initialize.cpp",
"run.cpp",
"str_fields.cpp",
"find_db.cpp",
"sqlite3_wrapper.cpp",
"login_cookie.cpp",
"backend_logic/server_data_interact.cpp",
"backend_logic/client_server_interact.cpp",
"backend_logic/when_login.cpp",
"backend_logic/when_list_rooms.cpp",
"backend_logic/when_chat.cpp",
"backend_logic/when_user.cpp",
"backend_logic/when_register.cpp",
"backend_logic/polling.cpp",
"backend_logic/api_sendmessage.cpp",
"backend_logic/api_deletemessage.cpp",
"backend_logic/api_addmembertochat.cpp",
"backend_logic/api_removememberfromchat.cpp",
"backend_logic/api_createchat.cpp",
"backend_logic/api_leavechat.cpp",
"backend_logic/admin_control_procedure.cpp",
};
for (std::string& u: T.units)
u = "web_chat/iu9_ca_web_chat_lib/" + u;
T.exported_headers = {
"actions.h"
};
for (std::string& u: T.exported_headers)
u = "iu9_ca_web_chat_lib/" + u;
T.include_pr = "web_chat";
T.installation_dir = "iu9cawebchat";
my_targets.push_back(T);
}
{ CTarget T{"iu9-ca-web-chat-service", "executable"};
T.additional_compilation_flags = getSomeRadFlags();
T.proj_deps = {
CTargetDependenceOnProjectsLibrary{"iu9_ca_web_chat_lib"},
};
T.units = {"service.cpp"};
for (std::string& u: T.units)
u = "web_chat/iu9_ca_web_chat_service/" + u;
T.include_pr = "web_chat";
my_targets.push_back(T);
}
{ CTarget T{"iu9-ca-web-chat-admin-cli", "executable"};
T.additional_compilation_flags = getSomeRadFlags();
T.proj_deps = {
CTargetDependenceOnProjectsLibrary{"engine_engine_number_9"},
};
T.units = {
"cli.cpp", // Main file
};
for (std::string& u: T.units)
u = "web_chat/iu9_ca_web_chat_admin_cli/" + u;
T.include_pr = "web_chat";
T.installation_dir = "";
my_targets.push_back(T);
}
regular_ctargets_to_2bus_conversion(ext_targets, my_targets, runlevel_1, runlevel_2,
@ -133,7 +216,7 @@ int main(int argc, char** argv) {
}
NormalCBuildSystemCommandMeaning cmd;
regular_bs_cli_cmd_interpret(args, cmd);
CAWebChat bs("debug", cmd);
CAWebChat bs(cmd);
if (cmd.need_to_build)
complete_tasks_of_build_units(bs.runlevel_1);
umask(~0755);

View File

@ -1,3 +1,24 @@
{
"name": "Web chat"
"lang": {
"whitelist": ["*"],
"force-order": [
"ru"
]
},
"assets": "./assets",
"database": {
"type": "sqlite3",
"file": "./iu9-ca-web-chat.db"
},
"limits": {
"max-users": 100000,
"max-rooms": 100000,
"max-messages": 10000000000000,
"storage-size-limit": 100000000000
},
"server": {
"workers": 16,
"http-listen": ["127.0.0.1:1025"],
"admin-command-listen": ["[::1]:1026"]
}
}

View File

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

View File

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

View File

@ -5,15 +5,9 @@
#include <string.h>
namespace een9 {
ServerError::ServerError(const std::string &err, const std::string &file, const std::string &func, int line): err(err),
FILE(file),
func(func),
LINE(line) {
char buf[4096];
snprintf(buf, 4096, "Error occured in function %s (line %d of %s)\n"
"Error: %s",
func.c_str(), LINE, FILE.c_str(), err.c_str());
WHAT = buf;
ServerError::ServerError(const std::string &err, const std::string &file, const std::string &func, int line){
WHAT = "Error occured in function " + func + " (line " + std::to_string(line) + " of " +
file + ")\nError: " + err;
}
const char * ServerError::what() const noexcept {
@ -34,14 +28,30 @@ namespace een9 {
return false;
}
bool endsIn(const std::string &a, const std::string &b) {
bool endsWith(const std::string &a, const std::string &b) {
if (b.size() > a.size())
return false;
return std::equal(a.end() - (ssize_t)b.size(), a.end(), b.begin());
}
bool beginsWith(const std::string &a, const std::string &b) {
if (b.size() > a.size())
return false;
return std::equal(a.begin(), a.begin() + (ssize_t)b.size(), b.begin());
}
std::string getSubstring(const std::string &str, size_t A, size_t B) {
ASSERT(A <= B && B <= str.size(), "Incorrect substring segment");
return str.substr(A, B - A);
}
std::string make_uppercase(const std::string &source) {
std::string result(source);
for (size_t i = 0; i < source.size(); i++) {
char ch = source[i];
if ('a' <= ch && ch <= 'z')
result[i] = (char)(ch - 'a' + 'A');
}
return result;
}
}

View File

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

View File

@ -23,9 +23,9 @@ namespace een9 {
ASSERT_on_iret(ret, "stat(\"" + cur + "\")");
if (S_ISDIR(info.st_mode)) {
DIR* D = opendir(path_to_cur_ent.c_str());
cur += "/";
ASSERT(D != NULL, prettyprint_errno("opendir(\"" + cur +"\")"));
struct Guard1{ DIR*& D; ~Guard1(){ closedir(D); } } g1{D};
ASSERT(D != NULL, prettyprint_errno("opendir(\"" + cur +"\")"));
cur += "/";
while (true) {
errno = 0;
struct dirent* Dent = readdir(D);
@ -53,7 +53,7 @@ namespace een9 {
std::vector<std::string> c_files = detour_over_regular_folder(dir_rule.directory);
for (const std::string& file: c_files) {
for (const StaticAssetManagerRulePostfixFilter& ext: dir_rule.postfix_rules_type_assign) {
if (endsIn(file, ext.required_postfix)) {
if (endsWith(file, ext.required_postfix)) {
/* Found it! */
StaticAsset etot{ext.assigned_type, };
readFile(dir_rule.directory + "/" + file, etot.content);

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

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

View File

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

View File

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

View File

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

View File

@ -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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,6 @@
{% ELDEF main JSON userprofile %}
AAA
--> {% WRITE userprofile.name %}
AAA
{% 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

@ -0,0 +1,29 @@
#include <jsonincpp/string_representation.h>
#include <new_york_transit_line/templater.h>
#include <new_york_transit_line/core.h>
int main(int argc, char** argv) {
if (argc < 2) {
fprintf(stderr, "Usage: test assets_dir");
exit(1);
}
std::string dir_path = argv[1];
nytl::Templater templater(nytl::TemplaterSettings{nytl::TemplaterDetourRules{dir_path}});
templater.update();
nytl::debug_print_templater(templater);
json::JSON cba;
cba["boba"] = json::JSON("<>");
cba["arr"][0] = json::JSON("zero");
cba["arr"][1] = json::JSON("one");
cba["arr"][2] = json::JSON("two");
cba["k"] = json::JSON("arr");
cba["i"] = json::JSON(1l);
// printf("DEBUG WAS: %p\n", &cba["boba"].g());
// printf("%s\n", json::generate_str(cba["boba"].g(), json::print_compact).c_str());
// return 0;
std::string answer2 = templater.render("test", {&cba});
printf("%s\n<a><f><t><e><r><><l><f>\n", answer2.c_str());
return 0;
}

View File

@ -0,0 +1,28 @@
#include <jsonincpp/string_representation.h>
#include <new_york_transit_line/templater.h>
#include <new_york_transit_line/core.h>
#include <engine_engine_number_9/os_utils.h>
int main(int argc, char** argv) {
if (argc < 3) {
fprintf(stderr, "Usage: test assets_dir config_file");
exit(1);
}
// std::string dir_path = "./src/http_server/misc_tests/HypertextPages";
std::string dir_path = "/home/gregory/cpp_projects/iu9-ca-web-chat/assets/HypertextPages";
nytl::Templater templater(nytl::TemplaterSettings{nytl::TemplaterDetourRules{dir_path}});
templater.update();
json::JSON userprofile;
userprofile["uid"].asInteger() = json::Integer(0l);
userprofile["name"].asString() = "radasdasdasdadsdasd";
userprofile["nickname"].asString() = "root";
userprofile["bio"].asString() = "Your mother";
json::JSON errors;
errors = json::JSON(json::array);
std::string answer2 = templater.render("err-404", {});
printf("%s\n<a><f><t><e><r><><l><f>\n", answer2.c_str());
return 0;
}

View File

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

View File

@ -0,0 +1,95 @@
#include "alotalot.h"
#include <algorithm>
#include <errno.h>
#include <string.h>
#include <vector>
namespace nytl {
FUp::FUp(const std::string &err, const std::string &file, const std::string &func, int line){
WHAT = "Error occured in function " + func + " (line " + std::to_string(line) + " of " +
file + ")\nError: " + err;
}
const char * FUp::what() const noexcept {
return WHAT.c_str();
}
std::string prettyprint_errno(const std::string &pref) {
const char* d = strerrorname_np(errno);
return pref.empty() ? std::string(d) : std::string(pref) + ": " + d;
}
bool endsIn(const std::string &a, const std::string &b) {
if (b.size() > a.size())
return false;
return std::equal(a.end() - (ssize_t)b.size(), a.end(), b.begin());
}
std::string throwout_postfix(const std::string &a, size_t bsz) {
return a.substr(0, a.size() >= bsz ? a.size() - bsz : 0);
}
bool isALPHA(char ch) {
return ('a' <= ch && ch <= 'z') || ('A' <= ch && ch <= 'Z');
}
bool isNUM(char ch) {
return '0' <= ch && ch <= '9';
}
bool isUNCHAR(char ch) {
return isALPHA(ch) || isNUM(ch) || ch == '-' || ch == '_';
}
bool isUNCHARnonNUM(char ch) {
return isALPHA(ch) || ch == '-' || ch == '_';
}
bool isSPACE(char ch) {
return ch == ' ' || ch == '\r' || ch == '\t' || ch == '\n';
}
bool isUname(const std::string &str) {
if (str.empty() || str == "_")
return false;
if (isNUM(str[0]))
return false;
for (char ch: str)
if (!isUNCHAR(ch))
return false;
return true;
}
bool is_uname_dotted_sequence(const std::string& uinp) {
if (uinp.empty())
return false;
std::vector<std::string> r = {""};
for (char ch: uinp) {
if (ch == '.') {
r.emplace_back();
} else {
r.back() += ch;
}
}
for (const std::string& c: r)
if (!isUname(c))
return false;
return true;
}
std::string make_uppercase(const std::string &source) {
std::string result(source);
for (size_t i = 0; i < source.size(); i++) {
char ch = source[i];
if ('a' <= ch && ch <= 'z')
result[i] = (char)(ch - 'a' + 'A');
}
return result;
}
void rstrip(std::string &str) {
while (!str.empty() && isSPACE(str.back()))
str.resize(str.size() - 1);
}
}

View File

@ -0,0 +1,53 @@
#ifndef NEW_YORK_TRANSIT_LINE_ALOTALOT_H
#define NEW_YORK_TRANSIT_LINE_ALOTALOT_H
#include <stdexcept>
#include <memory>
/* A little of this, a little of that
* DO NOT EXPORT THIS FILE */
namespace nytl {
template<typename T>
using uptr = std::unique_ptr<T>;
template<typename Tp>
constexpr std::remove_reference_t<Tp>&&
mv(Tp&& t) noexcept
{ return static_cast<std::remove_reference_t<Tp>&&>(t); }
class FUp : public std::exception{
std::string WHAT;
public:
FUp(const std::string &err, const std::string &file, const std::string &func, int line);
const char *what() const noexcept override;
};
std::string prettyprint_errno(const std::string& pref);
#define THROW(err) throw FUp(err, __FILE__, __func__, __LINE__)
#define THROW_on_errno(err) THROW(prettyprint_errno(err))
#define ASSERT(cond, err) do { if (!(cond)) { THROW(err); } } while (0);
#define ASSERT_pl(cond) ASSERT(cond, "Failed assertion `" #cond "`")
#define ASSERT_on_iret(iret, err) ASSERT((iret) >= 0, prettyprint_errno(err));
bool endsIn(const std::string& a, const std::string& b);
std::string throwout_postfix(const std::string& a, size_t bsz);
bool isALPHA(char ch);
bool isNUM(char ch);
bool isUNCHAR(char ch);
bool isUNCHARnonNUM(char ch);
bool isSPACE(char ch);
bool isUname(const std::string& str);
bool is_uname_dotted_sequence(const std::string& uinp);
std::string make_uppercase(const std::string& source);
void rstrip(std::string& str);
}
#endif

View File

@ -0,0 +1,24 @@
#ifndef NEW_YORK_TRANSIT_LINE_CORE_H
#define NEW_YORK_TRANSIT_LINE_CORE_H
#include "templater.h"
#include <functional>
/* Do not export this header */
namespace nytl {
void debug_print_templater(const Templater& T);
/* ============== For parsing =============================*/
void parse_bare_file(const std::string& filename, const std::string& content,
global_elem_set_t& result);
void parse_special_file(const std::string& filename, const std::string& content,
global_elem_set_t& result, TemplaterSettings& syntax);
/* =================== For rendering ====================*/
std::string rendering_core(const std::string& entry_func, const std::vector<const json::JSON*>& entry_arguments,
const global_elem_set_t& elem_ns, const std::function<std::string(std::string)>& escape);
}
#endif

View File

@ -0,0 +1,54 @@
#include "templater.h"
#include "alotalot.h"
#include "jsonincpp/string_representation.h"
#include <assert.h>
namespace nytl {
void debug_print_templater(const Templater& T) {
printf("===== TEMPLATER INTERNAL RESOURCES =====\n");
for (auto& p: T.elements) {
if (!p.second.is_element) {
printf("=== %s is empty =====\n", p.first.c_str());
continue;
}
printf("=== %s element =====\n", p.first.c_str());
assert(p.second.when_element);
const Element& el = *p.second.when_element;
printf("%s, %s\n", el.base ? "BASE" : "NOT BASE", el.is_hidden ? "HIDDEN" : "NOT HIDDEN");
if (!el.is_hidden) {
std::string signature;
for (const json::JSON& arg_type: el.arguments) {
if (!signature.empty())
signature += " ";
signature += json::generate_str(arg_type, json::print_compact);
}
printf("Signature: %s\n", signature.c_str());
}
for (const ElementPart& part: el.parts) {
if (part.type == ElementPart::p_code) {
printf("code: <b><e><f><o><r><e><><l><f>\n%s\n<a><f><t><e><r><><l><f>\n", part.when_code.lines.c_str());
} else if (part.type == ElementPart::p_for_put) {
const ElementPart::when_for_put_S& P = part.when_for_put;
printf("dor cycle call:\ninternal_element: %s,\nref_over:%s,\nwhere_key_var: %ld, where_value_var: %ld, %s\n",
P.internal_element.c_str(), json::generate_str(P.ref_over, json::print_pretty).c_str(),
P.where_key_var, P.where_value_var, P.line_feed ? "LF" : "NOLF");
} else if (part.type == ElementPart::p_ref_put) {
const ElementPart::when_ref_put_S& P = part.when_ref_put;
printf("ref block call:\ninternal_element: %s\nref_over:%s\n",
P.internal_element.c_str(), json::generate_str(P.ref_over, json::print_pretty).c_str());
} else {
assert(part.type == ElementPart::p_put);
const ElementPart::when_put_S& P = part.when_put;
printf("PUT:\ncalled_element: %s\n",
json::generate_str(P.called_element, json::print_pretty).c_str());
for (size_t i = 0; i < P.passed_arguments.size(); i++) {
printf("passed_arguments[%lu] = %s\n", i,
json::generate_str(P.passed_arguments[i], json::print_pretty).c_str());
}
}
}
printf("=== That was element %s ====\n", p.first.c_str());
}
printf("===== DEBUG IS OVER =====\n");
}
}

View File

@ -0,0 +1,30 @@
#include "html_case.h"
namespace nytl {
std::string html_case_espace_string(const std::string &inp) {
std::string res;
res.reserve(inp.size());
for (char ch: inp) {
switch (ch) {
case '&':
res += "&amp";
break;
case '<':
res += "&lt";
break;
case '>':
res += "&gt";
break;
case '"':
res += "&quot";
break;
case '\'':
res += "&#39";
break;
default:
res += ch;
}
}
return res;
}
}

View File

@ -0,0 +1,10 @@
#ifndef NEW_YORK_TRANSIT_LINE_HTML_CASE_H
#define NEW_YORK_TRANSIT_LINE_HTML_CASE_H
#include <string>
namespace nytl {
std::string html_case_espace_string(const std::string &inp);
}
#endif

View File

@ -0,0 +1,596 @@
#include "core.h"
#include "alotalot.h"
#include <vector>
#include <assert.h>
namespace nytl {
size_t first_nw_char(const std::string& str) {
size_t i = 0;
for (; i < str.size(); i++)
if (!isSPACE(str[i]))
break;
return i;
}
bool is_space_only(const std::string& str) {
return first_nw_char(str) == str.size();
}
std::string clement_lstrip(const std::string& str) {
size_t gone = 0;
size_t n = str.size();
for (size_t i = 0; i < n; i++) {
if (str[i] == '\n') {
gone = i + 1;
} else if (!isSPACE(str[i])) {
break;
}
}
return str.substr(gone);
}
struct ParsingContext {
std::string text;
size_t pos = 0;
size_t column = 0;
size_t line = 0;
};
constexpr int EOFVAL = -999;
int peep(ParsingContext &ctx) {
if (ctx.text.size() <= ctx.pos)
return EOFVAL;
return ctx.text[ctx.pos];
}
char advance(ParsingContext& ctx) {
if (ctx.text[ctx.pos] == '\n') {
ctx.line++;
ctx.column = 0;
} else {
ctx.column++;
}
return ctx.text[ctx.pos++];
}
char skip(ParsingContext& ctx) {
if (ctx.pos >= ctx.text.size())
THROW("Unexpected EOF");
return advance(ctx);
}
void skip(ParsingContext& ctx, char ch) {
if (ctx.pos >= ctx.text.size())
THROW("Unexpected EOF");
if (ctx.text[ctx.pos] != ch)
THROW("Unexpected character");
advance(ctx);
}
void skipWhitespace(ParsingContext &ctx) {
while (peep(ctx) >= 0 && isSPACE((char)peep(ctx)))
skip(ctx);
}
void skipString(ParsingContext &ctx, const std::string &str) {
for (char ch: str)
skip(ctx, ch);
}
std::string readName(ParsingContext &ctx) {
std::string result;
int f = peep(ctx);
if (f >= 0 && isUNCHARnonNUM((char)f)) {
skip(ctx);
result += (char)f;
while (peep(ctx) >= 0 && isUNCHAR((char)peep(ctx)))
result += skip(ctx);
}
return result;
}
std::string readUint(ParsingContext &ctx) {
if (peep(ctx) == '0') {
skip(ctx);
return "0";
}
std::string result;
while (peep(ctx) >= 0 && isNUM((char)peep(ctx)))
result += skip(ctx);
return result;
}
std::vector<std::string> splitIntoLines(const std::string &str) {
std::vector<std::string> result = {""};
for (char ch: str) {
if (ch == '\n')
result.emplace_back();
else
result.back() += ch;
}
return result;
}
std::string concatenateLines(const std::vector<std::string>& lines) {
std::string result;
size_t n = lines.size();
for (size_t i = 0; i < n; i++) {
if (i)
result += '\n';
result += lines[i];
}
return result;
}
bool is_relevant_in_tab_cut(size_t PN, size_t I, size_t LN, size_t j, const std::string& line) {
if (j == 0 && I != 0)
return false;
if (!is_space_only(line))
return true;
return j + 1 == LN && I + 1 < PN;
}
void one_part_update_min_start_wsp_non_empty(const std::string& str, size_t I, size_t PN, size_t& min) {
std::vector<std::string> lines = splitIntoLines(str);
size_t LN = lines.size();
for (size_t j = 0; j < LN; j++) {
if (is_relevant_in_tab_cut(PN, I, LN, j, lines[j]))
min = std::min(min, first_nw_char(lines[j]));
}
}
std::string one_part_cut_excess_tab(const std::string& str, size_t I, size_t PN, size_t cut) {
std::vector<std::string> lines = splitIntoLines(str);
size_t LN = lines.size();
for (size_t j = 0; j < LN; j++) {
if (is_relevant_in_tab_cut(PN, I, LN, j, lines[j]))
lines[j] = lines[j].substr(cut);
}
return concatenateLines(lines);
}
Element& add_hidden_element(const std::string& new_el_name, global_elem_set_t& result) {
if (result.count(new_el_name) != 0)
THROW("Repated element " + new_el_name);
TemplaterRegPref& rp = result[new_el_name];
rp.is_element = 1;
rp.when_element = std::make_unique<Element>();
rp.when_element->is_hidden = true;
return *rp.when_element;
}
Element& add_new_element(const std::string& new_el_name, global_elem_set_t& result) {
if (!is_uname_dotted_sequence(new_el_name))
THROW("Krabovaya oshibka");
if (result.count(new_el_name) != 0 && result.at(new_el_name).is_element)
THROW("Repated element " + new_el_name);
size_t n = new_el_name.size();
for (size_t i = 0; i < n; i++) {
if (new_el_name[i] == '.') {
std::string pref = new_el_name.substr(0, i);
result[pref];
}
}
TemplaterRegPref& rp = result[new_el_name];
rp.is_element = 1;
rp.when_element = std::make_unique<Element>();
return *rp.when_element;
}
void parse_bare_file(const std::string& filename, const std::string& content, global_elem_set_t& result) {
Element& el = add_new_element(filename, result);
std::string txt = clement_lstrip(content);
rstrip(txt);
size_t cut = 9999999999999;
one_part_update_min_start_wsp_non_empty(txt, 0, 1, cut);
txt = one_part_cut_excess_tab(txt, 0, 1, cut);
el.parts = {ElementPart{}};
el.parts[0].when_code.lines = mv(txt);
}
/* Type parsing frame */
struct TPFrame {
json::JSON& result;
explicit TPFrame(json::JSON& result_): result(result_){}
uptr<TPFrame> toMe(bool returned, ParsingContext& ctx) {
if (!returned) {
std::string nm = readName(ctx);
if (nm.empty())
THROW("Type specification expected");
nm = make_uppercase(nm);
if (nm == "JSON") {
result = json::JSON(true);
return NULL;
}
if (nm != "EL")
THROW("Type of argument variable is either JSON or EL(...signature)");
skip(ctx, '(');
result.asArray();
assert(result.isArray());
}
skipWhitespace(ctx);
if (peep(ctx) == ')')
return NULL;
result.asArray().emplace_back();
return std::make_unique<TPFrame>(result.asArray().back());
}
};
json::JSON parse_type(ParsingContext& ctx) {
json::JSON result;
std::vector<uptr<TPFrame>> stack;
stack.push_back(mv(std::make_unique<TPFrame>(result)));
bool returned = false;
while (!stack.empty()) {
uptr<TPFrame> ret = stack.back()->toMe(returned, ctx);
returned = !(bool)ret;
if (ret)
stack.push_back(mv(ret));
else
stack.pop_back();
}
return result;
}
/* From arg name to arg ID */
typedef std::map<std::string, size_t> arg_name_list_t;
/* Expression parsing frame */
struct EPFrame {
json::JSON& result;
explicit EPFrame(json::JSON& result_): result(result_){}
uptr<EPFrame> toMe(bool returned, ParsingContext& ctx, const arg_name_list_t& local_var_names) {
if (!returned) {
std::string first = readName(ctx);
if (first.empty())
THROW("Expression should start with 'root' name of global package or local variable");
if (first == "_")
THROW("Expression root can't be _");
if (local_var_names.count(first) == 1) {
result["V"].asInteger() = json::Integer((int64_t)local_var_names.at(first));
} else {
result["V"].asString() = first;
}
result["C"].asArray();
} else {
skipWhitespace(ctx);
skip(ctx, ']');
}
std::vector<json::JSON>& chain = result["C"].asArray();
while (true) {
if (peep(ctx) == '.') {
skip(ctx, '.');
chain.emplace_back();
std::string t;
t = readName(ctx);
if (!t.empty()) {
chain.back() = json::JSON(t);
continue;
}
t = readUint(ctx);
if (!t.empty()) {
size_t v = std::stoul(t);
if (v >= INT64_MAX)
THROW("Index is too big");
chain.back() = json::JSON((int64_t)v);
continue;
}
THROW("Bad expression after . operator in expression");
} else if (peep(ctx) == '[') {
skip(ctx, '[');
skipWhitespace(ctx);
chain.emplace_back();
return std::make_unique<EPFrame>(chain.back());
} else
return NULL;
}
}
};
json::JSON parse_expression(ParsingContext& ctx, const arg_name_list_t& local_var_names) {
json::JSON result;
std::vector<uptr<EPFrame>> stack;
stack.push_back(mv(std::make_unique<EPFrame>(result)));
bool returned = false;
while (!stack.empty()) {
uptr<EPFrame> ret = stack.back()->toMe(returned, ctx, local_var_names);
returned = !(bool)ret;
if (ret)
stack.push_back(mv(ret));
else
stack.pop_back();
}
return result;
}
std::string read_code_up_to_mag_block_start(ParsingContext& ctx, const TemplaterSettings& syntax) {
size_t begin = ctx.pos;
while (peep(ctx) != EOFVAL && peep(ctx) != syntax.magic_block_start[0]) {
skip(ctx);
}
size_t end = ctx.pos;
return ctx.text.substr(begin, end - begin);
}
void skip_magic_block_start(ParsingContext& ctx, const TemplaterSettings& syntax) {
skipString(ctx, syntax.magic_block_start);
skipWhitespace(ctx);
}
void skip_magic_block_end(ParsingContext& ctx, const TemplaterSettings& syntax) {
skipWhitespace(ctx);
skipString(ctx, syntax.magic_block_end);
}
bool isIt_magic_block_end(ParsingContext& ctx, const TemplaterSettings& syntax) {
return peep(ctx) == syntax.magic_block_end[0];
}
/* Element content parsing frame */
struct ECPFrame {
enum block_type{
gone_for_nothing,
gone_for_for,
gone_for_ref,
};
std::string el_name;
block_type myself;
arg_name_list_t local_var_names;
int& ret_data_int; // Received from the top (and passed down to get for_put LF mode value)
Element& result;
block_type stopped_for = gone_for_nothing;
size_t free_hidden = 0;
ECPFrame(const std::string& el_name, block_type myself, const arg_name_list_t &local_var_names, int &ret_data_int,
Element& result)
: el_name(el_name),
myself(myself),
local_var_names(local_var_names),
ret_data_int(ret_data_int),
result(result) {
}
uptr<ECPFrame> toMe(bool returned, ParsingContext& ctx, const TemplaterSettings& syntax, global_elem_set_t& elem_ns) {
if (returned) {
if (stopped_for == gone_for_for) {
assert(result.parts.back().type == ElementPart::p_for_put);
if (ret_data_int == 1)
result.parts.back().when_for_put.line_feed = false;
else if (ret_data_int == 2)
result.parts.back().when_for_put.line_feed = true;
else
assert(false);
} else
assert(ret_data_int == 0);
}
ret_data_int = 0;
ya_e_ya_h_i_ya_g_d_o:
result.parts.emplace_back();
result.parts.back().when_code.lines = read_code_up_to_mag_block_start(ctx, syntax);
skip_magic_block_start(ctx, syntax);
if (isIt_magic_block_end(ctx, syntax)) {
skip_magic_block_end(ctx, syntax);
goto ya_e_ya_h_i_ya_g_d_o;
}
std::string op = make_uppercase(readName(ctx));
if (op == "FOR") {
result.parts.emplace_back();
result.parts.back().type = ElementPart::p_for_put;
ElementPart::when_for_put_S& P = result.parts.back().when_for_put;
skipWhitespace(ctx);
std::string V1 = readName(ctx);
if (V1.empty())
THROW("Expected variable name");
skipWhitespace(ctx);
bool have_colon_and_2 = false;
std::string V2;
if (peep(ctx) == ':') {
have_colon_and_2 = true;
skip(ctx, ':');
skipWhitespace(ctx);
V2 = readName(ctx);
skipWhitespace(ctx);
}
op = make_uppercase(readName(ctx));
if (op != "IN")
THROW("Expected IN");
skipWhitespace(ctx);
P.ref_over = parse_expression(ctx, local_var_names);
P.internal_element = el_name + ".~" + std::to_string(free_hidden++);
Element& newborn = add_hidden_element(P.internal_element, elem_ns);
arg_name_list_t local_var_names_of_nxt = local_var_names;
if (V1 != "_") {
if (local_var_names_of_nxt.count(V1) != 0)
THROW("Repeated local variable");
size_t k = local_var_names_of_nxt.size();
local_var_names_of_nxt.emplace(V1, k);
(have_colon_and_2 ? P.where_key_var : P.where_value_var) = (ssize_t)k;
}
if (have_colon_and_2 && V2 != "_") {
if (local_var_names_of_nxt.count(V2) != 0)
THROW("Repeated local variable");
size_t k = local_var_names_of_nxt.size();
local_var_names_of_nxt.emplace(V2, k);
P.where_value_var = (ssize_t)k;
}
skip_magic_block_end(ctx, syntax);
/* Yep, I am passing this int random data reference, that was actually given to me, I CAN DO THAT TRUST ME */
stopped_for = gone_for_for;
return std::make_unique<ECPFrame>(P.internal_element, gone_for_for, local_var_names_of_nxt,
ret_data_int, newborn);
}
if (op == "REF") {
result.parts.emplace_back();
result.parts.back().type = ElementPart::p_ref_put;
ElementPart::when_ref_put_S& P = result.parts.back().when_ref_put;
skipWhitespace(ctx);
std::string Vn = readName(ctx);
if (Vn.empty() || Vn == "_")
THROW("REF: expected variable name");
skipWhitespace(ctx);
op = make_uppercase(readName(ctx));
if (op != "AS")
THROW("Expected AS");
skipWhitespace(ctx);
P.ref_over = parse_expression(ctx, local_var_names);
P.internal_element = el_name + ".~" + std::to_string(free_hidden++);
Element& newborn = add_hidden_element(P.internal_element, elem_ns);
arg_name_list_t local_var_names_of_nxt = local_var_names;
size_t k = local_var_names_of_nxt.size();
local_var_names_of_nxt.emplace(Vn, k);
skip_magic_block_end(ctx, syntax);
stopped_for = gone_for_ref;
return std::make_unique<ECPFrame>(P.internal_element, gone_for_ref, local_var_names_of_nxt,
ret_data_int, newborn);
}
if (op == "PUT" || op == "P") {
result.parts.emplace_back();
result.parts.back().type = ElementPart::p_put;
ElementPart::when_put_S& P = result.parts.back().when_put;
skipWhitespace(ctx);
P.called_element = parse_expression(ctx, local_var_names);
while (true) {
skipWhitespace(ctx);
if (isIt_magic_block_end(ctx, syntax)) {
skip_magic_block_end(ctx, syntax);
break;
}
P.passed_arguments.push_back(parse_expression(ctx, local_var_names));
}
goto ya_e_ya_h_i_ya_g_d_o;
}
auto mediocre_operator = [&](const std::string& base_el) -> void {
result.parts.emplace_back();
result.parts.back().type = ElementPart::p_put;
ElementPart::when_put_S& P = result.parts.back().when_put;
P.called_element["V"] = json::JSON(base_el);
P.called_element["C"] = json::JSON(json::array);
skipWhitespace(ctx);
P.passed_arguments = {parse_expression(ctx, local_var_names)};
skip_magic_block_end(ctx, syntax);
};
if (op == "WRITE" || op == "W") {
mediocre_operator("str2text");
goto ya_e_ya_h_i_ya_g_d_o;;
}
if (op == "ROUGHINSERT" || op == "RI") {
mediocre_operator("str2code");
goto ya_e_ya_h_i_ya_g_d_o;;
}
auto prepare_to_depart_parts = [&]() {
assert(!result.parts.empty());
if (result.parts[0].type == ElementPart::p_code)
result.parts[0].when_code.lines = clement_lstrip(result.parts[0].when_code.lines);
if (result.parts.back().type == ElementPart::p_code)
rstrip(result.parts.back().when_code.lines);
size_t cut = 999999999999;
size_t N = result.parts.size();
for (size_t i = 0; i < N; i++) {
if (result.parts[i].type == ElementPart::p_code) {
one_part_update_min_start_wsp_non_empty(result.parts[i].when_code.lines, i, N, cut);
}
}
for (size_t i = 0; i < N; i++) {
if (result.parts[i].type == ElementPart::p_code) {
result.parts[i].when_code.lines = one_part_cut_excess_tab(result.parts[i].when_code.lines, i, N, cut);
}
}
};
if (op == "ENDELDEF") {
if (myself != gone_for_nothing)
THROW("Unexpected ENDELDEF");
skip_magic_block_end(ctx, syntax);
prepare_to_depart_parts();
return NULL;
}
if (op == "ENDFOR") {
if (myself != gone_for_for)
THROW("Unexpected ENDFOR");
skipWhitespace(ctx);
/* Here I am using ret_data_int to return info about NOLF(1)/LF(2) decision */
ret_data_int = 2; // Default is to do LF
if (!isIt_magic_block_end(ctx, syntax)) {
op = make_uppercase(readName(ctx));
if (op == "LF") {
ret_data_int = 2;
} else if (op == "NOLF") {
ret_data_int = 1;
} else
THROW("Expected LF, NOLF or end of magic block");
}
skip_magic_block_end(ctx, syntax);
prepare_to_depart_parts();
return NULL;
}
if (op == "ENDREF") {
if (myself != gone_for_ref)
THROW("Unexpected ENDREF");
skip_magic_block_end(ctx, syntax);
prepare_to_depart_parts();
return NULL;
}
THROW("Unknown operator. Expected FOR, REF, PUT, WRITE, ROUGHINSERT, ENDELDEF, ENDFOR, ENDREF");
}
};
void parse_element_content(const std::string& el_name, ParsingContext& ctx, const TemplaterSettings& syntax,
const arg_name_list_t& local_var_names, Element& result, global_elem_set_t& elem_ns) {
int random_junk; // Used onlt for for_put blocks
std::vector<uptr<ECPFrame>> stack;
stack.push_back(mv(std::make_unique<ECPFrame>(el_name, ECPFrame::gone_for_nothing, local_var_names, random_junk, result)));
bool returned = false;
while (!stack.empty()) {
uptr<ECPFrame> ret = stack.back()->toMe(returned, ctx, syntax, elem_ns);
returned = !(bool)ret;
if (ret)
stack.push_back(mv(ret));
else
stack.pop_back();
}
}
void parse_special_file(const std::string& filename, const std::string& content,
global_elem_set_t& result, TemplaterSettings& syntax)
{
ParsingContext ctx{content};
while(true) {
skipWhitespace(ctx);
if (peep(ctx) == EOFVAL)
break;
skip_magic_block_start(ctx, syntax);
if (make_uppercase(readName(ctx)) != "ELDEF")
THROW("Expected ELDEF");
skipWhitespace(ctx);
std::string elname_postfix = readName(ctx);
if (elname_postfix == "_")
THROW("Can't use _ as element name");
std::string fullname = elname_postfix == "main" ? filename : filename + "." + elname_postfix;
Element& newborn = add_new_element(fullname, result);
arg_name_list_t arglist;
while (true) {
skipWhitespace(ctx);
if (isIt_magic_block_end(ctx, syntax))
break;
newborn.arguments.push_back(parse_type(ctx));
skipWhitespace(ctx);
std::string argname = readName(ctx);
if (argname.empty())
THROW("Expected argument name");
if (argname != "_") {
if (arglist.count(argname) != 0)
THROW("Repeated argument (" + argname + ")");
size_t k = arglist.size();
arglist[argname] = k;
}
}
skip_magic_block_end(ctx, syntax);
parse_element_content(fullname, ctx, syntax, arglist, newborn, result);
}
}
}

View File

@ -0,0 +1,369 @@
#include "core.h"
#include "alotalot.h"
#include <string.h>
#include <jsonincpp/string_representation.h>
#include <assert.h>
namespace nytl {
struct LocalVarValue {
bool is_json = false;
std::string EL_name;
const json::JSON* JSON_subval = NULL;
};
/* Expression Execution Frame */
struct EEFrame {
const json::JSON& expr;
LocalVarValue& result;
LocalVarValue temp_ret;
size_t chain_el = 0;
EEFrame(const json::JSON &expr, LocalVarValue &result)
: expr(expr),
result(result) {
}
void descend(const json::JSON& what, const global_elem_set_t& global_elems) {
if (result.is_json) {
const json::JSON& P = *result.JSON_subval;
if (P.isArray() && what.isInteger()) {
const std::vector<json::JSON>& arr_p = P.asArray();
int64_t ind_w = what.asInteger().get_int();
if (!(ind_w > 0 && ind_w < arr_p.size()))
THROW("Expression \"array[integer]\" caused out-of-bound situation");
result = LocalVarValue{true, "", &arr_p[ind_w]};
} else if (P.isDictionary() && what.isString()) {
const std::map<std::string, json::JSON>& dict_p = P.asDictionary();
const std::string& key_w = what.asString();
if (dict_p.count(key_w) != 1)
THROW("No such key exception (" + key_w + ")");
result = LocalVarValue{true, "", &dict_p.at(key_w)};
} else
THROW("Incorrect type of \"json[json]\" expression. Unallowed signature of [] operator");
} else {
if (!what.isString())
THROW("Expression \"element[X]\" allowed only if X is string (json object)");
if (!isUname(what.asString()))
THROW("Expression \"element[str]\" has incorrect str (" + what.asString() + ")");
result.EL_name += ("." + what.asString());
if (global_elems.count(result.EL_name) != 1)
THROW("Can't descend. No such element (" + result.EL_name + ")");
}
}
uptr<EEFrame> toMe(bool returned, const global_elem_set_t& global_elems,
const std::vector<LocalVarValue>& local_vars) {
if (returned) {
if (!temp_ret.is_json)
THROW("Expression \"X[ element ]\" is not allowed");
assert(temp_ret.JSON_subval);
descend(*(temp_ret.JSON_subval), global_elems);
} else {
assert(expr.isDictionary());
const json::JSON& val = expr["V"];
if (val.isInteger()) {
size_t lv_ind = val.asInteger().get_int();
assert(lv_ind < local_vars.size());
result = local_vars[lv_ind];
} else if (val.isString()) {
std::string cur_el_name_str = expr["V"].asString();
if (global_elems.count(cur_el_name_str) != 1)
THROW("Bad expression, no such element (" + cur_el_name_str + ")");
result = LocalVarValue{false, cur_el_name_str, NULL};
} else
assert(false);
}
const std::vector<json::JSON>& chain = expr["C"].asArray();
while (true) {
if (chain_el >= chain.size())
return NULL;
const json::JSON& t = chain[chain_el++];
if (t.isDictionary())
return std::make_unique<EEFrame>(t, temp_ret);
descend(t, global_elems);
}
}
};
/* No new JSON object will ever be created, I have N root json arguments,
* all the other json variables are subtrees of them. With one exception: Key iterators for arrays
* and dictionaries. They are stored in json in rendering stack */
LocalVarValue rendering_core_execute_expression(const global_elem_set_t& global_elems,
const std::vector<LocalVarValue>& local_vars, const json::JSON& expr) {
// todo: check if root element exists (if root value is not local variable, then it is element of package)
bool returned = false;
std::vector<uptr<EEFrame>> stack;
LocalVarValue result;
stack.push_back(std::make_unique<EEFrame>(expr, result));
while (!stack.empty()) {
EEFrame& cur = *stack.back();
uptr<EEFrame> todo = cur.toMe(returned, global_elems, local_vars);
returned = !(bool)todo;
if (todo)
stack.push_back(mv(todo));
else
stack.pop_back();
}
return result;
}
struct Ditch {
std::string result;
size_t cur_line_width = 0;
/* Fix idea: get rid of newlined_somewhere */
void append(const std::string& text, size_t wsp_before_newlines, bool& newlined_somewhere) {
size_t n = result.size();
size_t m = text.size();
result.reserve(n + m);
for (size_t i = 0; i < m; i++) {
result += text[i];
if (text[i] == '\n') {
result.resize(result.size() + wsp_before_newlines, ' ');
cur_line_width = wsp_before_newlines;
} else {
cur_line_width++;
}
}
}
};
#define RFrame_passed const global_elem_set_t& elem_ns, Ditch& result, const std::function<std::string(std::string)>& escape
/* Rendering Frame */
struct RFrame {
size_t wsp_before_newlines = 0;
bool newlined_somewhere = false;
void append(const std::string& text, Ditch& ditch) {
ditch.append(text, wsp_before_newlines, newlined_somewhere);
}
explicit RFrame(size_t multiline_put_start)
: wsp_before_newlines(multiline_put_start) {
}
virtual uptr<RFrame> toMe(bool returned, RFrame_passed) {assert(false);}
virtual ~RFrame() = default;
};
struct RFrame_OverParts : public RFrame{
std::string name;
std::vector<LocalVarValue> passed_args;
/* This parameter incapsulates `cur_line_width` at some point for multiline `put-parts` */
/* main iterator of this frame. Persistent across control returns */
size_t part_to_do = 0;
RFrame_OverParts(const std::string &name, const std::vector<LocalVarValue>& passed_args,
size_t multiline_put_start)
: RFrame(multiline_put_start), name(name),
passed_args(passed_args) {
}
uptr<RFrame> toMe(bool returned, RFrame_passed) override;
};
struct RFrame_OverJSON: public RFrame {
const ElementPart::when_for_put_S& part;
/* During the course of iteration execution, given arg list will expand and shrink back */
std::vector<LocalVarValue> saved_args_plus_iter;
RFrame_OverJSON(const ElementPart::when_for_put_S &part, size_t multiline_put_start,
const std::vector<LocalVarValue> &saved_args)
: RFrame(multiline_put_start), part(part),
saved_args_plus_iter(saved_args) {
if (part.where_key_var > -1)
this->saved_args_plus_iter.emplace_back();
if (part.where_value_var > -1)
this->saved_args_plus_iter.emplace_back();
}
};
struct RFrame_OverArray : public RFrame_OverJSON {
const std::vector<json::JSON>& arr;
size_t it = 0;
/* Crutch. I can't pass simple integer as nytl local variable, I need persistent json wrapper */
json::JSON additional_json_wrapper;
RFrame_OverArray(const ElementPart::when_for_put_S& part, size_t multiline_put_start, const std::vector<LocalVarValue> &saved_args,
const std::vector<json::JSON> &arr): RFrame_OverJSON(part, multiline_put_start, saved_args),
arr(arr) {
if (part.where_key_var >= 0)
additional_json_wrapper = json::JSON(json::Integer(0l));
}
uptr<RFrame> toMe(bool returned, const global_elem_set_t &elem_ns, Ditch &result,
const std::function<std::string(std::string)> &escape) override;
};
struct RFrame_OverDictionary: public RFrame_OverJSON {
const std::map<std::string, json::JSON>& dict;
std::map<std::string, json::JSON>::const_iterator it;
/* Crutch */
json::JSON addition_json_wrapper;
RFrame_OverDictionary(const ElementPart::when_for_put_S& part, size_t multiline_put_start, const std::vector<LocalVarValue> &saved_args_plus_iter,
const std::map<std::string, json::JSON> &dict): RFrame_OverJSON(part, multiline_put_start, saved_args_plus_iter),
dict(dict) {
it = dict.begin();
if (part.where_key_var >= 0)
addition_json_wrapper = json::JSON("");
}
uptr<RFrame> toMe(bool returned, const global_elem_set_t &elem_ns, Ditch &result,
const std::function<std::string(std::string)> &escape) override;
};
/* Rendering Frame */
uptr<RFrame> RFrame_OverParts::toMe(bool returned, const global_elem_set_t &elem_ns, Ditch &result,
const std::function<std::string(std::string)> &escape) {
if (!returned)
if ((elem_ns.count(name) != 1) || (!elem_ns.at(name).is_element))
THROW("Can't render. No such element (" + name + ")");
const Element& el = *elem_ns.at(name).when_element;
if (!returned) {
/* Continue to do checks */
/* hidden elements (internal) do not need any check */
if (!el.is_hidden) {
size_t n = el.arguments.size();
ASSERT(n == passed_args.size(), "Argument count mismatch");
for (size_t i = 0; i < n; i++) {
if (el.arguments[i].type == json::true_symbol) {
if (!passed_args[i].is_json)
THROW("Expected json element argument, got element");
} else {
// If not json is expected, element must be expected
assert(el.arguments[i].isArray());
if (passed_args[i].is_json)
THROW("Expected element element arguemnt, got json");
const std::string& passed_el_as_arg = passed_args[i].EL_name;
if ((elem_ns.count(passed_el_as_arg) != 1) || !elem_ns.at(passed_el_as_arg).is_element)
THROW("No such element, can't compare signatures of argument value (" + passed_el_as_arg + ")");
const Element& arg_element = elem_ns.at(passed_el_as_arg).when_element.operator*();
if(el.arguments[i].asArray() != arg_element.arguments)
THROW("Signature of argument " + std::to_string(i) + " does not match");
}
}
}
}
if (el.base) {
assert(!returned);
assert(passed_args.size() == 1);
const json::JSON* X = passed_args[0].JSON_subval;
assert(X);
if (name == "jsinsert") {
std::string pure_json = json::generate_str(*X, json::print_pretty);
rstrip(pure_json);
append(pure_json, result);
} else if (name == "jesc") {
std::string escaped_json = escape(json::generate_str(*X, json::print_pretty));
rstrip(escaped_json);
append(escaped_json, result);
} else if (name == "jesccomp") {
append(escape(json::generate_str(*X, json::print_compact)), result);
} else if (name == "str2text") {
ASSERT(X->isString(), "str2text takes json string");
append(escape(X->asString()), result);
} else if (name == "str2code") {
ASSERT(X->isString(), "str2code takes json string");
append(X->asString(), result);
}
return NULL;
}
while (true) {
if (part_to_do == el.parts.size())
return NULL;
const ElementPart& cur_part = el.parts[part_to_do++];
if (cur_part.type == ElementPart::p_code) {
const ElementPart::when_code_S& pt = cur_part.when_code;
append(pt.lines, result);
} else if (cur_part.type == ElementPart::p_put) {
const ElementPart::when_put_S& pt = cur_part.when_put;
LocalVarValue called_element_expv = rendering_core_execute_expression(elem_ns, passed_args, pt.called_element);
ASSERT(!called_element_expv.is_json, "Can't PUT json variable");
size_t AN = pt.passed_arguments.size();
std::vector<LocalVarValue> passed_arguments_expv(AN);
for (size_t i = 0; i < AN; i++)
passed_arguments_expv[i] = rendering_core_execute_expression(elem_ns, passed_args, pt.passed_arguments[i]);
return std::make_unique<RFrame_OverParts>(called_element_expv.EL_name, passed_arguments_expv,
result.cur_line_width);
} else if (cur_part.type == ElementPart::p_for_put) {
const ElementPart::when_for_put_S& pt = cur_part.when_for_put;
LocalVarValue iting_over = rendering_core_execute_expression(elem_ns, passed_args, pt.ref_over);
ASSERT(iting_over.is_json, "Can't iterate over element");
const json::JSON& container = *iting_over.JSON_subval;
if (container.isArray()) {
return std::make_unique<RFrame_OverArray>(pt, result.cur_line_width, passed_args, container.asArray());
} else if (container.isDictionary()) {
return std::make_unique<RFrame_OverDictionary>(pt, result.cur_line_width, passed_args, container.asDictionary());
} else
THROW("Can't iterate over non-natalistic jsobject");
} else if (cur_part.type == ElementPart::p_ref_put) {
const ElementPart::when_ref_put_S& pt = cur_part.when_ref_put;
std::vector<LocalVarValue> more_variables(passed_args.size() + 1);
std::copy(passed_args.begin(), passed_args.end(), more_variables.begin());
more_variables.back() = rendering_core_execute_expression(elem_ns, passed_args, pt.ref_over);
return std::make_unique<RFrame_OverParts>(pt.internal_element, more_variables, result.cur_line_width);
}
}
}
uptr<RFrame> RFrame_OverArray::toMe(bool returned, RFrame_passed) {
if (it >= arr.size())
return NULL;
if (returned && part.line_feed)
append("\n", result);
if (part.where_key_var > -1) {
additional_json_wrapper.asInteger() = json::Integer((int64_t)it);
saved_args_plus_iter[part.where_key_var] = {true, "", &additional_json_wrapper};
}
if (part.where_value_var > -1) {
saved_args_plus_iter[part.where_value_var] = {true, "", &(arr[it])};
}
it++;
return std::make_unique<RFrame_OverParts>(part.internal_element, saved_args_plus_iter, wsp_before_newlines);
}
uptr<RFrame> RFrame_OverDictionary::toMe(bool returned, RFrame_passed) {
if (it == dict.end())
return NULL;
if (returned && part.line_feed)
append("\n", result);
if (part.where_key_var > -1) {
addition_json_wrapper.asString() = it->first;
saved_args_plus_iter[part.where_key_var] = {true, "", &addition_json_wrapper};
}
if (part.where_value_var > -1) {
saved_args_plus_iter[part.where_value_var] = {true, "", &it->second};
}
++it;
return std::make_unique<RFrame_OverParts>(part.internal_element, saved_args_plus_iter, wsp_before_newlines);
}
std::string rendering_core(const std::string& entry_func, const std::vector<const json::JSON*>& entry_arguments,
const global_elem_set_t& elem_ns, const std::function<std::string(std::string)>& escape)
{
Ditch result;
std::vector<uptr<RFrame>> stack;
{
size_t AN = entry_arguments.size();
std::vector<LocalVarValue> entry_arguments_conv(AN);
for (size_t i = 0; i < AN; i++)
entry_arguments_conv[i] = {true, "", entry_arguments[i]};
stack.push_back(std::make_unique<RFrame_OverParts>(entry_func, entry_arguments_conv, 0));
}
bool returned = false;
while (!stack.empty()) {
uptr<RFrame> ret = stack.back()->toMe(returned, elem_ns, result, escape);
returned = !(bool)ret;
if (ret)
stack.push_back(mv(ret));
else
stack.pop_back();
}
assert(returned);
return result.result;
}
}

View File

@ -0,0 +1,140 @@
#include "templater.h"
#include <sys/stat.h>
#include <dirent.h>
#include "alotalot.h"
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include "core.h"
namespace nytl {
/* Throws std::runtime_exception on incorrect settings */
void check_settings(const TemplaterSettings& settings) {
#define lmao_serving_kid_with_identity_issues throw std::runtime_error("What was wrong with {% %} ????")
if (settings.magic_block_start.empty() || settings.magic_block_end.empty())
lmao_serving_kid_with_identity_issues;
char incode = settings.magic_block_start[0];
if (isSPACE(incode) || isALPHA(incode) || isNUM(incode))
lmao_serving_kid_with_identity_issues;
char ender = settings.magic_block_end[0];
if (isUNCHAR(ender) || ender == ':' || isSPACE(ender) || ender == '[' || ender == ']' || ender == '.')
lmao_serving_kid_with_identity_issues;
}
Templater::Templater(TemplaterSettings settings): settings(std::move(settings)) {
check_settings(this->settings);
}
struct InterestingFile {
std::string path;
std::string dot_name;
bool special_syntax_applied;
};
std::vector<InterestingFile> indexing_detour(const TemplaterDetourRules& rules) {
std::vector<InterestingFile> result;
int ret;
std::vector<std::string> todo;
todo.emplace_back();
while (!todo.empty()) {
std::string cur = mv(todo.back());
todo.pop_back();
std::string path_to_cur_dir = rules.root_dir_path + "/" + cur;
DIR* D = opendir(path_to_cur_dir.c_str());
struct Guard1{ DIR*& D; ~Guard1(){ closedir(D); } } g1{D};
ASSERT(D != NULL, prettyprint_errno("opendir(\"" + cur +"\")"));
while (true) {
errno = 0;
struct dirent* Dent = readdir(D);
if (Dent == NULL) {
if (errno == 0)
break;
THROW_on_errno("dirent in \"" + cur + "\"");
}
std::string child_entry = Dent->d_name;
if (child_entry == "." || child_entry == "..")
continue;
std::string path_to_cur_child = path_to_cur_dir + "/" + child_entry;
struct stat info;
ret = stat(path_to_cur_child.c_str(), &info);
ASSERT_on_iret(ret, "stat(" + path_to_cur_child + ")");
if (S_ISDIR(info.st_mode)) {
if (isUname(child_entry))
todo.push_back(cur.empty() ? child_entry : cur + "/" + child_entry);
} else if (S_ISREG(info.st_mode)) {
auto replace_sep = [](const std::string& slashed) -> std::string {
std::string dotted;
dotted.reserve(slashed.size());
for (char ch: slashed) {
if (ch == '/')
dotted += '.';
else
dotted += ch;
}
return dotted;
};
auto np_reg_categ_result = [&](const std::string& no_postfix, bool applied) {
if (isUname(no_postfix))
result.push_back({path_to_cur_child, replace_sep(cur.empty() ? no_postfix : cur + "/" + no_postfix), applied});
};
if (endsIn(child_entry, rules.postfix_rule_for_element_cont)) {
np_reg_categ_result(throwout_postfix(child_entry, rules.postfix_rule_for_element_cont.size()), true);
} else if (endsIn(child_entry, rules.postfix_rule_for_static_files)) {
np_reg_categ_result(throwout_postfix(child_entry, rules.postfix_rule_for_static_files.size()), false);
}
} else {
THROW("unknown fs entry type \"" + cur + "\"");
}
}
}
return result;
}
std::string readFile(const std::string& path) {
std::string result;
int ret;
int fd = open(path.c_str(), O_RDONLY);
ASSERT_on_iret(fd, "Opening \"" + path + "\"");
char buf[2048];
while ((ret = (int)read(fd, buf, 2048)) > 0) {
size_t oldN = result.size();
result.resize(oldN + ret);
memcpy(result.data() + oldN, buf, ret);
}
if (ret < 0) {
close(fd);
THROW("reading file");
}
return result;
}
TemplaterRegPref gen_base_element() {
Element* e = new Element{{json::JSON(true)}, true, false, {}};
return {1, std::unique_ptr<Element>(e)};
}
void Templater::update() {
elements[""] = TemplaterRegPref{0, NULL};
elements["jsinsert"] = gen_base_element();
elements["jesc"] = gen_base_element();
elements["str2text"] = gen_base_element();
elements["str2code"] = gen_base_element();
std::vector<InterestingFile> intersting_files = indexing_detour(settings.det);
for (const InterestingFile& file: intersting_files) {
std::string content = readFile(file.path);
if (file.special_syntax_applied) {
parse_special_file(file.dot_name, content, elements, settings);
} else {
parse_bare_file(file.dot_name, content, elements);
}
}
}
/* Still can throw some stuff derived from std::exception (like bad alloc) */
std::string Templater::render(const std::string& element, const std::vector<const json::JSON*> &arguments) const {
if (!is_uname_dotted_sequence(element))
THROW("Incorrect entry element name");
return rendering_core(element, arguments, elements, settings.escape);
}
}

View File

@ -0,0 +1,88 @@
#ifndef NEW_YORK_TRANSIT_LINE_TEMPLATER_H
#define NEW_YORK_TRANSIT_LINE_TEMPLATER_H
#include <vector>
#include <string>
#include <jsonincpp/jsonobj.h>
#include <functional>
#include "html_case.h"
#include <memory>
#include <forward_list>
namespace nytl {
typedef json::JSON expression_t;
struct ElementPart {
/* Used with all types */
enum element_part_type_E {
p_code,
/* write statements really mean PUT str2text X */
p_put,
p_for_put,
p_ref_put
} type = p_code;
struct when_code_S {
std::string lines;
} when_code;
struct when_put_S {
expression_t called_element;
std::vector<expression_t> passed_arguments;
} when_put;
struct when_for_put_S {
expression_t ref_over;
ssize_t where_key_var = -1;
ssize_t where_value_var = -1;
std::string internal_element;
bool line_feed = true;
} when_for_put;
struct when_ref_put_S {
expression_t ref_over;
std::string internal_element;
} when_ref_put;
};
struct Element {
/* Stores signature of element */
std::vector<json::JSON> arguments;
/* `base` is true for builtin elements (jesc str2code str2text). Parts for such ' are empty */
bool base = false;
bool is_hidden = false;
std::vector<ElementPart> parts;
};
struct TemplaterDetourRules {
std::string root_dir_path;
std::string postfix_rule_for_element_cont = ".nytl.html";
std::string postfix_rule_for_static_files = ".html";
};
struct TemplaterSettings {
TemplaterDetourRules det;
std::string magic_block_start = "{%";
std::string magic_block_end = "%}";
std::function<std::string(std::string)> escape = html_case_espace_string;
};
struct TemplaterRegPref {
int is_element = 0;
std::unique_ptr<Element> when_element = NULL;
};
typedef std::map<std::string, TemplaterRegPref> global_elem_set_t;
struct Templater {
TemplaterSettings settings;
global_elem_set_t elements;
explicit Templater(TemplaterSettings settings);
/* Throws exception, derived from std::exception */
void update();
/* Throws exception, derived from std::exception */
std::string render(const std::string& element, const std::vector<const json::JSON*>& arguments) const;
};
}
#endif

View File

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

View File

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

View File

@ -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

@ -0,0 +1,91 @@
#include "client_server_interact.h"
#include <engine_engine_number_9/http_structures/cookies.h>
#include <functional>
namespace iu9cawebchat {
void initial_extraction_of_all_the_useful_info_from_cookies(
SqliteConnection& conn, const een9::ClientRequest& req,
std::vector<std::pair<std::string, std::string>> &ret_cookies,
std::vector<LoginCookie> &ret_login_cookies,
json::JSON &ret_userinfo,
int64_t& ret_logged_in_user
) {
ret_cookies = een9::findAllClientCookies(req.headers);
ret_login_cookies = select_login_cookies(ret_cookies);
ret_logged_in_user = -1; /* Negative means that user is not logged in */
if (!ret_login_cookies.empty()){
size_t oldest_ind = select_oldest_login_cookie(ret_login_cookies);
LoginCookie& tried = ret_login_cookies[oldest_ind];
ret_logged_in_user = find_user_by_credentials(conn, tried.nickname, tried.password);
if (ret_logged_in_user >= 0) {
ret_userinfo["uid"] = json::JSON(ret_logged_in_user);
ret_userinfo["nickname"] = json::JSON(tried.nickname);
ret_userinfo["name"] = json::JSON(get_user_name(conn, ret_logged_in_user));
}
}
}
std::string http_R200(const std::string &el_name, WorkerGuestData &wgd,
const std::vector<const json::JSON *> &args) {
std::string page = wgd.templater->render(el_name, args);
return een9::form_http_server_response_200("text/html", page);
}
json::JSON jsonify_html_message_list(const std::vector<HtmlMsgBox>& messages) {
json::JSON jmessages(json::array);
for (size_t i = 0; i < messages.size(); i++) {
jmessages[i]["class"].asString() = messages[i].class_;
jmessages[i]["text"].asString() = messages[i].text;
}
return jmessages;
}
std::string page_E404(WorkerGuestData &wgd) {
return een9::form_http_server_response_404("text/html",
wgd.templater->render("err-404", {}));
}
/* ========================= API =========================*/
std::string when_internalapi(WorkerGuestData& wgd, const een9::ClientRequest& req, int64_t uid,
const std::function<json::JSON(SqliteConnection&, int64_t, const json::JSON&)>& F) {
const json::JSON& Sent = json::parse_str_flawless(req.body);
std::string result = json::generate_str(F(*wgd.db, uid, Sent), json::print_pretty);
return een9::form_http_server_response_200("text/json", result);
}
std::string when_internalapi_chatpollevents(WorkerGuestData& wgd, const een9::ClientRequest& req, int64_t uid) {
return when_internalapi(wgd, req, uid, internalapi_chatPollEvents);
}
std::string when_internalapi_chatlistpollevents(WorkerGuestData &wgd, const een9::ClientRequest &req, int64_t uid) {
return when_internalapi(wgd, req, uid, internalapi_chatListPollEvents);
}
std::string when_internalapi_getmessageneighbours(WorkerGuestData &wgd, const een9::ClientRequest &req, int64_t uid) {
return when_internalapi(wgd, req, uid, internalapi_getMessageNeighbours);
}
std::string when_internalapi_sendmessage(WorkerGuestData &wgd, const een9::ClientRequest &req, int64_t uid) {
return when_internalapi(wgd, req, uid, internalapi_sendMessage);
}
std::string when_internalapi_deletemessage(WorkerGuestData &wgd, const een9::ClientRequest &req, int64_t uid) {
return when_internalapi(wgd, req, uid, internalapi_deleteMessage);
}
std::string when_internalapi_addmembertochat(WorkerGuestData &wgd, const een9::ClientRequest &req, int64_t uid) {
return when_internalapi(wgd, req, uid, internalapi_addMemberToChat);
}
std::string when_internalapi_removememberfromchat(WorkerGuestData &wgd, const een9::ClientRequest &req, int64_t uid) {
return when_internalapi(wgd, req, uid, internalapi_removeMemberFromChat);
}
std::string when_internalapi_createchat(WorkerGuestData &wgd, const een9::ClientRequest &req, int64_t uid) {
return when_internalapi(wgd, req, uid, internalapi_createChat);
}
std::string when_internalapi_leavechat(WorkerGuestData &wgd, const een9::ClientRequest &req, int64_t uid) {
return when_internalapi(wgd, req, uid, internalapi_leaveChat);
}
}

View File

@ -0,0 +1,81 @@
#ifndef IU9_CA_WEB_CHAT_LIB_BACKEND_LOGIC_SERVER_DATA_INTERACT_PAGES_H
#define IU9_CA_WEB_CHAT_LIB_BACKEND_LOGIC_SERVER_DATA_INTERACT_PAGES_H
#include "server_data_interact.h"
#include "../sqlite3_wrapper.h"
#include <engine_engine_number_9/http_structures/client_request.h>
#include <engine_engine_number_9/http_structures/response_gen.h>
#include "../login_cookie.h"
#include <jsonincpp/jsonobj.h>
#include <new_york_transit_line/templater.h>
#include <memory>
#include "../localizator.h"
namespace iu9cawebchat {
struct WorkerGuestData {
/* Because templaters use libjsonincpp, they can't be READ by two thread simultaneously */
std::unique_ptr<nytl::Templater> templater;
std::unique_ptr<SqliteConnection> db;
std::unique_ptr<Localizator> locales;
};
void initial_extraction_of_all_the_useful_info_from_cookies(
SqliteConnection& conn, const een9::ClientRequest& req,
std::vector<std::pair<std::string, std::string>>& ret_cookies,
std::vector<LoginCookie>& ret_login_cookies,
json::JSON& ret_userinfo,
int64_t& ret_logged_in_user
);
std::string http_R200(const std::string& el_name, WorkerGuestData& wgd,
const std::vector<const json::JSON*>& args);
struct HtmlMsgBox {
std::string class_;
std::string text;
};
json::JSON jsonify_html_message_list(const std::vector<HtmlMsgBox>& messages);
std::string page_E404(WorkerGuestData& wgd);
/* ========================== PAGES ================================== */
std::string when_page_list_rooms(WorkerGuestData& wgd, const json::JSON& config_presentation,
const een9::ClientRequest& req, const json::JSON& userinfo);
std::string when_page_login(WorkerGuestData& wgd, const json::JSON& config_presentation,
const een9::ClientRequest& req, const std::vector<LoginCookie>& login_cookies, const json::JSON& userinfo);
std::string when_page_chat(WorkerGuestData& wgd, const json::JSON& config_presentation,
const een9::ClientRequest& req, const json::JSON& userinfo);
std::string when_page_user(WorkerGuestData& wgd, const json::JSON& config_presentation,
const een9::ClientRequest& req, const std::vector<LoginCookie>& login_cookies, const json::JSON& userinfo);
std::string when_page_register(WorkerGuestData& wgd, const json::JSON& config_presentation,
const een9::ClientRequest& req, const json::JSON& userinfo);
/* ======================== API ============================== */
std::string when_internalapi_chatpollevents(WorkerGuestData& wgd, const een9::ClientRequest& req, int64_t uid);
std::string when_internalapi_chatlistpollevents(WorkerGuestData& wgd, const een9::ClientRequest& req, int64_t uid);
std::string when_internalapi_getmessageneighbours(WorkerGuestData& wgd, const een9::ClientRequest& req, int64_t uid);
std::string when_internalapi_sendmessage(WorkerGuestData &wgd, const een9::ClientRequest &req, int64_t uid);
std::string when_internalapi_deletemessage(WorkerGuestData &wgd, const een9::ClientRequest &req, int64_t uid);
std::string when_internalapi_addmembertochat(WorkerGuestData &wgd, const een9::ClientRequest &req, int64_t uid);
std::string when_internalapi_removememberfromchat(WorkerGuestData &wgd, const een9::ClientRequest &req, int64_t uid);
std::string when_internalapi_createchat(WorkerGuestData &wgd, const een9::ClientRequest &req, int64_t uid);
std::string when_internalapi_leavechat(WorkerGuestData &wgd, const een9::ClientRequest &req, int64_t uid);
}
#endif

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

@ -0,0 +1,246 @@
#include "server_data_interact.h"
#include <engine_engine_number_9/baza_throw.h>
#include <engine_engine_number_9/http_structures/cookies.h>
#include "../str_fields.h"
namespace iu9cawebchat {
json::JSON at_api_error_gen_bad_recv(int64_t code) {
return json::JSON(json::jdict{{"status", json::JSON(code)}});
}
const char* stringify_user_chat_role(int64_t role) {
if (role == user_chat_role_admin)
return "admin";
if (role == user_chat_role_regular)
return "regular";
if (role == user_chat_role_read_only)
return "read-only";
return "not-a-member";
}
int64_t find_user_by_credentials (SqliteConnection& conn, const std::string& nickname, const std::string& password) {
SqliteStatement sql_req(conn,
"SELECT `id` FROM `user` WHERE `nickname` = ?1 AND `password` = ?2",
{}, {{1, nickname}, {2, password}});
fsql_integer_or_null id_col;
int status = sqlite_stmt_step(sql_req, {{0, &id_col}}, {});
if (status == SQLITE_ROW) {
een9_ASSERT_pl(id_col.exist & id_col.value >= 0);
return id_col.value;
}
return -1;
}
std::string get_user_name (SqliteConnection& conn, int64_t uid) {
een9_ASSERT(uid >= 0, "Are you crazy?");
SqliteStatement sql_req(conn,
"SELECT `name` FROM `user` WHERE `id` = ?1",
{{1, uid}}, {});
fsql_text8_or_null name_col;
int status = sqlite_stmt_step(sql_req, {}, {{0, &name_col}});
if (status == SQLITE_ROW) {
een9_ASSERT_pl(name_col.exist);
return name_col.value;
}
een9_THROW("No such user");
}
RowUser_Content lookup_user_content(SqliteConnection &conn, int64_t uid) {
een9_ASSERT(uid >= 0, "Are you crazy?");
SqliteStatement sql_req(conn,
"SELECT `nickname`, `name` FROM `user` WHERE `id` = ?1",
{{1, uid}}, {});
fsql_text8_or_null nickname_col;
fsql_text8_or_null name_col;
int status = sqlite_stmt_step(sql_req, {}, {{0, &nickname_col}, {1, &name_col}});
if (status == SQLITE_ROW) {
return {uid, std::move(nickname_col.value), std::move(name_col.value)};
}
een9_THROW("No such user");
}
RowUser_Content lookup_user_content_by_nickname(SqliteConnection& conn, const std::string& nickname) {
SqliteStatement sql_req(conn,
"SELECT `id`, `name` FROM `user` WHERE `nickname` = ?1",
{}, {{1, nickname}});
fsql_integer_or_null id_col;
fsql_text8_or_null name_col;
int status = sqlite_stmt_step(sql_req, {{0, &id_col}}, {{1, &name_col}});
if (status == SQLITE_ROW) {
return {id_col.value, nickname, std::move(name_col.value)};
}
een9_THROW("No such user");
}
RowChat_Content lookup_chat_content(SqliteConnection &conn, int64_t chatId) {
een9_ASSERT(chatId >= 0, "Are you crazy?");
SqliteStatement sql_req(conn,
"SELECT `nickname`, `name`, `lastMsgId` FROM `chat` WHERE `id` = ?1",
{{1, chatId}}, {});
fsql_text8_or_null nickname_col;
fsql_text8_or_null name_col;
fsql_integer_or_null last_msg_id_col;
int status = sqlite_stmt_step(sql_req, {{2, &last_msg_id_col}}, {{0, &nickname_col}, {1, &name_col}});
if (status == SQLITE_ROW) {
return {chatId, std::move(nickname_col.value), std::move(name_col.value),
last_msg_id_col.exist ? last_msg_id_col.value : -1};
}
een9_THROW("No such chat");
}
RowChat_Content lookup_chat_content_by_nickname(SqliteConnection &conn, const std::string& nickname) {
SqliteStatement sql_req(conn,
"SELECT `id`, `name`, `lastMsgId` FROM `chat` WHERE `nickname` = ?1",
{}, {{1, nickname}});
fsql_integer_or_null id_col;
fsql_text8_or_null name_col;
fsql_integer_or_null last_msg_id_col;
int status = sqlite_stmt_step(sql_req, {{0, &id_col}, {2, &last_msg_id_col}}, {{1, &name_col}});
if (status == SQLITE_ROW) {
return {id_col.value, nickname, std::move(name_col.value),
last_msg_id_col.exist ? last_msg_id_col.value : -1};
}
een9_THROW("No such chat");
}
RowMessage_Content lookup_message_content(SqliteConnection& conn, int64_t chatId, int64_t msgId) {
SqliteStatement req(conn,
"SELECT `senderUserId`, `exists`, `isSystem`, `text` FROM `message` WHERE "
"`chatId` = ?1 AND `id` = ?2", {{1, chatId}, {2, msgId}}, {});
fsql_integer_or_null senderUserId, exists, isSystem;
fsql_text8_or_null msg_text;
int status = sqlite_stmt_step(req, {{0, &senderUserId}, {1, &exists}, {2, &isSystem}},
{{3, &msg_text}});
if (status == SQLITE_ROW) {
return {msgId, senderUserId.exist ? senderUserId.value : -1, (bool)exists.value,
(bool)isSystem.value, msg_text.value};
}
een9_THROW("No such message");
}
int64_t get_role_of_user_in_chat(SqliteConnection& conn, int64_t userId, int64_t chatId) {
SqliteStatement req(conn,
"SELECT `role` FROM `user_chat_membership` WHERE `userId` = ?1 AND `chatId` = ?2",
{{1, userId}, {2, chatId}}, {});
fsql_integer_or_null role;
int status = sqlite_stmt_step(req, {{0, &role}}, {});
if (status == SQLITE_ROW) {
return role.exist ? (int)role.value : user_chat_role_deleted;
}
return user_chat_role_deleted;
}
int64_t get_lastMsgId_of_chat(SqliteConnection &conn, int64_t chatId) {
een9_ASSERT(chatId >= 0, "Are you crazy?");
SqliteStatement sql_req(conn,
"SELECT `lastMsgId` FROM `chat` WHERE `id` = ?1", {{1, chatId}}, {});
fsql_integer_or_null last_msg_id_col;
int status = sqlite_stmt_step(sql_req, {{0, &last_msg_id_col}}, {});
if (status == SQLITE_ROW) {
return last_msg_id_col.exist ? last_msg_id_col.value : -1;
}
een9_THROW("No such chat");
}
/* All the api calls processing is done in dedicated files.
* All functions related to polling are defined in api_pollevents.cpp */
int64_t get_current_history_id_of_chat(SqliteConnection& conn, int64_t chatId) {
SqliteStatement req(conn, "SELECT `it_HistoryId` FROM `chat` WHERE `id` = ?1", {{1, chatId}}, {});
fsql_integer_or_null HistoryId;
int status = sqlite_stmt_step(req, {{0, &HistoryId}}, {});
een9_ASSERT_pl(status == SQLITE_ROW);
return HistoryId.value;
}
int64_t get_current_history_id_of_user_chatList(SqliteConnection& conn, int64_t userId) {
SqliteStatement req(conn, "SELECT `chatList_HistoryId` FROM `user` WHERE `id` = ?1", {{1, userId}}, {});
fsql_integer_or_null HistoryId;
int status = sqlite_stmt_step(req, {{0, &HistoryId}}, {});
een9_ASSERT_pl(status == SQLITE_ROW);
return HistoryId.value;
}
// todo: extract useful clues from deprecated code.
// todo: deprecated code goes here:
/* !!! DEPRECATED FUNCTION */
json::JSON toremoveinternalapi_getChatList(SqliteConnection& conn, int64_t uid, const json::JSON& Sent) {
json::JSON Recv;
Recv["status"] = json::JSON(0l);
Recv["chats"] = json::JSON(json::array);
std::vector<json::JSON>& chats = Recv["chats"].asArray();
SqliteStatement req(conn,
"SELECT `chat`.`id`, `chat`.`nickname`, `chat`.`name`, `chat`.`lastMsgId`, "
"`user_chat_membership`.`role` FROM `chat` "
"RIGHT JOIN `user_chat_membership` ON `chat`.`id` = `user_chat_membership`.`chatId` "
"WHERE `user_chat_membership`.`userId` = ?1 ", {{1, uid}}, {});
while (true) {
fsql_integer_or_null chat_id;
fsql_text8_or_null chat_nickname, chat_name;
fsql_integer_or_null chat_lastMsgId, role_here;
int status = sqlite_stmt_step(req, {{0, &chat_id}, {3, &chat_lastMsgId}, {4, &role_here}},
{{1, &chat_nickname}, {2, &chat_name}});
if (status != SQLITE_ROW)
break;
chats.emplace_back();
json::JSON& chat = chats.back();
chat["id"] = json::JSON(chat_id.value);
chat["content"]["nickname"] = json::JSON(chat_nickname.value);
chat["content"]["name"] = json::JSON(chat_name.value);
chat["content"]["lastMsgId"] = json::JSON(chat_lastMsgId.exist ? chat_lastMsgId.value : -1);
chat["content"]["roleHere"] = json::JSON(stringify_user_chat_role(role_here.value));
}
return Recv;
}
/* !!! DEPRECATED FUNCTION */
json::JSON toremoveinternalapi_getChatMemberList(SqliteConnection& conn, int64_t uid, const json::JSON& Sent) {
int64_t chatId = Sent["id"].asInteger().get_int();
int64_t my_role_here = get_role_of_user_in_chat(conn, uid, chatId);
if (my_role_here == user_chat_role_deleted)
een9_THROW("Unauthorized user tries to access internalapi_getChatInfo");
json::JSON Recv;
Recv["status"] = json::JSON(0l);
Recv["members"] = json::JSON(json::array);
std::vector<json::JSON>& members = Recv["members"].asArray();
SqliteStatement req(conn,
"SELECT `user`.`id`, `user`.`nickname`, `user`.`name`, `user_chat_membership`.`role` FROM "
"`user` RIGHT JOIN `user_chat_membership` ON `user`.`id` = `user_chat_membership`.`userId` "
"WHERE `user_chat_membership`.`chatId` = ?1",
{{1, chatId}}, {});
while (true) {
fsql_integer_or_null this_user_id;
fsql_text8_or_null this_user_nickname, this_user_name;
fsql_integer_or_null this_users_role;
int status = sqlite_stmt_step(req, {{0, &this_user_id}, {3, &this_users_role}},
{{1, &this_user_nickname}, {2, &this_user_name}});
if (status != SQLITE_ROW)
break;
members.emplace_back();
json::JSON& member = members.back();
member["id"] = json::JSON(this_user_id.value);
member["content"]["nickname"] = json::JSON(this_user_nickname.value);
member["content"]["name"] = json::JSON(this_user_name.value);
member["content"]["role"] = json::JSON(this_users_role.value);
}
return Recv;
}
bool is_nickname_taken(SqliteConnection& conn, const std::string& nickname) {
if (!check_nickname(nickname))
return true;
SqliteStatement req(conn, "SELECT EXISTS(SELECT 1 FROM `nickname` WHERE `it` = ?1)",
{}, {{1, nickname}});
fsql_integer_or_null r{true, 0};
int status = sqlite_stmt_step(req, {{0, &r}}, {});
return r.value;
}
void reserve_nickname(SqliteConnection& conn, const std::string& nickname) {
if (!check_nickname(nickname))
een9_THROW("PRECAUTION! Trying to insert incorrect nickname into nickname table");
sqlite_nooutput(conn, "INSERT INTO `nickname` (`it`) VALUES (?1)", {}, {{1, nickname}});
}
}

View File

@ -0,0 +1,94 @@
#ifndef IU9_CA_WEB_CHAT_LIB_BACKEND_LOGIC_SERVER_DATA_INTERACT_H
#define IU9_CA_WEB_CHAT_LIB_BACKEND_LOGIC_SERVER_DATA_INTERACT_H
/* This folder covers all code that helps to interact with database and templater,
* or dictates the logic of how this web chat functions */
#include "../sqlite3_wrapper.h"
#include <jsonincpp/string_representation.h>
namespace iu9cawebchat {
json::JSON at_api_error_gen_bad_recv(int64_t code = -1);
constexpr int64_t user_chat_role_admin = 1;
constexpr int64_t user_chat_role_regular = 2;
constexpr int64_t user_chat_role_read_only = 3;
constexpr int64_t user_chat_role_deleted = 4;
const char* stringify_user_chat_role(int64_t role);
int64_t find_user_by_credentials (SqliteConnection& conn, const std::string& nickname, const std::string& password);
std::string get_user_name (SqliteConnection& conn, int64_t uid);
struct RowUser_Content {
int64_t id;
std::string nickname;
std::string name;
};
struct RowChat_Content {
int64_t id;
std::string nickname;
std::string name;
int64_t lastMsgId; // Negative if it does not exist
};
RowUser_Content lookup_user_content(SqliteConnection& conn, int64_t uid);
RowUser_Content lookup_user_content_by_nickname(SqliteConnection& conn, const std::string& nickname);
/* Does not make authorization check */
RowChat_Content lookup_chat_content(SqliteConnection& conn, int64_t chatId);
RowChat_Content lookup_chat_content_by_nickname(SqliteConnection &conn, const std::string& nickname);
struct RowMessage_Content {
int64_t id;
int64_t senderUserId;
bool exists;
bool isSystem;
std::string text;
};
RowMessage_Content lookup_message_content(SqliteConnection& conn, int64_t chatId, int64_t msgId);
/* Does not make authorization check */
int64_t get_role_of_user_in_chat(SqliteConnection& conn, int64_t userId, int64_t chatId);
/* Does not make authorization check */
int64_t get_lastMsgId_of_chat(SqliteConnection& conn, int64_t chatId);
int64_t get_current_history_id_of_chat(SqliteConnection& conn, int64_t chatId);
int64_t get_current_history_id_of_user_chatList(SqliteConnection& conn, int64_t userId);
json::JSON poll_update_chat_list_resp(SqliteConnection& conn, int64_t userId, int64_t LocalHistoryId);
void poll_update_chat_list(SqliteConnection& conn, int64_t userId, const json::JSON& Sent, json::JSON& Recv);
json::JSON poll_update_chat_ONE_MSG_resp(SqliteConnection& conn, int64_t chatId, int64_t selectedMsg);
void poll_update_chat(SqliteConnection& conn, const json::JSON& Sent, json::JSON& Recv);
void alter_user_chat_role(SqliteConnection& conn, int64_t chatId, int64_t alienUserId, int64_t role);
void make_her_a_member_of_the_midnight_crew(SqliteConnection& conn, int64_t chatId, int64_t alienUserId, int64_t role);
void kick_from_chat(SqliteConnection& conn, int64_t chatId, int64_t alienUserId);
bool is_nickname_taken(SqliteConnection& conn, const std::string& nickname);
void reserve_nickname(SqliteConnection& conn, const std::string& nickname);
void insert_new_message(SqliteConnection& conn, int64_t uid, int64_t chatId, const std::string& text, bool isSystem);
void insert_system_message_svo(SqliteConnection& conn, int64_t chatId,
int64_t subject, const std::string& verb, int64_t object);
/* ============================= API ==================================== */
json::JSON internalapi_chatPollEvents(SqliteConnection& conn, int64_t uid, const json::JSON& Sent);
json::JSON internalapi_chatListPollEvents(SqliteConnection& conn, int64_t uid, const json::JSON& Sent);
json::JSON internalapi_getMessageNeighbours(SqliteConnection& conn, int64_t uid, const json::JSON& Sent);
json::JSON internalapi_sendMessage(SqliteConnection& conn, int64_t uid, const json::JSON& Sent);
json::JSON internalapi_deleteMessage(SqliteConnection& conn, int64_t uid, const json::JSON& Sent);
json::JSON internalapi_addMemberToChat(SqliteConnection& conn, int64_t uid, const json::JSON& Sent);
json::JSON internalapi_removeMemberFromChat(SqliteConnection& conn, int64_t uid, const json::JSON& Sent);
json::JSON internalapi_createChat(SqliteConnection& conn, int64_t uid, const json::JSON& Sent);
json::JSON internalapi_leaveChat(SqliteConnection& conn, int64_t uid, const json::JSON& Sent);
/**/
void add_user(SqliteConnection& conn, const std::string& nickname, const std::string& name,
const std::string& password, const std::string& bio, int64_t forced_id = -1);
std::string admin_control_procedure(SqliteConnection& conn, const std::string& req, bool& termination);
}
#endif

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