chris-website/source/app.d

216 lines
5.8 KiB
D

import std.experimental.logger;
import std.range;
import std.string;
import std.stdio;
import std.typecons;
import vibe.d;
import article;
import page;
import project;
import watcher;
/**
* Internal list of articles, pages and projects by slug.
*/
Article[string] articles;
Page[string] pages;
Project[string] projects;
/**
* Default ordering and list with pointers to ordered articles.
* (Note: this is code which will actually be compiled and passed on!)
*/
immutable string articleSortPred = "a.firstPublished > b.firstPublished";
Article*[] articleList;
immutable string pageSortPred = "a.title < b.title";
Page*[] pageList;
immutable string projectSortPred = "a.title < b.title";
Project*[] projectList;
/**
* Output types for the content.
*/
enum OutputType {
HTML,
MARKDOWN
}
immutable string MIME_MARKDOWN = "text/markdown";
immutable Duration CACHE_TIME = days(16);
/**
* Get's the document type for the given slug based on extension
* and returns the slug without extension and the document type. Also removes the extension from the
* slug.
*/
private OutputType getOutputType(ref string slug) {
if (slug.endsWith(".md")) {
slug = chomp(slug, ".md");
return OutputType.MARKDOWN;
} else if (slug.endsWith(".html")){
// If explicitly asking for HTML, we'll return HTML
slug = chomp(slug, ".html");
return OutputType.HTML;
} else {
// If in the future, for any reason, we no longer use HTML
// this allows to us to keep the current urls with an option
// to change the output in the future.
return OutputType.HTML;
}
}
void addCachingHeader(bool publicCache = true)(ref HTTPServerResponse res) {
string header = "";
static if (publicCache) {
header ~= "public";
} else {
header ~= "private";
}
header ~= ", max-age=" ~ to!string(CACHE_TIME.total!"seconds");
res.headers["Cache-Control"] = header;
}
/**
* Template method for fetching a single page for a subclass of page.
* Params:
* T = the data structure/class to be passed as template parameter. Must have a slug parameter.
* templ = The template to use when rendering.
* array = An associative array where the keys are slugs for parameters and the values the template parameter.
* req = The server request to consume. Assumes there is an slug parameter.
* res = The server response to write to.
*
*/
void getSingle(T, string templ)(ref T[string] array, HTTPServerRequest req, HTTPServerResponse res) {
string slug = req.params["slug"];
OutputType outputType = getOutputType(slug);
enforceHTTP(slug in array, HTTPStatus.notFound, "Page not found");
T content = array[slug];
switch(outputType) with (OutputType) {
case MARKDOWN:
res.writeBody(content.contentSource, MIME_MARKDOWN);
break;
default:
case HTML:
res.render!(templ, content);
break;
}
}
struct TranslationContext {
import std.typetuple;
enum enforceExistingKeys = true;
alias languages = TypeTuple!("en_GB", "nl_NL");
mixin translationModule!"mijnblog";
}
@translationContext!TranslationContext
class MijnBlog {
/**
* Generates response for /posts/:slug and /palen/:slug.
*/
@path("/posts/:slug")
void getArticleSingle(string _slug, HTTPServerRequest req, HTTPServerResponse res) {
getSingle!(Article, "pages/article.dt")(articles, req, res);
}
/**
* Generates response for /posts and /palen
*/
@path("/posts/")
void getArticleOverview(HTTPServerRequest req, HTTPServerResponse res) {
addCachingHeader(res);
render!("pages/article-list.dt", articleList)(res);
}
/**
* Generates response for /projects and /projecten
*/
@path("/projects/")
void getProjectOverview(HTTPServerRequest req, HTTPServerResponse res) {
addCachingHeader(res);
render!("pages/project-list.dt", projectList)(res);
}
/**
* Generate response for a project page
*/
@path("/projects/:slug")
void getProject(HTTPServerRequest req, HTTPServerResponse res) {
res.headers["Cache-Control"] = "public";
getSingle!(Project, "pages/project.dt")(projects, req, res);
}
/**
* Generate response for a page
*/
@path("/:slug")
void getPage(HTTPServerRequest req, HTTPServerResponse res) {
addCachingHeader(res);
getSingle!(Page, "pages/page.dt")(pages, req, res);
}
@path("/")
void getIndexPage(HTTPServerRequest req, HTTPServerResponse res) {
addCachingHeader(res);
// If no slug is supplied, it will be adjusted to "index"
req.params.addField("slug", "index");
getSingle!(Page, "pages/page.dt")(pages, req, res);
}
}
/**
* Generates response whenever an error occurs.
*/
@safe
void errorPage(HTTPServerRequest req, HTTPServerResponse res, HTTPServerErrorInfo error) {
render!("pages/error.dt", error)(res);
}
void main() {
HTTPServerSettings settings = new HTTPServerSettings;
settings.bindAddresses = ["0.0.0.0"];
settings.port = 3465;
settings.serverString = "zeg ik lekker niet";
settings.errorPageHandler = toDelegate(&errorPage);
settings.keepAliveTimeout = dur!"seconds"(60);
debug {
settings.accessLogToConsole = true;
}
HTTPFileServerSettings fSettings = new HTTPFileServerSettings;
fSettings.maxAge = days(16);
URLRouter router = new URLRouter;
router.get("/static/*", serveStaticFiles("./public/", fSettings));
router.registerWebInterface(new MijnBlog);
/*router.get("/posts/:slug", &articleGetSingle);
router.get("/palen/:slug", &articleGetSingle);
router.get("/posts/", &articleGetOverview);
router.get("/palen/", &articleGetOverview);
router.get("/projects/", &projectGetOverview);
router.get("/projects/:slug", &projectGet);
router.get("/projecten/:slug", &projectGet);
router.get("/:slug", &pageGet);
router.get("/", &pageGet);*/
listenHTTP(settings, router);
// Start indexing pages.
runTask({
initPages!(Page, pageSortPred)(pages, pageList, "pages");
});
runTask({
initPages!(Article, articleSortPred)(articles, articleList, "articles");
});
runTask({
initPages!(Project, projectSortPred)(projects, projectList, "projects");
});
runApplication();
}