Compare commits

...

6 Commits

35 changed files with 1487 additions and 663 deletions

View File

@ -7,9 +7,10 @@
<link rel="stylesheet" href="/assets/css/chat.css"> <link rel="stylesheet" href="/assets/css/chat.css">
</head> </head>
<body> <body>
<div class="chat-container"> <div class="chat-container">
<div class="chat-header"> <div class="chat-header">
Веб чат <span class="room-name">Веб чат</span>
<button class="members" onclick="openMembersList()">Показать участников</button>
</div> </div>
<div class="chat-messages" id="chat-messages"> <div class="chat-messages" id="chat-messages">
<!-- Сообщения чата будут здесь --> <!-- Сообщения чата будут здесь -->
@ -18,8 +19,20 @@
<input type="text" class="chat-input" id="chat-input" placeholder="Введите сообщение..."> <input type="text" class="chat-input" id="chat-input" placeholder="Введите сообщение...">
<button class="chat-send-button" onclick="sendMessage()">Отправить</button> <button class="chat-send-button" onclick="sendMessage()">Отправить</button>
</div> </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>
<div class="members-list-body">
<script src="/assets/js/chat.js"></script> <ul id="members-list-body">
<!-- Список участников будет добавлен динамически -->
</ul>
</div>
</div>
</div>
<script src="/assets/js/chat.js"></script>
</body> </body>
</html> </html>

View File

@ -0,0 +1,65 @@
<!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="Название комнаты">
<input type="password" id="newRoomNickname" placeholder="Никнейм комнаты">
</div>
<div id="error"></div>
<div class="modal-footer">
<button class="join-button" onclick="createRoom()">Создать</button>
</div>
</div>
</div>
<!-- Модальное окно для добавления участников -->
<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,54 +0,0 @@
{% ELDEF main JSON pres %}
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% WRITE pres.phr.decl.list-of-chat-rooms %}</title>
<link rel="stylesheet" href="/assets/css/list-rooms.css">
</head>
<body>
<div class="container">
<h1 style="color: white;">{% WRITE pres.phr.decl.select-chat-room %}</h1>
<ul class="room-list">
<!-- Здесь будет список комнат -->
</ul>
<button class="create-room-button" onclick="openCreateRoomModal()">{% WRITE pres.phr.act.create-room %}</button>
</div>
<div id="passwordModal" class="modal">
<div class="modal-content">
<div class="modal-header">
<span class="close" onclick="closeModal()">&times;</span>
<h2>VVedite parol</h2> <!-- Nam ne nuzhen parol ot komnat -->
</div>
<div class="modal-body">
<input type="password" id="roomPassword" placeholder="Пароль">
</div>
<div class="modal-footer">
<button class="join-button" onclick="validatePassword()">{% WRITE pres.phr.act.confirm %}</button>
</div>
</div>
</div>
<!-- Модальное окно для создания комнаты -->
<div id="createRoomModal" class="modal">
<div class="modal-content">
<div class="modal-header">
<span class="close" onclick="closeCreateRoomModal()">&times;</span>
<h2>{% WRITE pres.phr.decl.create-room %}</h2>
</div>
<div class="modal-body">
<input type="text" id="newRoomName" placeholder="{% WRITE pres.phr.decl.name-of-room %}">
<input type="password" id="newRoomPassword" placeholder="Пароль"> <!-- Fedya, nam ne nuzhen parol -->
</div>
<div class="modal-footer">
<button class="join-button" onclick="createRoom()">{% WRITE pres.phr.act.create %}</button>
</div>
</div>
</div>
<script src="/assets/js/list-rooms.js"></script>
</body>
</html>
{% ENDELDEF %}

View File

@ -9,13 +9,14 @@
<div class="main-container"> <div class="main-container">
<div class="profile-header"> <div class="profile-header">
<h1 style="color: white; text-align: center;">Профиль пользователя</h1> <h1 style="color: white; text-align: center;">Профиль пользователя</h1>
<a class="return" href="chat.html">Назад</a>
</div> </div>
<form> <form>
<div class="columns"> <div class="columns">
<div class="column"> <div class="column">
<img class="avatar" src="/assets/img/empty_avatar.png" id="avatar" height="200" width="200"><br> <img class="avatar" src="/assets/img/empty_avatar.png" id="avatar" height="200" width="200"><br>
<input type="file" id="fileInput" style="display:none"> <input type="file" id="fileInput" style="display:none">
<button class="add" type="button" onclick="document.getElementById('fileInput').click();"></button><br> <button class="add" type="button" onclick="document.getElementById('fileInput').click();">Изменить фото</button><br>
</div> </div>
<div class="column"> <div class="column">
<input type="text" name="username" placeholder = "Имя пользователя" value="Some Name" id="username"><br> <input type="text" name="username" placeholder = "Имя пользователя" value="Some Name" id="username"><br>

View File

@ -4,12 +4,12 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Страница Регистрации</title> <title>Страница Регистрации</title>
<link rel="stylesheet" href="assets/css/registration.css"> <link rel="stylesheet" href="/assets/css/registration.css">
</head> </head>
<body> <body>
<div class="form-container"> <div class="form-container">
<h1 class="hide-cursor no-select">Вход</h1> <h1 class="hide-cursor no-select">Вход</h1>
<form action="assets/html/list-rooms.html" method="post"> <form action="assets/html/list-rooms.html" method="post">
<input type="text" name="username" placeholder="Имя пользователя" id="username"><br> <input type="text" name="username" placeholder="Имя пользователя" id="username"><br>
@ -18,8 +18,8 @@
<button type="submit" class="hide-cursor no-select">Зарегистрироваться</button> <button type="submit" class="hide-cursor no-select">Зарегистрироваться</button>
<div id="error"></div> <div id="error"></div>
</form> </form>
</div> </div>
<script src="assets/js/registration.js"></script> <script src="assets/js/registration.js"></script>
</body> </body>
</html> </html>

View File

