begin work on posse support

This commit is contained in:
Cadey Ratio 2020-11-04 13:41:46 -05:00
parent 62489eaff4
commit c310891743
11 changed files with 198 additions and 25 deletions

13
backend/Cargo.lock generated
View File

@ -533,7 +533,6 @@ version = "0.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc" checksum = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc"
dependencies = [ dependencies = [
"backtrace",
"version_check 0.9.2", "version_check 0.9.2",
] ]
@ -866,17 +865,6 @@ dependencies = [
"wasm-bindgen", "wasm-bindgen",
] ]
[[package]]
name = "jsonfeed"
version = "0.3.0"
source = "git+https://github.com/Xe/site#22575fca380f561006f44079ebe59019babe24d7"
dependencies = [
"error-chain",
"serde",
"serde_derive",
"serde_json",
]
[[package]] [[package]]
name = "kankyo" name = "kankyo"
version = "0.3.0" version = "0.3.0"
@ -1013,7 +1001,6 @@ dependencies = [
"diesel", "diesel",
"futures-io", "futures-io",
"hex", "hex",
"jsonfeed",
"kankyo", "kankyo",
"log 0.4.11", "log 0.4.11",
"mime 0.3.16", "mime 0.3.16",

View File

@ -32,8 +32,6 @@ ureq = { version = "1", features = ["json", "charset"] }
uuid = { version = "0.7", features = ["serde", "v4"] } uuid = { version = "0.7", features = ["serde", "v4"] }
url = "2" url = "2"
jsonfeed = { git = "https://github.com/Xe/site" }
[dependencies.diesel] [dependencies.diesel]
version = "1" version = "1"
features = ["sqlite", "r2d2", "uuidv07", "chrono"] features = ["sqlite", "r2d2", "uuidv07", "chrono"]

View File

@ -31,10 +31,3 @@ api_secret = "..."
instance = "..." instance = "..."
token = "..." token = "..."
account = "..." account = "..."
# Get these values by creating an oAuth app
[global.reddit]
app_id = "..."
app_secret = "..."
username = "..."
password = "..."

View File

@ -1,4 +1,4 @@
CREATE TABLE IF NOT EXISTS blogposts CREATE TABLE IF NOT EXISTS blogposts
( url UNIQUE NOT NULL PRIMARY KEY ( url TEXT UNIQUE NOT NULL PRIMARY KEY
, title TEXT NOT NULL , title TEXT NOT NULL
) )

View File

@ -12,6 +12,7 @@ use rocket::{
use rocket_contrib::json::Json; use rocket_contrib::json::Json;
use std::io::Read; use std::io::Read;
pub mod posse;
pub mod switch; pub mod switch;
pub mod webmention; pub mod webmention;

122
backend/src/api/posse.rs Normal file
View File

@ -0,0 +1,122 @@
use super::Result;
use crate::{
models, paseto, schema,
web::{DiscordWebhook, Error as WebError, Mastodon, Result as WebResult, Twitter},
MainDatabase,
};
use diesel::prelude::*;
use rocket::State;
use serde::Deserialize;
use std::fmt::Write;
#[derive(Deserialize)]
pub struct Jsonfeed {
pub version: String,
pub home_page_url: String,
pub items: Vec<Item>,
}
#[derive(Deserialize, Clone, Debug)]
pub struct Item {
pub url: String,
pub title: String,
pub tags: Option<Vec<String>>,
}
impl Item {
fn render(self) -> String {
let mut result = String::new();
write!(result, "{}\n\n{}", self.title, self.url).unwrap();
if let Some(tags) = self.tags {
write!(result, "\n\n").unwrap();
for tag in tags.iter() {
write!(result, "#{} ", tag).unwrap();
}
}
result
}
}
impl Into<models::Blogpost> for Item {
fn into(self) -> models::Blogpost {
models::Blogpost {
url: self.url,
title: self.title,
}
}
}
pub fn read_jsonfeed(url: String) -> WebResult<Jsonfeed> {
let resp = ureq::get(&url)
.set("User-Agent", crate::APPLICATION_NAME)
.call();
if resp.ok() {
Ok(resp.into_json_deserialize()?)
} else {
Err(match resp.synthetic_error() {
Some(why) => WebError::UReq(why.to_string()),
None => WebError::HttpStatus(resp.status()),
})
}
}
#[instrument(skip(dw, tw, ma), err)]
fn posse(item: Item, dw: &DiscordWebhook, tw: &Twitter, ma: &Mastodon) -> WebResult {
let message = item.render();
dw.send(message.clone())?;
tw.tweet(message.clone())?;
ma.toot(message.clone())?;
Ok(())
}
pub static BLOG_FEED_URL: &'static str = "https://christine.website/blog.json";
#[post("/blog/refresh")]
#[instrument(skip(conn, dw, tw, ma), err)]
pub fn refresh_blog(
tok: paseto::Token,
conn: MainDatabase,
dw: State<DiscordWebhook>,
tw: State<Twitter>,
ma: State<Mastodon>,
) -> Result {
use schema::blogposts::dsl::blogposts;
let feed = read_jsonfeed(BLOG_FEED_URL.to_string())?;
for item in feed.items.into_iter() {
match blogposts
.find(item.url.clone())
.get_result::<models::Blogpost>(&*conn)
{
Ok(_) => continue,
Err(_) => {
diesel::insert_into(schema::blogposts::table)
.values(&{
let post: models::Blogpost = item.clone().into();
post
})
.execute(&*conn)?;
posse(item, &dw, &tw, &ma)?
}
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::{read_jsonfeed, BLOG_FEED_URL};
#[test]
fn valid_jsonfeed() {
read_jsonfeed(BLOG_FEED_URL.to_string()).unwrap();
}
}

View File

@ -1,5 +1,5 @@
use super::{Error, Result}; use super::{Error, Result};
use crate::{models, schema, web::discord_webhook::Client as DiscordWebhook, MainDatabase}; use crate::{models, paseto, schema, web::discord_webhook::Client as DiscordWebhook, MainDatabase};
use diesel::prelude::*; use diesel::prelude::*;
use rocket::{ use rocket::{
request::Form, request::Form,
@ -118,3 +118,27 @@ pub fn get(conn: MainDatabase, mention_id: String) -> Result<Json<models::WebMen
.map_err(Error::Database)?, .map_err(Error::Database)?,
)) ))
} }
#[get("/webmention?<count>&<page>")]
#[instrument(skip(conn), err)]
pub fn list(
conn: MainDatabase,
count: Option<i64>,
page: Option<i64>,
tok: paseto::Token,
) -> Result<Json<Vec<models::WebMention>>> {
use schema::webmentions;
let count = count.unwrap_or(30);
let page = page.unwrap_or(0);
let count = if count < 100 { count } else { 100 };
Ok(Json(
webmentions::table
.limit(count)
.offset(count * (page - 1))
.load::<models::WebMention>(&*conn)
.map_err(Error::Database)?,
))
}

