about summary refs log tree commit diff
diff options
context:
space:
mode:
authorVika Shleina <vika@fireburn.ru>2021-07-15 04:32:29 +0300
committerVika <vika@fireburn.ru>2021-07-19 04:57:11 +0300
commitd399fd0bd00c9ea073e5b057de70c9ffdd9356f8 (patch)
treea9dafbeb62fb2aeb56e2d190565c70cd7f4b9d7a
parent93b56879298810d60d121203403e2416f35e4765 (diff)
downloadkittybox-d399fd0bd00c9ea073e5b057de70c9ffdd9356f8.tar.zst
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.
-rw-r--r--.gitlab-ci.yml8
-rw-r--r--Cargo.lock7
-rw-r--r--Cargo.toml15
-rw-r--r--flake.nix16
-rw-r--r--src/bin/kittybox_bulk_import.rs50
-rw-r--r--src/bin/pyindieblog_to_kittybox.rs48
-rw-r--r--src/main.rs6
7 files changed, 133 insertions, 17 deletions
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 <vika@fireburn.ru>"]
 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 = "<kittybox_micropub package from the official flake>";
+            default = self.packages.${config.nixpkgs.localSystem.system}.kittybox;
+            defaultText = "<kittybox package from the official flake>";
             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::<Vec<String>>();
+    if args.iter().skip(1).any(|s| s == "--help") {
+        println!("Usage: {} <url> [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<serde_json::Value> = (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("<unidentified post>")))).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<serde_json::Value>,
+    cards: Vec<serde_json::Value>
+}
+
+#[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<serde_json::Value>;
+
+    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<String, String>>("posts").await?
+        .values()
+        .map(|s| serde_json::from_str::<serde_json::Value>(s)
+             .with_context(|| format!("Failed to parse the following entry: {:?}", s)))
+        .collect::<std::result::Result<Vec<serde_json::Value>, anyhow::Error>>()
+        .with_context(|| "Failed to export h-entries from pyindieblog")?;
+    data.extend(conn.hgetall::<&str, HashMap<String, String>>("hcards").await?
+        .values()
+        .map(|s| serde_json::from_str::<serde_json::Value>(s)
+             .with_context(|| format!("Failed to parse the following card: {:?}", s)))
+        .collect::<std::result::Result<Vec<serde_json::Value>, 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,