New Plume API: no more canapi + Better error handling

Fix #4
Fix #5
This commit is contained in:
Baptiste Gelez 2019-04-21 13:40:50 +01:00
parent e39700c9cb
commit 9a8502cb7d
5 changed files with 146 additions and 127 deletions

8
.env
View file

@ -1,5 +1,5 @@
PLUME_API_URL=localhost:2020 PLUME_API_URL=pr-540.joinplu.me
PLUME_CLIENT_ID=b62af083cd34da7ad36fbf1f2133b5c74ad16e05da9fcabdcb8b6672e63 PLUME_CLIENT_ID="e1a2e321542191e6e9d74a60e6f4803d93d956bd7efd3d19777056c6bcf9e299"
PLUME_CLIENT_SECRET=b22c28a4e6cf9a9eb9c4749aefaf4b26afa38ac7c6f6dd42aa324e4cce224e PLUME_CLIENT_SECRET="43db6ef980d85fa332ca49c6fc5cdc8e5912d3d7e765a5a9fa53783dac738"
PLUME_API_TOKEN=c7e38c11bc1543637f9e85a123513b4c22e3029c74cb6b6412aa3979d22d11a PLUME_API_TOKEN=c59536eab888ed4a04e7074a3df7d29a9b68ae2547a9694c7924068e4bc89

19
Cargo.lock generated
View file

