Files
chookchat/server/src/main.cpp

324 lines
13 KiB
C++
Raw Normal View History

2026-07-20 19:02:04 +10:00
#include <cstdint>
2026-07-20 10:57:07 +10:00
#include <mutex>
#include <sstream>
#include <stdexcept>
#include <string>
#include <vector>
#include "httplib/httplib.h"
#include "bcrypt/bcrypt.h"
#include "db.h"
#include "post.h"
2026-07-20 20:11:06 +10:00
#include "generated/e404.h"
2026-07-20 20:31:25 +10:00
#include "generated/header_top.h"
#include "generated/header_bottom.h"
2026-07-20 10:57:07 +10:00
#include "generated/footer.h"
2026-07-21 12:06:46 +10:00
#include "generated/login.h"
2026-07-20 11:48:27 +10:00
#include "generated/style.h"
2026-07-20 19:02:04 +10:00
#include "generated/script.h"
2026-07-20 10:57:07 +10:00
2026-07-21 12:06:46 +10:00
std::optional<User> getLoggedInUser(const httplib::Request& request, Database& database) {
if (!request.has_header("Cookie")) {
return {};
}
std::string cookieHeader = request.get_header_value("Cookie");
const std::string key = "session=";
size_t pos = cookieHeader.find(key);
if (pos == std::string::npos) {
return {};
}
pos += key.length();
size_t end = cookieHeader.find(';', pos);
std::string token = cookieHeader.substr(pos, end == std::string::npos ? std::string::npos : end - pos);
return database.getUserByToken(token);
}
2026-07-20 10:57:07 +10:00
int main() {
2026-07-20 20:31:25 +10:00
const bin2cpp::File& headerTopFile = bin2cpp::getHeader_topHtmlFile();
const bin2cpp::File& headerBottomFile = bin2cpp::getHeader_bottomHtmlFile();
2026-07-20 10:57:07 +10:00
const bin2cpp::File& footerfile = bin2cpp::getFooterHtmlFile();
2026-07-21 12:06:46 +10:00
const bin2cpp::File& loginfile = bin2cpp::getLoginHtmlFile();
2026-07-20 20:11:06 +10:00
const bin2cpp::File& e404file = bin2cpp::getE404HtmlFile();
2026-07-20 11:48:27 +10:00
const bin2cpp::File& stylefile = bin2cpp::getStyleCssFile();
2026-07-20 19:02:04 +10:00
const bin2cpp::File& scriptfile = bin2cpp::getScriptJsFile();
2026-07-20 10:57:07 +10:00
2026-07-20 20:31:25 +10:00
std::string headerTop{headerTopFile.getBuffer(), headerTopFile.getSize()};
std::string headerBottom{headerBottomFile.getBuffer(), headerBottomFile.getSize()};
2026-07-20 10:57:07 +10:00
std::string footer{footerfile.getBuffer(), footerfile.getSize()};
2026-07-21 12:06:46 +10:00
std::string login{loginfile.getBuffer(), loginfile.getSize()};
2026-07-20 20:11:06 +10:00
std::string e404{e404file.getBuffer(), e404file.getSize()};
2026-07-20 11:48:27 +10:00
std::string style{stylefile.getBuffer(), stylefile.getSize()};
2026-07-20 19:02:04 +10:00
std::string script{scriptfile.getBuffer(), scriptfile.getSize()};
2026-07-20 10:57:07 +10:00
Database database{"chookchat.db"};
std::mutex data_mutex;
httplib::Server svr;
2026-07-20 11:48:27 +10:00
svr.Get("/style.css", [&style](const httplib::Request& request, httplib::Response& response) {
response.set_content(style, "text/css");
});
2026-07-20 19:02:04 +10:00
svr.Get("/script.js", [&script](const httplib::Request& request, httplib::Response& response) {
2026-07-21 16:05:08 +10:00
response.set_content(script, "text/javascript");
2026-07-20 19:02:04 +10:00
});
2026-07-21 12:06:46 +10:00
svr.Get("/login", [&login](const httplib::Request& request, httplib::Response& response) {
response.set_content(login, "text/html");
});
svr.Get("/login.html", [&login](const httplib::Request& request, httplib::Response& response) {
response.set_content(login, "text/html");
});
svr.Post("/login", [&database, &data_mutex](const httplib::Request& request, httplib::Response& response) {
std::string username = request.form.get_field("username");
std::string password = request.form.get_field("password");
std::lock_guard<std::mutex> lock(data_mutex);
try {
std::optional<User> user = database.getUserByName(username);
if (user.has_value()) {
if (!bcrypt::validatePassword(password, user->passwordHash)) {
response.status = 401;
2026-07-21 13:11:20 +10:00
response.set_content("<p>wrong password lmao</p><img src='https://media.tenor.com/wWX7upr7SvwAAAAM/byuntear-cat.gif' alt='your stupid lol'>", "text/html");
2026-07-21 12:06:46 +10:00
return;
}
2026-07-21 16:05:08 +10:00
} else {
response.status = 400;
response.set_content("<p>that username doesn't exist</p>", "text/html");
return;
}
std::optional<std::string> token = database.createNewToken(user->id);
if (!token.has_value()) {
response.status = 500;
response.set_content("<p>couldn't create a session, sorry</p>", "text/html");
return;
}
// HttpOnly so script.js can't read/leak it, SameSite=Lax so it
// isn't sent on cross-site POSTs (basic CSRF mitigation),
// Max-Age matches the 30 day expiry stored in the DB
response.set_header(
"Set-Cookie",
"session=" + *token + "; Path=/; HttpOnly; SameSite=Lax; Max-Age=2592000"
);
response.set_redirect("/");
} 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");
}
});
svr.Post("/register", [&database, &data_mutex](const httplib::Request& request, httplib::Response& response) {
std::string username = request.form.get_field("username");
std::string password = request.form.get_field("password");
std::lock_guard<std::mutex> lock(data_mutex);
try {
std::optional<User> user = database.getUserByName(username);
if (user.has_value()) {
response.status = 400;
response.set_content("<p>that username already exists</p>", "text/html");
return;
2026-07-21 12:06:46 +10:00
} else {
if (username.empty()) {
response.status = 400;
response.set_content("<p>hey you can't have an empty username!!!!1!!!1! >:(</p>", "text/html");
return;
}
User newUser{0, username, bcrypt::generateHash(password)};
database.addUser(newUser);
user = newUser;
}
std::optional<std::string> token = database.createNewToken(user->id);
if (!token.has_value()) {
response.status = 500;
response.set_content("<p>couldn't create a session, sorry</p>", "text/html");
return;
}
// HttpOnly so script.js can't read/leak it, SameSite=Lax so it
// isn't sent on cross-site POSTs (basic CSRF mitigation),
// Max-Age matches the 30 day expiry stored in the DB
response.set_header(
"Set-Cookie",
"session=" + *token + "; Path=/; HttpOnly; SameSite=Lax; Max-Age=2592000"
);
response.set_redirect("/");
} 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");
}
});
2026-07-20 20:31:25 +10:00
svr.Get("/", [&headerTop, &headerBottom, &footer, &database, &data_mutex](const httplib::Request& request, httplib::Response& response) {
2026-07-20 10:57:07 +10:00
std::stringstream ss;
2026-07-20 20:31:25 +10:00
ss << headerTop << "<meta content='Chookchat' property='og:title' /><meta content='See posts from cool people' property='og:description' />" << headerBottom;
2026-07-20 10:57:07 +10:00
std::lock_guard<std::mutex> lock(data_mutex);
try {
std::vector<Post> posts = database.getTopPosts(100);
for (const auto& post : posts) {
ss << post.genHtml();
}
ss << footer;
response.set_content(ss.str(), "text/html");
} catch (const std::runtime_error& e) {
2026-07-20 20:11:06 +10:00
response.status = 500;
response.set_content("<p>there was an error :( it is: " + std::string(e.what()) + "</p>", "text/html");
}
});
2026-07-20 20:31:25 +10:00
svr.Get("/posts/:id", [&headerTop, &headerBottom, &footer, &e404, &database, &data_mutex](const httplib::Request& request, httplib::Response& response) {
2026-07-20 20:11:06 +10:00
std::string postId = request.path_params.at("id");
2026-07-21 16:05:08 +10:00
std::lock_guard<std::mutex> lock(data_mutex);
2026-07-20 20:11:06 +10:00
try {
uint64_t postIdNum = std::stoll(postId);
std::optional<Post> post = database.getPost(postIdNum);
if (!post.has_value()) {
response.status = 404;
response.set_content(e404, "text/html");
return;
}
std::stringstream ss;
2026-07-20 20:31:25 +10:00
ss << headerTop << post->genHtmlMeta() << headerBottom << post->genHtml() << footer;
2026-07-20 20:11:06 +10:00
response.set_content(ss.str(), "text/html");
} 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;
2026-07-20 10:57:07 +10:00
response.set_content("<p>there was an error :( it is: " + std::string(e.what()) + "</p>", "text/html");
}
});
2026-07-21 16:05:08 +10:00
// endpoint to be used by HTML forms
2026-07-20 19:02:04 +10:00
svr.Post("/make_post", [&database, &data_mutex](const httplib::Request& request, httplib::Response& response) {
2026-07-20 10:57:07 +10:00
std::string username = request.get_param_value("username");
std::string password = request.get_param_value("password");
std::string post = request.get_param_value("post");
std::lock_guard<std::mutex> lock(data_mutex);
try {
uint64_t userId = 0;
std::optional<User> user = database.getUserByName(username);
if (user.has_value()) {
if (!bcrypt::validatePassword(password, user->passwordHash)) {
// noooo wrong password
response.set_content("<p>wrong password lmao</p>", "text/html");
return;
}
userId = user->id;
} else {
if (username.empty()) {
response.set_content("<p>hey you can't have an empty username!!!!1!!!1! >:(</p>", "text/html");
return;
}
// create user
User newUser{0, username, bcrypt::generateHash(password)};
database.addUser(newUser);
userId = newUser.id;
}
// and now we add their post
database.addPost(Post(post, userId));
response.set_redirect("/");
} catch (const std::runtime_error& e) {
2026-07-20 20:11:06 +10:00
response.status = 500;
2026-07-20 19:02:04 +10:00
response.set_content("<p>there was an error :( it is: " + std::string(e.what()) + "</p>", "text/html");
2026-07-20 10:57:07 +10:00
}
});
2026-07-21 16:05:08 +10:00
// endpoint to be used in Javascript
svr.Post("/post", [&database, &data_mutex](const httplib::Request& request, httplib::Response& response) {
try {
std::lock_guard<std::mutex> lock(data_mutex);
std::string post = request.form.get_field("post");
std::optional<User> user = getLoggedInUser(request, database);
if (!user.has_value()) {
response.status = 401;
response.set_content("<p>you're not logged in, so you can't post</p>", "text/html");
return;
}
database.addPost(Post(post, user->id));
response.status = 200;
response.set_content("OK", "text/text");
} 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");
}
});
2026-07-20 19:02:04 +10:00
svr.Post("/like", [&database, &data_mutex](const httplib::Request& request, httplib::Response& response) {
try {
2026-07-20 19:24:50 +10:00
std::string post = request.form.get_field("post");
std::lock_guard<std::mutex> lock(data_mutex);
2026-07-21 16:58:44 +10:00
std::optional<User> user = getLoggedInUser(request, database);
if (!user.has_value()) {
response.status = 401;
response.set_content("i dunno that user", "text/plain");
return;
}
2026-07-20 19:02:04 +10:00
uint64_t postNum = std::stoull(post);
2026-07-21 16:58:44 +10:00
database.addLike(postNum, user->id);
2026-07-20 19:02:04 +10:00
} catch (const std::runtime_error& e) {
2026-07-20 20:11:06 +10:00
response.status = 500;
2026-07-20 19:02:04 +10:00
response.set_content("<p>there was an error :( it is: " + std::string(e.what()) + "</p>", "text/html");
2026-07-20 19:24:50 +10:00
} catch (const std::exception& e) {
2026-07-20 20:11:06 +10:00
response.status = 500;
2026-07-20 19:24:50 +10:00
response.set_content("<p>there was an error :( it is: " + std::string(e.what()) + "</p>", "text/html");
2026-07-20 19:02:04 +10:00
}
});
2026-07-21 13:09:52 +10:00
svr.Get("/me", [&database, &data_mutex](const httplib::Request& request, httplib::Response& response) {
2026-07-21 16:05:08 +10:00
std::lock_guard<std::mutex> lock(data_mutex);
2026-07-21 13:09:52 +10:00
std::optional<User> user = getLoggedInUser(request, database);
2026-07-21 16:05:08 +10:00
response.set_header("Cache-Control", "no-store");
2026-07-21 13:09:52 +10:00
if (!user.has_value()) {
response.set_header("X-Logged-In", "false");
return;
}
2026-07-21 16:05:08 +10:00
response.set_header("X-Logged-In", "true");
2026-07-21 13:09:52 +10:00
response.set_header("X-Username", user->name);
});
2026-07-20 10:57:07 +10:00
svr.listen("0.0.0.0", 8080);
}