fkin finally

This commit is contained in:
2025-08-21 23:23:14 +01:00
parent 217adf91e9
commit 6360b41e9a
10 changed files with 112 additions and 254 deletions

15
include/errors.hpp Normal file
View File

@@ -0,0 +1,15 @@
#ifndef ERRORS_HPP
#define ERRORS_HPP
#include <crow.h>
#include "templating.hpp"
extern Templating templating;
struct CustomErrorHandler {
struct context {};
void before_handle(crow::request& req, crow::response& res, context&) {}
void after_handle(crow::request& req, crow::response& res, context&);
};
#endif

View File

@@ -7,12 +7,19 @@
class Templating {
public:
Templating(const std::string& template_dir) : inja_env{template_dir} {}
explicit Templating(const std::string& template_dir);
crow::response render_template(const std::string& template_name, const inja::json& data);
crow::response render_template(const std::string& template_name);
std::string render_template_string(const std::string& template_name, const inja::json& data);
std::string render_template_string(const std::string& template_name);
private:
inja::Environment inja_env;
std::string template_dir; // absolute path to templates
std::string preprocess_template(const std::string& template_name);
};
#endif
#endif

13
src/errors.cpp Normal file
View File

@@ -0,0 +1,13 @@
#include "errors.hpp"
std::string render_404_template(const crow::request& req) {
return templating.render_template_string("errors/404.html", {{"requested_url", req.url}});
}
void CustomErrorHandler::after_handle(crow::request& req, crow::response& res, context&) {
if (res.code == 404 && res.body.empty()) {
res.set_header("Content-Type", "text/html");
res.body = render_404_template(req);
}
}

View File