@ -15,10 +15,9 @@ dependencies = [
name = "amsterdam" name = "amsterdam"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"canapi 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)", "clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)",
"dotenv 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)", "dotenv 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)",
"plume-api 0.2.0 (git+https://github.com/Plume-org/Plume?rev=88a6a81ad8ce8cdb18b9ae3467961cf9490e4411)", "plume-api 0.3.0 (git+https://github.com/Plume-org/Plume?rev=aee604751193aaebb0a858dc81dc15a9ab67659e)",
"reqwest 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)", "reqwest 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)",
"rpassword 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "rpassword 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.32 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.32 (registry+https://github.com/rust-lang/crates.io-index)",
@ -104,14 +103,6 @@ dependencies = [
"iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]]
name = "canapi"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"serde 1.0.80 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.0.25" version = "1.0.25"
@ -660,10 +651,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
name = "plume-api" name = "plume-api"
version = "0.2.0" version = "0.3.0"
source = "git+https://github.com/Plume-org/Plume?rev=88a6a81ad8ce8cdb18b9ae3467961cf9490e4411#88a6a81ad8ce8cdb18b9ae3467961cf9490e4411" source = "git+https://github.com/Plume-org/Plume?rev=aee604751193aaebb0a858dc81dc15a9ab67659e#aee604751193aaebb0a858dc81dc15a9ab67659e"
dependencies = [ dependencies = [
"canapi 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.80 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.80 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.80 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.80 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
@ -1309,7 +1299,6 @@ dependencies = [
"checksum build_const 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "39092a32794787acd8525ee150305ff051b0aa6cc2abaf193924f5ab05425f39" "checksum build_const 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "39092a32794787acd8525ee150305ff051b0aa6cc2abaf193924f5ab05425f39"
"checksum byteorder 1.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "90492c5858dd7d2e78691cfb89f90d273a2800fc11d98f60786e5d87e2f83781" "checksum byteorder 1.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "90492c5858dd7d2e78691cfb89f90d273a2800fc11d98f60786e5d87e2f83781"
"checksum bytes 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "0ce55bd354b095246fc34caf4e9e242f5297a7fd938b090cadfea6eee614aa62" "checksum bytes 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "0ce55bd354b095246fc34caf4e9e242f5297a7fd938b090cadfea6eee614aa62"
"checksum canapi 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aab4d6d1edcef8bf19b851b7730d3d1a90373c06321a49a984baebe0989c962c"
"checksum cc 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)" = "f159dfd43363c4d08055a07703eb7a3406b0dac4d0584d96965a3262db3c9d16" "checksum cc 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)" = "f159dfd43363c4d08055a07703eb7a3406b0dac4d0584d96965a3262db3c9d16"
"checksum cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "082bb9b28e00d3c9d39cc03e64ce4cea0f1bb9b3fde493f0cbc008472d22bdf4" "checksum cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "082bb9b28e00d3c9d39cc03e64ce4cea0f1bb9b3fde493f0cbc008472d22bdf4"
"checksum clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b957d88f4b6a63b9d70d5f454ac8011819c6efa7727858f458ab71c756ce2d3e" "checksum clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b957d88f4b6a63b9d70d5f454ac8011819c6efa7727858f458ab71c756ce2d3e"
@ -1372,7 +1361,7 @@ dependencies = [
"checksum phf_generator 0.7.23 (registry+https://github.com/rust-lang/crates.io-index)" = "03dc191feb9b08b0dc1330d6549b795b9d81aec19efe6b4a45aec8d4caee0c4b" "checksum phf_generator 0.7.23 (registry+https://github.com/rust-lang/crates.io-index)" = "03dc191feb9b08b0dc1330d6549b795b9d81aec19efe6b4a45aec8d4caee0c4b"
"checksum phf_shared 0.7.23 (registry+https://github.com/rust-lang/crates.io-index)" = "b539898d22d4273ded07f64a05737649dc69095d92cb87c7097ec68e3f150b93" "checksum phf_shared 0.7.23 (registry+https://github.com/rust-lang/crates.io-index)" = "b539898d22d4273ded07f64a05737649dc69095d92cb87c7097ec68e3f150b93"
"checksum pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)" = "676e8eb2b1b4c9043511a9b7bea0915320d7e502b0a079fb03f9635a5252b18c" "checksum pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)" = "676e8eb2b1b4c9043511a9b7bea0915320d7e502b0a079fb03f9635a5252b18c"
"checksum plume-api 0.2.0 (git+https://github.com/Plume-org/Plume?rev=88a6a81ad8ce8cdb18b9ae3467961cf9490e4411)" = "<none>" "checksum plume-api 0.3.0 (git+https://github.com/Plume-org/Plume?rev=aee604751193aaebb0a858dc81dc15a9ab67659e)" = "<none>"
"checksum proc-macro2 0.4.20 (registry+https://github.com/rust-lang/crates.io-index)" = "3d7b7eaaa90b4a90a932a9ea6666c95a389e424eff347f0f793979289429feee" "checksum proc-macro2 0.4.20 (registry+https://github.com/rust-lang/crates.io-index)" = "3d7b7eaaa90b4a90a932a9ea6666c95a389e424eff347f0f793979289429feee"
"checksum quote 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)" = "dd636425967c33af890042c483632d33fa7a18f19ad1d7ea72e8998c6ef8dea5" "checksum quote 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)" = "dd636425967c33af890042c483632d33fa7a18f19ad1d7ea72e8998c6ef8dea5"
"checksum rand 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e464cd887e869cddcae8792a4ee31d23c7edd516700695608f5b98c67ee0131c" "checksum rand 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e464cd887e869cddcae8792a4ee31d23c7edd516700695608f5b98c67ee0131c"

View file

@ -1,14 +1,14 @@
[package] [package]
name = "amsterdam" name = "amsterdam"
version = "0.1.0" version = "0.1.0"
authors = ["Bat' <baptiste@gelez.xyz>"] authors = ["Ana Gelez <ana@gelez.xyz>"]
license = "GPLv3.0" license = "GPLv3.0"
edition = "2018"
[dependencies] [dependencies]
canapi = "0.2.0"
clap = "2.0" clap = "2.0"
dotenv = "0.13" dotenv = "0.13"
plume-api = { git = "https://github.com/Plume-org/Plume", rev = "88a6a81ad8ce8cdb18b9ae3467961cf9490e4411" } plume-api = { git = "https://github.com/Plume-org/Plume", rev = "aee604751193aaebb0a858dc81dc15a9ab67659e" }
reqwest = "0.9.3" reqwest = "0.9.3"
rpassword = "2.0.0" rpassword = "2.0.0"
serde_json = "1.0" serde_json = "1.0"

View file

@ -1 +1 @@
nightly-2018-07-17 nightly-2018-12-06

View file

@ -1,76 +1,94 @@
extern crate canapi;
extern crate clap;
extern crate dotenv;
extern crate plume_api;
extern crate serde_json;
extern crate reqwest;
extern crate rpassword;
use canapi::*;
use clap::{App, Arg, ArgMatches, SubCommand}; use clap::{App, Arg, ArgMatches, SubCommand};
use dotenv::dotenv; use dotenv::dotenv;
use plume_api::{apps::AppEndpoint, posts::PostEndpoint}; use plume_api::{apps::NewAppData, posts::NewPostData};
use reqwest::{ClientBuilder, Method}; use reqwest::Client;
use std::{io::{self, prelude::*, BufRead, BufReader}, env, fs::{metadata, File, OpenOptions}, str::FromStr, fmt::Display}; use std::{
env,
fmt::Display,
fs::{metadata, File, OpenOptions},
io::{self, prelude::*, BufRead, BufReader},
};
fn main() { fn main() {
let client = Client::new();
if let Err(e) = run(&client) {
println!("Error: {}", e);
}
}
fn run(client: &Client) -> Result<(), String> {
dotenv().ok(); dotenv().ok();
let mut app = App::new("Amsterdam") let mut app = App::new("Amsterdam")
.bin_name("amsterdam") .bin_name("amsterdam")
.version(env!("CARGO_PKG_VERSION")) .version(env!("CARGO_PKG_VERSION"))
.about("Import articles to Plume.") .about("Import articles to Plume.")
.subcommand(SubCommand::with_name("md") .subcommand(
.arg(Arg::with_name("FILES") SubCommand::with_name("md")
.takes_value(true) .arg(
.multiple(true) Arg::with_name("FILES")
.help("The Markdown files to import")) .takes_value(true)
.about("Import Markown files") .multiple(true)
.help("The Markdown files to import"),
)
.about("Import Markown files"),
); );
if env::var("PLUME_API_TOKEN").is_err() { if env::var("PLUME_API_TOKEN").is_err() {
get_token(); get_token(&client)?;
} }
match app.clone().get_matches().subcommand() { match app.clone().get_matches().subcommand() {
("md", Some(args)) => md(args), ("md", Some(args)) => md(args, client),
_ => app.print_help().unwrap(), _ => app.print_help().map_err(|_| "Try 'amsterdam md'".to_string()),
} }
} }
fn write_to_env<A: Display, B: Display>(var: A, val: B) { fn write_to_env<A: Display, B: Display>(var: A, val: B) -> Result<(), String> {
let mut file = OpenOptions::new() let mut file = OpenOptions::new()
.create(true)
.write(true) .write(true)
.append(true) .append(true)
.open(".env") .open(".env")
.unwrap(); .map_err(|_| "Couldn't open .env")?;
writeln!(file, "{}={}", var, val).expect("Couldn't write to file"); writeln!(file, "{}={}", var, val).map_err(|_| "Couldn't write to .env")?;
dotenv().ok(); dotenv().ok();
Ok(())
} }
fn make_client() -> (String, String) { fn make_client(req: &Client) -> Result<(String, String), String> {
println!("Please enter your instance domain (without https:// or http://)"); println!("Please enter your instance domain (without https:// or http://)");
let mut url = String::new(); let mut url = String::new();
io::stdin().read_line(&mut url).ok(); io::stdin().read_line(&mut url).ok();
write_to_env("PLUME_API_URL", url); write_to_env("PLUME_API_URL", url)?;
let client = AppEndpoint::default().create::<ReqwestFetch>(AppEndpoint { let client: serde_json::Value = req
name: String::from("Amsterdam"), .post(&format!(
..AppEndpoint::default() "https://{}/api/v1/apps",
}); env::var("PLUME_API_URL").unwrap()
if let Ok(client) = client { ))
write_to_env("PLUME_CLIENT_ID", client.client_id.clone().unwrap()); .json(&NewAppData {
write_to_env("PLUME_CLIENT_SECRET", client.client_secret.clone().unwrap()); name: "Amsterdam".into(),
(client.client_id.unwrap(), client.client_secret.unwrap()) website: Some("https://github.com/Plume-org/amsterdam".into()),
} else { redirect_uri: None,
panic!("client is err"); })
} .send()
.and_then(|mut r| r.json())
.map_err(|_| "Couldn't register the Amsterdam app".to_string())?;
write_to_env("PLUME_CLIENT_ID", client["client_id"].to_string())?;
write_to_env("PLUME_CLIENT_SECRET", client["client_secret"].to_string())?;
Ok((
env::var("PLUME_CLIENT_ID").unwrap(),
env::var("PLUME_CLIENT_SECRET").unwrap(),
))
} }
fn get_token() { fn get_token(client: &Client) -> Result<(), String> {
let (c_id, c_secret) = env::var("PLUME_CLIENT_ID") let (c_id, c_secret) = env::var("PLUME_CLIENT_ID")
.and_then(|id| env::var("PLUME_CLIENT_SECRET").map(|sec| (id, sec))) .and_then(|id| env::var("PLUME_CLIENT_SECRET").map(|sec| (id, sec)))
.unwrap_or_else(|_| make_client()); .or_else(|_| make_client(&client))?;
println!("What is your username?"); println!("What is your username?");
let mut name = String::new(); let mut name = String::new();
io::stdin().read_line(&mut name).ok(); io::stdin().read_line(&mut name).ok();
@ -87,90 +105,102 @@ fn get_token() {
c_secret, c_secret,
); );
let json: serde_json::Value = ClientBuilder::new().danger_accept_invalid_certs(true).build().unwrap() let json: serde_json::Value = client
.get(url.as_str()).send().and_then(|mut r| r.text()) .get(&url)
.map(|t| serde_json::from_str(t.as_str()).unwrap()) .send()
.unwrap(); .and_then(|mut r| r.json())
.map_err(|_| "Couldn't obtain an API token")?;
write_to_env("PLUME_API_TOKEN", json["token"].as_str().unwrap()) write_to_env("PLUME_API_TOKEN", json["token"].as_str().unwrap())
} }
enum ParseState { enum ParseState {
Ready, Ready,
FrontMatter(PostEndpoint), FrontMatter(NewPostData),
Body(PostEndpoint), Body(NewPostData),
} }
fn md<'a>(args: &'a ArgMatches) { fn md<'a>(args: &'a ArgMatches, client: &Client) -> Result<(), String> {
for path in args.values_of("FILES").unwrap() { for path in args
if metadata(path).expect("File not found").is_file() { .values_of("FILES")
println!("Importing {}", path); .ok_or("Please provide files to upload".to_string())?
{
if metadata(path)
.or(Err(format!("Couldn't read {}", path)))?
.is_file()
{
println!("Importing {}", path);
let f = File::open(path).expect("File not found"); let f = File::open(path).or(Err(format!("Couldn't read {}", path)))?;
let file = BufReader::new(f); let file = BufReader::new(f);
let parse_result = file.lines().fold(ParseState::Ready, |state, line| { let parse_result = file.lines().fold(ParseState::Ready, |state, line| {
let line = line.expect("Couldn't read line"); let line = line.map_err(|_| println!("Couldn't read {}", path)).ok();
match state { if let Some(line) = line {
ParseState::Ready => if line.chars().all(|c| c == '-') { match state {
ParseState::FrontMatter(PostEndpoint::default()) ParseState::Ready => {
} else { if line.chars().all(|c| c == '-') {
panic!("Your articles should use FrontMatter metadata") let mut post = NewPostData::default();
}, post.published = Some(true);
ParseState::FrontMatter(mut post) => { ParseState::FrontMatter(post)
let mut parsed = line.splitn(2, ':'); } else {
let mut field = parsed.next().expect("No metadata field"); ParseState::Ready
if let Some(value) = parsed.next() { }
let value = value.trim(); }
match field { ParseState::FrontMatter(mut post) => {
"title" => post.title = Some(value.to_string()), let mut parsed = line.splitn(2, ':');
"subtitle" => post.subtitle = Some(value.to_string()), let field = parsed.next().expect("THIS IS NOT POSSIBLE");
"tags" => post.tags = Some(value.split(",").map(str::trim).map(String::from).collect()), if let Some(value) = parsed.next() {
"date" => post.creation_date = Some(value.to_string()), let value = value.trim();
x => println!("The {} field will be ignored.", x), match field {
}; "title" => post.title = value.to_string(),
ParseState::FrontMatter(post) "subtitle" => post.subtitle = Some(value.to_string()),
} else { "tags" => {
post.tags = Some(
value
.split(",")
.map(str::trim)
.map(String::from)
.collect(),
)
}
"date" => post.creation_date = Some(value.to_string()),
"license" => post.license = Some(value.to_string()),
x => println!("WARNING: The {} field will be ignored.", x),
};
ParseState::FrontMatter(post)
} else {
ParseState::Body(post)
}
}
ParseState::Body(mut post) => {
post.source = post.source + "\n" + line.as_ref();
ParseState::Body(post) ParseState::Body(post)
} }
},
ParseState::Body(mut post) => {
if let Some(source) = post.source {
post.source = Some(source + "\n" + line.as_ref());
} else {
post.source = Some(line);
}
ParseState::Body(post)
} }
} else {
state
} }
}); });
if let ParseState::Body(article) = parse_result { if let ParseState::Body(article) = parse_result {
println!("Publishing {}...", article.title.clone().unwrap()); println!("Publishing {}", article.title);
PostEndpoint::default().create::<ReqwestFetch>(article).ok();
client
.post(&format!(
"https://{}/api/v1/posts/",
env::var("PLUME_API_URL").unwrap()
))
.json(&article)
.bearer_auth(env::var("PLUME_API_TOKEN").unwrap_or(String::new()))
.send()
.map_err(|_| "Error during publication")?
.json::<serde_json::Value>()
.map_err(|_| "Couldn't read response as JSON")?
["error"].as_str().map(|e| println!("ERROR: The API returned: {}\nMake sure you have created a blog on Plume.", e));
} else { } else {
panic!("Error while parsing article metadata"); println!("ERROR: Couldn't parse article metadata for {}", path);
} }
} }
} }
} println!(""); // Just print a blank line to separate each import
Ok(())
struct ReqwestFetch;
impl Fetch for ReqwestFetch {
fn fetch<T: Endpoint>(method: &'static str, url: String, data: Option<T>) -> Result<T, Error> {
ClientBuilder::new()
.danger_accept_invalid_certs(true)
.build()
.unwrap()
.request(
Method::from_str(method).expect("Canapi: invalid method"),
format!("https://{}{}", env::var("PLUME_API_URL").unwrap(), url).as_str()
)
.body(serde_json::to_string(&data).expect("Error while serializing post"))
.header("Content-Type", "application/json")
.bearer_auth(env::var("PLUME_API_TOKEN").unwrap_or(String::new()))
.send()
.and_then(|mut r| r.text())
.map_err(|e| { println!("err: {}", e.to_string()); Error::Fetch(e.to_string()) })
.and_then(|t| { println!("got: {}", t); serde_json::from_str(t.as_ref()).map_err(|e| Error::SerDe(e.to_string())) })
}
} }