Compare commits

..

4 Commits

39 changed files with 792 additions and 231 deletions

View File

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

View File

@ -8,25 +8,25 @@
<link rel="stylesheet" href="/assets/css/common.css"> <link rel="stylesheet" href="/assets/css/common.css">
<link rel="stylesheet" href="/assets/css/common-popup.css"> <link rel="stylesheet" href="/assets/css/common-popup.css">
<link rel="stylesheet" href="/assets/css/chat-members.css"> <link rel="stylesheet" href="/assets/css/chat-members.css">
<title>Chat Members</title> <title>{%w pres.chat-members.members-of %} {%w openedchat.name %}</title>
</head> </head>
<body> <body>
{% PUT chat.pass pres userinfo openedchat initial_chatUpdResp %} {% PUT chat.pass pres userinfo openedchat initial_chatUpdResp %}
<div id="user-summoning-win" class="popup-window"> <div id="user-summoning-win" class="popup-window">
<h1 class="popup-window-msg">Nickname for summoned user</h1> <h1 class="popup-window-msg">{%w pres.chat-members.summon-label-nickname %}</h1>
<input class="one-line-input" id="summoned-user-nickname"> <input class="one-line-input" id="summoned-user-nickname">
<input type="checkbox" id="summoned-user-is-read-only"> <input type="checkbox" id="summoned-user-is-read-only">
<label>Make read only</label><br> <label>{%w pres.chat-members.summon-label-ro %}</label><br>
<button class="popup-window-btn-yes" id="user-summoning-yes">Yes, summon</button> <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">No, cancel</button> <button class="popup-window-btn-no" id="user-summoning-no">{%w pres.chat-members.no-summon %}</button>
</div> </div>
<div id="user-deletion-win" class="popup-window"> <div id="user-deletion-win" class="popup-window">
<!-- header will actually be rewritten before showing the window to include user nickname --> <!-- header will actually be rewritten before showing the window to include user nickname -->
<h1 id="user-deletion-win-title" class="popup-window-msg">Are you sure you want to ban user?</h1> <h1 id="user-deletion-win-title" class="popup-window-msg"> ||||||||| </h1>
<button class="popup-window-btn-yes" id="user-deletion-yes">Yes, delete</button> <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">No, cancel</button> <button class="popup-window-btn-no" id="user-deletion-no">{%w pres.chat-members.no-kick %}</button>
</div> </div>
<div class="document-container resp-container"> <div class="document-container resp-container">
@ -34,11 +34,13 @@
<a href="/list-rooms" id="go-to-chat-list" class="panel-thing"> <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"> <img alt="Go to list of chats" src="/assets/img/list-rooms.svg" width="32px">
</a> </a>
<a href="/user/{% WRITE userinfo.nickname %}" id="go-to-my-profile" class="panel-thing"> <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"> <img alt="Go to my profile" src="/assets/img/user.svg" width="32px">
</a> </a>
<p class="panel-thing panel-header-txt"> Members list of {% WRITE openedchat.name %} ({% WRITE openedchat.nickname %})</p> <p class="panel-thing panel-header-txt">
<a href="/chat/{% WRITE openedchat.nickname %}" id="go-to-chat-settings" class="panel-thing"> {%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"> <img alt="Back to chat" src="/assets/img/return.svg" width="32px">
</a> </a>
</div> </div>

View File

@ -18,17 +18,17 @@
<link rel="stylesheet" href="/assets/css/common.css"> <link rel="stylesheet" href="/assets/css/common.css">
<link rel="stylesheet" href="/assets/css/common-popup.css"> <link rel="stylesheet" href="/assets/css/common-popup.css">
<link rel="stylesheet" href="/assets/css/chat.css"> <link rel="stylesheet" href="/assets/css/chat.css">
<title>Chat</title> <title>{%w pres.chat.header-chat %} {%w openedchat.name %}</title>
</head> </head>
<body> <body>
{% PUT chat.pass pres userinfo openedchat initial_chatUpdResp %} {% PUT chat.pass pres userinfo openedchat initial_chatUpdResp %}
<div id="msg-deletion-win" class="popup-window"> <div id="msg-deletion-win" class="popup-window">
<h1 class="popup-window-msg">Are you sure you want to delete this message?</h1> <h1 class="popup-window-msg">{%w pres.chat.reask-delete-message %}</h1>
<!-- message preview will be actually rewritten before each window activation--> <!-- mesage preview will be actually rewritten before each window activation-->
<p class="message-in-popup-preview" id="win-deletion-msg-preview">Lorem ipsum dolor</p> <p class="message-in-popup-preview" id="win-deletion-msg-preview">|||||||||</p>
<button class="popup-window-btn-yes" id="msg-deletion-yes">Yes, delete</button> <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">No, cancel</button> <button class="popup-window-btn-no" id="msg-deletion-no">{%w pres.chat.no-delete %}</button>
</div> </div>
<div class="fullscreen-container resp-container"> <div class="fullscreen-container resp-container">
@ -36,11 +36,11 @@
<a href="/list-rooms" id="go-to-chat-list" class="panel-thing"> <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"> <img alt="Go to list of chats" src="/assets/img/list-rooms.svg" width="32px">
</a> </a>
<a href="/user/{% WRITE userinfo.nickname %}" id="go-to-my-profile" class="panel-thing"> <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"> <img alt="Go to my profile" src="/assets/img/user.svg" width="32px">
</a> </a>
<p class="panel-thing panel-header-txt"> {% WRITE openedchat.name %} ({% WRITE openedchat.nickname %})</p> <p class="panel-thing panel-header-txt"> {% W openedchat.name %} ({% W openedchat.nickname %})</p>
<a href="/chat-members/{% WRITE openedchat.nickname %}" id="go-to-chat-settings" class="panel-thing"> <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"> <img alt="Settings of chat. List of members" src="/assets/img/settings-iron.svg" width="32px">
</a> </a>
</div> </div>

View File

@ -7,7 +7,7 @@
<link rel="icon" type="image/png" href="/assets/img/favicon.png"> <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.css">
<link rel="stylesheet" href="/assets/css/edit-profile.css"> <link rel="stylesheet" href="/assets/css/edit-profile.css">
<title>Edit user Profile</title> <title>{%w pres.edit-profile.header-profile-of %} {%w alienprofile.name %}</title>
</head> </head>
<body> <body>
@ -16,50 +16,52 @@
<a href="/list-rooms" id="go-to-chat-list" class="panel-thing"> <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"> <img alt="Go to list of chats" src="/assets/img/list-rooms.svg" width="32px">
</a> </a>
<a href="/user/{% WRITE userinfo.nickname %}" id="go-to-my-profile" class="panel-thing"> <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"> <img alt="Go to my profile" src="/assets/img/user.svg" width="32px">
</a> </a>
</div> </div>
{% FOR error IN errors %} {% FOR error IN errors %}
<div class="server-notif-error-msg-box"> <div class="server-notif-error-msg-box">
{% WRITE error.text %} {% W error.text %}
</div> </div>
{% ENDFOR %} {% ENDFOR %}
<div class="profile-container"> <div class="profile-container">
<h2 class="profile-name-text">{% WRITE alienprofile.name %}</h2> <h2 class="profile-name-text">{% W alienprofile.name %}</h2>
<h3 class="profile-nickname-text">Nickname: {% WRITE alienprofile.nickname %}</h3> <h3 class="profile-nickname-text">{%w pres.edit-profile.directive-nickname %} {% W alienprofile.nickname %}</h3>
<p class="profile-bio-text"> <p class="profile-bio-text">
{% WRITE alienprofile.bio %} {% W alienprofile.bio %}
</p> </p>
</div> </div>
<div class="profile-container resp-container"> <div class="profile-container">
<h1 class="wide-centered-header">Change user attributes</h1> <h1 class="wide-centered-header">{%w pres.edit-profile.change-user-attributes %}</h1>
<form action = "/user/{% WRITE alienprofile.nickname %}" method="post" enctype="application/x-www-form-urlencoded"> <form action = "/user/{% W alienprofile.nickname %}" method="post" enctype="application/x-www-form-urlencoded">
<table class="logins-input-table"> <table class="logins-input-table">
<tr> <tr>
<td class="logins-input-td1"> <td class="logins-input-td1">
<label for="new-name-input">Enter new name:</label> <label for="new-name-input">{%w pres.edit-profile.directive-name %}</label>
</td> </td>
<td class="logins-input-td2"> <td class="logins-input-td2">
<input name="name" id="new-name-input" type="text" placeholder="New name" class="one-line-input"> <input name="name" id="new-name-input" type="text"
placeholder="{%w pres.edit-profile.placeholder-name %}" class="one-line-input">
</td> </td>
</tr> </tr>
<tr> <tr>
<td class="logins-input-td1"> <td class="logins-input-td1">
<label for="new-password-input">Enter new password: </label> <label for="new-password-input">{%w pres.edit-profile.directive-password %}</label>
</td> </td>
<td class="logins-input-td2"> <td class="logins-input-td2">
<input name="password" id="new-password-input" type="password" placeholder="New password" class="one-line-input"> <input name="password" id="new-password-input" type="password"
placeholder="{%w pres.edit-profile.placeholder-password %}" class="one-line-input">
</td> </td>
</tr> </tr>
</table> </table>
<label for="input-change-bio">Change description:</label> <label for="input-change-bio">{%w pres.edit-profile.directive-bio %}</label>
<br> <br>
<textarea name="bio" id="input-change-bio" class="multiline-input"></textarea> <textarea name="bio" id="input-change-bio" class="multiline-input"></textarea>
<button class="action-button centered-block-el" type="submit">Submit changes</button> <button class="action-button centered-block-el" type="submit">{%w pres.edit-profile.act-submit %}</button>
</form> </form>
</div> </div>
</div> </div>

View File

@ -1,10 +1,10 @@
{% ELDEF main JSON pres JSON userinfo JSON initial_chatListUpdResp %} {% ELDEF main JSON pres JSON userinfo JSON initial_chatListUpdResp %}
<!DOCTYPE html> <!DOCTYPE html>
<html lang="{% WRITE pres.lang %}"> <html lang="{% W pres.lang %}">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>List of chat rooms</title> <title>{%w pres.list-rooms.header %}</title>
<link rel="icon" type="image/png" href="/assets/img/favicon.png"> <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.css">
<link rel="stylesheet" href="/assets/css/common-popup.css"> <link rel="stylesheet" href="/assets/css/common-popup.css">
@ -17,45 +17,46 @@
let initial_chatListUpdResp = {% PUT jsinsert initial_chatListUpdResp %}; let initial_chatListUpdResp = {% PUT jsinsert initial_chatListUpdResp %};
</script> </script>
<div id="chat-creation-win" class="popup-window"> <div id="chat-creation-win" class="popup-window">
<h1 class="popup-window-msg">Input identifying information for your new chat</h1> <h1 class="popup-window-msg">{%w pres.list-rooms.new-chat-header %}</h1>
<table class="id-str-input-table"> <table class="id-str-input-table">
<tr> <tr>
<td class="id-str-input-td1"> <td class="id-str-input-td1">
<label for="chat-nickname-input">Enter nickname for new chat:</label> <label for="chat-nickname-input">{%w pres.list-rooms.directive-nickname %}</label>
</td> </td>
<td class="id-str-input-td2"> <td class="id-str-input-td2">
<input id="chat-nickname-input" type="text" placeholder="Take a nickname" class="one-line-input" required> <input id="chat-nickname-input" type="text" class="one-line-input"
placeholder="{%w pres.list-rooms.placeholder-nickname %}" required>
</td> </td>
</tr> </tr>
<tr> <tr>
<td class="id-str-input-td1"> <td class="id-str-input-td1">
<label for="chat-name-input">Enter name for new chat:</label> <label for="chat-name-input">{%w pres.list-rooms.directive-name %}</label>
</td> </td>
<td class="id-str-input-td2"> <td class="id-str-input-td2">
<input id="chat-name-input" type="text" placeholder="Come up with name" class="one-line-input" required> <input id="chat-name-input" type="text" class="one-line-input"
placeholder="{%w pres.list-rooms.placeholder-name %}" required>
</td> </td>
</tr> </tr>
</table> </table>
<h1 class="popup-window-msg">Create new chat?</h1> <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">Yes, create</button> <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">No, cancel</button> <button class="popup-window-btn-no" id="chat-creation-win-no">{%w pres.list-rooms.no-create %}</button>
</div> </div>
<div id="chat-renunciation-win" class="popup-window"> <div id="chat-renunciation-win" class="popup-window">
<!-- header will actually be rewritten before showing the window to include chat nickname --> <!-- header will actually be rewritten before showing the window to include chat nickname -->
<h1 id="chat-renunciation-win-title" class="popup-window-msg">Are you sure you want to leave chat?</h1> <h1 id="chat-renunciation-win-title" class="popup-window-msg">||||||||||</h1>
<button class="popup-window-btn-yes" id="chat-renunciation-win-yes">Yes, leave</button> <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">No, cancel</button> <button class="popup-window-btn-no" id="chat-renunciation-win-no">{%w pres.list-rooms.no-leave %}</button>
</div> </div>
x
<div class="document-container resp-container"> <div class="document-container resp-container">
<div id="navigation-panel" class="panel"> <div id="navigation-panel" class="panel">
<a href="/user/{% WRITE userinfo.nickname %}" id="go-to-my-profile" class="panel-thing"> <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"> <img alt="Go to my profile" src="/assets/img/user.svg" width="32px">
</a> </a>
<p class="panel-thing panel-header-txt"> <p class="panel-thing panel-header-txt">
List of available rooms {%w pres.list-rooms.page-description %}
</p> </p>
</div> </div>

View File

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

View File

@ -7,7 +7,7 @@
<link rel="icon" type="image/png" href="/assets/img/favicon.png"> <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.css">
<!-- This page is so simple, that it does not even have it's separate css file --> <!-- This page is so simple, that it does not even have it's separate css file -->
<title>User Profile</title> <title>{%w pres.view-profile.header-profile-of %} {%w alienprofile.name %}</title>
</head> </head>
<body> <body>
@ -16,16 +16,16 @@
<a href="/list-rooms" id="go-to-chat-list" class="panel-thing"> <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"> <img alt="Go to list of chats" src="/assets/img/list-rooms.svg" width="32px">
</a> </a>
<a href="/user/{% WRITE userinfo.nickname %}" id="go-to-my-profile" class="panel-thing"> <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"> <img alt="Go to my profile" src="/assets/img/user.svg" width="32px">
</a> </a>
</div> </div>
<div class="profile-container"> <div class="profile-container">
<h2 class="profile-name-text">{% WRITE alienprofile.name %}</h2> <h2 class="profile-name-text">{%w alienprofile.name %}</h2>
<h3 class="profile-nickname-text">Nickname: {% WRITE alienprofile.nickname %}</h3> <h3 class="profile-nickname-text">{%w pres.view-profile.directive-nickname%} {%w alienprofile.nickname %}</h3>
<p class="profile-bio-text"> <p class="profile-bio-text">
{% WRITE alienprofile.bio %} {%w alienprofile.bio %}
</p> </p>
</div> </div>
</div> </div>

View File

@ -62,7 +62,7 @@ function convertMemberStToBox(memberSt){
return; return;
userDeletionWinStoredUId = ID; userDeletionWinStoredUId = ID;
document.getElementById("user-deletion-win-title").innerText = document.getElementById("user-deletion-win-title").innerText =
"Do you really want to kick user " + memberSt.nickname + "?"; pres['chat-members']['reask-kick-user-X'] + " " + memberSt.nickname + "?";
activatePopupWindowById("user-deletion-win"); activatePopupWindowById("user-deletion-win");
}; };
box.querySelector(".CM-member-box-leave-btn").style.display = box.querySelector(".CM-member-box-leave-btn").style.display =
@ -124,7 +124,7 @@ function configureSummonUserInterface(){
updateLocalStateFromRecv(Recv); updateLocalStateFromRecv(Recv);
}).catch((e) => { }).catch((e) => {
console.log(e); console.log(e);
alert("Failed to add user to chat"); alert(pres['chat-members']["failed-summon-member"]);
}); });
}; };
@ -158,7 +158,7 @@ function configureKickUserInterfaceWinPart(){
updateLocalStateFromRecv(Recv); updateLocalStateFromRecv(Recv);
}).catch((e) => { }).catch((e) => {
console.log(e); console.log(e);
alert("Failed to kick user from chat"); alert(pres['chat-members']["failed-kick-member"]);
}); });
} }

