diff options
author | Toni Uhlig <matzeton@googlemail.com> | 2021-10-14 18:41:55 +0200 |
---|---|---|
committer | Toni Uhlig <matzeton@googlemail.com> | 2021-10-14 18:41:55 +0200 |
commit | 51d9d23c99d77edad03e6425f3ab35a390156117 (patch) | |
tree | 82b632c2aac22c3d79f43e427f311a46591f30ca | |
parent | f0f4b8a4d139a855ad15f9d79190edf6320eba51 (diff) |
Blog Metadata parsing, validationa and templating.
Signed-off-by: Toni Uhlig <matzeton@googlemail.com>
-rw-r--r-- | CMakeLists.txt | 6 | ||||
-rw-r--r-- | blog/my-very-first-blog-entry.json | 5 | ||||
-rw-r--r-- | blog/my-very-first-blog-entry.md | 2 | ||||
-rw-r--r-- | src/Content.hpp | 2 | ||||
-rw-r--r-- | src/ContentManager.cpp | 17 | ||||
-rw-r--r-- | src/TemplateManager.cpp | 11 | ||||
-rw-r--r-- | src/content/blog/Blog.cpp | 138 | ||||
-rw-r--r-- | src/content/blog/Blog.hpp | 28 | ||||
-rw-r--r-- | src/content/markdown/Markdown.cpp | 47 | ||||
-rw-r--r-- | src/content/markdown/Markdown.hpp | 11 | ||||
-rw-r--r-- | src/content/static/Static.cpp | 12 | ||||
-rw-r--r-- | src/content/static/Static.hpp | 1 | ||||
-rw-r--r-- | wwwroot/index.html | 5 |
13 files changed, 249 insertions, 36 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index ecc3b2d..b107660 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,9 +2,9 @@ cmake_minimum_required(VERSION 3.1.9) project(cpp-web) set(CMAKE_CXX_FLAGS "-Wall -Wextra -std=c++17") -set(CMAKE_CXX_FLAGS_DEBUG "-g3 -fpic -fomit-frame-pointer -flto -fno-rtti -ffunction-sections -fdata-sections -fsanitize=address -fsanitize=leak") -set(CMAKE_CXX_FLAGS_RELEASE "-Os") -set(CMAKE_EXE_LINKER_FLAGS "-flto") +set(CMAKE_CXX_FLAGS_DEBUG "-g3 -fpic -fno-omit-frame-pointer -fno-rtti -ffunction-sections -fdata-sections -fsanitize=address -fsanitize=leak") +set(CMAKE_CXX_FLAGS_RELEASE "-Os -fpic -fomit-frame-pointer -ffunction-sections -fdata-sections -flto") +set(CMAKE_EXE_LINKER_FLAGS "-Wl,-gc-sections") set(INJA_SRCDIR "${PROJECT_SOURCE_DIR}/deps/inja" CACHE STRING "Path to the inja source directory.") if(NOT EXISTS "${INJA_SRCDIR}/single_include/inja/inja.hpp") diff --git a/blog/my-very-first-blog-entry.json b/blog/my-very-first-blog-entry.json new file mode 100644 index 0000000..dec2711 --- /dev/null +++ b/blog/my-very-first-blog-entry.json @@ -0,0 +1,5 @@ +{ + "createDate": "22.02.1989 23:59", + "publishDate": "31.12.2020 1:00", + "published": false +} diff --git a/blog/my-very-first-blog-entry.md b/blog/my-very-first-blog-entry.md new file mode 100644 index 0000000..292b1e1 --- /dev/null +++ b/blog/my-very-first-blog-entry.md @@ -0,0 +1,2 @@ +Text +======= diff --git a/src/Content.hpp b/src/Content.hpp index 18c3d70..7539e9a 100644 --- a/src/Content.hpp +++ b/src/Content.hpp @@ -9,7 +9,7 @@ #include <vector> using Redirections = std::vector<std::string>; -using RenderData = std::unordered_map<std::string, std::string>; +using RenderData = inja::json; class Content { diff --git a/src/ContentManager.cpp b/src/ContentManager.cpp index 8bff717..a110e05 100644 --- a/src/ContentManager.cpp +++ b/src/ContentManager.cpp @@ -8,15 +8,14 @@ void ContentManager::SetTemplateSystem(std::shared_ptr<TemplateManager> const & bool ContentManager::RegisterModule(std::shared_ptr<Content> ctnt) { std::string const & basePath = ctnt->GetUriBasePath(); - Redirections const & rs = ctnt->GetRedirections(); - m_ContentModules[basePath] = ctnt; - for (auto & redirect : rs) + if (basePath.empty() == true) { - m_ContentModulesRoutes[redirect] = ctnt; + return false; } + m_ContentModules[basePath] = ctnt; - return false; + return true; } bool ContentManager::InitAll(void) @@ -29,6 +28,12 @@ bool ContentManager::InitAll(void) { ret = false; } + + Redirections const & rs = content.second->GetRedirections(); + for (auto & redirect : rs) + { + m_ContentModulesRoutes[redirect] = content.second; + } } return ret; @@ -76,10 +81,10 @@ bool ContentManager::Render(char const * basePath, RequestResponse & rr, std::st cntm = m_ContentModules[basePath]; } - RenderData rd; auto & main = cntm->GetMainTemplate(); out.clear(); + RenderData rd; if (cntm->Render(rr, rd, out) == false) { return false; diff --git a/src/TemplateManager.cpp b/src/TemplateManager.cpp index a751116..99af2ad 100644 --- a/src/TemplateManager.cpp +++ b/src/TemplateManager.cpp @@ -50,8 +50,15 @@ bool TemplateManager::RenderTemplate(std::string const & templatePath, RenderDat return false; } - inja::json ij(rd); - out = m_Inja.render(m_Templates[templatePath].content, ij); + try + { + out = m_Inja.render(m_Templates[templatePath].content, rd); + } + catch (inja::RenderError & re) + { + std::cerr << "Render Error: " << re.what() << std::endl; + return false; + } return true; } diff --git a/src/content/blog/Blog.cpp b/src/content/blog/Blog.cpp index b473bf3..1893cc5 100644 --- a/src/content/blog/Blog.cpp +++ b/src/content/blog/Blog.cpp @@ -1,11 +1,13 @@ #include "Blog.hpp" +#include <filesystem> + Blog::Blog(std::string uriBasePath, std::string mainTemplatePath, std::string blogPath) : Content(), m_UriBasePath(uriBasePath), m_MainTemplatePath(mainTemplatePath), m_BlogPath(blogPath), - m_BlogEntries("", blogPath) + m_BlogContents("", blogPath) { m_Redirections.push_back(uriBasePath + "/"); m_Redirections.push_back(uriBasePath + "/index.html"); @@ -13,25 +15,48 @@ Blog::Blog(std::string uriBasePath, std::string mainTemplatePath, std::string bl bool Blog::Init() { - std::cout << "Blog entries path: " << m_BlogPath << std::endl; + bool retval = true; + std::cout << "Blog entries path: " << m_BlogPath << std::endl; std::vector<std::string> extensions = {"json"}; - if (m_BlogEntriesMetadata.Scan(m_BlogPath, extensions, false) == false) + Filesystem fs; + if (fs.Scan(m_BlogPath, extensions, false) == false) { return false; } - m_BlogEntries.Init(); + for (auto const & jfile : fs.GetFiles()) + { + auto const json_metadata = inja::json::parse(jfile.second.data); + BlogEntry be = + std::make_shared<struct blog_entry>(jfile.first, + std::string(std::filesystem::path(jfile.first).stem()) + ".md"); + if (Blog::ValidateAndSetMetdadata(json_metadata, be) == false) + { + std::cerr << "Blog Metadata validation failed." << std::endl; + retval = false; + } + m_BlogEntriesSortedByDate.push_back(be); - return true; + m_Redirections.push_back(std::filesystem::path(jfile.first).stem()); + } + + m_BlogContents.Init(); + + if (retval == false) + { + return false; + } + + return ValidateEntries(); } void Blog::Shutdown() { std::cout << "Blog module shutdown" << std::endl; - m_BlogEntries.Shutdown(); + m_BlogContents.Shutdown(); } bool Blog::Render(RequestResponse & rr, RenderData & rd, std::string & out) @@ -40,7 +65,15 @@ bool Blog::Render(RequestResponse & rr, RenderData & rd, std::string & out) (void)rd; (void)out; - rd["blah"] = "Yooooh!"; + if (rr.GetUriPath() == m_UriBasePath || rr.GetUriPath() == m_UriBasePath + "/" || + rr.GetUriPath() == m_UriBasePath + "/index.html") + { + GenerateBlogListing(rd["blog_listing"]); + } + else + { + rd["blog_content"] = "bla"; + } return true; } @@ -59,3 +92,94 @@ Redirections const & Blog::GetRedirections() const { return m_Redirections; } + +bool Blog::ValidateAndSetMetdadata(BlogMetadata const & blogMetadata, BlogEntry & blogEntry) +{ + bool retval = true; + std::function<bool(BlogMetadata const &, std::string const)> validateMetadata = + [blogEntry](BlogMetadata const & bm, std::string const tname) { + if (bm.find(tname) == bm.cend()) + { + std::cerr << "Metadata validation: JSON key '" << tname << "' missing in " + << blogEntry->metadata_filename << std::endl; + return false; + } + return true; + }; + std::function<bool(std::string const &, std::time_t &)> parseDateTime = [](std::string const & timeStr, + std::time_t & time) { + std::tm tm = {}; + std::stringstream ss(timeStr); + ss >> std::get_time(&tm, "%d.%m.%y %H:%M"); + time = std::mktime(&tm); + if (time <= 0) + { + std::cerr << "Metadata validation: Invalid time string '" << timeStr + << "', format required: '%d.%m.%y %H:%M'" << std::endl; + return false; + } + return true; + }; + + if (validateMetadata(blogMetadata, "createDate") == false || + parseDateTime(blogMetadata["createDate"], blogEntry->createDate) == false) + { + retval = false; + } + + if (validateMetadata(blogMetadata, "publishDate") == false || + parseDateTime(blogMetadata["publishDate"], blogEntry->publishDate) == false) + { + retval = false; + } + + if (validateMetadata(blogMetadata, "published") == false) + { + retval = false; + } + blogEntry->published = blogMetadata["published"]; + + return retval; +} + +bool Blog::ValidateEntries() const +{ + bool retval = true; + + for (auto const & e : m_BlogEntriesSortedByDate) + { + if (m_BlogContents.HasMarkdownFile(e->content_filename) == false) + { + std::cerr << "Blog entry metadata " << e->metadata_filename << " exists, but markdown file " + << e->content_filename << " not." << std::endl; + retval = false; + } + } + for (auto const & m : m_BlogContents.GetMarkdowns()) + { + if (std::any_of(m_BlogEntriesSortedByDate.cbegin(), + m_BlogEntriesSortedByDate.cend(), + [m](BlogEntry const & be) { return m.first == be->content_filename; }) == false) + { + std::cerr << "Blog entry markdown " << m.first << " exists, but metadata not." << std::endl; + retval = false; + } + } + + return retval; +} + +void Blog::GenerateBlogListing(RenderData & rd) const +{ + for (auto const & e : m_BlogEntriesSortedByDate) + { + RenderData re; + re["metadata_filename"] = e->metadata_filename; + re["content_filename"] = e->content_filename; + re["createDate"] = e->createDate; + re["publishDate"] = e->publishDate; + re["published"] = e->published; + + rd += re; + } +} diff --git a/src/content/blog/Blog.hpp b/src/content/blog/Blog.hpp index 970c7fd..a0e35b1 100644 --- a/src/content/blog/Blog.hpp +++ b/src/content/blog/Blog.hpp @@ -1,10 +1,30 @@ #ifndef BLOG_H #define BLOG_H 1 +#include <inja/inja.hpp> + #include "../../Content.hpp" #include "../../Filesystem.hpp" #include "../markdown/Markdown.hpp" +struct blog_entry +{ + explicit blog_entry(std::string const & metadata_filename, std::string const & content_filename) + : metadata_filename(metadata_filename), content_filename(content_filename) + { + } + + std::string const metadata_filename; + std::string const content_filename; + std::time_t createDate; + std::time_t publishDate; + bool published; +}; + +using BlogMetadata = inja::json; +using BlogEntry = std::shared_ptr<struct blog_entry>; +using BlogEntries = std::vector<BlogEntry>; + class Blog : public Content { public: @@ -18,13 +38,17 @@ public: std::string const & GetMainTemplate() const; Redirections const & GetRedirections() const; + static bool ValidateAndSetMetdadata(BlogMetadata const & blogMetadata, BlogEntry & blogEntry); + bool ValidateEntries() const; + void GenerateBlogListing(RenderData & rd) const; + private: std::string m_UriBasePath; std::string m_MainTemplatePath; std::string m_BlogPath; Redirections m_Redirections; - Filesystem m_BlogEntriesMetadata; - Markdown m_BlogEntries; + Markdown m_BlogContents; + BlogEntries m_BlogEntriesSortedByDate; }; #endif diff --git a/src/content/markdown/Markdown.cpp b/src/content/markdown/Markdown.cpp index 2009772..63358b4 100644 --- a/src/content/markdown/Markdown.cpp +++ b/src/content/markdown/Markdown.cpp @@ -1,7 +1,10 @@ #include "Markdown.hpp" -Markdown::Markdown(std::string uriBasePath, std::string markdownFilesPath) - : Content(), m_UriBasePath(uriBasePath), m_MainTemplatePath(""), m_MarkdownFilesPath(markdownFilesPath) +Markdown::Markdown(std::string uriBasePath, std::string markdownFilesPath, std::string mainTemplatePath) + : Content(), + m_UriBasePath(uriBasePath), + m_MainTemplatePath(mainTemplatePath), + m_MarkdownFilesPath(markdownFilesPath) { } @@ -11,11 +14,18 @@ bool Markdown::Init() std::vector<std::string> extensions = {"md"}; - if (m_MarkdownFiles.Scan(m_MarkdownFilesPath, extensions, false) == false) + Filesystem fs; + if (fs.Scan(m_MarkdownFilesPath, extensions, false) == false) { return false; } + for (auto const & mfile : fs.GetFiles()) + { + m_Markdowns[mfile.first] = + std::make_shared<std::string>(std::string(mfile.second.data.begin(), mfile.second.data.end())); + } + return true; } @@ -26,13 +36,16 @@ void Markdown::Shutdown() bool Markdown::Render(RequestResponse & rr, RenderData & rd, std::string & out) { + (void)rr; + (void)rd; (void)out; - rd["blub"] = "Yoh21!"; - rr.UseOutputHeader(); - rr.AddOutputHeader("blaaaa", "blubb"); + if (m_MainTemplatePath.empty() == true) + { + return false; + } - return true; + return false; /* TODO: Make markdown module usable as standalone module?! */ } std::string const & Markdown::GetUriBasePath() const @@ -49,3 +62,23 @@ Redirections const & Markdown::GetRedirections() const { return m_Redirections; } + +bool Markdown::HasMarkdownFile(std::string filePath) const +{ + return m_Markdowns.find(filePath) != m_Markdowns.end(); +} + +bool Markdown::HasMarkdownURI(std::string uriPath) const +{ + return HasMarkdownFile(uriPath.substr(m_UriBasePath.length() + 1, std::string::npos)); +} + +Markdowns const & Markdown::GetMarkdowns() const +{ + return m_Markdowns; +} + +std::shared_ptr<std::string> const & Markdown::GetMarkdownHTML(std::string uriPath) +{ + return m_Markdowns[uriPath]; +} diff --git a/src/content/markdown/Markdown.hpp b/src/content/markdown/Markdown.hpp index 41c8b4d..9c4d3c4 100644 --- a/src/content/markdown/Markdown.hpp +++ b/src/content/markdown/Markdown.hpp @@ -4,10 +4,12 @@ #include "../../Content.hpp" #include "../../Filesystem.hpp" +using Markdowns = std::unordered_map<std::string, std::shared_ptr<std::string> >; + class Markdown : public Content { public: - explicit Markdown(std::string uriBasePath, std::string markdownFilesPath); + explicit Markdown(std::string uriBasePath, std::string markdownFilesPath, std::string mainTemplatePath = ""); bool Init(); void Shutdown(); @@ -17,12 +19,17 @@ public: std::string const & GetMainTemplate() const; Redirections const & GetRedirections() const; + bool HasMarkdownFile(std::string filePath) const; + bool HasMarkdownURI(std::string uriPath) const; + Markdowns const & GetMarkdowns() const; + std::shared_ptr<std::string> const & GetMarkdownHTML(std::string uriPath); + private: std::string m_UriBasePath; std::string m_MainTemplatePath; std::string m_MarkdownFilesPath; Redirections m_Redirections; - Filesystem m_MarkdownFiles; + Markdowns m_Markdowns; }; #endif diff --git a/src/content/static/Static.cpp b/src/content/static/Static.cpp index b9977a9..132f0a0 100644 --- a/src/content/static/Static.cpp +++ b/src/content/static/Static.cpp @@ -6,7 +6,6 @@ Static::Static(std::string uriBasePath, std::shared_ptr<Filesystem> const & fs) for (auto const & file : fs->GetFiles()) { m_Redirections.push_back(uriBasePath + "/" + file.first); - m_UriToFsMapping[uriBasePath + "/" + file.first] = file.first; } } @@ -26,16 +25,21 @@ bool Static::Render(RequestResponse & rr, RenderData & rd, std::string & out) { (void)rd; + if (rr.GetUriPath() == m_UriBasePath) + { + return false; + } + rr.UseOutputHeader(); auto & files = m_StaticFiles->GetFiles(); - auto const & path = std::string(rr.GetUriPath()); + auto const & path = std::string(rr.GetUriPath()).substr(m_UriBasePath.length() + 1, std::string::npos); - if (rr.AddOutputHeader("Content-Type", files[m_UriToFsMapping[path]].mime) == false) + if (rr.AddOutputHeader("Content-Type", files[path].mime) == false) { return false; } - out = std::string(files[m_UriToFsMapping[path]].data.begin(), files[m_UriToFsMapping[path]].data.end()); + out = std::string(files[path].data.begin(), files[path].data.end()); return true; } diff --git a/src/content/static/Static.hpp b/src/content/static/Static.hpp index 8dcf410..b9de983 100644 --- a/src/content/static/Static.hpp +++ b/src/content/static/Static.hpp @@ -22,7 +22,6 @@ private: std::string m_MainTemplatePath; Redirections m_Redirections; std::shared_ptr<Filesystem> const m_StaticFiles; - std::unordered_map<std::string, std::string> m_UriToFsMapping; }; #endif diff --git a/wwwroot/index.html b/wwwroot/index.html index 002ea72..b0a9fbf 100644 --- a/wwwroot/index.html +++ b/wwwroot/index.html @@ -3,6 +3,9 @@ <b>blabla</b> Test fn....: <b>{{ test_fn }}</b><br> Test RetFN.: <b>{{ test_return_true }}</b><br> -Blog Render: <b>{{ blah }}</b><br> +## for entry in blog_listing + {{ loop.index1 }}: {{ entry.content_filename }} -> {{ entry.published }}<br> + <b>{{ entry }}</b><br> +## endfor </p> </body></html> |