Skip to content

Commit

Permalink
Implement migration from legacy blog
Browse files Browse the repository at this point in the history
  • Loading branch information
MetroWind committed Sep 26, 2024
1 parent a36ef2d commit 2e665ca
Show file tree
Hide file tree
Showing 6 changed files with 172 additions and 11 deletions.
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ set(SOURCE_FILES
src/url.hpp
src/utils.hpp
src/attachment.hpp
src/legacy-migration.hpp
)

set(LIBS
Expand Down
13 changes: 6 additions & 7 deletions src/app.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -417,15 +417,12 @@ void App::handlePost(const httplib::Request& req, httplib::Response& res)
}

ASSIGN_OR_RESPOND_ERROR(nlohmann::json pj, renderPostToJson(*std::move(p)), res);
spdlog::debug("aaa");
nlohmann::json data{{"post", std::move(pj)},
{"blog_title", config.blog_title},
{"session_user", session->user.name}};
spdlog::debug(data.dump());
std::string result = templates.render_file(
"post.html", std::move(data));
spdlog::debug(result);
res.set_content(result, "text/html");
res.set_content(std::move(result), "text/html");
}

void App::handleDrafts(const httplib::Request& req, httplib::Response& res)
Expand Down Expand Up @@ -707,16 +704,18 @@ E<nlohmann::json> App::renderPostToJson(Post&& p)
result["id"] = std::to_string(*p.id);
}
// Do template substitution in the post content. This allows
// writer to write {{ url_for(...) }} in the post.
// writer to write {{ url_for(...) }} in the post. If this fails,
// we’ll just use the post content as-is.
nlohmann::json data;
try
{
p.raw_content = templates.render(p.raw_content, std::move(data));
}
catch(const inja::InjaError& e)
{
return std::unexpected(runtimeError(
std::format("Blog content failed to render: {}", e.message)));
spdlog::warn(
"Failed to do template substitution on the post content: {}",
e.message);
}
ASSIGN_OR_RETURN(result["content"], post_cache.renderPost(p));
return result;
Expand Down
23 changes: 20 additions & 3 deletions src/data.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -164,11 +164,11 @@ E<int64_t> DataSourceSqlite::saveDraft(Post&& new_draft) const
return std::unexpected(runtimeError("New draft should not have ID"));
}
ASSIGN_OR_RETURN(auto sql, db->statementFromStr(
"INSERT INTO Posts (markup, title, abstract, content, language) VALUES "
"(?, ?, ?, ?, ?)"));
"INSERT INTO Posts (markup, title, abstract, content, language, author)"
" VALUES (?, ?, ?, ?, ?, ?)"));
DO_OR_RETURN(sql.bind(
static_cast<int>(new_draft.markup), new_draft.title, new_draft.abstract,
new_draft.raw_content, new_draft.language));
new_draft.raw_content, new_draft.language, new_draft.author));
DO_OR_RETURN(db->execute(std::move(sql)));
return db->lastInsertRowID();
}
Expand Down Expand Up @@ -360,6 +360,23 @@ E<void> DataSourceSqlite::addAttachmentReferral(
}
}

E<void> DataSourceSqlite::forceSetPostTimes(
int64_t id, const Time& publish, const std::optional<Time>& update) const
{
ASSIGN_OR_RETURN(auto sql, db->statementFromStr(
"UPDATE Posts SET publish_time = ? WHERE id = ?;"));
DO_OR_RETURN(sql.bind(timeToSeconds(publish), id));
DO_OR_RETURN(db->execute(std::move(sql)));
if(update.has_value())
{
ASSIGN_OR_RETURN(auto sql, db->statementFromStr(
"UPDATE Posts SET update_time = ? WHERE id = ?;"));
DO_OR_RETURN(sql.bind(timeToSeconds(*update), id));
DO_OR_RETURN(db->execute(std::move(sql)));
}
return {};
}

