From d399fd0bd00c9ea073e5b057de70c9ffdd9356f8 Mon Sep 17 00:00:00 2001 From: Vika Shleina Date: Thu, 15 Jul 2021 04:32:29 +0300 Subject: Renamed main executable to kittybox, added tools The new tools are: - kittybox-bulk-import, a bare-bones Micropub client that reads a JSON list of posts and then sends them one by one to the Micropub endpoint - pyindieblog-export, my personal tool which directly connects to Pyindieblog's redis instance and extracts data from it in JSON format suitable for use with kittybox-bulk-import. --- .gitlab-ci.yml | 8 ++++-- Cargo.lock | 7 +++--- Cargo.toml | 15 +++++++++++- flake.nix | 16 ++++++------ src/bin/kittybox_bulk_import.rs | 50 ++++++++++++++++++++++++++++++++++++++ src/bin/pyindieblog_to_kittybox.rs | 48 ++++++++++++++++++++++++++++++++++++ src/main.rs | 6 ++--- 7 files changed, 133 insertions(+), 17 deletions(-) create mode 100644 src/bin/kittybox_bulk_import.rs create mode 100644 src/bin/pyindieblog_to_kittybox.rs diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index fc893bd..6135793 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -41,7 +41,9 @@ build-x86_64-musl: artifacts: expire_in: 30 days paths: - - target/x86_64-unknown-linux-musl/release/kittybox_micropub + - target/x86_64-unknown-linux-musl/release/kittybox + - target/x86_64-unknown-linux-musl/release/kittybox-bulk-import + - target/x86_64-unknown-linux-musl/release/pyindieblog-export build-aarch64-musl: needs: ["test-rust-stable"] @@ -56,7 +58,9 @@ build-aarch64-musl: artifacts: expire_in: 30 days paths: - - target/aarch64-unknown-linux-musl/release/kittybox_micropub + - target/aarch64-unknown-linux-musl/release/kittybox + - target/aarch64-unknown-linux-musl/release/kittybox-bulk-import + - target/aarch64-unknown-linux-musl/release/pyindieblog-export .build-docker: stage: build diff --git a/Cargo.lock b/Cargo.lock index 902f362..58776a1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -67,9 +67,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.41" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15af2628f6890fe2609a3b91bef4c83450512802e59489f9c1cb1fa5df064a61" +checksum = "595d3cfa7a60d4555cb5067b99f07142a08ea778de5cf993f7b75c7d8fabc486" [[package]] name = "arrayref" @@ -1184,9 +1184,10 @@ dependencies = [ ] [[package]] -name = "kittybox_micropub" +name = "kittybox" version = "0.1.0" dependencies = [ + "anyhow", "async-std", "async-trait", "chrono", diff --git a/Cargo.toml b/Cargo.toml index 52095fb..b07da8a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,9 +1,22 @@ [package] -name = "kittybox_micropub" +name = "kittybox" version = "0.1.0" authors = ["Vika "] edition = "2018" +[features] +default = ["util"] +util = ["anyhow"] + +[[bin]] +name = "kittybox-bulk-import" +path = "src/bin/kittybox_bulk_import.rs" +required-features = ["util"] + +[[bin]] +name = "pyindieblog-export" +path = "src/bin/pyindieblog_to_kittybox.rs" +required-features = ["util"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dev-dependencies] diff --git a/flake.nix b/flake.nix index a24981e..b05e05c 100644 --- a/flake.nix +++ b/flake.nix @@ -35,8 +35,8 @@ }; package = mkOption { type = types.package; - default = self.packages.${config.nixpkgs.localSystem.system}.kittybox-micropub; - defaultText = ""; + default = self.packages.${config.nixpkgs.localSystem.system}.kittybox; + defaultText = ""; description = "Which Kittybox derivation to use."; }; @@ -98,7 +98,7 @@ }; serviceConfig = { - ExecStart = "${cfg.package}/bin/kittybox_micropub"; + ExecStart = "${cfg.package}/bin/kittybox"; DynamicUser = true; }; }; @@ -114,9 +114,9 @@ }; rust-bin = pkgs.rust-bin.stable.latest; packages = { - kittybox-micropub = { stdenv, lib, redis, naersk-lib }: + kittybox = { stdenv, lib, redis, naersk-lib }: naersk-lib.buildPackage { - pname = "kittybox-micropub"; + pname = "kittybox"; version = "0.1.0"; src = ./.; @@ -127,7 +127,7 @@ meta = with lib.meta; { maintainers = with maintainers; [ vika_nezrimaya ]; platforms = supportedSystems; - mainProgram = "kittybox_micropub"; + mainProgram = "kittybox"; }; }; }; @@ -138,9 +138,9 @@ cargo = pkgs.rust-bin.nightly.latest.minimal; }; in { - kittybox-micropub = pkgs.callPackage packages.kittybox-micropub { inherit naersk-lib; }; + kittybox = pkgs.callPackage packages.kittybox { inherit naersk-lib; }; }; - defaultPackage = self.packages.${system}.kittybox-micropub; + defaultPackage = self.packages.${system}.kittybox; checks = { nixos-test = pkgs.nixosTest ({ lib }: { diff --git a/src/bin/kittybox_bulk_import.rs b/src/bin/kittybox_bulk_import.rs new file mode 100644 index 0000000..652a4c2 --- /dev/null +++ b/src/bin/kittybox_bulk_import.rs @@ -0,0 +1,50 @@ +use std::io::{self, Read}; +use std::fs::File; +use anyhow::{anyhow, Context, Result, bail}; + +#[async_std::main] +async fn main() -> Result<()> { + let args = std::env::args().collect::>(); + if args.iter().skip(1).any(|s| s == "--help") { + println!("Usage: {} [file]", args[0]); + println!("\nIf launched with no arguments, reads from stdin."); + println!("\nUse KITTYBOX_AUTH_TOKEN environment variable to authorize to the Micropub endpoint."); + std::process::exit(0); + } + + let token = std::env::var("KITTYBOX_AUTH_TOKEN").map_err(|_| anyhow!("No auth token found! Use KITTYBOX_AUTH_TOKEN env variable."))?; + let data: Vec = (if args.len() == 2 || (args.len() == 3 && args[2] == "-") { + serde_json::from_reader(io::stdin()) + } else if args.len() == 3 { + serde_json::from_reader(File::open(&args[2]).with_context(|| "Error opening input file")?) + } else { + bail!("See `{} --help` for usage.", args[0]); + }).with_context(|| "Error while loading the input file")?; + + let url = surf::Url::parse(&args[1])?; + let client = surf::Client::new(); + + let mut iter = data.into_iter(); + + while let Some(post) = iter.next() { + println!("Processing {}...", post["properties"]["url"][0].as_str().or(post["properties"]["published"][0].as_str().or(post["properties"]["name"][0].as_str().or(Some("")))).unwrap()); + match client.post(&url) + .body(surf::http::Body::from_string( + serde_json::to_string(&post)?)) + .header("Content-Type", "application/json") + .header("Authorization", format!("Bearer {}", &token)) + .send().await { + Ok(mut response) => { + if response.status() == 201 || response.status() == 202 { + println!("Posted at {}", response.header("location").unwrap().last()); + } else { + println!("Error: {:?}", response.body_string().await); + } + } + Err(err) => { + println!("{}", err); + } + } + } + Ok(()) +} diff --git a/src/bin/pyindieblog_to_kittybox.rs b/src/bin/pyindieblog_to_kittybox.rs new file mode 100644 index 0000000..7935da5 --- /dev/null +++ b/src/bin/pyindieblog_to_kittybox.rs @@ -0,0 +1,48 @@ +use std::collections::HashMap; +use std::fs::File; +use anyhow::{Error, Result, Context, anyhow, bail}; +use mobc_redis::redis; +use mobc_redis::redis::AsyncCommands; +use serde::{Serialize, Deserialize}; +use serde_json::json; + +#[derive(Default, Serialize, Deserialize)] +struct PyindieblogData { + posts: Vec, + cards: Vec +} + +#[async_std::main] +async fn main() -> Result<()> { + let mut args = std::env::args(); + args.next(); // skip argv[0] which is the name + let redis_uri = args.next().ok_or(anyhow!("No Redis URI provided"))?; + let client = redis::Client::open(redis_uri.as_str()).with_context(|| format!("Failed to construct Redis client on {}", redis_uri))?; + + let filename = args.next().ok_or(anyhow!("No filename provided for export"))?; + + let mut data: Vec; + + let file = File::create(filename)?; + + let mut conn = client.get_async_std_connection().await.with_context(|| "Failed to connect to the Redis server")?; + + data = conn.hgetall::<&str, HashMap>("posts").await? + .values() + .map(|s| serde_json::from_str::(s) + .with_context(|| format!("Failed to parse the following entry: {:?}", s))) + .collect::, anyhow::Error>>() + .with_context(|| "Failed to export h-entries from pyindieblog")?; + data.extend(conn.hgetall::<&str, HashMap>("hcards").await? + .values() + .map(|s| serde_json::from_str::(s) + .with_context(|| format!("Failed to parse the following card: {:?}", s))) + .collect::, anyhow::Error>>() + .with_context(|| "Failed to export h-cards from pyindieblog")?); + + data.sort_by_key(|v| v["properties"]["published"][0].as_str().map(|s| s.to_string())); + + serde_json::to_writer(file, &data)?; + + Ok(()) +} diff --git a/src/main.rs b/src/main.rs index 1b333a2..23b5ddb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,4 @@ -use kittybox_micropub as micropub; +use kittybox; use log::{debug, error, info}; use std::env; use surf::Url; @@ -9,7 +9,7 @@ async fn main() -> Result<(), std::io::Error> { let logger_env = env_logger::Env::new().filter_or("RUST_LOG", "info"); env_logger::init_from_env(logger_env); - info!("Starting the Micropub server..."); + info!("Starting the kittybox server..."); let redis_uri: String; match env::var("REDIS_URI") { @@ -62,7 +62,7 @@ async fn main() -> Result<(), std::io::Error> { let host = env::var("SERVE_AT") .ok() .unwrap_or_else(|| "0.0.0.0:8080".to_string()); - let app = micropub::get_app_with_redis( + let app = kittybox::get_app_with_redis( token_endpoint, authorization_endpoint, redis_uri, -- cgit 1.4.1