@@ -1,5 +1,6 @@
#define CROW_STATIC_DIRECTORY "../static"
#include "templating.hpp"
#include "errors.hpp"
#include <crow.h>
@@ -7,7 +8,7 @@ Templating templating{"../templates"};
int main() {
crow::SimpleApp app;
crow::App<CustomErrorHandler> app;
CROW_ROUTE(app, "/")([]() {
return templating.render_template("index.html");

View File

@@ -1,16 +1,76 @@
#include "templating.hpp"
#include <filesystem>
#include <fstream>
#include <regex>
#include <stdexcept>
Templating::Templating(const std::string& template_dir)
: inja_env(std::filesystem::canonical(template_dir).string()),
template_dir(std::filesystem::canonical(template_dir).string())
{
inja_env.set_search_included_templates_in_files(true);
}
std::string Templating::preprocess_template(const std::string& template_name) {
namespace fs = std::filesystem;
fs::path abs_template_dir = fs::path(template_dir);
fs::path abs_template_file = fs::canonical(abs_template_dir / template_name);
std::ifstream file(abs_template_file);
if (!file.is_open()) {
throw std::runtime_error("Failed to open template file: " + abs_template_file.string());
}
std::string content((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
std::regex extends_regex(R"(\{\%\s*extends\s*(['"])(.+?)\1\s*\%\})");
std::smatch match;
if (std::regex_search(content, match, extends_regex)) {
std::string quote = match[1].str();
std::string original_path = match[2].str();
if (original_path.find("/") == std::string::npos &&
!original_path.empty() &&
original_path.front() != '/') {
fs::path abs_extended_template = fs::canonical(abs_template_dir / original_path);
fs::path rel_path = fs::relative(abs_extended_template, abs_template_file.parent_path());
std::string new_path = rel_path.generic_string();
content = std::regex_replace(content, extends_regex,
"{% extends " + quote + new_path + quote + " %}");
}
}
return content;
}
crow::response Templating::render_template(const std::string& template_name, const inja::json& data) {
try {
inja::Template template_obj = inja_env.parse_template(template_name);
std::string rendered = inja_env.render(template_obj, data);
std::string preprocessed = preprocess_template(template_name);
inja::Template tpl = inja_env.parse(preprocessed);
std::string rendered = inja_env.render(tpl, data);
return crow::response(rendered);
} catch (const inja::RenderError& e) {
} catch (const std::exception& e) {
return crow::response(500, e.what());
}
}
crow::response Templating::render_template(const std::string& template_name) {
return render_template(template_name, inja::json{});
}
}
std::string Templating::render_template_string(const std::string& template_name, const inja::json& data) {
std::string preprocessed = preprocess_template(template_name);
inja::Template tpl = inja_env.parse(preprocessed);
return inja_env.render(tpl, data);
}
std::string Templating::render_template_string(const std::string& template_name) {
return render_template_string(template_name, inja::json{});
}

View File

@@ -1,155 +0,0 @@
#snakeContainer {
padding: 15px;
}
canvas#snakeCanvas {
box-sizing: border-box;
border: 2px solid var(--secondary-background-color);
border-radius: 10px;
width: 100%;
}
form {
display: flex;
flex-direction: column;
width: min-content;
gap: 1rem;
padding: 15px;
}
form input[type="text"] {
padding: 10px;
box-sizing: border-box;
border: 1px solid var(--secondary-background-color);
border-radius: 6px;
background-color: var(--secondary-background-color-but-slightly-transparent);
color: var(--text-color);
}
form button[type="submit"] {
padding: 10px;
box-sizing: border-box;
border: 1px solid var(--secondary-background-color);
border-radius: 6px;
background-color: var(--secondary-background-color-but-slightly-transparent);
color: var(--text-color);
font-weight: bold;
cursor: pointer;
}
.flex-row {
display: flex;
flex-direction: row;
gap: 1rem;
}
.min-width {
width: min-content;
}
.max-width {
width: 100%;
}
#snakeLeaderboardSection {
display: flex;
flex-direction: column;
align-items: center;
padding: 0;
max-height: 272px ;
}
#snakeLeaderboard {
display: flex;
flex-direction: column;
list-style: none;
padding: 0;
margin: 0;
width: 100%;
overflow-y: scroll;
}
#snakeLeaderboard li {
padding: 5px 20px;
background-color: var(--secondary-background-color-but-slightly-transparent);
color: var(--text-color);
font-size: 1rem;
display: flex;
justify-content: space-between;
align-items: center;
}
#snakeLeaderboard li:nth-child(even) {
background-color: var(--secondary-background-color);
}
dialog {
width: 90%;
max-width: 500px;
padding: 20px;
background-color: var(--background-color);
color: var(--text-color);
border: 2px solid var(--secondary-background-color);
border-radius: 8px;
}
dialog button {
padding: 10px;
width: 100%;
box-sizing: border-box;
border: 2px solid var(--secondary-background-color);
border-radius: 6px;
background-color: var(--secondary-background-color-but-slightly-transparent);
color: var(--text-color);
font-weight: bold;
cursor: pointer;
margin-top: 10px;
}
dialog h2 {
margin: 0;
font-size: 1.5rem;
}
dialog p {
margin: 0;
font-size: 1rem;
}
@media screen and (max-width: 850px) {
.flex-row {
flex-direction: column;
}
form {
align-items: center;
width: 100%;
}
.min-width {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
}
}
@media screen and (max-width: 650px) {
.mobileOnly {
display: flex;
}
.pcOnly {
display: none;
}
}
@media screen and (min-width: 651px) {
.mobileOnly {
display: none !important;
}
.pcOnly {
display: flex;
}
}

View File