E<void> DataSourceSqlite::setSchemaVersion(int64_t v) const
{
return db->execute(std::format("PRAGMA user_version = {};", v));
Expand Down
2 changes: 2 additions & 0 deletions src/data.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,8 @@ class DataSourceSqlite : public DataSourceInterface
E<void> addAttachmentReferral(const std::string& attachment_hash,
const std::string& url) const override;

E<void> forceSetPostTimes(int64_t id, const Time& publish,
const std::optional<Time>& update) const;
// Do not use.
DataSourceSqlite() = default;

Expand Down
123 changes: 123 additions & 0 deletions src/legacy-migration.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
#pragma once

#include <ctime>
#include <fstream>
#include <filesystem>
#include <sstream>
#include <tuple>
#include <string_view>

#include "config.hpp"
#include "data.hpp"
#include "error.hpp"
#include "post.hpp"
#include "utils.hpp"

namespace fs = std::filesystem;

inline std::tuple<std::string_view, std::string_view>
parseHeaderLine(std::string_view line)
{
auto idx = line.find(": ");
return {line.substr(0, idx), line.substr(idx + 2)};
}

inline Post readLegacyPost(const fs::path& filename)
{
std::ifstream file(filename);
std::string line;
bool header_done = false;
Post p;
p.author = "mw";
p.markup = Post::COMMONMARK;
while(std::getline(file, line))
{
if(line.empty() && !header_done)
{
header_done = true;
continue;
}

if(!header_done)
{
auto [key, value] = parseHeaderLine(line);
if(key == "Language")
{
std::string lang(value);
toLower(lang);
if(lang == "zh")
{
p.language = "zh-CN";
}
else
{
p.language = lang;
}
}
else if(key == "Title")
{
p.title = value;
}
else if(key == "Time")
{
std::tm tm;
std::stringstream ss{std::string(value)};
ss >> std::get_time(&tm, "%Y-%m-%d %H:%M");
p.publish_time = Clock::from_time_t(std::mktime(&tm));
}
else if(key == "Updated")
{
std::tm tm;
std::stringstream ss{std::string(value)};
ss >> std::get_time(&tm, "%Y-%m-%d %H:%M");
p.update_time = Clock::from_time_t(std::mktime(&tm));
}
else if(key == "Renderer")
{
if(value == "asciidoctor")
{
p.markup = Post::ASCIIDOC;
}
}
else if(key == "Abstract")
{
p.abstract = value;
}
}
else
{
p.raw_content += line;
p.raw_content += "\n";
}
}
return p;
}

inline std::vector<Post> discoverPosts(const fs::path& post_dir)
{
std::vector<Post> ps;
for(const fs::directory_entry& entry:
fs::recursive_directory_iterator(post_dir))
{
if(!entry.is_regular_file())
{
continue;
}
ps.push_back(readLegacyPost(entry.path()));
}
return ps;
}

inline E<void> migrate(const fs::path& post_dir, const Configuration& config)
{
ASSIGN_OR_RETURN(auto data_source, DataSourceSqlite::fromFile(
(std::filesystem::path(config.data_dir) / "data.db").string()));
for(const Post& p: discoverPosts(post_dir))
{
ASSIGN_OR_RETURN(int64_t id, data_source->saveDraft(Post(p)));
DO_OR_RETURN(data_source->publishPost(id));
DO_OR_RETURN(data_source->forceSetPostTimes(
id, *p.publish_time, p.update_time));
}
return {};
}
21 changes: 20 additions & 1 deletion src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include "config.hpp"
#include "data.hpp"
#include "http_client.hpp"
#include "legacy-migration.hpp"
#include "spdlog/common.h"
#include "spdlog/spdlog.h"
#include "utils.hpp"
Expand All @@ -22,6 +23,8 @@ int main(int argc, char** argv)
cmd_options.add_options()
("c,config", "Config file",
cxxopts::value<std::string>()->default_value("/etc/nsweekly.yaml"))
("legacy-migration", "Migrate the legacy posts from a directory",
cxxopts::value<std::string>())
("h,help", "Print this message.");
auto opts = cmd_options.parse(argc, argv);

Expand All @@ -36,10 +39,26 @@ int main(int argc, char** argv)
auto conf = Configuration::fromYaml(std::move(config_file));
if(!conf.has_value())
{
spdlog::error("Failed to load configuration: {}", errorMsg(conf.error()));
spdlog::error("Failed to load configuration: {}",
errorMsg(conf.error()));
return 3;
}

if(opts.count("legacy-migration") == 1)
{
auto ok_maybe = migrate(
opts["legacy-migration"].as<std::string>(), *conf);
if(ok_maybe)
{
return 0;
}
else
{
spdlog::error(errorMsg(ok_maybe.error()));
return 1;
}
}

auto url_prefix = URL::fromStr(conf->base_url);
if(!url_prefix.has_value())
{
Expand Down

0 comments on commit 2e665ca

Please sign in to comment.