fkin finally
This commit is contained in:
		
							
								
								
									
										15
									
								
								include/errors.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								include/errors.hpp
									
									
									
									
									
										Normal 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
 | 
			
		||||
@@ -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
									
								
							
							
						
						
									
										13
									
								
								src/errors.cpp
									
									
									
									
									
										Normal 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);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -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");
 | 
			
		||||
 
 | 
			
		||||
@@ -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{});
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -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;
 | 
			
		||||
}
 | 
			
		||||
@@ -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>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
{% extends "bases/base.html" %}
 | 
			
		||||
{% extends "base.html" %}
 | 
			
		||||
 | 
			
		||||
{% block title %}/{{ directory }} - Alfie's basement{% endblock %}
 | 
			
		||||
{% block description %}server backend survivor{% endblock %}
 | 
			
		||||
 
 | 
			
		||||
@@ -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>
 | 
			
		||||
		Reference in New Issue
	
	Block a user