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 {
|
class Templating {
|
||||||
public:
|
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, const inja::json& data);
|
||||||
crow::response render_template(const std::string& template_name);
|
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:
|
private:
|
||||||
inja::Environment inja_env;
|
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"
|
#define CROW_STATIC_DIRECTORY "../static"
|
||||||
#include "templating.hpp"
|
#include "templating.hpp"
|
||||||
|
#include "errors.hpp"
|
||||||
#include <crow.h>
|
#include <crow.h>
|
||||||
|
|
||||||
|
|
||||||
@@ -7,7 +8,7 @@ Templating templating{"../templates"};
|
|||||||
|
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
crow::SimpleApp app;
|
crow::App<CustomErrorHandler> app;
|
||||||
|
|
||||||
CROW_ROUTE(app, "/")([]() {
|
CROW_ROUTE(app, "/")([]() {
|
||||||
return templating.render_template("index.html");
|
return templating.render_template("index.html");
|
||||||
|
@@ -1,16 +1,76 @@
|
|||||||
#include "templating.hpp"
|
#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) {
|
crow::response Templating::render_template(const std::string& template_name, const inja::json& data) {
|
||||||
try {
|
try {
|
||||||
inja::Template template_obj = inja_env.parse_template(template_name);
|
std::string preprocessed = preprocess_template(template_name);
|
||||||
std::string rendered = inja_env.render(template_obj, data);
|
inja::Template tpl = inja_env.parse(preprocessed);
|
||||||
|
std::string rendered = inja_env.render(tpl, data);
|
||||||
return crow::response(rendered);
|
return crow::response(rendered);
|
||||||
|
|
||||||
} catch (const inja::RenderError& e) {
|
} catch (const std::exception& e) {
|
||||||
return crow::response(500, e.what());
|
return crow::response(500, e.what());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
crow::response Templating::render_template(const std::string& template_name) {
|
crow::response Templating::render_template(const std::string& template_name) {
|
||||||
return render_template(template_name, inja::json{});
|
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://www.youtube.com/@acetheking987">YouTube</a></li>
|
||||||
<li><a href="https://acetheking987.tumblr.com/">Tumblr</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="https://www.reddit.com/user/acetheking987">Reddit</a></li>
|
||||||
<li><a href="/404">404 >:3</a></li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</section>
|
</section>
|
||||||
</nav>
|
</nav>
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
{% extends "bases/base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
{% block title %}/{{ directory }} - Alfie's basement{% endblock %}
|
{% block title %}/{{ directory }} - Alfie's basement{% endblock %}
|
||||||
{% block description %}server backend survivor{% endblock %}
|
{% block description %}server backend survivor{% endblock %}
|
||||||
|
@@ -3,80 +3,18 @@
|
|||||||
{% block title %}404 - Not Found{% endblock %}
|
{% block title %}404 - Not Found{% endblock %}
|
||||||
{% block description %}The page you are looking for does not exist.{% 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 %}
|
{% block content %}
|
||||||
<section>
|
<section>
|
||||||
<h1>404</h1>
|
<h1>404</h1>
|
||||||
<p>
|
<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>
|
<br><br>
|
||||||
<span class="pcOnly">
|
So why not try going back to the <a href="/">homepage</a>?
|
||||||
while you're here, why not play some snake?
|
|
||||||
</span>
|
|
||||||
<span class="mobileOnly">
|
|
||||||
You can't play snake on mobile, sorry :(
|
|
||||||
</span>
|
|
||||||
</p>
|
</p>
|
||||||
<div class="pcOnly" id="snakeContainer">
|
|
||||||
<canvas id="snakeCanvas"></canvas>
|
|
||||||
</div>
|
|
||||||
</section>
|
</section>
|
||||||
<section class="pcOnly flex-row">
|
<section>
|
||||||
<section class="min-width">
|
<h2>The actual error for the 2 ppl who care</h2>
|
||||||
<h2>Submit score</h2>
|
<p>
|
||||||
<form action="/snake/submit" method="POST" id="snakeForm">
|
404: {{ requested_url }} not found :3
|
||||||
<input type="text" id="name" name="name" maxlength=15 minlength=3 placeholder="Your name" required>
|
</p>
|
||||||
<cap-widget id="captcha" data-cap-api-endpoint="https://cap.alfieking.dev/{{ cap_key }}/"></cap-widget>
|
</section>
|
||||||
<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 %}
|
|
Reference in New Issue
Block a user