View File

@ -0,0 +1,39 @@
#[macro_use]
extern crate tracing;
use color_eyre::eyre::Result;
use diesel::{prelude::*, SqliteConnection};
use std::env;
use mi::{api::posse::*, *};
fn main() -> Result<()> {
let _ = kankyo::init();
color_eyre::install()?;
tracing_subscriber::fmt::init();
info!("{} blogpost importer starting up", mi::APPLICATION_NAME);
let conn = establish_connection();
let feed = read_jsonfeed(BLOG_FEED_URL.to_string())?;
let posts: Vec<models::Blogpost> = feed
.items
.into_iter()
.map(|item| {
let post: models::Blogpost = item.into();
post
})
.collect();
diesel::insert_into(schema::blogposts::table)
.values(&posts)
.execute(&conn)?;
Ok(())
}
pub fn establish_connection() -> SqliteConnection {
let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set");
SqliteConnection::establish(&database_url)
.expect(&format!("Error connecting to {}", database_url))
}

View File

@ -53,12 +53,14 @@ fn main() -> Result<()> {
.mount( .mount(
"/api", "/api",
routes![ routes![
api::posse::refresh_blog,
api::switch::current_front, api::switch::current_front,
api::switch::get, api::switch::get,
api::switch::list, api::switch::list,
api::switch::switch, api::switch::switch,
api::webmention::accept, api::webmention::accept,
api::webmention::get, api::webmention::get,
api::webmention::list,
api::get_members, api::get_members,
api::token_info, api::token_info,
api::tweet, api::tweet,

View File

@ -58,3 +58,10 @@ pub struct WebMention {
pub source_url: String, pub source_url: String,
pub target_url: String, pub target_url: String,
} }
#[derive(Queryable, Associations, Insertable)]
#[table_name = "blogposts"]
pub struct Blogpost {
pub url: String,
pub title: String,
}

View File

@ -1,6 +1,6 @@
table! { table! {
blogposts (url) { blogposts (url) {
url -> Binary, url -> Text,
title -> Text, title -> Text,
} }
} }