@ -21,13 +21,33 @@ body {
} }
.chat-header { .chat-header {
background-color: #0088cc; background-color: #007bb5;
color: white; color: white;
padding: 15px; padding: 25px;
text-align: center; display: flex;
font-size: 20px; 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 { .chat-messages {
flex: 1; flex: 1;
padding: 15px; padding: 15px;
@ -97,7 +117,86 @@ body {
cursor: pointer; cursor: pointer;
outline: none; outline: none;
} }
.members-list {
display: none;
position: fixed;
background-color: #fff;
margin: 10% auto;
padding: 20px;
border: 1px solid #888;
width: 80%;
max-width: 400px;
border-radius: 10px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
}
.members-list-header {
display: flex;
}
.all-members {
position: absolute;
left: 32%;
top: 0%;
margin-bottom: 30px;
font-family: Arial, sans-serif;
}
.close {
position: absolute;
right: 5%;
font-size: 24px;
font-weight: bold;
}
.members-list span {
cursor: pointer;
}
.members-list-body ul {
list-style-type: none;
left: 0%;
}
.members-list-body img {
margin-top: 10px;
left: 0%;
height: 30px;
width: 30px;
border-radius: 50%;
}
.members-list-body a {
margin-left: 5px;
margin-top: 10px;
text-decoration: none;
color: black;
}
.members-list-body a:hover {
text-decoration: underline;
color: #0088cc;
}
.members-list-body button {
padding: 5px 10px;
border: none;
background-color: #dc2e45;
color: white;
border-radius: 20px;
position: absolute;
left: 300px;
margin-top: 20px;
cursor: pointer;
transition: background-color 0.3s ease;
}
.members-list-body button:hover {
background-color: #881527;
}
.overlay {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
justify-content: center;
align-items: center;
z-index: 1000;
}
.chat-send-button:hover { .chat-send-button:hover {
background-color: #007bb5; background-color: #007bb5;
} }

View File

@ -55,7 +55,62 @@ h1 {
cursor: pointer; cursor: pointer;
transition: background-color 0.3s ease; 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 { .join-button:hover {
background-color: #0056b3; background-color: #0056b3;
} }
@ -96,19 +151,6 @@ h1 {
text-align: right; text-align: right;
} }
.close {
color: #aaa;
float: right;
font-size: 28px;
font-weight: bold;
}
.close:hover, .close:focus {
color: black;
text-decoration: none;
cursor: pointer;
}
.modal input { .modal input {
width: 93.5%; width: 93.5%;
padding: 10px; padding: 10px;
@ -134,3 +176,78 @@ h1 {
.create-room-button:hover { .create-room-button:hover {
background-color: #218838; background-color: #218838;
} }
.overlay {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
justify-content: center;
align-items: center;
z-index: 1000;
}
.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;
}
.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;
position: absolute;
margin-left: 20px;
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;
}
.close {
color: #aaa;
float: right;
font-size: 28px;
font-weight: bold;
cursor: pointer;
}

View File

@ -24,6 +24,27 @@ body {
border-color: antiquewhite; border-color: antiquewhite;
background-color: #0088cc; background-color: #0088cc;
border-radius: 10px; border-radius: 10px;
position: relative;
}
.return {
background-color: #f0f0f0;
cursor: pointer;
width: 100px;
text-decoration: none;
color: black;
display: flex;
justify-content: center;
align-items: center;
height: 30px;
border-radius: 10px;
position: absolute;
left: 20px;
top: 25px;
border: none;
}
.return:hover{
text-decoration: underline;
color: #0088cc;
} }
form { form {
display: flex; display: flex;
@ -45,14 +66,16 @@ form {
align-items: center; align-items: center;
} }
.add { .add {
background-image: url("/assets/img/add_photo.svg"); width: 100px;
background-size: cover; height: 40px;
width: 30px; border-width: 2px;
height: 30px;
border: none;
cursor: pointer; cursor: pointer;
font-size: 16px;
border-radius: 10px; border-radius: 10px;
} }
.add:hover {
background-color: #007bb5;
}
.image-button:hover { .image-button:hover {
opacity: 0.8; opacity: 0.8;
} }
@ -95,10 +118,11 @@ form {
border-radius: 15px; border-radius: 15px;
border-color: #2F4F4F; border-color: #2F4F4F;
height: 40px; height: 40px;
color: white;
background-color: #0088cc;
width: 150px; width: 150px;
} }
.save:hover {
background-color: #007bb5;
}
.avatar { .avatar {
border-radius: 50%; border-radius: 50%;
object-fit: cover; object-fit: cover;

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

View File

@ -1,8 +1,58 @@
function sendMessage() { let members = [
{ username: 'Адель', nickname: 'cold_siemens52', avatar: 'https://sun9-59.userapi.com/impg/t8GhZ7FkynVifY1FQCnaf31tGprbV_rfauZzgg/fSq4lyc6V0U.jpg?size=1280x1280&quality=96&sign=e3c309a125cb570d2e18465eba65f940&type=album' },
{ username: 'Антон', nickname: 'antyak_01', avatar: 'https://cdn.pixabay.com/photo/2015/10/05/22/37/blank-profile-picture-973460_1280.png' },
{ username: 'Владимир', nickname: 'kkrkk2006', avatar: 'https://cdn.pixabay.com/photo/2015/10/05/22/37/blank-profile-picture-973460_1280.png' }
];
let currentHistoryId = 0;
let currentChatID = null;
function renderMembersList() {
const membersListBody = document.getElementById('members-list-body');
membersListBody.innerHTML = '';
members.forEach((member, index) => {
const memberItem = document.createElement('li');
memberItem.innerHTML = `
<img src="${member.avatar}" alt="${member.username}">
<a href="profile.html">${member.username}</a>
<button class="delete-member" onclick="deleteMember(${index})">Удалить из чата</button>
`;
membersListBody.appendChild(memberItem);
});
}
function deleteMember(index) {
members.splice(index, 1);
renderMembersList();
}
async function sendMessage() {
const chatMessages = document.getElementById('chat-messages'); const chatMessages = document.getElementById('chat-messages');
const chatInput = document.getElementById('chat-input'); const chatInput = document.getElementById('chat-input');
const message = chatInput.value; const message = chatInput.value;
if (message.trim() !== '') { if (message.trim() !== '') {
const request = {
'chatId': currentChatID,
'LocalHistoryId': currentHistoryId,
'content': {
'text': message
}
};
const response = await fetch("/internalapi/sendMessage", {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(request)
});
const res = await response.json();
if (res.update) {
const update = res.update[0];
currentHistoryId = update.HistoryId;
const messageElement = document.createElement('div'); const messageElement = document.createElement('div');
messageElement.classList.add('chat-message'); messageElement.classList.add('chat-message');
@ -18,7 +68,7 @@ function sendMessage() {
const usernameElement = document.createElement('div'); const usernameElement = document.createElement('div');
usernameElement.classList.add('username'); usernameElement.classList.add('username');
usernameElement.textContent = 'Адель'; usernameElement.textContent = await getUserName();
const textElement = document.createElement('div'); const textElement = document.createElement('div');
textElement.classList.add('text'); textElement.classList.add('text');
@ -35,6 +85,18 @@ function sendMessage() {
chatInput.value = ''; chatInput.value = '';
chatMessages.scrollTop = chatMessages.scrollHeight; chatMessages.scrollTop = chatMessages.scrollHeight;
} }
}
}
function openMembersList() {
renderMembersList();
document.getElementById("members-list").style.display = "block";
document.getElementById("overlay").style.display = "flex";
}
function closeMembersList() {
document.getElementById("members-list").style.display = "none";
document.getElementById("overlay").style.display = "none";
} }
document.getElementById('chat-input').addEventListener('keydown', function (event) { document.getElementById('chat-input').addEventListener('keydown', function (event) {
@ -42,3 +104,59 @@ document.getElementById('chat-input').addEventListener('keydown', function (even
sendMessage(); sendMessage();
} }
}); });
async function getUserID() {
const response = await fetch('/internalapi/mirror', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({})
});
const res = await response.json();
return res.id;
}
async function getChatID() {
const chatNickname = window.location.pathname.split('/').pop();
const response = await fetch('/internalapi/getChatList', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({})
});
const res = await response.json();
for (const chat of res.chats) {
if (chat.content.nickname === chatNickname) {
return chat.id;
}
}
return -1;
}
async function editMessage(new_message) {
const req = {
'chatId': currentChatID,
'LocalHistoryId': currentHistoryId,
'id': getUserID(),
'content': {
'text': new_message
}
};
const res = await fetch('/internalapi/editMessage', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(req)
});
const response = await res.json();
if (response.update) {
currentHistoryId = response.update[0].HistoryId;
}
}
document.addEventListener("DOMContentLoaded", async function() {
currentChatID = await getChatID();
});

View File

@ -1,21 +1,47 @@
let rooms = {}; let rooms = {};
let roomToDelete = null;
let currentRoom = null;
let currentHistoryId = 0;
function openModal(roomName) { function openRoom(currentRoom) {
currentRoom = roomName;
document.getElementById('passwordModal').style.display = 'block';
}
function closeModal() {
document.getElementById('passwordModal').style.display = 'none';
}
function validatePassword() {
const enteredPassword = document.getElementById('roomPassword').value;
if (enteredPassword === rooms[currentRoom]) {
alert('Вы вошли в комнату: ' + currentRoom); alert('Вы вошли в комнату: ' + currentRoom);
closeModal(); }
function closeAdd() {
document.getElementById('add_members').style.display = 'none';
}
function openAdd() {
document.getElementById('add_members').style.display = 'flex';
}
function openConfirm(roomNickname) {
roomToDelete = roomNickname;
document.getElementById("delete-chat").style.display = "flex";
}
function closeConfirm() {
roomToDelete = null;
document.getElementById("delete-chat").style.display = "none";
}
function deleteChat() {
if (roomToDelete && rooms[roomToDelete]) {
delete rooms[roomToDelete];
removeRoomFromList(roomToDelete);
closeConfirm();
} else { } else {
alert('Неверный пароль. Попробуйте снова.'); alert("Не удалось найти выбранную комнату.");
}
}
function addMember() {
const login = document.getElementById('newMemberLogin').value;
if (login) {
alert(`Участник с никнеймом '${login}' добавлен`);
closeAdd();
} else {
alert('Пожалуйста, введите логин участника');
} }
} }
@ -27,28 +53,56 @@ function closeCreateRoomModal() {
document.getElementById('createRoomModal').style.display = 'none'; document.getElementById('createRoomModal').style.display = 'none';
} }
function createRoom() { async function createRoom() {
const errorElement = document.getElementById('error');
const roomName = document.getElementById('newRoomName').value.trim(); const roomName = document.getElementById('newRoomName').value.trim();
const roomPassword = document.getElementById('newRoomPassword').value.trim(); const roomNickname = document.getElementById('newRoomNickname').value.trim();
if (roomName === '' || roomPassword === '') {
alert('Пожалуйста, заполните все поля.'); errorElement.style.display = 'none';
errorElement.textContent = '';
if (roomName === '' || roomNickname === '') {
errorElement.textContent = 'Пожалуйста, заполните все поля';
errorElement.style.display = 'block';
return; return;
} }
if (rooms[roomName]) { const request = {
alert('Комната с таким названием уже существует.'); LocalHistoryId: currentHistoryId,
return; content: {
name: roomName,
nickname: roomNickname
} }
};
rooms[roomName] = roomPassword; try {
addRoomToList(roomName); const response = await fetch('/internalapi/createChat', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(request)
});
const res = await response.json();
if (res.status === 0) {
addRoomToList(roomName, roomNickname);
rooms[roomNickname] = true;
closeCreateRoomModal(); closeCreateRoomModal();
currentHistoryId = res.update.LocalHistoryId;
window.location.href = '/chat/' + roomNickname;
} else {
throw new Error(res.error || 'Ошибка');
}
} catch (error) {
alert('Ошибка создания чата: ' + error.message);
}
} }
function addRoomToList(roomName) { function addRoomToList(roomName) {
const roomList = document.querySelector('.room-list'); const roomList = document.querySelector('.room-list');
const existingRoomItem = Array.from(roomList.children).find(item => item.querySelector('.room-name').textContent === roomName); const existingRoomItem = Array.from(roomList.children).find(item => item.querySelector('.room-name').textContent === roomName);
if (existingRoomItem) { if (existingRoomItem) {
existingRoomItem.remove(); existingRoomItem.remove();
@ -59,25 +113,74 @@ function addRoomToList(roomName) {
roomItem.innerHTML = ` roomItem.innerHTML = `
<span class="room-name">${roomName}</span> <span class="room-name">${roomName}</span>
<button class="join-button" onclick="openModal('${roomName}')">Войти</button> <button class="delete-chat-button" onclick="openConfirm('${roomNickname}')">Удалить чат</button>
<button class="add-members-button" onclick="openAdd()">Добавить участников</button>
<button class="join-button" onclick="window.location.href = '/chat/${roomNickname}'">Войти</button>
`; `;
roomList.appendChild(roomItem); roomList.appendChild(roomItem);
} }
function initializeRoomList() { function removeRoomFromList(roomName, roomNickname) {
Object.keys(rooms).forEach(roomName => { const roomList = document.querySelector('.room-list');
addRoomToList(roomName); const roomItem = Array.from(roomList.children).find(item => item.querySelector('.room-name').textContent === roomName);
}); if (roomItem) {
roomList.removeChild(roomItem);
}
} }
initializeRoomList(); async function initializeRoomList() {
try {
const response = await fetch('/internalapi/getChatList', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({})
});
window.onclick = function(event) { const res = await response.json();
if (event.target === document.getElementById('passwordModal')) {
closeModal(); if (res.status === 0) {
res.chats.forEach(chat => {
addRoomToList(chat.content.name, chat.content.nickname);
});
} else {
throw new Error(res.error || 'Неизвестная ошибка');
} }
} catch (error) {
alert('Ошибка загрузки списка чатов: ' + error.message);
}
}
async function getChatID() {
const chatNickname = window.location.pathname.split('/').pop();
const response = await fetch('/internalapi/getChatList', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({})
});
const res = await response.json();
for (const chat of res.chats) {
if (chat.content.nickname === chatNickname) {
return chat.id;
}
}
return -1;
}
window.onclick = function(event) {
if (event.target === document.getElementById('createRoomModal')) { if (event.target === document.getElementById('createRoomModal')) {
closeCreateRoomModal(); closeCreateRoomModal();
} }
} }
document.getElementById('newRoomName').addEventListener('keydown', function(event) {
if (event.key === 'Enter') {
createRoom();
}
});
document.addEventListener('DOMContentLoaded', initializeRoomList);

View File

@ -7,4 +7,4 @@ document.getElementById('fileInput').addEventListener('change', function(event)
}; };
reader.readAsDataURL(file); reader.readAsDataURL(file);
} }
}); });