View File

@ -27,6 +27,7 @@ let storeHiddenMsgIdForDeletionWin = -1;
// Positive in production, negative for debug // Positive in production, negative for debug
let softZoneSz = -150; let softZoneSz = -150;
let chatPadding = 300; let chatPadding = 300;
const msgErased = pres.chat.msgErased;
function genSentBase(){ function genSentBase(){
return { return {
@ -132,11 +133,10 @@ function updateOffsets(){
} }
function shouldShowDeleteMesgBtn(messageSt){ function shouldShowDeleteMesgBtn(messageSt){
return !messageSt.isSystem && messageSt.exists && (messageSt.myRoleHere !== userChatRoleReadOnly) &&( return !messageSt.isSystem && messageSt.exists && (myRoleHere !== userChatRoleReadOnly) &&(
messageSt.myRoleHere === userChatRoleAdmin || messageSt.senderUserId === userinfo.uid); myRoleHere === userChatRoleAdmin || messageSt.senderUserId === userinfo.uid);
} }
// todo: fix messageboxes
function getMsgTypeClassSenderBased(messageSt){ function getMsgTypeClassSenderBased(messageSt){
if (messageSt.isSystem) if (messageSt.isSystem)
return "message-box-system" return "message-box-system"
@ -153,6 +153,8 @@ function getMsgFullTypeClassName(messageSt){
* Supercontainer.container is persistent, Supercontainer.box can change it's class */ * Supercontainer.container is persistent, Supercontainer.box can change it's class */
function updateMessageSupercontainer(supercontainer, messageSt){ function updateMessageSupercontainer(supercontainer, messageSt){
let box = supercontainer.box; let box = supercontainer.box;
if (messageSt.isSystem)
return;
setElementVisibility(box.querySelector(".message-box-button-delete"), shouldShowDeleteMesgBtn(messageSt), "inline"); setElementVisibility(box.querySelector(".message-box-button-delete"), shouldShowDeleteMesgBtn(messageSt), "inline");
box.className = getMsgFullTypeClassName(messageSt); 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 // Notice, that no check of previous state is performed. Double loading is a rare event, I can afford to be slow
@ -167,15 +169,13 @@ function decodeSystemMessage(text){
let subjectRef = members.has(subjectId) ? members.get(subjectId).nickname : "???"; let subjectRef = members.has(subjectId) ? members.get(subjectId).nickname : "???";
let objectRef = members.has(objectId) ? members.get(objectId).nickname : "???"; let objectRef = members.has(objectId) ? members.get(objectId).nickname : "???";
if (verb === "kicked"){ if (verb === "kicked"){
return subjectRef + " kicked " + objectRef; return subjectRef + " " + pres.chat.syslog.kicked + " " + objectRef;
} else if (verb === "summoned"){ } else if (verb === "summoned"){
return subjectRef + " summoned " + objectId; return subjectRef + " " + pres.chat.syslog.summoned + " " + objectRef;
} else if (verb === "left"){ } else if (verb === "left"){
return subjectRef + " left chat"; return subjectRef + " " + pres.chat.syslog.left;
} else if (verb === "joined"){
return subjectId + " joined chat";
} else if (verb === "created"){ } else if (verb === "created"){
return subjectId + " created this chat"; return subjectRef + " " + pres.chat.syslog.created;
} }
return "... Bad log ..."; return "... Bad log ...";
} }
@ -190,56 +190,58 @@ function convertMessageStToSupercontainer(messageSt){
let ID = messageSt.id; let ID = messageSt.id;
let topPart = document.createElement("div"); if (messageSt.isSystem){
box.appendChild(topPart);
topPart.className = "message-box-top";
if (!members.has(messageSt.senderUserId)) } else {
throw new Error("First - update members"); let topPart = document.createElement("div");
let senderMemberSt = members.get(messageSt.senderUserId); box.appendChild(topPart);
let senderProfileURI = "/user/" + senderMemberSt.nickname; topPart.className = "message-box-top";
let inTopPartSenderName = document.createElement("a"); if (!members.has(messageSt.senderUserId))
topPart.appendChild(inTopPartSenderName); throw new Error("First - update members");
inTopPartSenderName.className = "message-box-sender-name"; let senderMemberSt = members.get(messageSt.senderUserId);
inTopPartSenderName.innerText = senderMemberSt.name; let senderProfileURI = "/user/" + senderMemberSt.nickname;
inTopPartSenderName.href = senderProfileURI;
let inTopPartSenderNickname = document.createElement("a"); let inTopPartSenderName = document.createElement("a");
topPart.appendChild(inTopPartSenderNickname); topPart.appendChild(inTopPartSenderName);
inTopPartSenderNickname.className = "message-box-sender-name message-box-sender-shortname" inTopPartSenderName.className = "message-box-sender-name";
inTopPartSenderNickname.innerText = senderMemberSt.nickname; inTopPartSenderName.innerText = senderMemberSt.name;
inTopPartSenderNickname.href = senderProfileURI; inTopPartSenderName.href = senderProfileURI;
let inTopPartButtonDelete = document.createElement("img"); let inTopPartSenderNickname = document.createElement("a");
topPart.appendChild(inTopPartButtonDelete); topPart.appendChild(inTopPartSenderNickname);
inTopPartButtonDelete.className = "message-box-button message-box-button-delete"; inTopPartSenderNickname.className = "message-box-sender-name message-box-sender-shortname"
inTopPartButtonDelete.src = "/assets/img/delete.svg"; inTopPartSenderNickname.innerText = senderMemberSt.nickname;
inTopPartButtonDelete.onclick = (ev) => { inTopPartSenderNickname.href = senderProfileURI;
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"); let inTopPartButtonDelete = document.createElement("img");
topPart.appendChild(inTopPartButtonGetLink); topPart.appendChild(inTopPartButtonDelete);
inTopPartButtonGetLink.className = "message-box-button"; inTopPartButtonDelete.className = "message-box-button message-box-button-delete";
inTopPartButtonGetLink.src = "/assets/img/link.svg"; inTopPartButtonDelete.src = "/assets/img/delete.svg";
inTopPartButtonGetLink.onclick = (ev) => { inTopPartButtonDelete.onclick = (ev) => {
if (ev.button !== 0) if (ev.button !== 0)
return; return;
let URI = window.location.host + "/chat/" + openedchat.nickname + "/m/" + String(ID); let msgText = box.querySelector(".message-box-msg").innerText;
document.getElementById("message-input").innerText += (" " + URI + ""); let previewText = senderMemberSt.nickname + ":\n" + msgText;
console.log("Tried to get link on message " + ID); 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"); let msgPart = document.createElement("p");
box.appendChild(msgPart); box.appendChild(msgPart);
@ -387,7 +389,7 @@ function configureMsgDeletionPopupButtons(){
deactivateActivePopup(); deactivateActivePopup();
let Sent = genSentBase(); let Sent = genSentBase();
Sent.id = storeHiddenMsgIdForDeletionWin; Sent.id = storeHiddenMsgIdForDeletionWin;
safeApiRequestWithLocalStUpdate("deleteMessage", Sent, "Failed to delete message"); safeApiRequestWithLocalStUpdate("deleteMessage", Sent, pres.chat['failed-delete-message']);
}; };
document.getElementById("msg-deletion-no").onclick = function (ev){ document.getElementById("msg-deletion-no").onclick = function (ev){
@ -424,7 +426,7 @@ window.onload = function (){
let Sent = genSentBase(); let Sent = genSentBase();
Sent.content = {}; Sent.content = {};
Sent.content.text = text; Sent.content.text = text;
safeApiRequestWithLocalStUpdate("sendMessage", Sent, "Failed to send message"); safeApiRequestWithLocalStUpdate("sendMessage", Sent, pres.chat['failed-send-message']);
} }
}); });

View File

@ -57,9 +57,6 @@ function roleToColor(role) {
return "#286500" // Bug return "#286500" // Bug
} }
// todo: replace it with translation
const msgErased = "[ ERASED ]";
function hideHTMLElement(el){ function hideHTMLElement(el){
el.style.display = "none"; el.style.display = "none";
} }

View File

@ -13,7 +13,8 @@ let chatBoxes = new Map();
/* Generate text that is displayed on the right side of chat intro box */ /* Generate text that is displayed on the right side of chat intro box */
function youAreXHere(myRoleHere){ function youAreXHere(myRoleHere){
return "You are " + myRoleHere + " here"; // todo: TRANSLATE IT
return pres['list-rooms']['you-are-X-here'][0] + " " + myRoleHere + " " + pres['list-rooms']['you-are-X-here'][1];
} }
@ -67,7 +68,7 @@ function convertMyMembershipStToBox(myMembershipSt){
return; return;
chatRenunciationWinStoredId = ID; chatRenunciationWinStoredId = ID;
document.getElementById("chat-renunciation-win-title").innerText = document.getElementById("chat-renunciation-win-title").innerText =
"Do you really want to leave chat " + myMembershipSt.chatNickname + "?"; pres['list-rooms']['reask-leave-chat-X'] + " " + myMembershipSt.chatNickname + "?";
activatePopupWindowById("chat-renunciation-win"); activatePopupWindowById("chat-renunciation-win");
}; };
box.querySelector(".CL-my-chat-box-leave-btn").style.display = box.querySelector(".CL-my-chat-box-leave-btn").style.display =
@ -119,7 +120,7 @@ function configureChatCreationInterface(){
).then((Recv) => { ).then((Recv) => {
updateLocalStateFromRecv(Recv); updateLocalStateFromRecv(Recv);
}).catch((e) => { }).catch((e) => {
alert("Failed to create chat"); alert(pres['list-rooms']["failed-create-chat"]);
console.log(e); console.log(e);
}); });
}; };
@ -138,7 +139,6 @@ function configureChatCreationInterface(){
chatNicknameInput.value = ""; chatNicknameInput.value = "";
chatNameInput.value = ""; chatNameInput.value = "";
activatePopupWindowById("chat-creation-win"); activatePopupWindowById("chat-creation-win");
console.log("Tried to show chat creation window");
}; };
} }
@ -156,7 +156,7 @@ function configureChatRenunciationInterfaceWinPart(){
).then((Recv) => { ).then((Recv) => {
updateLocalStateFromRecv(Recv); updateLocalStateFromRecv(Recv);
}).catch((e) => { }).catch((e) => {
alert("Failed to leave chat"); alert(pres['list-rooms']["failed-create-chat"]);
console.log(e); console.log(e);
}); });
} }

