forked from chookspace/chookchat
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0d3c76086b | |||
| 6598162680 | |||
| 999a9fe346 | |||
| 1159bdebde | |||
| d61946d2ec | |||
| abd51e347c |
17
README.md
17
README.md
@@ -6,6 +6,8 @@ Features:
|
|||||||
|
|
||||||
- Username/password access (passwords secured by bcrypt2)
|
- Username/password access (passwords secured by bcrypt2)
|
||||||
- Markdown rendering
|
- Markdown rendering
|
||||||
|
- User profile pages
|
||||||
|
- Friends (direct messages system) (WIP)
|
||||||
|
|
||||||
## Building
|
## Building
|
||||||
|
|
||||||
@@ -24,3 +26,18 @@ meson compile -C builddir
|
|||||||
# run with
|
# run with
|
||||||
./builddir/chookchat
|
./builddir/chookchat
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Credits
|
||||||
|
|
||||||
|
The server uses the following libraries:
|
||||||
|
|
||||||
|
- [cpp-httplib](https://github.com/yhirose/cpp-httplib) for the web server
|
||||||
|
- [Maddy](https://github.com/progsource/maddy) for the Markdown processor
|
||||||
|
- [Bcrypt.cpp](https://github.com/hilch/Bcrypt.cpp) for password hashing/salting
|
||||||
|
- [JSON for Modern C++](https://github.com/nlohmann/json) for JSON processing
|
||||||
|
- [bin2cpp](https://github.com/end2endzone/bin2cpp) for packing site files into the binary
|
||||||
|
|
||||||
|
The following people have contributed to the project:
|
||||||
|
|
||||||
|
- [SpookyDervish](https://chookspace.com/SpookyDervish) for client navbar and notification sound
|
||||||
|
- [reallynniroprobably](https://chookspace.com/reallynniroprobably) for error message
|
||||||
|
|||||||
11
build.sh
11
build.sh
@@ -8,6 +8,13 @@ bin2cpp --file=client/header_bottom.html --output=server/src/generated
|
|||||||
bin2cpp --file=client/footer.html --output=server/src/generated
|
bin2cpp --file=client/footer.html --output=server/src/generated
|
||||||
bin2cpp --file=client/login.html --output=server/src/generated
|
bin2cpp --file=client/login.html --output=server/src/generated
|
||||||
bin2cpp --file=client/settings.html --output=server/src/generated
|
bin2cpp --file=client/settings.html --output=server/src/generated
|
||||||
|
bin2cpp --file=client/friends.html --output=server/src/generated
|
||||||
bin2cpp --file=client/e404.html --output=server/src/generated
|
bin2cpp --file=client/e404.html --output=server/src/generated
|
||||||
bin2cpp --file=client/style.css --output=server/src/generated
|
|
||||||
bin2cpp --file=client/script.js --output=server/src/generated
|
bin2cpp --file=client/style.css --output=server/src/generated
|
||||||
|
bin2cpp --file=client/style_friends.css --output=server/src/generated
|
||||||
|
|
||||||
|
bin2cpp --file=client/script.js --output=server/src/generated
|
||||||
|
bin2cpp --file=client/script_friends.js --output=server/src/generated
|
||||||
|
|
||||||
|
bin2cpp --file=client/notify.opus --output=server/src/generated
|
||||||
|
|||||||
43
client/friends.html
Normal file
43
client/friends.html
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>friends</title>
|
||||||
|
<link rel="stylesheet" type="text/css" href="/style.css">
|
||||||
|
<link rel="stylesheet" type="text/css" href="/style_friends.css">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<script src="/script.js"></script>
|
||||||
|
<script src="/script_friends.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<nav>
|
||||||
|
<div id="brand">
|
||||||
|
<h2><a href="/">chookchat</a></h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><a id="loginLink" href="/login">login</a></li>
|
||||||
|
<li><a href="/friends">friends</a></li>
|
||||||
|
<li><a href="/settings">settings</a></li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
<div id="friends">
|
||||||
|
<div id="friends-list">
|
||||||
|
<div id="friends-header">
|
||||||
|
<h1>friends</h1>
|
||||||
|
</div>
|
||||||
|
<div id="friends-list-links">
|
||||||
|
<!-- List of friends inserted here -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="friends-messenger">
|
||||||
|
<div id="friends-messages">
|
||||||
|
<!-- List of messages inserted here -->
|
||||||
|
</div>
|
||||||
|
<div id="friends-message-box">
|
||||||
|
<textarea id="friends-textarea"></textarea>
|
||||||
|
<button onclick="sendMessage()">Send</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
<li><a id="loginLink" href="/login">login</a></li>
|
<li><a id="loginLink" href="/login">login</a></li>
|
||||||
|
<li><a href="/friends">friends</a></li>
|
||||||
<li><a href="/settings">settings</a></li>
|
<li><a href="/settings">settings</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
<li><a id="loginLink" href="/login">login</a></li>
|
<li><a id="loginLink" href="/login">login</a></li>
|
||||||
|
<li><a href="/friends">friends</a></li>
|
||||||
<li><a href="/settings">settings</a></li>
|
<li><a href="/settings">settings</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
|
|||||||
BIN
client/notify.opus
Normal file
BIN
client/notify.opus
Normal file
Binary file not shown.
@@ -104,7 +104,7 @@ document.addEventListener('DOMContentLoaded', async function() {
|
|||||||
// update nav link / redirect regardless of whether a postbox exists
|
// update nav link / redirect regardless of whether a postbox exists
|
||||||
const loginLink = document.getElementById("loginLink");
|
const loginLink = document.getElementById("loginLink");
|
||||||
if (userStatus.username === "") {
|
if (userStatus.username === "") {
|
||||||
if (window.location.pathname === "/settings") {
|
if (window.location.pathname === "/settings" || window.location.pathname === "/friends") {
|
||||||
window.location.href = "/login";
|
window.location.href = "/login";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
289
client/script_friends.js
Normal file
289
client/script_friends.js
Normal file
@@ -0,0 +1,289 @@
|
|||||||
|
class Message {
|
||||||
|
sender = 0;
|
||||||
|
content = "";
|
||||||
|
|
||||||
|
constructor(sender, content) {
|
||||||
|
this.sender = sender;
|
||||||
|
this.content = content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// websocket connection
|
||||||
|
/** @type {WebSocket} */
|
||||||
|
let socket;
|
||||||
|
|
||||||
|
// array of userId's
|
||||||
|
/** @type {Array<number>} */
|
||||||
|
let connectedFriends = [];
|
||||||
|
|
||||||
|
// map of userId's to arrays of Message objects
|
||||||
|
/** @type {Map<number, Array<Message>>} */
|
||||||
|
let recievedMessages = new Map();
|
||||||
|
|
||||||
|
// map of userId's to usernames
|
||||||
|
/** @type {Map<number, string>} */
|
||||||
|
let usernames = new Map();
|
||||||
|
|
||||||
|
// map of userId's to how many unread messages there are
|
||||||
|
/** @type {Map<number, number>} */
|
||||||
|
let unreads = new Map();
|
||||||
|
|
||||||
|
// userId of the current user who is being talked to
|
||||||
|
let currentUser = 0;
|
||||||
|
|
||||||
|
// userId of the logged-in user (this client). Fetched from /me on load.
|
||||||
|
// /me responds with headers X-Logged-In, X-Username, X-Bio, X-User-ID
|
||||||
|
// (no body), so we read the id back out of the response headers.
|
||||||
|
let selfId = 0;
|
||||||
|
|
||||||
|
// notification sound, loaded after ws initialization
|
||||||
|
let sound;
|
||||||
|
|
||||||
|
async function fetchSelfId() {
|
||||||
|
try {
|
||||||
|
const res = await fetch("/me");
|
||||||
|
if (!res.ok) {
|
||||||
|
console.error("Failed to fetch /me:", res.status);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const loggedIn = res.headers.get("X-Logged-In");
|
||||||
|
if (loggedIn !== "true") {
|
||||||
|
console.log("Not logged in");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const idHeader = res.headers.get("X-User-ID");
|
||||||
|
selfId = parseInt(idHeader, 10);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Error fetching /me:", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number} id
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
async function fetchUsername(id) {
|
||||||
|
try {
|
||||||
|
const res = await fetch("/userinfo/" + id);
|
||||||
|
if (!res.ok) {
|
||||||
|
console.error("Failed to fetch /userinfo/" + id + " :", res.status, await res.text());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const usernameHeader = res.headers.get("X-Username");
|
||||||
|
usernames.set(id, usernameHeader);
|
||||||
|
return usernameHeader;
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Error fetching /me:", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {number} id
|
||||||
|
* @returns {Array<Message>}
|
||||||
|
*/
|
||||||
|
async function getMessageHistory(id) {
|
||||||
|
try {
|
||||||
|
const res = await fetch("/friends/chatHistory/" + id);
|
||||||
|
if (!res.ok) {
|
||||||
|
console.error("Failed to fetch /friends/chatHistory/" + id + " :", res.status, await res.text());
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const json = await res.json();
|
||||||
|
// the server wraps the array in { "messages": [...] }, not a bare array
|
||||||
|
return json.messages.map(m => new Message(m.sender, m.content));
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Error fetching /me:", err);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
fetchSelfId();
|
||||||
|
|
||||||
|
if (typeof Notification !== "undefined") {
|
||||||
|
Notification.requestPermission();
|
||||||
|
}
|
||||||
|
|
||||||
|
// create a websocket
|
||||||
|
socket = new WebSocket("wss://" + document.location.hostname + ":" + document.location.port + "/friends/ws");
|
||||||
|
|
||||||
|
socket.onopen = function() {
|
||||||
|
console.log("connected to server!");
|
||||||
|
setInterval(() => { // ping the server every 5 seconds
|
||||||
|
if (socket.readyState === WebSocket.OPEN) {
|
||||||
|
socket.send(JSON.stringify({ type: 3, userId: 0 }));
|
||||||
|
}
|
||||||
|
}, 5000);
|
||||||
|
};
|
||||||
|
|
||||||
|
socket.onmessage = async function(event) {
|
||||||
|
console.log("recieved data", event.data);
|
||||||
|
const data = JSON.parse(event.data);
|
||||||
|
switch (data.type) {
|
||||||
|
case "ok": {
|
||||||
|
console.log("Server is ok");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "error": {
|
||||||
|
console.error("Server sent an error:", data.content);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "message": {
|
||||||
|
console.log("message from", data.userId, ":", data.content);
|
||||||
|
// update the messages variable
|
||||||
|
if (recievedMessages.get(data.userId) === undefined) {
|
||||||
|
// get previously recieved messages
|
||||||
|
recievedMessages.set(data.userId, await getMessageHistory(data.userId));
|
||||||
|
}
|
||||||
|
recievedMessages.get(data.userId).push(new Message(data.userId, data.content));
|
||||||
|
if (currentUser === data.userId) {
|
||||||
|
updateMessages();
|
||||||
|
}
|
||||||
|
// send a notification to the user
|
||||||
|
if (document.hidden || currentUser != data.userId) {
|
||||||
|
if (Notification !== undefined) {
|
||||||
|
new Notification(usernames.get(data.userId), {body: data.content});
|
||||||
|
}
|
||||||
|
if (audio !== undefined) {
|
||||||
|
audio.play();
|
||||||
|
}
|
||||||
|
const currentUnreads = unreads.get(data.userId);
|
||||||
|
if (currentUnreads === undefined) {
|
||||||
|
unreads.set(data.userId, 1);
|
||||||
|
} else {
|
||||||
|
unreads.set(data.userId, currentUnreads + 1);
|
||||||
|
}
|
||||||
|
updateOnlineUsers();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "users": {
|
||||||
|
console.log("updating user list");
|
||||||
|
connectedFriends = data.content;
|
||||||
|
updateOnlineUsers();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.onerror = function(error) {
|
||||||
|
console.log(error);
|
||||||
|
alert("There was an error with the socket! Check console logs for details. Refresh the page to reconnect");
|
||||||
|
socket.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.onclose = function(event) {
|
||||||
|
console.log("disconnected from server");
|
||||||
|
}
|
||||||
|
|
||||||
|
audio = new Audio();
|
||||||
|
audio.preload = "auto";
|
||||||
|
audio.src = "/notify.opus";
|
||||||
|
document.body.appendChild(audio);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
async function updateOnlineUsers() {
|
||||||
|
const friendsList = document.getElementById("friends-list-links");
|
||||||
|
friendsList.innerHTML = "";
|
||||||
|
|
||||||
|
// Get everyone's username
|
||||||
|
for (const user of connectedFriends) {
|
||||||
|
if (usernames.get(user) === undefined) {
|
||||||
|
await fetchUsername(user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const user of connectedFriends) {
|
||||||
|
const newElement = document.createElement("p");
|
||||||
|
const userUnreads = unreads.get(user);
|
||||||
|
if (userUnreads !== undefined) {
|
||||||
|
newElement.textContent = "[" + userUnreads + "] ";
|
||||||
|
} else {
|
||||||
|
newElement.textContent = "";
|
||||||
|
}
|
||||||
|
newElement.textContent += usernames.get(user);
|
||||||
|
newElement.onclick = function() {
|
||||||
|
openUser(user);
|
||||||
|
}
|
||||||
|
friendsList.appendChild(newElement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param {Message} message */
|
||||||
|
function renderMessage(message) {
|
||||||
|
const messagesList = document.getElementById("friends-messages");
|
||||||
|
const newElement = document.createElement("p");
|
||||||
|
const who = message.sender === selfId ? "You" : usernames.get(message.sender);
|
||||||
|
newElement.textContent = who + ": " + message.content;
|
||||||
|
messagesList.appendChild(newElement);
|
||||||
|
messagesList.scrollTop = messagesList.scrollHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateMessages() {
|
||||||
|
const messages = recievedMessages.get(currentUser);
|
||||||
|
if (messages === undefined || messages.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const message = messages[messages.length - 1];
|
||||||
|
renderMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param {number} user */
|
||||||
|
async function openUser(user) {
|
||||||
|
currentUser = user;
|
||||||
|
|
||||||
|
const messagesList = document.getElementById("friends-messages");
|
||||||
|
messagesList.innerHTML = "";
|
||||||
|
|
||||||
|
if (recievedMessages.get(user) === undefined) {
|
||||||
|
recievedMessages.set(user, await getMessageHistory(user));
|
||||||
|
}
|
||||||
|
|
||||||
|
// another openUser() call may have run while we were awaiting, and
|
||||||
|
// switched the user again - bail out if we're no longer viewing this one
|
||||||
|
if (currentUser !== user) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const messages = recievedMessages.get(user);
|
||||||
|
for (const message of messages) {
|
||||||
|
renderMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
unreads.delete(user);
|
||||||
|
updateOnlineUsers();
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendMessage() {
|
||||||
|
if (currentUser === 0) {
|
||||||
|
alert("Select a friend to message first");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const textarea = document.getElementById("friends-textarea");
|
||||||
|
const content = textarea.value.trim();
|
||||||
|
if (content === "") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (socket.readyState !== WebSocket.OPEN) {
|
||||||
|
alert("Not connected to the server. Refresh the page and try again.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.send(JSON.stringify({ type: 0, userId: currentUser, content: content }));
|
||||||
|
|
||||||
|
// optimistically show the message locally, since the server only
|
||||||
|
// relays messages between the two other parties, not back to the sender
|
||||||
|
if (recievedMessages.get(currentUser) === undefined) {
|
||||||
|
recievedMessages.set(currentUser, []);
|
||||||
|
}
|
||||||
|
const sentMessage = new Message(selfId, content);
|
||||||
|
recievedMessages.get(currentUser).push(sentMessage);
|
||||||
|
renderMessage(sentMessage);
|
||||||
|
|
||||||
|
textarea.value = "";
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>posts</title>
|
<title>settings</title>
|
||||||
<link rel="stylesheet" type="text/css" href="/style.css">
|
<link rel="stylesheet" type="text/css" href="/style.css">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<script src="/script.js"></script>
|
<script src="/script.js"></script>
|
||||||
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
<li><a id="loginLink" href="/login">login</a></li>
|
<li><a id="loginLink" href="/login">login</a></li>
|
||||||
|
<li><a href="/friends">friends</a></li>
|
||||||
<li><a href="/settings">settings</a></li>
|
<li><a href="/settings">settings</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
|
|||||||
@@ -103,3 +103,4 @@ nav {
|
|||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
113
client/style_friends.css
Normal file
113
client/style_friends.css
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
#friends {
|
||||||
|
display: flex;
|
||||||
|
height: calc(100vh - 60px);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
#friends-list {
|
||||||
|
width: 280px;
|
||||||
|
min-width: 280px;
|
||||||
|
border-right: 1px solid lime;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
#friends-header {
|
||||||
|
padding: 16px;
|
||||||
|
border-bottom: 1px solid lime;
|
||||||
|
}
|
||||||
|
|
||||||
|
#friends-header h1 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1.3rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#friends-list-links {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 8px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#friends-list-links p {
|
||||||
|
margin: 0;
|
||||||
|
padding: 10px 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
border-left: 2px solid transparent;
|
||||||
|
transition: background-color 0.15s, border-color 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
#friends-list-links p:hover {
|
||||||
|
background-color: rgba(0, 255, 0, 0.1);
|
||||||
|
border-left-color: lime;
|
||||||
|
}
|
||||||
|
|
||||||
|
#friends-messenger {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
#friends-messages {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 16px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#friends-messages p {
|
||||||
|
margin: 0;
|
||||||
|
padding: 4px 0;
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
#friends-messages p:empty {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#friends-message-box {
|
||||||
|
position: static;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 12px 16px;
|
||||||
|
border-top: 1px solid lime;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
#friends-message-box textarea {
|
||||||
|
flex: 1;
|
||||||
|
resize: none;
|
||||||
|
height: 40px;
|
||||||
|
padding: 8px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
#friends-message-box button {
|
||||||
|
padding: 0 20px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
#friends {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
#friends-list {
|
||||||
|
width: 100%;
|
||||||
|
min-width: unset;
|
||||||
|
max-height: 200px;
|
||||||
|
border-right: none;
|
||||||
|
border-bottom: 1px solid lime;
|
||||||
|
}
|
||||||
|
|
||||||
|
#friends-message-box {
|
||||||
|
position: static;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,8 +10,12 @@ sources = [
|
|||||||
'src/generated/footer.cpp',
|
'src/generated/footer.cpp',
|
||||||
'src/generated/login.cpp',
|
'src/generated/login.cpp',
|
||||||
'src/generated/settings.cpp',
|
'src/generated/settings.cpp',
|
||||||
|
'src/generated/friends.cpp',
|
||||||
|
'src/generated/script_friends.cpp',
|
||||||
'src/generated/style.cpp',
|
'src/generated/style.cpp',
|
||||||
|
'src/generated/style_friends.cpp',
|
||||||
'src/generated/script.cpp',
|
'src/generated/script.cpp',
|
||||||
|
'src/generated/notify.cpp',
|
||||||
|
|
||||||
# bcrypt
|
# bcrypt
|
||||||
'src/bcrypt/bcrypt.cpp',
|
'src/bcrypt/bcrypt.cpp',
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#include "db.h"
|
#include "db.h"
|
||||||
|
#include <cstdint>
|
||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
@@ -60,6 +61,14 @@ Database::Database(const std::string& path) {
|
|||||||
postid INTEGER NOT NULL,
|
postid INTEGER NOT NULL,
|
||||||
userid INTEGER NOT NULL
|
userid INTEGER NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS messages (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
senderid INTEGER NOT NULL,
|
||||||
|
recieverid INTEGER NOT NULL,
|
||||||
|
timestamp INTEGER NOT NULL,
|
||||||
|
text TEXT NOT NULL
|
||||||
|
);
|
||||||
)";
|
)";
|
||||||
|
|
||||||
char* errmsg = nullptr;
|
char* errmsg = nullptr;
|
||||||
@@ -448,3 +457,69 @@ void Database::invalidateUserSessions(const User& user) {
|
|||||||
|
|
||||||
sqlite3_finalize(stmt);
|
sqlite3_finalize(stmt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
std::vector<Message> Database::getMessages(uint64_t userA, uint64_t userB, uint64_t amount) {
|
||||||
|
const char* sql = R"(
|
||||||
|
SELECT * FROM messages
|
||||||
|
WHERE (senderid = ? AND recieverid = ?) OR (senderid = ? AND recieverid = ?)
|
||||||
|
ORDER BY timestamp DESC
|
||||||
|
LIMIT ?;
|
||||||
|
)";
|
||||||
|
|
||||||
|
sqlite3_stmt* stmt;
|
||||||
|
if (sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr) != SQLITE_OK) {
|
||||||
|
throw std::runtime_error("sqlite3 error: " + std::string(sqlite3_errmsg(db)));
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlite3_bind_int64(stmt, 1, userA);
|
||||||
|
sqlite3_bind_int64(stmt, 2, userB);
|
||||||
|
sqlite3_bind_int64(stmt, 3, userB);
|
||||||
|
sqlite3_bind_int64(stmt, 4, userA);
|
||||||
|
sqlite3_bind_int64(stmt, 5, amount);
|
||||||
|
|
||||||
|
std::vector<Message> messages = {};
|
||||||
|
while (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||||
|
|
||||||
|
uint64_t id = sqlite3_column_int64(stmt, 0);
|
||||||
|
uint64_t senderId = sqlite3_column_int64(stmt, 1);
|
||||||
|
uint64_t recieverId = sqlite3_column_int64(stmt, 2);
|
||||||
|
std::time_t timestamp = sqlite3_column_int64(stmt, 3);
|
||||||
|
const char* textptr = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 4));
|
||||||
|
std::string text;
|
||||||
|
if (textptr == NULL) {
|
||||||
|
text = "";
|
||||||
|
} else {
|
||||||
|
text = std::string(textptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
messages.emplace_back(id, senderId, recieverId, text, timestamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlite3_finalize(stmt);
|
||||||
|
return messages;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Database::addMessage(Message& message) {
|
||||||
|
const char* sql = R"(
|
||||||
|
INSERT INTO messages (senderid, recieverid, timestamp, text)
|
||||||
|
VALUES (?, ?, ?, ?);
|
||||||
|
)";
|
||||||
|
|
||||||
|
sqlite3_stmt* stmt;
|
||||||
|
if (sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr) != SQLITE_OK) {
|
||||||
|
throw std::runtime_error("sqlite3 error: " + std::string(sqlite3_errmsg(db)));
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlite3_bind_int64(stmt, 1, message.sender);
|
||||||
|
sqlite3_bind_int64(stmt, 2, message.reciever);
|
||||||
|
sqlite3_bind_int64(stmt, 3, message.timestamp);
|
||||||
|
sqlite3_bind_text(stmt, 4, message.content.c_str(), -1, SQLITE_STATIC);
|
||||||
|
|
||||||
|
if (sqlite3_step(stmt) != SQLITE_DONE) {
|
||||||
|
throw std::runtime_error("sqlite3 error: " + std::string(sqlite3_errmsg(db)));
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlite3_finalize(stmt);
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#include <optional>
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <sqlite3.h>
|
#include <sqlite3.h>
|
||||||
|
#include <sys/types.h>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "post.h"
|
#include "post.h"
|
||||||
@@ -19,13 +20,33 @@ struct User {
|
|||||||
std::string passwordHash = "";
|
std::string passwordHash = "";
|
||||||
std::string bio = "";
|
std::string bio = "";
|
||||||
|
|
||||||
User(uint64_t id, std::string namein, const std::string& passwordHash, const std::string& bio) :
|
User(uint64_t id, const std::string& namein, const std::string& passwordHash, const std::string& bio) :
|
||||||
|
id(id), passwordHash(passwordHash), bio(bio), name(namein) {}
|
||||||
|
|
||||||
|
User(uint64_t id, std::string namein, const std::string& passwordHash, const std::string& bio, int doSanitize) :
|
||||||
id(id), passwordHash(passwordHash), bio(bio) {
|
id(id), passwordHash(passwordHash), bio(bio) {
|
||||||
sanitize(namein);
|
sanitize(namein);
|
||||||
name = namein;
|
name = namein;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct Message {
|
||||||
|
uint64_t id = 0;
|
||||||
|
uint64_t sender = 0;
|
||||||
|
uint64_t reciever = 0;
|
||||||
|
std::time_t timestamp;
|
||||||
|
std::string content = "";
|
||||||
|
|
||||||
|
Message(uint64_t id, uint64_t sender, uint64_t reciever, const std::string& content, std::time_t timestamp) :
|
||||||
|
id(id), sender(sender), reciever(reciever), content(content), timestamp(timestamp) {}
|
||||||
|
|
||||||
|
Message(uint64_t id, uint64_t sender, uint64_t reciever, std::string contentin, std::time_t timestamp, int doSanitize) :
|
||||||
|
id(id), sender(sender), reciever(reciever), timestamp(timestamp) {
|
||||||
|
sanitize(contentin);
|
||||||
|
content = contentin;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
class Database {
|
class Database {
|
||||||
|
|
||||||
sqlite3* db;
|
sqlite3* db;
|
||||||
@@ -52,6 +73,10 @@ class Database {
|
|||||||
void updateUser(const User& user);
|
void updateUser(const User& user);
|
||||||
void invalidateUserSessions(const User& user);
|
void invalidateUserSessions(const User& user);
|
||||||
|
|
||||||
|
std::vector<Message> getMessages(uint64_t userA, uint64_t userB, uint64_t amount);
|
||||||
|
// modifies the message to have it's id
|
||||||
|
void addMessage(Message& message);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
25526
server/src/json/json.hpp
Normal file
25526
server/src/json/json.hpp
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,13 +1,20 @@
|
|||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <ctime>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "httplib/httplib.h"
|
#include "httplib/httplib.h"
|
||||||
#include "bcrypt/bcrypt.h"
|
#include "bcrypt/bcrypt.h"
|
||||||
|
|
||||||
|
#define JSON_USE_IMPLICIT_CONVERSIONS 0
|
||||||
|
#include "json/json.hpp"
|
||||||
|
|
||||||
|
using Json = nlohmann::json;
|
||||||
|
|
||||||
#include "db.h"
|
#include "db.h"
|
||||||
#include "post.h"
|
#include "post.h"
|
||||||
|
|
||||||
@@ -17,8 +24,12 @@
|
|||||||
#include "generated/footer.h"
|
#include "generated/footer.h"
|
||||||
#include "generated/login.h"
|
#include "generated/login.h"
|
||||||
#include "generated/settings.h"
|
#include "generated/settings.h"
|
||||||
|
#include "generated/friends.h"
|
||||||
|
#include "generated/script_friends.h"
|
||||||
#include "generated/style.h"
|
#include "generated/style.h"
|
||||||
|
#include "generated/style_friends.h"
|
||||||
#include "generated/script.h"
|
#include "generated/script.h"
|
||||||
|
#include "generated/notify.h"
|
||||||
|
|
||||||
std::optional<User> getLoggedInUser(const httplib::Request& request, Database& database) {
|
std::optional<User> getLoggedInUser(const httplib::Request& request, Database& database) {
|
||||||
if (!request.has_header("Cookie")) {
|
if (!request.has_header("Cookie")) {
|
||||||
@@ -46,38 +57,56 @@ int main() {
|
|||||||
const bin2cpp::File& footerfile = bin2cpp::getFooterHtmlFile();
|
const bin2cpp::File& footerfile = bin2cpp::getFooterHtmlFile();
|
||||||
const bin2cpp::File& loginfile = bin2cpp::getLoginHtmlFile();
|
const bin2cpp::File& loginfile = bin2cpp::getLoginHtmlFile();
|
||||||
const bin2cpp::File& settingsfile = bin2cpp::getSettingsHtmlFile();
|
const bin2cpp::File& settingsfile = bin2cpp::getSettingsHtmlFile();
|
||||||
|
const bin2cpp::File& friendsfile = bin2cpp::getFriendsHtmlFile();
|
||||||
|
const bin2cpp::File& friendsjsfile = bin2cpp::getScript_friendsJsFile();
|
||||||
const bin2cpp::File& e404file = bin2cpp::getE404HtmlFile();
|
const bin2cpp::File& e404file = bin2cpp::getE404HtmlFile();
|
||||||
const bin2cpp::File& stylefile = bin2cpp::getStyleCssFile();
|
const bin2cpp::File& stylefile = bin2cpp::getStyleCssFile();
|
||||||
|
const bin2cpp::File& friendscssfile = bin2cpp::getStyle_friendsCssFile();
|
||||||
const bin2cpp::File& scriptfile = bin2cpp::getScriptJsFile();
|
const bin2cpp::File& scriptfile = bin2cpp::getScriptJsFile();
|
||||||
|
const bin2cpp::File& notifyfile = bin2cpp::getNotifyOpusFile();
|
||||||
|
|
||||||
std::string headerTop{headerTopFile.getBuffer(), headerTopFile.getSize()};
|
std::string headerTop{headerTopFile.getBuffer(), headerTopFile.getSize()};
|
||||||
std::string headerBottom{headerBottomFile.getBuffer(), headerBottomFile.getSize()};
|
std::string headerBottom{headerBottomFile.getBuffer(), headerBottomFile.getSize()};
|
||||||
std::string footer{footerfile.getBuffer(), footerfile.getSize()};
|
std::string footer{footerfile.getBuffer(), footerfile.getSize()};
|
||||||
std::string login{loginfile.getBuffer(), loginfile.getSize()};
|
std::string login{loginfile.getBuffer(), loginfile.getSize()};
|
||||||
std::string settings{settingsfile.getBuffer(), settingsfile.getSize()};
|
std::string settings{settingsfile.getBuffer(), settingsfile.getSize()};
|
||||||
|
std::string friends{friendsfile.getBuffer(), friendsfile.getSize()};
|
||||||
|
std::string friendsjs{friendsjsfile.getBuffer(), friendsjsfile.getSize()};
|
||||||
|
std::string friendscss{friendscssfile.getBuffer(), friendscssfile.getSize()};
|
||||||
std::string e404{e404file.getBuffer(), e404file.getSize()};
|
std::string e404{e404file.getBuffer(), e404file.getSize()};
|
||||||
std::string style{stylefile.getBuffer(), stylefile.getSize()};
|
std::string style{stylefile.getBuffer(), stylefile.getSize()};
|
||||||
std::string script{scriptfile.getBuffer(), scriptfile.getSize()};
|
std::string script{scriptfile.getBuffer(), scriptfile.getSize()};
|
||||||
|
std::string notify{notifyfile.getBuffer(), notifyfile.getSize()};
|
||||||
|
|
||||||
Database database{"chookchat.db"};
|
Database database{"chookchat.db"};
|
||||||
|
|
||||||
std::mutex data_mutex;
|
std::mutex data_mutex;
|
||||||
|
|
||||||
|
std::unordered_map<uint64_t, httplib::ws::WebSocket*> connectedFriends;
|
||||||
|
|
||||||
httplib::Server svr;
|
httplib::Server svr;
|
||||||
|
|
||||||
svr.Get("/style.css", [&style](const httplib::Request& request, httplib::Response& response) {
|
svr.Get("/style.css", [&style](const httplib::Request& request, httplib::Response& response) {
|
||||||
response.set_content(style, "text/css");
|
response.set_content(style, "text/css");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
svr.Get("/style_friends.css", [&friendscss](const httplib::Request& request, httplib::Response& response) {
|
||||||
|
response.set_content(friendscss, "text/css");
|
||||||
|
});
|
||||||
|
|
||||||
svr.Get("/script.js", [&script](const httplib::Request& request, httplib::Response& response) {
|
svr.Get("/script.js", [&script](const httplib::Request& request, httplib::Response& response) {
|
||||||
response.set_content(script, "text/javascript");
|
response.set_content(script, "text/javascript");
|
||||||
});
|
});
|
||||||
|
|
||||||
svr.Get("/login", [&login](const httplib::Request& request, httplib::Response& response) {
|
svr.Get("/script_friends.js", [&friendsjs](const httplib::Request& request, httplib::Response& response) {
|
||||||
response.set_content(login, "text/html");
|
response.set_content(friendsjs, "text/javascript");
|
||||||
});
|
});
|
||||||
|
|
||||||
svr.Get("/login.html", [&login](const httplib::Request& request, httplib::Response& response) {
|
svr.Get("/notify.opus", [¬ify](const httplib::Request& request, httplib::Response& response) {
|
||||||
|
response.set_content(notify, "audio/ogg; codecs=opus");
|
||||||
|
});
|
||||||
|
|
||||||
|
svr.Get("/login", [&login](const httplib::Request& request, httplib::Response& response) {
|
||||||
response.set_content(login, "text/html");
|
response.set_content(login, "text/html");
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -85,8 +114,8 @@ int main() {
|
|||||||
response.set_content(settings, "text/html");
|
response.set_content(settings, "text/html");
|
||||||
});
|
});
|
||||||
|
|
||||||
svr.Get("/settings.html", [&settings](const httplib::Request& request, httplib::Response& response) {
|
svr.Get("/friends", [&friends](const httplib::Request& request, httplib::Response& response) {
|
||||||
response.set_content(settings, "text/html");
|
response.set_content(friends, "text/html");
|
||||||
});
|
});
|
||||||
|
|
||||||
svr.Post("/login", [&database, &data_mutex](const httplib::Request& request, httplib::Response& response) {
|
svr.Post("/login", [&database, &data_mutex](const httplib::Request& request, httplib::Response& response) {
|
||||||
@@ -150,7 +179,7 @@ int main() {
|
|||||||
response.set_content("<p>hey you can't have an empty username!!!!1!!!1! >:(</p>", "text/html");
|
response.set_content("<p>hey you can't have an empty username!!!!1!!!1! >:(</p>", "text/html");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
User newUser{0, username, bcrypt::generateHash(password), ""};
|
User newUser{0, username, bcrypt::generateHash(password), "", 1};
|
||||||
database.addUser(newUser);
|
database.addUser(newUser);
|
||||||
user = newUser;
|
user = newUser;
|
||||||
}
|
}
|
||||||
@@ -363,6 +392,198 @@ int main() {
|
|||||||
response.set_header("X-Logged-In", "true");
|
response.set_header("X-Logged-In", "true");
|
||||||
response.set_header("X-Username", user->name);
|
response.set_header("X-Username", user->name);
|
||||||
response.set_header("X-Bio", user->bio);
|
response.set_header("X-Bio", user->bio);
|
||||||
|
response.set_header("X-User-ID", std::to_string(user->id));
|
||||||
|
} catch (const std::runtime_error& e) {
|
||||||
|
response.status = 500;
|
||||||
|
response.set_content("<p>there was an error :( it is: " + std::string(e.what()) + "</p>", "text/html");
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
response.status = 500;
|
||||||
|
response.set_content("<p>there was an error :( it is: " + std::string(e.what()) + "</p>", "text/html");
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
svr.Get("/userinfo/:userid", [&database, &data_mutex](const httplib::Request& request, httplib::Response& response) {
|
||||||
|
std::lock_guard<std::mutex> lock(data_mutex);
|
||||||
|
try {
|
||||||
|
uint64_t userId = std::stoull(request.path_params.at("userid"));
|
||||||
|
std::optional<User> user = database.getUser(userId);
|
||||||
|
response.set_header("Cache-Control", "no-store");
|
||||||
|
|
||||||
|
if (!user.has_value()) {
|
||||||
|
response.status = 404;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
response.set_header("X-Logged-In", "true");
|
||||||
|
response.set_header("X-Username", user->name);
|
||||||
|
response.set_header("X-Bio", user->bio);
|
||||||
|
response.set_header("X-User-ID", std::to_string(user->id));
|
||||||
|
} catch (const std::runtime_error& e) {
|
||||||
|
response.status = 500;
|
||||||
|
response.set_content("<p>there was an error :( it is: " + std::string(e.what()) + "</p>", "text/html");
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
response.status = 500;
|
||||||
|
response.set_content("<p>there was an error :( it is: " + std::string(e.what()) + "</p>", "text/html");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// friends endpoints
|
||||||
|
svr.WebSocket("/friends/ws", [&database, &data_mutex, &connectedFriends](const httplib::Request& request, httplib::ws::WebSocket& ws) {
|
||||||
|
|
||||||
|
std::optional<User> user = getLoggedInUser(request, database);
|
||||||
|
if (!user.has_value()) {
|
||||||
|
ws.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// add us to the logged in users
|
||||||
|
connectedFriends[user->id] = &ws;
|
||||||
|
|
||||||
|
{
|
||||||
|
// send out an update to everyone with the new user list
|
||||||
|
Json data;
|
||||||
|
data["type"] = "users";
|
||||||
|
Json list;
|
||||||
|
for (const auto& [user, value] : connectedFriends) {
|
||||||
|
list.push_back(user);
|
||||||
|
}
|
||||||
|
data["content"] = list;
|
||||||
|
std::string datadump = data.dump();
|
||||||
|
for (const auto& [userid, conn] : connectedFriends) {
|
||||||
|
conn->send(datadump);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string msg;
|
||||||
|
while (ws.read(msg)) {
|
||||||
|
try {
|
||||||
|
/*
|
||||||
|
* type:
|
||||||
|
* 0 (message), 1 (friend request), 2 (change status), 3 (ping),
|
||||||
|
* 4 (get online friends)
|
||||||
|
* userId: <int>
|
||||||
|
* content:
|
||||||
|
* <string> (message),
|
||||||
|
* (0 (offline), 1 (online) (change status)),
|
||||||
|
* nil, (friend request)
|
||||||
|
*/
|
||||||
|
Json data = Json::parse(msg);
|
||||||
|
double type = data["type"].get<double>();
|
||||||
|
uint64_t userId = static_cast<uint64_t>(data["userId"].get<double>());
|
||||||
|
if (type == 0) { // message
|
||||||
|
std::string content = data["content"].get<std::string>();
|
||||||
|
if (connectedFriends.find(userId) == connectedFriends.end()) {
|
||||||
|
Json data;
|
||||||
|
data["type"] = "error";
|
||||||
|
data["content"] = "friend not online";
|
||||||
|
ws.send(data.dump());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
httplib::ws::WebSocket* friendWs = connectedFriends[userId];
|
||||||
|
if (friendWs->is_open()) {
|
||||||
|
Json data;
|
||||||
|
data["type"] = "message";
|
||||||
|
data["userId"] = user->id;
|
||||||
|
data["content"] = content;
|
||||||
|
friendWs->send(data.dump());
|
||||||
|
|
||||||
|
Json returnData;
|
||||||
|
returnData["type"] = "ok";
|
||||||
|
ws.send(returnData.dump());
|
||||||
|
}
|
||||||
|
// Store the message in database
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(data_mutex);
|
||||||
|
Message message{0, user->id, userId, content, std::time(nullptr), 1};
|
||||||
|
database.addMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO:
|
||||||
|
// - Verify users are friends
|
||||||
|
|
||||||
|
} else if (type == 1) { // friend request
|
||||||
|
Json data;
|
||||||
|
data["type"] = "error";
|
||||||
|
data["content"] = "not implemented yet";
|
||||||
|
ws.send(data.dump());
|
||||||
|
continue;
|
||||||
|
} else if (type == 2) {
|
||||||
|
// blank for now
|
||||||
|
// no need for a ping anymore
|
||||||
|
} else if (type == 3) {
|
||||||
|
Json data;
|
||||||
|
data["type"] = "ok";
|
||||||
|
ws.send(data.dump());
|
||||||
|
} else if (type == 4) {
|
||||||
|
Json data;
|
||||||
|
data["type"] = "users";
|
||||||
|
Json list;
|
||||||
|
for (const auto& [user, value] : connectedFriends) {
|
||||||
|
list.push_back(user);
|
||||||
|
}
|
||||||
|
data["content"] = list;
|
||||||
|
ws.send(data.dump());
|
||||||
|
}
|
||||||
|
} catch (const std::runtime_error& e) {
|
||||||
|
Json data;
|
||||||
|
data["type"] = "error";
|
||||||
|
data["content"] = e.what();
|
||||||
|
ws.send(data.dump());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// clean up the connection
|
||||||
|
connectedFriends.erase(user->id);
|
||||||
|
ws.close();
|
||||||
|
|
||||||
|
// send out an update to everyone with the new user list
|
||||||
|
{
|
||||||
|
Json data;
|
||||||
|
data["type"] = "users";
|
||||||
|
Json list;
|
||||||
|
for (const auto& [user, value] : connectedFriends) {
|
||||||
|
list.push_back(user);
|
||||||
|
}
|
||||||
|
data["content"] = list;
|
||||||
|
std::string datadump = data.dump();
|
||||||
|
for (const auto& [userid, conn] : connectedFriends) {
|
||||||
|
conn->send(datadump);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
svr.Get("/friends/chatHistory/:userid", [&database, &data_mutex](const httplib::Request& request, httplib::Response& response) {
|
||||||
|
std::string userIdStr = request.path_params.at("userid");
|
||||||
|
std::lock_guard<std::mutex> lock(data_mutex);
|
||||||
|
|
||||||
|
try {
|
||||||
|
std::optional<User> user = getLoggedInUser(request, database);
|
||||||
|
if (!user.has_value()) {
|
||||||
|
response.status = 401;
|
||||||
|
response.set_content("your session is invalid", "text/plain");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uint64_t friendUserId = std::stoull(userIdStr);
|
||||||
|
std::vector<Message> messages = database.getMessages(user->id, friendUserId, 100);
|
||||||
|
|
||||||
|
// construct json for response
|
||||||
|
Json list;
|
||||||
|
for (auto it = messages.rbegin(); it != messages.rend(); ++it) {
|
||||||
|
Json message;
|
||||||
|
message["sender"] = it->sender;
|
||||||
|
message["reciever"] = it->reciever;
|
||||||
|
message["id"] = it->id;
|
||||||
|
message["timestamp"] = it->timestamp;
|
||||||
|
message["content"] = it->content;
|
||||||
|
list.push_back(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
Json data;
|
||||||
|
data["messages"] = list;
|
||||||
|
|
||||||
|
response.set_content(data.dump(), "application/json");
|
||||||
|
|
||||||
} catch (const std::runtime_error& e) {
|
} catch (const std::runtime_error& e) {
|
||||||
response.status = 500;
|
response.status = 500;
|
||||||
response.set_content("<p>there was an error :( it is: " + std::string(e.what()) + "</p>", "text/html");
|
response.set_content("<p>there was an error :( it is: " + std::string(e.what()) + "</p>", "text/html");
|
||||||
|
|||||||
Reference in New Issue
Block a user