@@ -1,20 +0,0 @@
cap-widget {
--cap-background: var(--secondary-background-color-but-slightly-transparent);
--cap-border-color: var(--secondary-background-color);
--cap-border-radius: 14px;
--cap-widget-width: 230px;
--cap-widget-padding: 14px;
--cap-gap: 15px;
--cap-color: var(--text-color);
--cap-checkbox-size: 25px;
--cap-checkbox-border: 1px solid var(--secondary-background-color);
--cap-checkbox-border-radius: 6px;
--cap-checkbox-background: none;
--cap-checkbox-margin: 2px;
--cap-font: "Space Mono", "serif";
--cap-spinner-color: var(--primary-color);
--cap-spinner-background-color: var(--secondary-background-color-but-slightly-transparent);
--cap-spinner-thickness: 5px;
--cap-credits-font-size: 12px;
--cap-opacity-hover: 0.8;
}

View File

@@ -37,7 +37,6 @@
<li><a href="https://www.youtube.com/@acetheking987">YouTube</a></li>
<li><a href="https://acetheking987.tumblr.com/">Tumblr</a></li>
<li><a href="https://www.reddit.com/user/acetheking987">Reddit</a></li>
<li><a href="/404">404 >:3</a></li>
</ul>
</section>
</nav>

View File

@@ -1,4 +1,4 @@
{% extends "bases/base.html" %}
{% extends "base.html" %}
{% block title %}/{{ directory }} - Alfie's basement{% endblock %}
{% block description %}server backend survivor{% endblock %}

View File

@@ -3,80 +3,18 @@
{% block title %}404 - Not Found{% endblock %}
{% block description %}The page you are looking for does not exist.{% endblock %}
{% block head %}
<link rel="stylesheet" href="/static/css/404.css">
<link rel="stylesheet" href="/static/css/cap.css">
{% endblock %}
{% block content %}
<section>
<h1>404</h1>
<p>
It seems like the thing you are looking for is not here :[
Hey so you know that thing you were looking for? Yeah, it doesn't exist. :P
<br><br>
<span class="pcOnly">
while you're here, why not play some snake?
</span>
<span class="mobileOnly">
You can't play snake on mobile, sorry :(
</span>
So why not try going back to the <a href="/">homepage</a>?
</p>
<div class="pcOnly" id="snakeContainer">
<canvas id="snakeCanvas"></canvas>
</div>
</section>
<section class="pcOnly flex-row">
<section class="min-width">
<h2>Submit score</h2>
<form action="/snake/submit" method="POST" id="snakeForm">
<input type="text" id="name" name="name" maxlength=15 minlength=3 placeholder="Your name" required>
<cap-widget id="captcha" data-cap-api-endpoint="https://cap.alfieking.dev/{{ cap_key }}/"></cap-widget>
<input type="hidden" id="score" name="score" value="0">
<input type="hidden" id="game_token" name="game_token" value="{{ token}}">
<button type="submit" id="submit">Submit</button>
</form>
</section>
<section class="max-width" id="snakeLeaderboardSection">
<h2>Leaderboard</h2>
<ul id="snakeLeaderboard">
{% for score in scores %}
<li>
<span>{{ score.position }}</span>
<span>{{ score.name }}</span>
<span>{{ score.score }}</span>
</li>
{% endfor %}
</ul>
</section>
</section>
<section class="max-width mobileOnly" id="snakeLeaderboardSection">
<h2>Leaderboard</h2>
<ul id="snakeLeaderboard">
{% for score in scores %}
<li>
<span>{{ score.position }}</span>
<span>{{ score.name }}</span>
<span>{{ score.score }}</span>
</li>
{% endfor %}
</ul>
</section>
{% if error %}
<dialog id="errorDialog">
<h2>Error</h2>
<p>{{ error }}</p>
<button onclick="errorDialog.close()">Close</button>
</dialog>
<script>
const errorDialog = document.getElementById('errorDialog');
if (errorDialog) {
errorDialog.showModal();
}
</script>
{% endif %}
{% endblock %}
{% block scripts %}
<script src="/static/js/snake.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@cap.js/widget"></script>
{% endblock %}
<section>
<h2>The actual error for the 2 ppl who care</h2>
<p>
404: {{ requested_url }} not found :3
</p>
</section>