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; } } /** * Generates response for /posts/:slug and /palen/:slug. */ void articleGetSingle(HTTPServerRequest req, HTTPServerResponse res) { getSingle!(Article, "pages/article.dt")(articles, req, res); } /** * Generates response for /posts and /palen */ void articleGetOverview(HTTPServerRequest req, HTTPServerResponse res) { addCachingHeader(res); render!("pages/article-list.dt", articleList)(res); } /** * Generates response for /projects and /projecten */ void projectGetOverview(HTTPServerRequest req, HTTPServerResponse res) { addCachingHeader(res); render!("pages/project-list.dt", projectList)(res); } /** * Generate response for a page */ void pageGet(HTTPServerRequest req, HTTPServerResponse res) { addCachingHeader(res); // If no slug is supplied, it will be adjusted to "index" if (("slug" in req.params) is null) { req.params.addField("slug", "index"); } getSingle!(Page, "pages/page.dt")(pages, req, res); } /** * Generate response for a project page */ void projectGet(HTTPServerRequest req, HTTPServerResponse res) { res.headers["Cache-Control"] = "public"; getSingle!(Project, "pages/project.dt")(projects, 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("/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("/static/*", serveStaticFiles("./public/", fSettings)); 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(); }