Remove Canapi (#540)

* Remove Canapi

It added more complexity than it helped.

* Fail if there are many blog, but none was specified

* cargo fmt
This commit is contained in:
Baptiste Gelez
2019-04-28 22:17:21 +01:00
committed by GitHub
parent 787eb7f399
commit ec57f1e687
14 changed files with 298 additions and 435 deletions
+19 -7
View File
@@ -1,12 +1,24 @@
use canapi::Provider;
use rocket_contrib::json::Json;
use serde_json;
use plume_api::apps::AppEndpoint;
use plume_models::{apps::App, db_conn::DbConn, Connection};
use crate::api::Api;
use plume_api::apps::NewAppData;
use plume_common::utils::random_hex;
use plume_models::{apps::*, db_conn::DbConn};
#[post("/apps", data = "<data>")]
pub fn create(conn: DbConn, data: Json<AppEndpoint>) -> Json<serde_json::Value> {
let post = <App as Provider<Connection>>::create(&*conn, (*data).clone()).ok();
Json(json!(post))
pub fn create(conn: DbConn, data: Json<NewAppData>) -> Api<App> {
let client_id = random_hex();
let client_secret = random_hex();
let app = App::insert(
&*conn,
NewApp {
name: data.name.clone(),
client_id,
client_secret,
redirect_uri: data.redirect_uri.clone(),
website: data.website.clone(),
},
)?;
Ok(Json(app))
}
+8
View File
@@ -9,6 +9,8 @@ use serde_json;
use plume_common::utils::random_hex;
use plume_models::{api_tokens::*, apps::App, users::User, Error, PlumeRocket};
type Api<T> = Result<Json<T>, ApiError>;
#[derive(Debug)]
pub struct ApiError(Error);
@@ -18,6 +20,12 @@ impl From<Error> for ApiError {
}
}
impl From<std::option::NoneError> for ApiError {
fn from(err: std::option::NoneError) -> ApiError {
ApiError(err.into())
}
}
impl<'r> Responder<'r> for ApiError {
fn respond_to(self, req: &Request) -> response::Result<'r> {
match self.0 {
+221 -39
View File
@@ -1,54 +1,236 @@
use canapi::{Error as ApiError, Provider};
use rocket::http::uri::Origin;
use chrono::NaiveDateTime;
use heck::{CamelCase, KebabCase};
use rocket_contrib::json::Json;
use serde_json;
use serde_qs;
use api::authorization::*;
use plume_api::posts::PostEndpoint;
use plume_models::{posts::Post, users::User, PlumeRocket};
use crate::api::{authorization::*, Api};
use plume_api::posts::*;
use plume_common::{activity_pub::broadcast, utils::md_to_html};
use plume_models::{
blogs::Blog, db_conn::DbConn, instance::Instance, medias::Media, mentions::*, post_authors::*,
posts::*, safe_string::SafeString, tags::*, users::User, Error, PlumeRocket,
};
#[get("/posts/<id>")]
pub fn get(
id: i32,
auth: Option<Authorization<Read, Post>>,
mut rockets: PlumeRocket,
) -> Json<serde_json::Value> {
rockets.user = auth.and_then(|a| User::get(&*rockets.conn, a.0.user_id).ok());
let post = <Post as Provider<PlumeRocket>>::get(&rockets, id).ok();
Json(json!(post))
pub fn get(id: i32, auth: Option<Authorization<Read, Post>>, conn: DbConn) -> Api<PostData> {
let user = auth.and_then(|a| User::get(&conn, a.0.user_id).ok());
let post = Post::get(&conn, id)?;
if !post.published
&& !user
.and_then(|u| post.is_author(&conn, u.id).ok())
.unwrap_or(false)
{
return Err(Error::Unauthorized.into());
}
Ok(Json(PostData {
authors: post
.get_authors(&conn)?
.into_iter()
.map(|a| a.username)
.collect(),
creation_date: post.creation_date.format("%Y-%m-%d").to_string(),
tags: Tag::for_post(&conn, post.id)?
.into_iter()
.map(|t| t.tag)
.collect(),
id: post.id,
title: post.title,
subtitle: post.subtitle,
content: post.content.to_string(),
source: Some(post.source),
blog_id: post.blog_id,
published: post.published,
license: post.license,
cover_id: post.cover_id,
}))
}
#[get("/posts")]
#[get("/posts?<title>&<subtitle>&<content>")]
pub fn list(
uri: &Origin,
title: Option<String>,
subtitle: Option<String>,
content: Option<String>,
auth: Option<Authorization<Read, Post>>,
mut rockets: PlumeRocket,
) -> Json<serde_json::Value> {
rockets.user = auth.and_then(|a| User::get(&*rockets.conn, a.0.user_id).ok());
let query: PostEndpoint =
serde_qs::from_str(uri.query().unwrap_or("")).expect("api::list: invalid query error");
let post = <Post as Provider<PlumeRocket>>::list(&rockets, query);
Json(json!(post))
conn: DbConn,
) -> Api<Vec<PostData>> {
let user = auth.and_then(|a| User::get(&conn, a.0.user_id).ok());
let user_id = user.map(|u| u.id);
Ok(Json(
Post::list_filtered(&conn, title, subtitle, content)?
.into_iter()
.filter(|p| {
p.published
|| user_id
.and_then(|u| p.is_author(&conn, u).ok())
.unwrap_or(false)
})
.filter_map(|p| {
Some(PostData {
authors: p
.get_authors(&conn)
.ok()?
.into_iter()
.map(|a| a.username)
.collect(),
creation_date: p.creation_date.format("%Y-%m-%d").to_string(),
tags: Tag::for_post(&conn, p.id)
.ok()?
.into_iter()
.map(|t| t.tag)
.collect(),
id: p.id,
title: p.title,
subtitle: p.subtitle,
content: p.content.to_string(),
source: Some(p.source),
blog_id: p.blog_id,
published: p.published,
license: p.license,
cover_id: p.cover_id,
})
})
.collect(),
))
}
#[post("/posts", data = "<payload>")]
pub fn create(
auth: Authorization<Write, Post>,
payload: Json<PostEndpoint>,
mut rockets: PlumeRocket,
) -> Json<serde_json::Value> {
rockets.user = User::get(&*rockets.conn, auth.0.user_id).ok();
let new_post = <Post as Provider<PlumeRocket>>::create(&rockets, (*payload).clone());
Json(new_post.map(|p| json!(p)).unwrap_or_else(|e| {
json!({
"error": "Invalid data, couldn't create new post",
"details": match e {
ApiError::Fetch(msg) => msg,
ApiError::SerDe(msg) => msg,
ApiError::NotFound(msg) => msg,
ApiError::Authorization(msg) => msg,
}
})
payload: Json<NewPostData>,
rockets: PlumeRocket,
) -> Api<PostData> {
let conn = &*rockets.conn;
let search = &rockets.searcher;
let worker = &rockets.worker;
let author = User::get(conn, auth.0.user_id)?;
let slug = &payload.title.clone().to_kebab_case();
let date = payload.creation_date.clone().and_then(|d| {
NaiveDateTime::parse_from_str(format!("{} 00:00:00", d).as_ref(), "%Y-%m-%d %H:%M:%S").ok()
});
let domain = &Instance::get_local(conn)?.public_domain;
let (content, mentions, hashtags) = md_to_html(
&payload.source,
domain,
false,
Some(Media::get_media_processor(conn, vec![&author])),
);
let blog = payload.blog_id.or_else(|| {
let blogs = Blog::find_for_author(conn, &author).ok()?;
if blogs.len() == 1 {
Some(blogs[0].id)
} else {
None
}
})?;
if Post::find_by_slug(conn, slug, blog).is_ok() {
return Err(Error::InvalidValue.into());
}
let post = Post::insert(
conn,
NewPost {
blog_id: blog,
slug: slug.to_string(),
title: payload.title.clone(),
content: SafeString::new(content.as_ref()),
published: payload.published.unwrap_or(true),
license: payload.license.clone().unwrap_or_else(|| {
Instance::get_local(conn)
.map(|i| i.default_license)
.unwrap_or_else(|_| String::from("CC-BY-SA"))
}),
creation_date: date,
ap_url: String::new(),
subtitle: payload.subtitle.clone().unwrap_or_default(),
source: payload.source.clone(),
cover_id: payload.cover_id,
},
search,
)?;
PostAuthor::insert(
conn,
NewPostAuthor {
author_id: author.id,
post_id: post.id,
},
)?;
if let Some(ref tags) = payload.tags {
for tag in tags {
Tag::insert(
conn,
NewTag {
tag: tag.to_string(),
is_hashtag: false,
post_id: post.id,
},
)?;
}
}
for hashtag in hashtags {
Tag::insert(
conn,
NewTag {
tag: hashtag.to_camel_case(),
is_hashtag: true,
post_id: post.id,
},
)?;
}
if post.published {
for m in mentions.into_iter() {
Mention::from_activity(
&*conn,
&Mention::build_activity(&rockets, &m)?,
post.id,
true,
true,
)?;
}
let act = post.create_activity(&*conn)?;
let dest = User::one_by_instance(&*conn)?;
worker.execute(move || broadcast(&author, act, dest));
}
Ok(Json(PostData {
authors: post.get_authors(conn)?.into_iter().map(|a| a.fqn).collect(),
creation_date: post.creation_date.format("%Y-%m-%d").to_string(),
tags: Tag::for_post(conn, post.id)?
.into_iter()
.map(|t| t.tag)
.collect(),
id: post.id,
title: post.title,
subtitle: post.subtitle,
content: post.content.to_string(),
source: Some(post.source),
blog_id: post.blog_id,
published: post.published,
license: post.license,
cover_id: post.cover_id,
}))
}
#[delete("/posts/<id>")]
pub fn delete(auth: Authorization<Write, Post>, rockets: PlumeRocket, id: i32) -> Api<()> {
let author = User::get(&*rockets.conn, auth.0.user_id)?;
if let Ok(post) = Post::get(&*rockets.conn, id) {
if post.is_author(&*rockets.conn, author.id).unwrap_or(false) {
post.delete(&*rockets.conn, &rockets.searcher)?;
}
}
Ok(Json(()))
}
+4 -4
View File
@@ -1,10 +1,9 @@
#![allow(clippy::too_many_arguments)]
#![feature(decl_macro, proc_macro_hygiene)]
#![feature(decl_macro, proc_macro_hygiene, try_trait)]
extern crate activitypub;
extern crate askama_escape;
extern crate atom_syndication;
extern crate canapi;
extern crate chrono;
extern crate colored;
extern crate ctrlc;
@@ -102,8 +101,8 @@ Then try to restart Plume.
SearcherError::IndexOpeningError => panic!(
r#"
Plume was unable to open the search index. If you created the index
before, make sure to run Plume in the same directory it was created in, or
to set SEARCH_INDEX accordingly. If you did not yet create the search
before, make sure to run Plume in the same directory it was created in, or
to set SEARCH_INDEX accordingly. If you did not yet create the search
index, run this command:
plm search init
@@ -237,6 +236,7 @@ Then try to restart Plume
api::posts::get,
api::posts::list,
api::posts::create,
api::posts::delete,
],
)
.register(catchers![