View File

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

View File

@ -0,0 +1,76 @@
{
"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": "Не смог отправить сообщение"
}
}

View File

@ -78,6 +78,7 @@ struct CAWebChat {
"http_structures/client_request_parse.cpp", "http_structures/client_request_parse.cpp",
"http_structures/response_gen.cpp", "http_structures/response_gen.cpp",
"http_structures/cookies.cpp", "http_structures/cookies.cpp",
"http_structures/accept_language.cpp",
"connecting_assets/static_asset_manager.cpp", "connecting_assets/static_asset_manager.cpp",
"running_mainloop.cpp", "running_mainloop.cpp",
"form_data_structure/urlencoded_query.cpp", "form_data_structure/urlencoded_query.cpp",
@ -97,6 +98,7 @@ struct CAWebChat {
"http_structures/client_request.h", "http_structures/client_request.h",
"http_structures/cookies.h", "http_structures/cookies.h",
"http_structures/response_gen.h", "http_structures/response_gen.h",
"http_structures/accept_language.h",
"running_mainloop.h", "running_mainloop.h",
"form_data_structure/urlencoded_query.h", "form_data_structure/urlencoded_query.h",
"socket_address.h", "socket_address.h",
@ -141,6 +143,7 @@ struct CAWebChat {
CTargetDependenceOnExternalLibrary{"sqlite3", {true, true}} CTargetDependenceOnExternalLibrary{"sqlite3", {true, true}}
}; };
T.units = { T.units = {
"localizator.cpp",
"initialize.cpp", "initialize.cpp",
"run.cpp", "run.cpp",
"str_fields.cpp", "str_fields.cpp",

View File

View File

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

View File

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

View File

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

View File

@ -1,14 +1,20 @@
#include "cookies.h" #include "cookies.h"
#include "../baza_inter.h" #include "../baza_inter.h"
#include "grammar.h"
namespace een9 { namespace een9 {
bool isSPACE(char ch) { bool isSPACE(char ch) {
return ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n'; return ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n';
} }
bool isALPHANUM(char ch) {
return ('a' <= ch && ch <= 'z') || ('A' <= ch && ch <= 'Z') || ('0' <= ch && ch <= '9');
}
bool isToken(const std::string &str) { bool isToken(const std::string &str) {
for (char ch : str) { for (char ch : str) {
if (!(('a' <= ch && ch <= 'z') || ('A' <= ch && ch <= 'Z') || ('0' <= ch && ch <= '9') if (!(isALPHANUM(ch)
|| ch == '!' || ch == '#' || ch == '$' || ch == '%' || ch == '&' || ch == '\'' || ch == '*' || ch == '!' || ch == '#' || ch == '$' || ch == '%' || ch == '&' || ch == '\'' || ch == '*'
|| ch == '+' || ch == '-' || ch == '.' || ch == '^' || ch == '_' || ch == '`' || ch == '|' || ch == '+' || ch == '-' || ch == '.' || ch == '^' || ch == '_' || ch == '`' || ch == '|'
|| ch == '~' )) || ch == '~' ))

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

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

View File

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

View File

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

View File

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

View File

@ -150,17 +150,41 @@ namespace nytl {
return concatenateLines(lines); return concatenateLines(lines);
} }
void parse_bare_file(const std::string& filename, const std::string& content, Element& add_hidden_element(const std::string& new_el_name, global_elem_set_t& result) {
global_elem_set_t& result) if (result.count(new_el_name) != 0)
{ THROW("Repated element " + new_el_name);
if (result.count(filename) != 0) TemplaterRegPref& rp = result[new_el_name];
THROW("Repeated element " + filename); rp.is_element = 1;
rp.when_element = std::make_unique<Element>();
rp.when_element->is_hidden = true;
return *rp.when_element;
}
Element& add_new_element(const std::string& new_el_name, global_elem_set_t& result) {
if (!is_uname_dotted_sequence(new_el_name))
THROW("Krabovaya oshibka");
if (result.count(new_el_name) != 0 && result.at(new_el_name).is_element)
THROW("Repated element " + new_el_name);
size_t n = new_el_name.size();
for (size_t i = 0; i < n; i++) {
if (new_el_name[i] == '.') {
std::string pref = new_el_name.substr(0, i);
result[pref];
}
}
TemplaterRegPref& rp = result[new_el_name];
rp.is_element = 1;
rp.when_element = std::make_unique<Element>();
return *rp.when_element;
}
void parse_bare_file(const std::string& filename, const std::string& content, global_elem_set_t& result) {
Element& el = add_new_element(filename, result);
std::string txt = clement_lstrip(content); std::string txt = clement_lstrip(content);
rstrip(txt); rstrip(txt);
size_t cut = 9999999999999; size_t cut = 9999999999999;
one_part_update_min_start_wsp_non_empty(txt, 0, 1, cut); one_part_update_min_start_wsp_non_empty(txt, 0, 1, cut);
txt = one_part_cut_excess_tab(txt, 0, 1, cut); txt = one_part_cut_excess_tab(txt, 0, 1, cut);
Element& el = result[filename];
el.parts = {ElementPart{}}; el.parts = {ElementPart{}};
el.parts[0].when_code.lines = mv(txt); el.parts[0].when_code.lines = mv(txt);
} }
@ -379,8 +403,7 @@ namespace nytl {
skipWhitespace(ctx); skipWhitespace(ctx);
P.ref_over = parse_expression(ctx, local_var_names); P.ref_over = parse_expression(ctx, local_var_names);
P.internal_element = el_name + ".~" + std::to_string(free_hidden++); P.internal_element = el_name + ".~" + std::to_string(free_hidden++);
Element& newborn = elem_ns[P.internal_element]; Element& newborn = add_hidden_element(P.internal_element, elem_ns);
newborn.is_hidden = true;
arg_name_list_t local_var_names_of_nxt = local_var_names; arg_name_list_t local_var_names_of_nxt = local_var_names;
if (V1 != "_") { if (V1 != "_") {
if (local_var_names_of_nxt.count(V1) != 0) if (local_var_names_of_nxt.count(V1) != 0)
@ -417,8 +440,7 @@ namespace nytl {
skipWhitespace(ctx); skipWhitespace(ctx);
P.ref_over = parse_expression(ctx, local_var_names); P.ref_over = parse_expression(ctx, local_var_names);
P.internal_element = el_name + ".~" + std::to_string(free_hidden++); P.internal_element = el_name + ".~" + std::to_string(free_hidden++);
Element& newborn = elem_ns[P.internal_element]; Element& newborn = add_hidden_element(P.internal_element, elem_ns);
newborn.is_hidden = true;
arg_name_list_t local_var_names_of_nxt = local_var_names; arg_name_list_t local_var_names_of_nxt = local_var_names;
size_t k = local_var_names_of_nxt.size(); size_t k = local_var_names_of_nxt.size();
local_var_names_of_nxt.emplace(Vn, k); local_var_names_of_nxt.emplace(Vn, k);
@ -427,7 +449,7 @@ namespace nytl {
return std::make_unique<ECPFrame>(P.internal_element, gone_for_ref, local_var_names_of_nxt, return std::make_unique<ECPFrame>(P.internal_element, gone_for_ref, local_var_names_of_nxt,
ret_data_int, newborn); ret_data_int, newborn);
} }
if (op == "PUT") { if (op == "PUT" || op == "P") {
result.parts.emplace_back(); result.parts.emplace_back();
result.parts.back().type = ElementPart::p_put; result.parts.back().type = ElementPart::p_put;
ElementPart::when_put_S& P = result.parts.back().when_put; ElementPart::when_put_S& P = result.parts.back().when_put;
@ -453,11 +475,11 @@ namespace nytl {
P.passed_arguments = {parse_expression(ctx, local_var_names)}; P.passed_arguments = {parse_expression(ctx, local_var_names)};
skip_magic_block_end(ctx, syntax); skip_magic_block_end(ctx, syntax);
}; };
if (op == "WRITE") { if (op == "WRITE" || op == "W") {
mediocre_operator("str2text"); mediocre_operator("str2text");
goto ya_e_ya_h_i_ya_g_d_o;; goto ya_e_ya_h_i_ya_g_d_o;;
} }
if (op == "ROUGHINSERT") { if (op == "ROUGHINSERT" || op == "RI") {
mediocre_operator("str2code"); mediocre_operator("str2code");
goto ya_e_ya_h_i_ya_g_d_o;; goto ya_e_ya_h_i_ya_g_d_o;;
} }
@ -549,9 +571,7 @@ namespace nytl {
if (elname_postfix == "_") if (elname_postfix == "_")
THROW("Can't use _ as element name"); THROW("Can't use _ as element name");
std::string fullname = elname_postfix == "main" ? filename : filename + "." + elname_postfix; std::string fullname = elname_postfix == "main" ? filename : filename + "." + elname_postfix;
if (result.count(fullname) != 0) Element& newborn = add_new_element(fullname, result);
THROW("Element " + fullname + " has been already defined");
Element& newborn = result[fullname];
arg_name_list_t arglist; arg_name_list_t arglist;
while (true) { while (true) {
skipWhitespace(ctx); skipWhitespace(ctx);

View File

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

View File

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

View File

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

View File

@ -40,7 +40,6 @@ namespace iu9cawebchat {
void make_her_a_member_of_the_midnight_crew(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) {
assert(role != user_chat_role_deleted); assert(role != user_chat_role_deleted);
// int64_t old_role = get_role_of_user_in_chat(conn, alienUserId, chatId);
alter_user_chat_role(conn, chatId, alienUserId, role); alter_user_chat_role(conn, chatId, alienUserId, role);
} }
@ -67,6 +66,7 @@ namespace iu9cawebchat {
} else { } else {
return at_api_error_gen_bad_recv(-2l); return at_api_error_gen_bad_recv(-2l);
} }
insert_system_message_svo(conn, chatId, uid, "summoned", alien.id);
json::JSON Recv; json::JSON Recv;
poll_update_chat(conn, Sent, Recv); poll_update_chat(conn, Sent, Recv);

View File

@ -7,6 +7,7 @@ namespace iu9cawebchat {
if (get_role_of_user_in_chat(conn, uid, chatId) == user_chat_role_deleted) if (get_role_of_user_in_chat(conn, uid, chatId) == user_chat_role_deleted)
een9_THROW("Not a member"); een9_THROW("Not a member");
kick_from_chat(conn, chatId, uid); kick_from_chat(conn, chatId, uid);
insert_system_message_svo(conn, chatId, uid, "left", -1);
json::JSON Recv; json::JSON Recv;
poll_update_chat_list(conn, uid, Sent, Recv); poll_update_chat_list(conn, uid, Sent, Recv);
return Recv; return Recv;

View File

@ -13,6 +13,7 @@ namespace iu9cawebchat {
een9_THROW("Only admin can delete members of chat"); een9_THROW("Only admin can delete members of chat");
int64_t badAlienId = Sent["userId"].asInteger().get_int(); int64_t badAlienId = Sent["userId"].asInteger().get_int();
kick_from_chat(conn, chatId, badAlienId); kick_from_chat(conn, chatId, badAlienId);
insert_system_message_svo(conn, chatId, uid, "kicked", badAlienId);
json::JSON Recv; json::JSON Recv;
poll_update_chat(conn, Sent, Recv); poll_update_chat(conn, Sent, Recv);
return Recv; return Recv;

View File

@ -8,8 +8,7 @@ namespace iu9cawebchat {
* Chat's HistoryId will increment after this operation * Chat's HistoryId will increment after this operation
* if adding system message, uid is ignored * if adding system message, uid is ignored
*/ */
void insert_new_message(SqliteConnection& conn, int64_t uid, int64_t chatId, void insert_new_message(SqliteConnection& conn, int64_t uid, int64_t chatId, const std::string& text, bool isSystem) {
const std::string& text, bool isSystem) {
int64_t chat_HistoryId_BEFORE_MSG = get_current_history_id_of_chat(conn, chatId); int64_t chat_HistoryId_BEFORE_MSG = get_current_history_id_of_chat(conn, chatId);
int64_t chat_lastMsgId = get_lastMsgId_of_chat(conn, chatId); int64_t chat_lastMsgId = get_lastMsgId_of_chat(conn, chatId);
SqliteStatement req(conn, SqliteStatement req(conn,
@ -24,6 +23,13 @@ namespace iu9cawebchat {
{{1, chat_lastMsgId + 1}, {2, chat_HistoryId_BEFORE_MSG + 1}, {3, chatId}}, {}); {{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) { json::JSON internalapi_sendMessage(SqliteConnection& conn, int64_t uid, const json::JSON& Sent) {
debug_print_json(Sent); debug_print_json(Sent);
int64_t chatId = Sent["chatUpdReq"]["chatId"].asInteger().get_int(); int64_t chatId = Sent["chatUpdReq"]["chatId"].asInteger().get_int();

View File

@ -11,12 +11,14 @@
#include <jsonincpp/jsonobj.h> #include <jsonincpp/jsonobj.h>
#include <new_york_transit_line/templater.h> #include <new_york_transit_line/templater.h>
#include <memory> #include <memory>
#include "../localizator.h"
namespace iu9cawebchat { namespace iu9cawebchat {
struct WorkerGuestData { struct WorkerGuestData {
/* Because templaters use libjsonincpp, they can't be READ by two thread simultaneously */ /* Because templaters use libjsonincpp, they can't be READ by two thread simultaneously */
std::unique_ptr<nytl::Templater> templater; std::unique_ptr<nytl::Templater> templater;
std::unique_ptr<SqliteConnection> db; std::unique_ptr<SqliteConnection> db;
std::unique_ptr<Localizator> locales;
}; };
void initial_extraction_of_all_the_useful_info_from_cookies( void initial_extraction_of_all_the_useful_info_from_cookies(

View File

@ -70,6 +70,10 @@ namespace iu9cawebchat {
bool is_nickname_taken(SqliteConnection& conn, const std::string& nickname); bool is_nickname_taken(SqliteConnection& conn, const std::string& nickname);
void reserve_nickname(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 ==================================== */ /* ============================= API ==================================== */
json::JSON internalapi_chatPollEvents(SqliteConnection& conn, int64_t uid, const json::JSON& Sent); 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_chatListPollEvents(SqliteConnection& conn, int64_t uid, const json::JSON& Sent);

View File

@ -28,7 +28,7 @@ namespace iu9cawebchat {
if (uid < 0) { if (uid < 0) {
printf("Redirecting back to /login because of incorrect credentials\n"); printf("Redirecting back to /login because of incorrect credentials\n");
json::JSON msg_list = jsonify_html_message_list({{"", json::JSON msg_list = jsonify_html_message_list({{"",
config_presentation["phr"]["decl"]["incorrect-nickname-or-password"].asString()}}); config_presentation["login"]["incorrect-nickname-or-password"].asString()}});
return http_R200("login", wgd, {&config_presentation, &userinfo, &msg_list}); return http_R200("login", wgd, {&config_presentation, &userinfo, &msg_list});
} }
std::vector<std::pair<std::string, std::string>> response_hlines; std::vector<std::pair<std::string, std::string>> response_hlines;

View File

@ -104,7 +104,7 @@ namespace iu9cawebchat {
} catch (const std::exception& e) { } catch (const std::exception& e) {
printf("Redirecting back to /user/... because of incorrect credentials\n"); printf("Redirecting back to /user/... because of incorrect credentials\n");
json::JSON msg_list = jsonify_html_message_list({{"", json::JSON msg_list = jsonify_html_message_list({{"",
config_presentation["phr"]["decl"]["incorrect-profile-data"].asString()}}); config_presentation["edit-profile"]["incorrect-profile-data"].asString()}});
json::JSON alien_userprofile = user_row_to_userprofile_obj(conn, alien); json::JSON alien_userprofile = user_row_to_userprofile_obj(conn, alien);
return http_R200("edit-profile", wgd, {&config_presentation, &userinfo, &alien_userprofile, &msg_list}); return http_R200("edit-profile", wgd, {&config_presentation, &userinfo, &alien_userprofile, &msg_list});
} }

View File

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

View File

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

View File

@ -5,6 +5,7 @@
#include <engine_engine_number_9/connecting_assets/static_asset_manager.h> #include <engine_engine_number_9/connecting_assets/static_asset_manager.h>
#include "find_db.h" #include "find_db.h"
#include <engine_engine_number_9/running_mainloop.h> #include <engine_engine_number_9/running_mainloop.h>
#include <engine_engine_number_9/http_structures/accept_language.h>
#include <signal.h> #include <signal.h>
#include "str_fields.h" #include "str_fields.h"
#include "backend_logic/client_server_interact.h" #include "backend_logic/client_server_interact.h"
@ -32,11 +33,23 @@ namespace iu9cawebchat {
} }
}; };
LocalizatorSettings make_localizator_settings(const std::string& assets_dir, const json::JSON& config) {
std::vector<std::string> whitelist;
for (const json::JSON& entry: config["lang"]["whitelist"].asArray())
whitelist.push_back(languageRangeSimpler(entry.asString()));
std::vector<std::string> force_order;
for (const json::JSON& entry: config["lang"]["force-order"].asArray())
force_order.push_back(languageRangeSimpler(entry.asString()));
return LocalizatorSettings{assets_dir + "/lang", whitelist, force_order};
}
void run_website(const json::JSON& config) { void run_website(const json::JSON& config) {
een9_ASSERT(config["assets"].isString(), "config[\"assets\"] is not string"); een9_ASSERT(config["assets"].isString(), "config[\"assets\"] is not string");
const std::string& assets_dir = config["assets"].asString(); const std::string& assets_dir = config["assets"].asString();
een9_ASSERT(een9::isDirectory(assets_dir), "\"" + assets_dir + "\" is not a directory"); een9_ASSERT(een9::isDirectory(assets_dir), "\"" + assets_dir + "\" is not a directory");
LocalizatorSettings localizator_settings = make_localizator_settings(assets_dir, config);
een9::StaticAssetManagerSlaveModule samI; een9::StaticAssetManagerSlaveModule samI;
samI.update({ samI.update({
een9::StaticAssetManagerRule{assets_dir + "/css", "/assets/css", {{".css", "text/css"}} }, een9::StaticAssetManagerRule{assets_dir + "/css", "/assets/css", {{".css", "text/css"}} },
@ -47,7 +60,6 @@ namespace iu9cawebchat {
} }, } },
}); });
const json::JSON& config_presentation = config["presentation"];
int64_t slave_number = config["server"]["workers"].asInteger().get_int(); int64_t slave_number = config["server"]["workers"].asInteger().get_int();
een9_ASSERT(slave_number > 0 && slave_number <= 200, "E"); een9_ASSERT(slave_number > 0 && slave_number <= 200, "E");
@ -61,15 +73,24 @@ namespace iu9cawebchat {
nytl::TemplaterSettings{nytl::TemplaterDetourRules{assets_dir + "/HypertextPages"}}); nytl::TemplaterSettings{nytl::TemplaterDetourRules{assets_dir + "/HypertextPages"}});
worker_guest_data[i].templater->update(); worker_guest_data[i].templater->update();
worker_guest_data[i].db = std::make_unique<SqliteConnection>(sqlite_db_path); worker_guest_data[i].db = std::make_unique<SqliteConnection>(sqlite_db_path);
worker_guest_data[i].locales = std::make_unique<Localizator>(localizator_settings);
} }
een9::MainloopParameters params; een9::MainloopParameters params;
params.guest_core = [&samI, &worker_guest_data, config_presentation] params.guest_core = [&samI, &worker_guest_data]
(const een9::SlaveTask& task, const een9::ClientRequest& req, een9::worker_id_t worker_id) -> std::string { (const een9::SlaveTask& task, const een9::ClientRequest& req, een9::worker_id_t worker_id) -> std::string {
een9_ASSERT_pl(0 <= worker_id && worker_id < worker_guest_data.size()); een9_ASSERT_pl(0 <= worker_id && worker_id < worker_guest_data.size());
WorkerGuestData& wgd = worker_guest_data[worker_id]; WorkerGuestData& wgd = worker_guest_data[worker_id];
een9::StaticAsset sa; een9::StaticAsset sa;
ONE_SQLITE_TRANSACTION_GUARD conn_guard(*wgd.db); ONE_SQLITE_TRANSACTION_GUARD conn_guard(*wgd.db);
std::string AcceptLanguage;
for (const std::pair<std::string, std::string>& p: req.headers) {
if (p.first == "Accept-Language")
AcceptLanguage = p.second;
}
std::vector<std::string> AcceptLanguageB = een9::parse_header_Accept_Language(AcceptLanguage);
const LanguageFile& locale = wgd.locales->get_right_locale(AcceptLanguageB);
const json::JSON& pres = locale.content;
try { try {
std::vector<std::pair<std::string, std::string>> cookies; std::vector<std::pair<std::string, std::string>> cookies;
std::vector<LoginCookie> login_cookies; std::vector<LoginCookie> login_cookies;
@ -78,17 +99,17 @@ namespace iu9cawebchat {
initial_extraction_of_all_the_useful_info_from_cookies(*wgd.db, req, cookies, login_cookies, userinfo, logged_in_user); initial_extraction_of_all_the_useful_info_from_cookies(*wgd.db, req, cookies, login_cookies, userinfo, logged_in_user);
if (req.uri_path == "/" || req.uri_path == "/list-rooms") { if (req.uri_path == "/" || req.uri_path == "/list-rooms") {
return when_page_list_rooms(wgd, config_presentation, req, userinfo); return when_page_list_rooms(wgd, pres, req, userinfo);
} }
if (req.uri_path == "/login") { if (req.uri_path == "/login") {
return when_page_login(wgd, config_presentation, req, login_cookies, userinfo); return when_page_login(wgd, pres, req, login_cookies, userinfo);
} }
// todo: split // todo: split
if (een9::beginsWith(req.uri_path, "/chat/") || een9::beginsWith(req.uri_path, "/chat-members/")) { if (een9::beginsWith(req.uri_path, "/chat/") || een9::beginsWith(req.uri_path, "/chat-members/")) {
return when_page_chat(wgd, config_presentation, req, userinfo); return when_page_chat(wgd, pres, req, userinfo);
} }
if (een9::beginsWith(req.uri_path, "/user/")) { if (een9::beginsWith(req.uri_path, "/user/")) {
return when_page_user(wgd, config_presentation, req, login_cookies, userinfo); return when_page_user(wgd, pres, req, login_cookies, userinfo);
} }
if (req.uri_path == "/api/chatPollEvents") { if (req.uri_path == "/api/chatPollEvents") {
return when_internalapi_chatpollevents(wgd, req, logged_in_user); return when_internalapi_chatpollevents(wgd, req, logged_in_user);