View File

@ -29,22 +29,40 @@ document.addEventListener('DOMContentLoaded', function() {
}); });
form.addEventListener('submit', function(event) { form.addEventListener('submit', function(event) {
if (!validateForm()) {
event.preventDefault(); event.preventDefault();
} validateForm();
}); });
function validateForm() { async function validateForm() {
const username = document.getElementById('username').value.trim(); const name = document.getElementById('name').value.trim();
const login = document.getElementById('login').value.trim(); const nickname = document.getElementById('nickname').value.trim();
const password = document.getElementById('password').value.trim();
if (username === '' || login === '' || password === '') { if (name === '' || nickname === '') {
errorElement.textContent = 'Пожалуйста, заполните все поля'; errorElement.textContent = 'Пожалуйста, заполните все поля';
errorElement.style.display = 'block'; errorElement.style.display = 'block';
return false; return false;
} }
try {
return true; // Отправка данных для регистрации
} let response = await fetch('/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({name, nickname})
}); });
const result = await response.json();
if (result.status === 0) {
window.location.href = '/';
} else {
throw Error(result.error);
}
} catch(error) {
errorElement.textContent = 'Попробуйте еще раз';
errorElement.style.display = 'block';
}
}
});

View File

@ -25,7 +25,7 @@ struct CAWebChat {
BuildUnitsArray runlevel_2; BuildUnitsArray runlevel_2;
std::string build_type; std::string build_type;
bool build_tests = false; bool make_tests = false;
std::vector<std::string> warning_flags = {"-Wall", "-Wno-unused-variable", "-Werror=return-type","-pedantic", std::vector<std::string> warning_flags = {"-Wall", "-Wno-unused-variable", "-Werror=return-type","-pedantic",
"-Wno-unused-but-set-variable", "-Wno-reorder"}; "-Wno-unused-but-set-variable", "-Wno-reorder"};
@ -49,9 +49,11 @@ struct CAWebChat {
return my_flag_collection; return my_flag_collection;
} }
CAWebChat(const std::string& _build_type, bool _build_tests, const NormalCBuildSystemCommandMeaning& cmd) explicit CAWebChat(const NormalCBuildSystemCommandMeaning& cmd){
: build_type(_build_type), build_tests(_build_tests) 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"); ASSERT(build_type == "release" || build_type == "debug", "Unknown build type");
std::vector<ExternalLibraryTarget> ext_targets = { std::vector<ExternalLibraryTarget> ext_targets = {
@ -75,6 +77,7 @@ struct CAWebChat {
"os_utils.cpp", "os_utils.cpp",
"http_structures/client_request_parse.cpp", "http_structures/client_request_parse.cpp",
"http_structures/response_gen.cpp", "http_structures/response_gen.cpp",
"http_structures/cookies.cpp",
"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",
@ -92,6 +95,7 @@ struct CAWebChat {
"os_utils.h", "os_utils.h",
"connecting_assets/static_asset_manager.h", "connecting_assets/static_asset_manager.h",
"http_structures/client_request.h", "http_structures/client_request.h",
"http_structures/cookies.h",
"http_structures/response_gen.h", "http_structures/response_gen.h",
"running_mainloop.h", "running_mainloop.h",
"form_data_structure/urlencoded_query.h", "form_data_structure/urlencoded_query.h",
@ -127,27 +131,42 @@ struct CAWebChat {
T.installation_dir = "nytl"; T.installation_dir = "nytl";
my_targets.push_back(T); my_targets.push_back(T);
} }
{ CTarget T{"iu9-ca-web-chat", "executable"}; { CTarget T{"iu9_ca_web_chat_lib", "shared_library"};
T.additional_compilation_flags = getSomeRadFlags(); T.additional_compilation_flags = getSomeRadFlags();
T.proj_deps = { T.proj_deps = {
CTargetDependenceOnProjectsLibrary{"engine_engine_number_9"}, CTargetDependenceOnProjectsLibrary{"engine_engine_number_9", {true, true}},
CTargetDependenceOnProjectsLibrary{"new_york_transit_line"}, CTargetDependenceOnProjectsLibrary{"new_york_transit_line", {true, true}},
}; };
T.external_deps = { T.external_deps = {
CTargetDependenceOnExternalLibrary{"sqlite3"} CTargetDependenceOnExternalLibrary{"sqlite3", {true, true}}
}; };
T.units = { T.units = {
"main.cpp",
"initialize.cpp", "initialize.cpp",
"run.cpp", "run.cpp",
"str_fields_check.cpp", "str_fields.cpp",
"find_db.cpp", "find_db.cpp",
"sqlite3_wrapper.cpp", "sqlite3_wrapper.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) for (std::string& u: T.units)
u = "web_chat/iu9_ca_web_chat_service/" + u; u = "web_chat/iu9_ca_web_chat_service/" + u;
T.include_pr = "web_chat"; T.include_pr = "web_chat";
T.installation_dir = "";
my_targets.push_back(T); my_targets.push_back(T);
} }
{ CTarget T{"iu9-ca-web-chat-admin-cli", "executable"}; { CTarget T{"iu9-ca-web-chat-admin-cli", "executable"};
@ -177,9 +196,7 @@ int main(int argc, char** argv) {
} }
NormalCBuildSystemCommandMeaning cmd; NormalCBuildSystemCommandMeaning cmd;
regular_bs_cli_cmd_interpret(args, cmd); regular_bs_cli_cmd_interpret(args, cmd);
const char* BS_SCRIPT_TYPE = getenv("BSCRIPT_TYPE"); CAWebChat bs(cmd);
const char* BS_SCRIPT_TESTS = getenv("BSCRIPT_TESTS");
CAWebChat bs(BS_SCRIPT_TYPE ? BS_SCRIPT_TYPE : "release", (bool)BS_SCRIPT_TESTS, cmd);
if (cmd.need_to_build) if (cmd.need_to_build)
complete_tasks_of_build_units(bs.runlevel_1); complete_tasks_of_build_units(bs.runlevel_1);
umask(~0755); umask(~0755);

View File

@ -0,0 +1,83 @@
#include "cookies.h"
#include "../baza_inter.h"
namespace een9 {
bool isSPACE(char ch) {
return ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n';
}
bool isToken(const std::string &str) {
for (char ch : str) {
if (!(('a' <= ch && ch <= 'z') || ('A' <= ch && ch <= 'Z') || ('0' <= ch && ch <= '9')
|| 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 !str.empty();
}
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_ch = [&](char sch) -> std::string {
size_t S = pos;
while (hv.size() > pos && !isSPACE(hv[pos]) && hv[pos] != sch)
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()) {
std::string name_of_pechenye = read_to_space_or_ch('=');
ASSERT(isCookieName(name_of_pechenye), "Incorrect Cookie name");
skip_ows();
ASSERT(isThis('='), "Incorrect Cookie header line, missing =");
pos++;
skip_ows();
std::string value_of_pechenye;
if (isThis('"')) {
pos++;
value_of_pechenye = read_to_space_or_ch('"');
ASSERT(isThis('"'), "Incorrect Cookie header line, missing \"");
pos++;
} else {
value_of_pechenye = read_to_space_or_ch('"');;
}
ASSERT(isCookieValue(value_of_pechenye), "Incorrect Cookie value");
if (result.empty())
result.emplace_back();
result.back().first = std::move(name_of_pechenye);
result.back().second = std::move(value_of_pechenye);
skip_ows();
}
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");
}
}
}

View File

@ -0,0 +1,22 @@
#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);
/* 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 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,16 @@
#include "find_db.h"
namespace iu9cawebchat{
int find_db_sqlite_file_path(const json::JSON& config, std::string& res_path) {
const json::JSON& type = config["database"]["type"].g();
if (!type.isString() && type.asString() == "sqlite3")
return -1;
const json::JSON& path = config["database"]["file"].g();
if (!path.isString())
return -1;
if (path.asString().empty() || path.asString()[0] == ':')
return -1;
res_path = path.asString();
return 0;
}
}

View File

@ -3,6 +3,8 @@
#include <jsonincpp/jsonobj.h> #include <jsonincpp/jsonobj.h>
int find_db_sqlite_file_path(const json::JSON& config, std::string& res_path); namespace iu9cawebchat {
int find_db_sqlite_file_path(const json::JSON& config, std::string& res_path);
}
#endif #endif

View File

@ -0,0 +1,78 @@
#include "actions.h"
#include <engine_engine_number_9/baza_throw.h>
#include "str_fields.h"
#include <sqlite3.h>
#include <engine_engine_number_9/os_utils.h>
#include "find_db.h"
#include <unistd.h>
#include <assert.h>
#include "sqlite3_wrapper.h"
namespace iu9cawebchat {
void initialize_website(const json::JSON& config, const std::string& root_pw) {
printf("Initialization...\n");
een9_ASSERT(check_password(root_pw), "Bad root password");
std::string db_path;
int ret;
ret = find_db_sqlite_file_path(config, db_path);
een9_ASSERT(ret == 0, "Invalid settings[\"database\"] field");
if (een9::isRegularFile(db_path)) {
// todo: plaese, don't do this
ret = unlink(db_path.c_str());
een9_ASSERT_pl(ret == 0);
}
een9_ASSERT(!een9::isRegularFile(db_path), "Database file exists prior to initialization. "
"Can't preceed withut harming existing data");
SqliteConnection conn(db_path.c_str());
assert(sqlite3_errcode(conn.hand) == SQLITE_OK);
/* Role of memeber of chat:
* 1 - admin
* 2 - regular
* 3 - read-only member
* 4 - deleted (no longer a member)
*
* If user.id is 0, it is a root user
* If chat.lastMsgId is NULL, chat is empty
* If message.previous is NULL, this message is first in it's chat
*/
sqlite_single_statement(conn.hand, "PRAGMA foreign_keys = true");
sqlite_single_statement(conn.hand, "BEGIN");
sqlite_single_statement(conn.hand, "CREATE TABLE `nickname` (`it` TEXT PRIMARY KEY NOT NULL) WITHOUT ROWID");
sqlite_single_statement(conn.hand, "CREATE TABLE `user` ("
"`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,"
"`nickname` TEXT UNIQUE REFERENCES `nickname` NOT NULL,"
"`name` TEXT NOT NULL,"
"`chatList_HistoryId` INTEGER NOT NULL,"
"`password` TEXT NOT NULL"
")");
sqlite_single_statement(conn.hand, "CREATE TABLE `chat` ("
"`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,"
"`nickname` TEXT UNIQUE REFERENCES `nickname` NOT NULL,"
"`name` TEXT NOT NULL,"
"`it_HistoryId` INTEGER NOT NULL,"
"`lastMsgId` INTEGER REFERENCES `message`"
")");
sqlite_single_statement(conn.hand, "CREATE TABLE `user_chat_membership` ("
"`userId` INTEGER REFERENCES `user` NOT NULL,"
"`chatId` INTEGER REFERENCES `chat` NOT NULL,"
"`user_chatList_IncHistoryId` INTEGER NOT NULL,"
"`chat_IncHistoryId` INTEGER NOT NULL,"
"`role` INTEGER NOT NULL"
")");
sqlite_single_statement(conn.hand, "CREATE TABLE `message` ("
"`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,"
"`chatId` INTEGER REFERENCES `chat` NOT NULL,"
"`previous` INTEGER REFERENCES `message`,"
"`senderUserId` INTEGER REFERENCES `user` NOT NULL,"
"`exists` BOOLEAN NOT NULL,"
"`isSystem` BOOLEAN NOT NULL,"
"`text` TEXT NOT NULL,"
"`chat_IncHistoryId` INTEGER NOT NULL"
")");
sqlite_single_statement(conn.hand, "INSERT INTO `nickname` VALUES (?1)", {}, {{1, "root"}});
sqlite_single_statement(conn.hand, "INSERT INTO `user` (`id`, `nickname`, `name`, `chatList_HistoryId`, `password`) VALUES "
"(0, ?1, ?2, 0, ?3)", {},
{{1, "root"}, {2, "Rootov Root Rootovich"}, {3, root_pw}});
sqlite_single_statement(conn.hand, "END");
}
}

View File

@ -0,0 +1,140 @@
#include "actions.h"
#include <engine_engine_number_9/baza_throw.h>
#include <engine_engine_number_9/running_mainloop.h>
#include <engine_engine_number_9/http_structures/response_gen.h>
#include <signal.h>
#include <engine_engine_number_9/connecting_assets/static_asset_manager.h>
#include <assert.h>
#include <engine_engine_number_9/form_data_structure/urlencoded_query.h>
#include <new_york_transit_line/templater.h>
#include <sqlite3.h>
#include <engine_engine_number_9/socket_address.h>
#include "sqlite3_wrapper.h"
#include "str_fields.h"
#include "find_db.h"
namespace iu9cawebchat {
bool termination = false;
void sigterm_action(int) {
termination = true;
}
void run_website(const json::JSON& config) {
een9_ASSERT(config["assets"].g().isString(), "config[\"assets\"] is not string");
std::string assets_dir = config["assets"].g().asString();
een9_ASSERT(een9::isDirectory(assets_dir), "\"" + assets_dir + "\" is not a directory");
een9::StaticAssetManagerSlaveModule samI;
samI.update({
een9::StaticAssetManagerRule{assets_dir + "/css", "/assets/css", {{".css", "text/css"}} },
een9::StaticAssetManagerRule{assets_dir + "/js", "/assets/js", {{".js", "text/javascript"}} },
een9::StaticAssetManagerRule{assets_dir + "/img", "/assets/img", {
{".jpg", "image/jpg"}, {".png", "image/png"}, {".svg", "image/svg+xml"}
} },
});
const json::JSON& config_presentation = config["presentation"].g();
int64_t slave_number = config["server"]["workers"].g().asInteger().get_int();
een9_ASSERT(slave_number > 0 && slave_number <= 200, "E");
std::string sqlite_db_path;
int ret = find_db_sqlite_file_path(config, sqlite_db_path);
een9_ASSERT(ret == 0, "Can't find database file");
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::vector<WorkerGuestData> worker_guest_data(slave_number);
for (int i = 0; i < slave_number; i++) {
worker_guest_data[i].templater = std::make_unique<nytl::Templater>(
nytl::TemplaterSettings{nytl::TemplaterDetourRules{assets_dir + "/HypertextPages"}});
worker_guest_data[i].templater->update();
worker_guest_data[i].db = std::make_unique<SqliteConnection>(sqlite_db_path);
}
een9::MainloopParameters params;
params.guest_core = [&samI, &worker_guest_data, config_presentation]
(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());
WorkerGuestData& wgd = worker_guest_data[worker_id];
nytl::Templater& templater = *wgd.templater;
een9::StaticAsset sa;
int ret;
auto rteee = [&](const std::string& el_name, bool pass_phr) -> std::string {
std::string page = templater.render(el_name,
pass_phr ? std::vector<const json::JSON*>{&config_presentation} : std::vector<const json::JSON*>{});
return een9::form_http_server_response_200("text/html", page);
};
if (req.uri_path == "/" || req.uri_path == "/list-rooms") {
return rteee("list-rooms", false);
}
if (req.uri_path == "/chat") {
return rteee("chat", false);
}
if (req.uri_path == "/profile") {
return rteee("profile", false);
}
if (req.uri_path == "/registration") {
return rteee("registration", false);
}
/* Trying to interpret request as asset lookup */
ret = samI.get_asset(req.uri_path, sa);
if (ret >= 0) {
return een9::form_http_server_response_200(sa.type, sa.content);
}
return een9::form_http_server_response_404("text/html", "<h1> Not found! </h1>");
};
params.guest_core_admin_control = [&worker_guest_data]
(const een9::SlaveTask& task, const std::string& req, een9::worker_id_t worker_id) -> std::string {
een9_ASSERT_pl(0 <= worker_id && worker_id < worker_guest_data.size());
WorkerGuestData& wgd = worker_guest_data[worker_id];
try {
if (req == "hello") {
return ":0 omg! hiii!! Hewwou :3 !!!!";
}
if (req == "8") {
termination = true;
return "Bye";
}
std::string updaterootpw_pref = "updaterootpw";
if (een9::beginsWith(req, "updaterootpw")) {
size_t nid = updaterootpw_pref.size();
if (nid >= req.size() || !isSPACE(req[nid]))
return "Bad command syntax. Missing whitespace";
std::string new_password = req.substr(nid + 1);
if (!check_password(new_password))
een9_THROW("Bad password");
sqlite_single_statement(wgd.db->hand,
"UPDATE `user` SET `password` = ?1 WHERE `id` = 0 ",
{}, {{1, new_password}});
return "Successul update";
}
return "Incorrect command";
} catch (std::exception& e) {
return std::string("Server error\n") + e.what();
}
};
params.slave_number = slave_number;
een9::SocketAddressParser sap;
auto translate_addr_list_conf = [&sap](std::vector<een9::SocketAddress>& dest, const std::vector<json::JSON>& source) {
size_t N = source.size();
dest.resize(N);
for (size_t i = 0; i < N; i++)
een9::parse_socket_address(source[i].asString(), dest[i], sap);
};
translate_addr_list_conf(params.client_regular_listened, config["server"]["http-listen"].g().asArray());
translate_addr_list_conf(params.admin_control_listened, config["server"]["admin-command-listen"].g().asArray());
signal(SIGINT, sigterm_action);
signal(SIGTERM, sigterm_action);
een9::electric_boogaloo(params, termination);
}
}

View File

@ -0,0 +1,94 @@
#include "sqlite3_wrapper.h"
#include "str_fields.h"
#include <engine_engine_number_9/baza_throw.h>
#include <assert.h>
#include <limits.h>
namespace iu9cawebchat {
SqliteConnection::SqliteConnection(const std::string &file_path) {
int ret = sqlite3_open(file_path.c_str(), &hand);
if (ret != 0) {
een9_THROW(std::string("Can't open sqlite3 database ") + sqlite3_errstr(ret));
}
}
SqliteConnection::~SqliteConnection() {
if (sqlite3_close(hand) != 0) {abort();}
}
void sqlite_single_statement(sqlite3* db_hand, const std::string& req_statement,
const std::vector<std::pair<int, int64_t>>& int64_binds,
const std::vector<std::pair<int, std::string>>& text8_binds) {
sqlite3_stmt* stmt_obj = NULL;
int ret = sqlite3_prepare_v2(db_hand, req_statement.c_str(), -1, &stmt_obj, NULL);
if (ret != 0) {
int err_pos = sqlite3_error_offset(db_hand);
een9_THROW("Compilation of request\n" + req_statement + "\nfailed" +
((err_pos >= 0) ? " with offset " + std::to_string(err_pos) : ""));
}
een9_ASSERT(ret == 0, "Can't compile request expression");
assert(sqlite3_errcode(db_hand) == SQLITE_OK);
struct Guard1{sqlite3_stmt*& r; ~Guard1(){if (sqlite3_finalize(r) != 0) {abort();}}} guard1{stmt_obj};
for (const std::pair<int, int64_t>& bv: int64_binds) {
ret = sqlite3_bind_int64(stmt_obj, bv.first, bv.second);
een9_ASSERT(ret, "Can't bind to parameter #" + std::to_string(bv.first));
}
for (const std::pair<int, std::string>& bv: text8_binds) {
een9_ASSERT(is_orthodox_string(bv.second), "Can't bind this string to parameter");
een9_ASSERT(bv.second.size() + 1 < INT_MAX, "Ah, oh, senpai, your string is toooo huge");
ret = sqlite3_bind_text(stmt_obj, bv.first, bv.second.c_str(), (int)bv.second.size(), SQLITE_STATIC);
}
while (true) {
ret = sqlite3_step(stmt_obj);
if (ret == SQLITE_DONE)
break;
if (ret != SQLITE_ROW) {
printf("sqlite_row error!!!\n");
printf("%s\n", sqlite3_errmsg(db_hand));
break;
}
int cc = sqlite3_column_count(stmt_obj);
std::vector<int> types(cc);
for (int i = 0; i < cc; i++) {
types[i] = sqlite3_column_type(stmt_obj, i);
}
printf("Column: |");
for (int i = 0; i < cc; i++) {
switch (types[i]) {
#define ccase(tname) case SQLITE_ ## tname: printf(" " #tname " |"); break;
ccase(INTEGER)
ccase(FLOAT)
ccase(BLOB)
ccase(NULL)
case SQLITE3_TEXT:
printf(" TEXT |"); break;
default:
een9_THROW("AAAAAA");
}
}
printf("\n");
printf("Values: | ");
for (int i = 0; i < cc; i++) {
if (types[i] == SQLITE_INTEGER) {
printf("%lld | ", sqlite3_column_int64(stmt_obj, i));
} else if (types[i] == SQLITE_FLOAT) {
printf("%lf | ", sqlite3_column_double(stmt_obj, i));
} else if (types[i] == SQLITE_BLOB) {
const void* blob = sqlite3_column_blob(stmt_obj, i);
een9_ASSERT(sqlite3_errcode(db_hand) == SQLITE_OK, "oom in sqlite3_column_blob");
size_t sz = sqlite3_column_bytes(stmt_obj, i);
printf("Blob of size %lu | ", sz);
} else if (types[i] == SQLITE_NULL) {
printf("NULL | ");
} else {
const unsigned char* text = sqlite3_column_text(stmt_obj, i);
een9_ASSERT(text, "oom in sqlite3_column_text");
printf("%s | ", (const char*)text);
// todo: THIS F. B.S. IS NOT SAFE
}
}
printf("\n");
}
printf("Request steps are done\n");
}
}

View File

@ -0,0 +1,23 @@
#ifndef IU9_CA_WEB_CHAT_SERVICE_SQLITE_WRAP_H
#define IU9_CA_WEB_CHAT_SERVICE_SQLITE_WRAP_H
#include <sqlite3.h>
#include <vector>
#include <string>
namespace iu9cawebchat {
struct SqliteConnection {
sqlite3* hand = NULL;
SqliteConnection() = default;
explicit SqliteConnection(const std::string& file_path);
SqliteConnection (const SqliteConnection&) = delete;
SqliteConnection& operator= (const SqliteConnection&) = delete;
~SqliteConnection();
};
void sqlite_single_statement(sqlite3* db_hand, const std::string& req_statement,
const std::vector<std::pair<int, int64_t>>& int64_binds= {},
const std::vector<std::pair<int, std::string>>& text8_binds = {});
}
#endif

View File

@ -0,0 +1,141 @@
#include "str_fields.h"
#include <jsonincpp/utf8.h>
#include <engine_engine_number_9/baza_throw.h>
namespace iu9cawebchat {
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 isSPACE(char ch) {
return ch == ' ' || ch == '\r' || ch == '\t' || ch == '\n';
}
bool is_orthodox_string(const std::string &str) {
for (char ch: str)
if (ch == 0)
return false;
return json::isUtf8String(str);
}
bool check_password(const std::string &pwd) {
return is_orthodox_string(pwd) && pwd.size() >= 8;
}
bool check_name(const std::string &name) {
return is_orthodox_string(name);
}
bool check_nickname(const std::string &nickname) {
if (nickname.empty())
return false;
for (char ch: nickname) {
if (!isUNCHAR(ch))
return false;
}
return true;
}
/* Yeah baby, it's base64 time!!! */
static char encoding_table[] = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
'w', 'x', 'y', 'z', '0', '1', '2', '3',
'4', '5', '6', '7', '8', '9', '+', '/'};
static uint8_t decoding_table[256] = {
69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69,
69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69,
69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 62, 69, 69, 69, 63,
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 69, 69, 69, 69, 69, 69,
69, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 69, 69, 69, 69, 69,
69, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 69, 69, 69, 69, 69,
69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69,
69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69,
69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69,
69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69,
69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69,
69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69,
69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69,
69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69,
};
std::string base64_encode(const std::string& source) {
std::string result;
static const size_t szr2noes[3] = {0, 2, 1};
size_t noes = szr2noes[source.size() % 3];
size_t triplets = source.size() / 3;
result.reserve((triplets << 2) + noes);
size_t bt = 0;
for (size_t i = 0; i < triplets; i++) {
result.push_back(encoding_table[(uint8_t)source[bt] >> 2]);
result.push_back(encoding_table[(((uint8_t)source[bt] & 0x03) << 4) | ((uint8_t)source[bt + 1] >> 4)]);
result.push_back(encoding_table[(((uint8_t)source[bt + 1] & 0x0f) << 2) | ((uint8_t)source[bt + 2] >> 6)]);
result.push_back(encoding_table[(uint8_t)source[bt + 2] & 0x3f]);
bt += 3;
}
if (noes == 1) {
result.push_back(encoding_table[(uint8_t)source[bt] >> 2]);
result.push_back(encoding_table[(((uint8_t)source[bt] & 0x03) << 4) | ((uint8_t)source[bt + 1] >> 4)]);
result.push_back(encoding_table[((uint8_t)source[bt + 1] & 0x0f) << 2]);
result.push_back('=');
} else if (noes == 2) {
result.push_back(encoding_table[(uint8_t)source[bt] >> 2]);
result.push_back(encoding_table[((uint8_t)source[bt] & 0x03) << 4]);
result.push_back('=');
result.push_back('=');
}
return result;
}
std::string base64_decode(const std::string& source) {
#define myMsg "Bad base64 string. Can't decode"
een9_ASSERT((source.size() & 0x3) == 0, myMsg);
if (source.empty())
return "";
size_t fm = (source.size() >> 2) * 3;
size_t noes = 0;
if (*(source.end() - 2) == '=') {
noes = 2;
een9_ASSERT(source.back() == '=', myMsg);
} else if (source.back() == '=')
noes = 1;
for (size_t i = 0; i + noes < source.size(); i++)
een9_ASSERT(decoding_table[(uint8_t)source[i]] < 64, myMsg);
std::string result;
static const size_t noes2ab[3] = {0, 2, 1};
result.reserve(fm + noes2ab[noes]);
size_t naah = 0;
for (; naah < source.size(); naah += 4) {
result.push_back((char)((decoding_table[source[naah]] << 2) | (decoding_table[source[naah + 1]] >> 4)));
if (naah + 4 == source.size() && noes == 2) {
een9_ASSERT((decoding_table[source[naah + 1]] & 0x0f) == 0, myMsg);
break;
}
result.push_back((char)(((decoding_table[source[naah + 1]] & 0x0f) << 4) | (decoding_table[source[naah + 2]] >> 2)));
if (naah + 4 == source.size() && noes == 1) {
een9_ASSERT((decoding_table[source[naah + 2]] & 0x03) == 0, myMsg);
break;
}
result.push_back((char)(((decoding_table[source[naah + 2]] & 0x03) << 6) | decoding_table[source[naah + 3]]));
}
return result;
}
}

View File

@ -0,0 +1,25 @@
#ifndef IU9_CA_WEB_CHAT_SRC_WEB_CHAT_STR_FIELDS_CHECK_H
#define IU9_CA_WEB_CHAT_SRC_WEB_CHAT_STR_FIELDS_CHECK_H
#include <string>
#include <stdint.h>
namespace iu9cawebchat {
bool isALPHA(char ch);
bool isNUM(char ch);
bool isUNCHAR(char ch);
bool isSPACE(char ch);
bool is_orthodox_string(const std::string& str);
bool check_password(const std::string& pwd);
bool check_name(const std::string& name);
bool check_nickname(const std::string& nickname);
std::string base64_encode(const std::string& source);
/* Кусаеца */
std::string base64_decode(const std::string& source);
}
#endif

View File

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

View File

@ -1,14 +0,0 @@
#include "find_db.h"
int find_db_sqlite_file_path(const json::JSON& config, std::string& res_path) {
const json::JSON& type = config["database"]["type"].g();
if (!type.isString() && type.asString() == "sqlite3")
return -1;
const json::JSON& path = config["database"]["file"].g();
if (!path.isString())
return -1;
if (path.asString().empty() || path.asString()[0] == ':')
return -1;
res_path = path.asString();
return 0;
}

View File

@ -1,76 +0,0 @@
#include "actions.h"
#include <engine_engine_number_9/baza_throw.h>
#include "str_fields_check.h"
#include <sqlite3.h>
#include <engine_engine_number_9/os_utils.h>
#include "find_db.h"
#include <unistd.h>
#include <assert.h>
#include "sqlite3_wrapper.h"
void initialize_website(const json::JSON& config, const std::string& root_pw) {
printf("Initialization...\n");
een9_ASSERT(check_password(root_pw), "Bad root password");
std::string db_path;
int ret;
ret = find_db_sqlite_file_path(config, db_path);
een9_ASSERT(ret == 0, "Invalid settings[\"database\"] field");
if (een9::isRegularFile(db_path)) {
// todo: plaese, don't do this
ret = unlink(db_path.c_str());
een9_ASSERT_pl(ret == 0);
}
een9_ASSERT(!een9::isRegularFile(db_path), "Database file exists prior to initialization. "
"Can't preceed withut harming existing data");
SqliteConnection conn(db_path.c_str());
assert(sqlite3_errcode(conn.hand) == SQLITE_OK);
/* Role of memeber of chat:
* 1 - admin
* 2 - regular
* 3 - read-only member
* 4 - deleted (no longer a member)
*
* If user.id is 0, it is a root user
* If chat.lastMsgId is NULL, chat is empty
* If message.previous is NULL, this message is first in it's chat
*/
sqlite_single_statement(conn.hand, "PRAGMA foreign_keys = true");
sqlite_single_statement(conn.hand, "BEGIN");
sqlite_single_statement(conn.hand, "CREATE TABLE `nickname` (`it` TEXT PRIMARY KEY NOT NULL) WITHOUT ROWID");
sqlite_single_statement(conn.hand, "CREATE TABLE `user` ("
"`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,"
"`nickname` TEXT UNIQUE REFERENCES `nickname` NOT NULL,"
"`name` TEXT NOT NULL,"
"`chatList_HistoryId` INTEGER NOT NULL,"
"`password` TEXT NOT NULL"
")");
sqlite_single_statement(conn.hand, "CREATE TABLE `chat` ("
"`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,"
"`nickname` TEXT UNIQUE REFERENCES `nickname` NOT NULL,"
"`name` TEXT NOT NULL,"
"`it_HistoryId` INTEGER NOT NULL,"
"`lastMsgId` INTEGER REFERENCES `message`"
")");
sqlite_single_statement(conn.hand, "CREATE TABLE `user_chat_membership` ("
"`userId` INTEGER REFERENCES `user` NOT NULL,"
"`chatId` INTEGER REFERENCES `chat` NOT NULL,"
"`user_chatList_IncHistoryId` INTEGER NOT NULL,"
"`chat_IncHistoryId` INTEGER NOT NULL,"
"`role` INTEGER NOT NULL"
")");
sqlite_single_statement(conn.hand, "CREATE TABLE `message` ("
"`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,"
"`chatId` INTEGER REFERENCES `chat` NOT NULL,"
"`previous` INTEGER REFERENCES `message`,"
"`senderUserId` INTEGER REFERENCES `user` NOT NULL,"
"`exists` BOOLEAN NOT NULL,"
"`isSystem` BOOLEAN NOT NULL,"
"`text` TEXT NOT NULL,"
"`chat_IncHistoryId` INTEGER NOT NULL"
")");
sqlite_single_statement(conn.hand, "INSERT INTO `nickname` VALUES (?1)", {}, {{1, "root"}});
sqlite_single_statement(conn.hand, "INSERT INTO `user` (`id`, `nickname`, `name`, `chatList_HistoryId`, `password`) VALUES "
"(0, ?1, ?2, 0, ?3)", {},
{{1, "root"}, {2, "Rootov Root Rootovich"}, {3, root_pw}});
sqlite_single_statement(conn.hand, "END");
}

View File

@ -1,138 +0,0 @@
#include "actions.h"
#include <engine_engine_number_9/baza_throw.h>
#include <engine_engine_number_9/running_mainloop.h>
#include <engine_engine_number_9/http_structures/response_gen.h>
#include <signal.h>
#include <engine_engine_number_9/connecting_assets/static_asset_manager.h>
#include <assert.h>
#include <engine_engine_number_9/form_data_structure/urlencoded_query.h>
#include <new_york_transit_line/templater.h>
#include <sqlite3.h>
#include <engine_engine_number_9/socket_address.h>
#include "sqlite3_wrapper.h"
#include "str_fields_check.h"
#include "find_db.h"
bool termination = false;
void sigterm_action(int) {
termination = true;
}
void run_website(const json::JSON& config) {
een9_ASSERT(config["assets"].g().isString(), "config[\"assets\"] is not string");
std::string assets_dir = config["assets"].g().asString();
een9_ASSERT(een9::isDirectory(assets_dir), "\"" + assets_dir + "\" is not a directory");
een9::StaticAssetManagerSlaveModule samI;
samI.update({
een9::StaticAssetManagerRule{assets_dir + "/css", "/assets/css", {{".css", "text/css"}} },
een9::StaticAssetManagerRule{assets_dir + "/js", "/assets/js", {{".js", "text/js"}} },
een9::StaticAssetManagerRule{assets_dir + "/img", "/assets/img", {
{".jpg", "image/jpg"}, {".png", "image/png"}, {".svg", "image/svg+xml"}
} },
});
const json::JSON& config_presentation = config["presentation"].g();
int64_t slave_number = config["server"]["workers"].g().asInteger().get_int();
een9_ASSERT(slave_number > 0 && slave_number <= 200, "E");
std::string sqlite_db_path;
int ret = find_db_sqlite_file_path(config, sqlite_db_path);
een9_ASSERT(ret == 0, "Can't find database file");
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::vector<WorkerGuestData> worker_guest_data(slave_number);
for (int i = 0; i < slave_number; i++) {
worker_guest_data[i].templater = std::make_unique<nytl::Templater>(
nytl::TemplaterSettings{nytl::TemplaterDetourRules{assets_dir + "/HypertextPages"}});
worker_guest_data[i].templater->update();
worker_guest_data[i].db = std::make_unique<SqliteConnection>(sqlite_db_path);
}
een9::MainloopParameters params;
params.guest_core = [&samI, &worker_guest_data, config_presentation]
(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());
WorkerGuestData& wgd = worker_guest_data[worker_id];
nytl::Templater& templater = *wgd.templater;
een9::StaticAsset sa;
int ret;
auto rteee = [&](const std::string& el_name, bool pass_phr) -> std::string {
std::string page = templater.render(el_name,
pass_phr ? std::vector<const json::JSON*>{&config_presentation} : std::vector<const json::JSON*>{});
return een9::form_http_server_response_200("text/html", page);
};
if (req.uri_path == "/" || req.uri_path == "/list-rooms") {
return rteee("list-rooms", true);
}
if (req.uri_path == "/chat") {
return rteee("chat", false);
}
if (req.uri_path == "/profile") {
return rteee("profile", false);
}
if (req.uri_path == "/registration") {
return rteee("registration", false);
}
/* Trying to interpret request as asset lookup */
ret = samI.get_asset(req.uri_path, sa);
if (ret >= 0) {
return een9::form_http_server_response_200(sa.type, sa.content);
}
return een9::form_http_server_response_404("text/html", "<h1> Not found! </h1>");
};
params.guest_core_admin_control = [&worker_guest_data]
(const een9::SlaveTask& task, const std::string& req, een9::worker_id_t worker_id) -> std::string {
een9_ASSERT_pl(0 <= worker_id && worker_id < worker_guest_data.size());
WorkerGuestData& wgd = worker_guest_data[worker_id];
try {
if (req == "hello") {
return ":0 omg! hiii!! Hewwou :3 !!!!";
}
if (req == "8") {
termination = true;
return "Bye";
}
std::string updaterootpw_pref = "updaterootpw";
if (een9::beginsWith(req, "updaterootpw")) {
size_t nid = updaterootpw_pref.size();
if (nid >= req.size() || !isSPACE(req[nid]))
return "Bad command syntax. Missing whitespace";
std::string new_password = req.substr(nid + 1);
if (!check_password(new_password))
een9_THROW("Bad password");
sqlite_single_statement(wgd.db->hand,
"UPDATE `user` SET `password` = ?1 WHERE `id` = 0 ",
{}, {{1, new_password}});
return "Successul update";
}
return "Incorrect command";
} catch (std::exception& e) {
return std::string("Server error\n") + e.what();
}
};
params.slave_number = slave_number;
een9::SocketAddressParser sap;
auto translate_addr_list_conf = [&sap](std::vector<een9::SocketAddress>& dest, const std::vector<json::JSON>& source) {
size_t N = source.size();
dest.resize(N);
for (size_t i = 0; i < N; i++)
een9::parse_socket_address(source[i].asString(), dest[i], sap);
};
translate_addr_list_conf(params.client_regular_listened, config["server"]["http-listen"].g().asArray());
translate_addr_list_conf(params.admin_control_listened, config["server"]["admin-command-listen"].g().asArray());
signal(SIGINT, sigterm_action);
signal(SIGTERM, sigterm_action);
een9::electric_boogaloo(params, termination);
}

View File

@ -2,7 +2,7 @@
#include <engine_engine_number_9/os_utils.h> #include <engine_engine_number_9/os_utils.h>
#include <assert.h> #include <assert.h>
#include <jsonincpp/string_representation.h> #include <jsonincpp/string_representation.h>
#include "actions.h" #include <iu9_ca_web_chat_lib/actions.h>
#include <stdexcept> #include <stdexcept>
void usage(char* argv0) { void usage(char* argv0) {
@ -31,9 +31,9 @@ int main(int argc, char** argv){
een9_ASSERT(ROOT_PW, "No root password specified." een9_ASSERT(ROOT_PW, "No root password specified."
"Assign desired root password value to environment variable ROOT_PW"); "Assign desired root password value to environment variable ROOT_PW");
std::string root_pw = ROOT_PW; std::string root_pw = ROOT_PW;
initialize_website(config, root_pw); iu9cawebchat::initialize_website(config, root_pw);
} else if (cmd == "run") { } else if (cmd == "run") {
run_website(config); iu9cawebchat::run_website(config);
} else } else
een9_THROW("unknown action (known are 'run', 'initialize')"); een9_THROW("unknown action (known are 'run', 'initialize')");
} catch (std::exception& e) { } catch (std::exception& e) {

View File

@ -1,92 +0,0 @@
#include "sqlite3_wrapper.h"
#include "str_fields_check.h"
#include <engine_engine_number_9/baza_throw.h>
#include <assert.h>
#include <limits.h>
SqliteConnection::SqliteConnection(const std::string &file_path) {
int ret = sqlite3_open(file_path.c_str(), &hand);
if (ret != 0) {
een9_THROW(std::string("Can't open sqlite3 database ") + sqlite3_errstr(ret));
}
}
SqliteConnection::~SqliteConnection() {
if (sqlite3_close(hand) != 0) {abort();}
}
void sqlite_single_statement(sqlite3* db_hand, const std::string& req_statement,
const std::vector<std::pair<int, int64_t>>& int64_binds,
const std::vector<std::pair<int, std::string>>& text8_binds) {
sqlite3_stmt* stmt_obj = NULL;
int ret = sqlite3_prepare_v2(db_hand, req_statement.c_str(), -1, &stmt_obj, NULL);
if (ret != 0) {
int err_pos = sqlite3_error_offset(db_hand);
een9_THROW("Compilation of request\n" + req_statement + "\nfailed" +
((err_pos >= 0) ? " with offset " + std::to_string(err_pos) : ""));
}
een9_ASSERT(ret == 0, "Can't compile request expression");
assert(sqlite3_errcode(db_hand) == SQLITE_OK);
struct Guard1{sqlite3_stmt*& r; ~Guard1(){if (sqlite3_finalize(r) != 0) {abort();}}} guard1{stmt_obj};
for (const std::pair<int, int64_t>& bv: int64_binds) {
ret = sqlite3_bind_int64(stmt_obj, bv.first, bv.second);
een9_ASSERT(ret, "Can't bind to parameter #" + std::to_string(bv.first));
}
for (const std::pair<int, std::string>& bv: text8_binds) {
een9_ASSERT(is_orthodox_string(bv.second), "Can't bind this string to parameter");
een9_ASSERT(bv.second.size() + 1 < INT_MAX, "Ah, oh, senpai, your string is toooo huge");
ret = sqlite3_bind_text(stmt_obj, bv.first, bv.second.c_str(), (int)bv.second.size(), SQLITE_STATIC);
}
while (true) {
ret = sqlite3_step(stmt_obj);
if (ret == SQLITE_DONE)
break;
if (ret != SQLITE_ROW) {
printf("sqlite_row error!!!\n");
printf("%s\n", sqlite3_errmsg(db_hand));
break;
}
int cc = sqlite3_column_count(stmt_obj);
std::vector<int> types(cc);
for (int i = 0; i < cc; i++) {
types[i] = sqlite3_column_type(stmt_obj, i);
}
printf("Column: |");
for (int i = 0; i < cc; i++) {
switch (types[i]) {
#define ccase(tname) case SQLITE_ ## tname: printf(" " #tname " |"); break;
ccase(INTEGER)
ccase(FLOAT)
ccase(BLOB)
ccase(NULL)
case SQLITE3_TEXT:
printf(" TEXT |"); break;
default:
een9_THROW("AAAAAA");
}
}
printf("\n");
printf("Values: | ");
for (int i = 0; i < cc; i++) {
if (types[i] == SQLITE_INTEGER) {
printf("%lld | ", sqlite3_column_int64(stmt_obj, i));
} else if (types[i] == SQLITE_FLOAT) {
printf("%lf | ", sqlite3_column_double(stmt_obj, i));
} else if (types[i] == SQLITE_BLOB) {
const void* blob = sqlite3_column_blob(stmt_obj, i);
een9_ASSERT(sqlite3_errcode(db_hand) == SQLITE_OK, "oom in sqlite3_column_blob");
size_t sz = sqlite3_column_bytes(stmt_obj, i);
printf("Blob of size %lu | ", sz);
} else if (types[i] == SQLITE_NULL) {
printf("NULL | ");
} else {
const unsigned char* text = sqlite3_column_text(stmt_obj, i);
een9_ASSERT(text, "oom in sqlite3_column_text");
printf("%s | ", (const char*)text);
// todo: THIS F. B.S. IS NOT SAFE
}
}
printf("\n");
}
printf("Request steps are done\n");
}

View File

@ -1,21 +0,0 @@
#ifndef IU9_CA_WEB_CHAT_SERVICE_SQLITE_WRAP_H
#define IU9_CA_WEB_CHAT_SERVICE_SQLITE_WRAP_H
#include <sqlite3.h>
#include <vector>
#include <string>
struct SqliteConnection {
sqlite3* hand = NULL;
SqliteConnection() = default;
explicit SqliteConnection(const std::string& file_path);
SqliteConnection (const SqliteConnection&) = delete;
SqliteConnection& operator= (const SqliteConnection&) = delete;
~SqliteConnection();
};
void sqlite_single_statement(sqlite3* db_hand, const std::string& req_statement,
const std::vector<std::pair<int, int64_t>>& int64_binds= {},
const std::vector<std::pair<int, std::string>>& text8_binds = {});
#endif

View File

@ -1,44 +0,0 @@
#include "str_fields_check.h"
#include <jsonincpp/utf8.h>
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 isSPACE(char ch) {
return ch == ' ' || ch == '\r' || ch == '\t' || ch == '\n';
}
bool is_orthodox_string(const std::string &str) {
for (char ch: str)
if (ch == 0)
return false;
return json::isUtf8String(str);
}
bool check_password(const std::string &pwd) {
return is_orthodox_string(pwd) && pwd.size() >= 8;
}
bool check_name(const std::string &name) {
return is_orthodox_string(name);
}
bool check_nickname(const std::string &nickname) {
if (nickname.empty())
return false;
for (char ch: nickname) {
if (!isUNCHAR(ch))
return false;
}
return true;
}

View File

@ -1,17 +0,0 @@
#ifndef IU9_CA_WEB_CHAT_SRC_WEB_CHAT_STR_FIELDS_CHECK_H
#define IU9_CA_WEB_CHAT_SRC_WEB_CHAT_STR_FIELDS_CHECK_H
#include <string>
bool isALPHA(char ch);
bool isNUM(char ch);
bool isUNCHAR(char ch);
bool isSPACE(char ch);
bool is_orthodox_string(const std::string& str);
bool check_password(const std::string& pwd);
bool check_name(const std::string& name);
bool check_nickname(const std::string& nickname);
#endif

View File

@ -0,0 +1,80 @@
#include <iu9_ca_web_chat_lib/str_fields.h>
#include <random>
#include <stdexcept>
#include <engine_engine_number_9/baza.h>
int main() {
auto test = [&](const std::string& octets) {
std::string res = base64_encode(octets);
std::string back = base64_decode(res);
if (octets != back) {
printf("Oshibka\n");
abort();
}
printf("Test passed\n");
};
std::mt19937 mt(123);
std::uniform_int_distribution<int> dist(0, 255);
auto random_test = [&](int sz) {
std::vector<int> octets(sz);
std::string s0(sz, 0);
printf("Test: ");
for (int i = 0; i < sz; i++) {
octets[i] = dist(mt);
printf("%3d ", octets[i]);
s0[i] = (char)(uint8_t)octets[i];
}
printf("\n");
std::string got1 = base64_encode(s0);
printf("Base64: %s\n", got1.c_str());
std::string back2 = base64_decode(got1);
if (s0 != back2) {
printf("Test failed!\nBack: ");
for (size_t i = 0; i < back2.size(); i++) {
printf("%3d ", (int)(uint8_t)back2[i]);
}
printf("\n");
abort();
}
printf("Test passed\n");
};
test("//");
test("/0");
test("/ ");
test(" ");
test("");
test("1");
test("22");
test("333");
test("4444");
test("55555");
test("666666");
test("7777777");
test("88888888");
test("999999999");
test("1010101010");
test("11111111111");
for (int i = 0; i < 100; i++) {
for (int j = 0; j < 20; j++) {
random_test(j);
}
}
auto rb_test = [&](size_t sz) {
std::string octets(sz, 0);
for (int i = 0; i < sz; i++)
octets[i] = (char)(uint8_t)dist(mt);
try {
std::string gr = base64_decode(octets);
printf("Hoba, that was a good one\n");
} catch (een9::ServerError& e) {
printf("Finished with error\n");
}
};
printf("Now it's time for robustness test\n");
for (int j = 0; j < 16; j++) {
for (int i = 0; i < j * j * j / 100 + j * j / 5 + j * 100; i++) {
rb_test(i);
}
}
return 0;
}