mirror of
https://git.joinplu.me/Plume/Plume.git
synced 2024-11-25 21:11:01 +00:00
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:
parent
787eb7f399
commit
ec57f1e687
14 changed files with 298 additions and 435 deletions
12
Cargo.lock
generated
12
Cargo.lock
generated
|
@ -314,14 +314,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.89 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.0.30"
|
version = "1.0.30"
|
||||||
|
@ -1782,7 +1774,6 @@ dependencies = [
|
||||||
"activitypub 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
"activitypub 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"askama_escape 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"askama_escape 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"atom_syndication 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"atom_syndication 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"canapi 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"colored 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"colored 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"ctrlc 3.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"ctrlc 3.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
@ -1821,7 +1812,6 @@ dependencies = [
|
||||||
name = "plume-api"
|
name = "plume-api"
|
||||||
version = "0.3.0"
|
version = "0.3.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"canapi 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)",
|
"serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"serde_derive 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)",
|
"serde_derive 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
@ -1878,7 +1868,6 @@ dependencies = [
|
||||||
"ammonia 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"ammonia 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"askama_escape 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"askama_escape 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"bcrypt 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"bcrypt 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"canapi 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"diesel 1.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"diesel 1.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"diesel_migrations 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"diesel_migrations 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
@ -3296,7 +3285,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
"checksum byteorder 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "96c8b41881888cc08af32d47ac4edd52bc7fa27fef774be47a92443756451304"
|
"checksum byteorder 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "96c8b41881888cc08af32d47ac4edd52bc7fa27fef774be47a92443756451304"
|
||||||
"checksum byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a019b10a2a7cdeb292db131fc8113e57ea2a908f6e7894b0c3c671893b65dbeb"
|
"checksum byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a019b10a2a7cdeb292db131fc8113e57ea2a908f6e7894b0c3c671893b65dbeb"
|
||||||
"checksum bytes 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)" = "40ade3d27603c2cb345eb0912aec461a6dec7e06a4ae48589904e808335c7afa"
|
"checksum bytes 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)" = "40ade3d27603c2cb345eb0912aec461a6dec7e06a4ae48589904e808335c7afa"
|
||||||
"checksum canapi 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aab4d6d1edcef8bf19b851b7730d3d1a90373c06321a49a984baebe0989c962c"
|
|
||||||
"checksum cc 1.0.30 (registry+https://github.com/rust-lang/crates.io-index)" = "d01c69d08ff207f231f07196e30f84c70f1c815b04f980f8b7b01ff01f05eb92"
|
"checksum cc 1.0.30 (registry+https://github.com/rust-lang/crates.io-index)" = "d01c69d08ff207f231f07196e30f84c70f1c815b04f980f8b7b01ff01f05eb92"
|
||||||
"checksum census 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "641317709904ba3c1ad137cb5d88ec9d8c03c07de087b2cff5e84ec565c7e299"
|
"checksum census 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "641317709904ba3c1ad137cb5d88ec9d8c03c07de087b2cff5e84ec565c7e299"
|
||||||
"checksum cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "11d43355396e872eefb45ce6342e4374ed7bc2b3a502d1b28e36d6e23c05d1f4"
|
"checksum cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "11d43355396e872eefb45ce6342e4374ed7bc2b3a502d1b28e36d6e23c05d1f4"
|
||||||
|
|
|
@ -8,7 +8,6 @@ repository = "https://github.com/Plume-org/Plume"
|
||||||
activitypub = "0.1.3"
|
activitypub = "0.1.3"
|
||||||
askama_escape = "0.1"
|
askama_escape = "0.1"
|
||||||
atom_syndication = "0.6"
|
atom_syndication = "0.6"
|
||||||
canapi = "0.2"
|
|
||||||
colored = "1.7"
|
colored = "1.7"
|
||||||
dotenv = "0.13"
|
dotenv = "0.13"
|
||||||
gettext = { git = "https://github.com/Plume-org/gettext/", rev = "294c54d74c699fbc66502b480a37cc66c1daa7f3" }
|
gettext = { git = "https://github.com/Plume-org/gettext/", rev = "294c54d74c699fbc66502b480a37cc66c1daa7f3" }
|
||||||
|
|
|
@ -4,6 +4,5 @@ version = "0.3.0"
|
||||||
authors = ["Plume contributors"]
|
authors = ["Plume contributors"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
canapi = "0.2"
|
|
||||||
serde = "1.0"
|
serde = "1.0"
|
||||||
serde_derive = "1.0"
|
serde_derive = "1.0"
|
||||||
|
|
|
@ -1,13 +1,6 @@
|
||||||
use canapi::Endpoint;
|
#[derive(Clone, Serialize, Deserialize)]
|
||||||
|
pub struct NewAppData {
|
||||||
#[derive(Clone, Default, Serialize, Deserialize)]
|
|
||||||
pub struct AppEndpoint {
|
|
||||||
pub id: Option<i32>,
|
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub website: Option<String>,
|
pub website: Option<String>,
|
||||||
pub redirect_uri: Option<String>,
|
pub redirect_uri: Option<String>,
|
||||||
pub client_id: Option<String>,
|
|
||||||
pub client_secret: Option<String>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
api!("/api/v1/apps" => AppEndpoint);
|
|
||||||
|
|
|
@ -1,24 +1,6 @@
|
||||||
extern crate canapi;
|
|
||||||
extern crate serde;
|
extern crate serde;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate serde_derive;
|
extern crate serde_derive;
|
||||||
|
|
||||||
macro_rules! api {
|
|
||||||
($url:expr => $ep:ty) => {
|
|
||||||
impl Endpoint for $ep {
|
|
||||||
type Id = i32;
|
|
||||||
|
|
||||||
fn endpoint() -> &'static str {
|
|
||||||
$url
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod apps;
|
pub mod apps;
|
||||||
pub mod posts;
|
pub mod posts;
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct Api {
|
|
||||||
pub posts: posts::PostEndpoint,
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,13 +1,11 @@
|
||||||
use canapi::Endpoint;
|
|
||||||
|
|
||||||
#[derive(Clone, Default, Serialize, Deserialize)]
|
#[derive(Clone, Default, Serialize, Deserialize)]
|
||||||
pub struct PostEndpoint {
|
pub struct NewPostData {
|
||||||
pub id: Option<i32>,
|
pub title: String,
|
||||||
pub title: Option<String>,
|
|
||||||
pub subtitle: Option<String>,
|
pub subtitle: Option<String>,
|
||||||
pub content: Option<String>,
|
pub source: String,
|
||||||
pub source: Option<String>,
|
pub author: String,
|
||||||
pub author: Option<String>,
|
// If None, and that there is only one blog, it will be choosen automatically.
|
||||||
|
// If there are more than one blog, the request will fail.
|
||||||
pub blog_id: Option<i32>,
|
pub blog_id: Option<i32>,
|
||||||
pub published: Option<bool>,
|
pub published: Option<bool>,
|
||||||
pub creation_date: Option<String>,
|
pub creation_date: Option<String>,
|
||||||
|
@ -16,4 +14,18 @@ pub struct PostEndpoint {
|
||||||
pub cover_id: Option<i32>,
|
pub cover_id: Option<i32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
api!("/api/v1/posts" => PostEndpoint);
|
#[derive(Clone, Default, Serialize, Deserialize)]
|
||||||
|
pub struct PostData {
|
||||||
|
pub id: i32,
|
||||||
|
pub title: String,
|
||||||
|
pub subtitle: String,
|
||||||
|
pub content: String,
|
||||||
|
pub source: Option<String>,
|
||||||
|
pub authors: Vec<String>,
|
||||||
|
pub blog_id: i32,
|
||||||
|
pub published: bool,
|
||||||
|
pub creation_date: String,
|
||||||
|
pub license: String,
|
||||||
|
pub tags: Vec<String>,
|
||||||
|
pub cover_id: Option<i32>,
|
||||||
|
}
|
||||||
|
|
|
@ -8,7 +8,6 @@ activitypub = "0.1.1"
|
||||||
ammonia = "2.0.0"
|
ammonia = "2.0.0"
|
||||||
askama_escape = "0.1"
|
askama_escape = "0.1"
|
||||||
bcrypt = "0.2"
|
bcrypt = "0.2"
|
||||||
canapi = "0.2"
|
|
||||||
guid-create = "0.1"
|
guid-create = "0.1"
|
||||||
heck = "0.3.0"
|
heck = "0.3.0"
|
||||||
itertools = "0.8.0"
|
itertools = "0.8.0"
|
||||||
|
|
|
@ -1,13 +1,10 @@
|
||||||
use canapi::{Error as ApiError, Provider};
|
|
||||||
use chrono::NaiveDateTime;
|
use chrono::NaiveDateTime;
|
||||||
use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl};
|
use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl};
|
||||||
|
|
||||||
use plume_api::apps::AppEndpoint;
|
|
||||||
use plume_common::utils::random_hex;
|
|
||||||
use schema::apps;
|
use schema::apps;
|
||||||
use {ApiResult, Connection, Error, Result};
|
use {Error, Result};
|
||||||
|
|
||||||
#[derive(Clone, Queryable)]
|
#[derive(Clone, Queryable, Serialize)]
|
||||||
pub struct App {
|
pub struct App {
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
@ -28,52 +25,6 @@ pub struct NewApp {
|
||||||
pub website: Option<String>,
|
pub website: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Provider<Connection> for App {
|
|
||||||
type Data = AppEndpoint;
|
|
||||||
|
|
||||||
fn get(_conn: &Connection, _id: i32) -> ApiResult<AppEndpoint> {
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn list(_conn: &Connection, _query: AppEndpoint) -> Vec<AppEndpoint> {
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create(conn: &Connection, data: AppEndpoint) -> ApiResult<AppEndpoint> {
|
|
||||||
let client_id = random_hex();
|
|
||||||
|
|
||||||
let client_secret = random_hex();
|
|
||||||
let app = App::insert(
|
|
||||||
conn,
|
|
||||||
NewApp {
|
|
||||||
name: data.name,
|
|
||||||
client_id,
|
|
||||||
client_secret,
|
|
||||||
redirect_uri: data.redirect_uri,
|
|
||||||
website: data.website,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.map_err(|_| ApiError::NotFound("Couldn't register app".into()))?;
|
|
||||||
|
|
||||||
Ok(AppEndpoint {
|
|
||||||
id: Some(app.id),
|
|
||||||
name: app.name,
|
|
||||||
client_id: Some(app.client_id),
|
|
||||||
client_secret: Some(app.client_secret),
|
|
||||||
redirect_uri: app.redirect_uri,
|
|
||||||
website: app.website,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update(_conn: &Connection, _id: i32, _new_data: AppEndpoint) -> ApiResult<AppEndpoint> {
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn delete(_conn: &Connection, _id: i32) {
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
get!(apps);
|
get!(apps);
|
||||||
insert!(apps, NewApp);
|
insert!(apps, NewApp);
|
||||||
|
|
|
@ -6,7 +6,6 @@ extern crate activitypub;
|
||||||
extern crate ammonia;
|
extern crate ammonia;
|
||||||
extern crate askama_escape;
|
extern crate askama_escape;
|
||||||
extern crate bcrypt;
|
extern crate bcrypt;
|
||||||
extern crate canapi;
|
|
||||||
extern crate chrono;
|
extern crate chrono;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate diesel;
|
extern crate diesel;
|
||||||
|
@ -154,8 +153,6 @@ impl From<InboxError<Error>> for Error {
|
||||||
|
|
||||||
pub type Result<T> = std::result::Result<T, Error>;
|
pub type Result<T> = std::result::Result<T, Error>;
|
||||||
|
|
||||||
pub type ApiResult<T> = std::result::Result<T, canapi::Error>;
|
|
||||||
|
|
||||||
/// Adds a function to a model, that returns the first
|
/// Adds a function to a model, that returns the first
|
||||||
/// matching row for a given list of fields.
|
/// matching row for a given list of fields.
|
||||||
///
|
///
|
||||||
|
|
|
@ -4,7 +4,6 @@ use activitypub::{
|
||||||
object::{Article, Image, Tombstone},
|
object::{Article, Image, Tombstone},
|
||||||
CustomObject,
|
CustomObject,
|
||||||
};
|
};
|
||||||
use canapi::{Error as ApiError, Provider};
|
|
||||||
use chrono::{NaiveDateTime, TimeZone, Utc};
|
use chrono::{NaiveDateTime, TimeZone, Utc};
|
||||||
use diesel::{self, BelongingToDsl, ExpressionMethods, QueryDsl, RunQueryDsl, SaveChangesDsl};
|
use diesel::{self, BelongingToDsl, ExpressionMethods, QueryDsl, RunQueryDsl, SaveChangesDsl};
|
||||||
use heck::{CamelCase, KebabCase};
|
use heck::{CamelCase, KebabCase};
|
||||||
|
@ -15,10 +14,8 @@ use blogs::Blog;
|
||||||
use instance::Instance;
|
use instance::Instance;
|
||||||
use medias::Media;
|
use medias::Media;
|
||||||
use mentions::Mention;
|
use mentions::Mention;
|
||||||
use plume_api::posts::PostEndpoint;
|
|
||||||
use plume_common::{
|
use plume_common::{
|
||||||
activity_pub::{
|
activity_pub::{
|
||||||
broadcast,
|
|
||||||
inbox::{AsObject, FromId},
|
inbox::{AsObject, FromId},
|
||||||
Hashtag, Id, IntoId, Licensed, Source, PUBLIC_VISIBILITY,
|
Hashtag, Id, IntoId, Licensed, Source, PUBLIC_VISIBILITY,
|
||||||
},
|
},
|
||||||
|
@ -30,7 +27,7 @@ use schema::posts;
|
||||||
use search::Searcher;
|
use search::Searcher;
|
||||||
use tags::*;
|
use tags::*;
|
||||||
use users::User;
|
use users::User;
|
||||||
use {ap_url, ApiResult, Connection, Error, PlumeRocket, Result, CONFIG};
|
use {ap_url, Connection, Error, PlumeRocket, Result, CONFIG};
|
||||||
|
|
||||||
pub type LicensedArticle = CustomObject<Licensed, Article>;
|
pub type LicensedArticle = CustomObject<Licensed, Article>;
|
||||||
|
|
||||||
|
@ -67,282 +64,6 @@ pub struct NewPost {
|
||||||
pub cover_id: Option<i32>,
|
pub cover_id: Option<i32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Provider<PlumeRocket> for Post {
|
|
||||||
type Data = PostEndpoint;
|
|
||||||
|
|
||||||
fn get(rockets: &PlumeRocket, id: i32) -> ApiResult<PostEndpoint> {
|
|
||||||
let conn = &*rockets.conn;
|
|
||||||
if let Ok(post) = Post::get(conn, id) {
|
|
||||||
if !post.published
|
|
||||||
&& !rockets
|
|
||||||
.user
|
|
||||||
.as_ref()
|
|
||||||
.and_then(|u| post.is_author(conn, u.id).ok())
|
|
||||||
.unwrap_or(false)
|
|
||||||
{
|
|
||||||
return Err(ApiError::Authorization(
|
|
||||||
"You are not authorized to access this post yet.".to_string(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
Ok(PostEndpoint {
|
|
||||||
id: Some(post.id),
|
|
||||||
title: Some(post.title.clone()),
|
|
||||||
subtitle: Some(post.subtitle.clone()),
|
|
||||||
content: Some(post.content.get().clone()),
|
|
||||||
source: Some(post.source.clone()),
|
|
||||||
author: Some(
|
|
||||||
post.get_authors(conn)
|
|
||||||
.map_err(|_| ApiError::NotFound("Authors not found".into()))?[0]
|
|
||||||
.username
|
|
||||||
.clone(),
|
|
||||||
),
|
|
||||||
blog_id: Some(post.blog_id),
|
|
||||||
published: Some(post.published),
|
|
||||||
creation_date: Some(post.creation_date.format("%Y-%m-%d").to_string()),
|
|
||||||
license: Some(post.license.clone()),
|
|
||||||
tags: Some(
|
|
||||||
Tag::for_post(conn, post.id)
|
|
||||||
.map_err(|_| ApiError::NotFound("Tags not found".into()))?
|
|
||||||
.into_iter()
|
|
||||||
.map(|t| t.tag)
|
|
||||||
.collect(),
|
|
||||||
),
|
|
||||||
cover_id: post.cover_id,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
Err(ApiError::NotFound("Request post was not found".to_string()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn list(rockets: &PlumeRocket, filter: PostEndpoint) -> Vec<PostEndpoint> {
|
|
||||||
let conn = &*rockets.conn;
|
|
||||||
let mut query = posts::table.into_boxed();
|
|
||||||
if let Some(title) = filter.title {
|
|
||||||
query = query.filter(posts::title.eq(title));
|
|
||||||
}
|
|
||||||
if let Some(subtitle) = filter.subtitle {
|
|
||||||
query = query.filter(posts::subtitle.eq(subtitle));
|
|
||||||
}
|
|
||||||
if let Some(content) = filter.content {
|
|
||||||
query = query.filter(posts::content.eq(content));
|
|
||||||
}
|
|
||||||
|
|
||||||
query
|
|
||||||
.get_results::<Post>(conn)
|
|
||||||
.map(|ps| {
|
|
||||||
ps.into_iter()
|
|
||||||
.filter(|p| {
|
|
||||||
p.published
|
|
||||||
|| rockets
|
|
||||||
.user
|
|
||||||
.as_ref()
|
|
||||||
.and_then(|u| p.is_author(conn, u.id).ok())
|
|
||||||
.unwrap_or(false)
|
|
||||||
})
|
|
||||||
.map(|p| PostEndpoint {
|
|
||||||
id: Some(p.id),
|
|
||||||
title: Some(p.title.clone()),
|
|
||||||
subtitle: Some(p.subtitle.clone()),
|
|
||||||
content: Some(p.content.get().clone()),
|
|
||||||
source: Some(p.source.clone()),
|
|
||||||
author: Some(p.get_authors(conn).unwrap_or_default()[0].username.clone()),
|
|
||||||
blog_id: Some(p.blog_id),
|
|
||||||
published: Some(p.published),
|
|
||||||
creation_date: Some(p.creation_date.format("%Y-%m-%d").to_string()),
|
|
||||||
license: Some(p.license.clone()),
|
|
||||||
tags: Some(
|
|
||||||
Tag::for_post(conn, p.id)
|
|
||||||
.unwrap_or_else(|_| vec![])
|
|
||||||
.into_iter()
|
|
||||||
.map(|t| t.tag)
|
|
||||||
.collect(),
|
|
||||||
),
|
|
||||||
cover_id: p.cover_id,
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
})
|
|
||||||
.unwrap_or_else(|_| vec![])
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update(
|
|
||||||
_rockets: &PlumeRocket,
|
|
||||||
_id: i32,
|
|
||||||
_new_data: PostEndpoint,
|
|
||||||
) -> ApiResult<PostEndpoint> {
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn delete(rockets: &PlumeRocket, id: i32) {
|
|
||||||
let conn = &*rockets.conn;
|
|
||||||
let user_id = rockets
|
|
||||||
.user
|
|
||||||
.as_ref()
|
|
||||||
.expect("Post as Provider::delete: not authenticated")
|
|
||||||
.id;
|
|
||||||
if let Ok(post) = Post::get(conn, id) {
|
|
||||||
if post.is_author(conn, user_id).unwrap_or(false) {
|
|
||||||
post.delete(conn, &rockets.searcher)
|
|
||||||
.expect("Post as Provider::delete: delete error");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create(rockets: &PlumeRocket, query: PostEndpoint) -> ApiResult<PostEndpoint> {
|
|
||||||
let conn = &*rockets.conn;
|
|
||||||
let search = &rockets.searcher;
|
|
||||||
let worker = &rockets.worker;
|
|
||||||
if rockets.user.is_none() {
|
|
||||||
return Err(ApiError::Authorization(
|
|
||||||
"You are not authorized to create new articles.".to_string(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
let title = query.title.clone().expect("No title for new post in API");
|
|
||||||
let slug = query.title.unwrap().to_kebab_case();
|
|
||||||
|
|
||||||
let date = query.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)
|
|
||||||
.map_err(|_| ApiError::NotFound("posts::update: Error getting local instance".into()))?
|
|
||||||
.public_domain;
|
|
||||||
let author = rockets
|
|
||||||
.user
|
|
||||||
.clone()
|
|
||||||
.ok_or_else(|| ApiError::NotFound("Author not found".into()))?;
|
|
||||||
|
|
||||||
let (content, mentions, hashtags) = md_to_html(
|
|
||||||
query.source.clone().unwrap_or_default().clone().as_ref(),
|
|
||||||
domain,
|
|
||||||
false,
|
|
||||||
Some(Media::get_media_processor(conn, vec![&author])),
|
|
||||||
);
|
|
||||||
|
|
||||||
let blog = match query.blog_id {
|
|
||||||
Some(x) => x,
|
|
||||||
None => {
|
|
||||||
Blog::find_for_author(conn, &author)
|
|
||||||
.map_err(|_| ApiError::NotFound("No default blog".into()))?[0]
|
|
||||||
.id
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if Post::find_by_slug(conn, &slug, blog).is_ok() {
|
|
||||||
// Not an actual authorization problem, but we have nothing better for now…
|
|
||||||
// TODO: add another error variant to canapi and add it there
|
|
||||||
return Err(ApiError::Authorization(
|
|
||||||
"A post with the same slug already exists".to_string(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
let post = Post::insert(
|
|
||||||
conn,
|
|
||||||
NewPost {
|
|
||||||
blog_id: blog,
|
|
||||||
slug,
|
|
||||||
title,
|
|
||||||
content: SafeString::new(content.as_ref()),
|
|
||||||
published: query.published.unwrap_or(true),
|
|
||||||
license: query.license.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: query.subtitle.unwrap_or_default(),
|
|
||||||
source: query.source.expect("Post API::create: no source error"),
|
|
||||||
cover_id: query.cover_id,
|
|
||||||
},
|
|
||||||
search,
|
|
||||||
)
|
|
||||||
.map_err(|_| ApiError::NotFound("Creation error".into()))?;
|
|
||||||
|
|
||||||
PostAuthor::insert(
|
|
||||||
conn,
|
|
||||||
NewPostAuthor {
|
|
||||||
author_id: author.id,
|
|
||||||
post_id: post.id,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.map_err(|_| ApiError::NotFound("Error saving authors".into()))?;
|
|
||||||
|
|
||||||
if let Some(tags) = query.tags {
|
|
||||||
for tag in tags {
|
|
||||||
Tag::insert(
|
|
||||||
conn,
|
|
||||||
NewTag {
|
|
||||||
tag,
|
|
||||||
is_hashtag: false,
|
|
||||||
post_id: post.id,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.map_err(|_| ApiError::NotFound("Error saving tags".into()))?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for hashtag in hashtags {
|
|
||||||
Tag::insert(
|
|
||||||
conn,
|
|
||||||
NewTag {
|
|
||||||
tag: hashtag.to_camel_case(),
|
|
||||||
is_hashtag: true,
|
|
||||||
post_id: post.id,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.map_err(|_| ApiError::NotFound("Error saving hashtags".into()))?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if post.published {
|
|
||||||
for m in mentions.into_iter() {
|
|
||||||
Mention::from_activity(
|
|
||||||
&*conn,
|
|
||||||
&Mention::build_activity(&rockets, &m)
|
|
||||||
.map_err(|_| ApiError::NotFound("Couldn't build mentions".into()))?,
|
|
||||||
post.id,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
)
|
|
||||||
.map_err(|_| ApiError::NotFound("Error saving mentions".into()))?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let act = post
|
|
||||||
.create_activity(&*conn)
|
|
||||||
.map_err(|_| ApiError::NotFound("Couldn't create activity".into()))?;
|
|
||||||
let dest = User::one_by_instance(&*conn)
|
|
||||||
.map_err(|_| ApiError::NotFound("Couldn't list remote instances".into()))?;
|
|
||||||
worker.execute(move || broadcast(&author, act, dest));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(PostEndpoint {
|
|
||||||
id: Some(post.id),
|
|
||||||
title: Some(post.title.clone()),
|
|
||||||
subtitle: Some(post.subtitle.clone()),
|
|
||||||
content: Some(post.content.get().clone()),
|
|
||||||
source: Some(post.source.clone()),
|
|
||||||
author: Some(
|
|
||||||
post.get_authors(conn)
|
|
||||||
.map_err(|_| ApiError::NotFound("No authors".into()))?[0]
|
|
||||||
.username
|
|
||||||
.clone(),
|
|
||||||
),
|
|
||||||
blog_id: Some(post.blog_id),
|
|
||||||
published: Some(post.published),
|
|
||||||
creation_date: Some(post.creation_date.format("%Y-%m-%d").to_string()),
|
|
||||||
license: Some(post.license.clone()),
|
|
||||||
tags: Some(
|
|
||||||
Tag::for_post(conn, post.id)
|
|
||||||
.map_err(|_| ApiError::NotFound("Tags not found".into()))?
|
|
||||||
.into_iter()
|
|
||||||
.map(|t| t.tag)
|
|
||||||
.collect(),
|
|
||||||
),
|
|
||||||
cover_id: post.cover_id,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Post {
|
impl Post {
|
||||||
get!(posts);
|
get!(posts);
|
||||||
find_by!(posts, find_by_slug, slug as &str, blog_id as i32);
|
find_by!(posts, find_by_slug, slug as &str, blog_id as i32);
|
||||||
|
@ -441,6 +162,26 @@ impl Post {
|
||||||
.map_err(Error::from)
|
.map_err(Error::from)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn list_filtered(
|
||||||
|
conn: &Connection,
|
||||||
|
title: Option<String>,
|
||||||
|
subtitle: Option<String>,
|
||||||
|
content: Option<String>,
|
||||||
|
) -> Result<Vec<Post>> {
|
||||||
|
let mut query = posts::table.into_boxed();
|
||||||
|
if let Some(title) = title {
|
||||||
|
query = query.filter(posts::title.eq(title));
|
||||||
|
}
|
||||||
|
if let Some(subtitle) = subtitle {
|
||||||
|
query = query.filter(posts::subtitle.eq(subtitle));
|
||||||
|
}
|
||||||
|
if let Some(content) = content {
|
||||||
|
query = query.filter(posts::content.eq(content));
|
||||||
|
}
|
||||||
|
|
||||||
|
query.get_results::<Post>(conn).map_err(Error::from)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_recents(conn: &Connection, limit: i64) -> Result<Vec<Post>> {
|
pub fn get_recents(conn: &Connection, limit: i64) -> Result<Vec<Post>> {
|
||||||
posts::table
|
posts::table
|
||||||
.order(posts::creation_date.desc())
|
.order(posts::creation_date.desc())
|
||||||
|
|
|
@ -1,12 +1,24 @@
|
||||||
use canapi::Provider;
|
|
||||||
use rocket_contrib::json::Json;
|
use rocket_contrib::json::Json;
|
||||||
use serde_json;
|
|
||||||
|
|
||||||
use plume_api::apps::AppEndpoint;
|
use crate::api::Api;
|
||||||
use plume_models::{apps::App, db_conn::DbConn, Connection};
|
use plume_api::apps::NewAppData;
|
||||||
|
use plume_common::utils::random_hex;
|
||||||
|
use plume_models::{apps::*, db_conn::DbConn};
|
||||||
|
|
||||||
#[post("/apps", data = "<data>")]
|
#[post("/apps", data = "<data>")]
|
||||||
pub fn create(conn: DbConn, data: Json<AppEndpoint>) -> Json<serde_json::Value> {
|
pub fn create(conn: DbConn, data: Json<NewAppData>) -> Api<App> {
|
||||||
let post = <App as Provider<Connection>>::create(&*conn, (*data).clone()).ok();
|
let client_id = random_hex();
|
||||||
Json(json!(post))
|
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))
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,8 @@ use serde_json;
|
||||||
use plume_common::utils::random_hex;
|
use plume_common::utils::random_hex;
|
||||||
use plume_models::{api_tokens::*, apps::App, users::User, Error, PlumeRocket};
|
use plume_models::{api_tokens::*, apps::App, users::User, Error, PlumeRocket};
|
||||||
|
|
||||||
|
type Api<T> = Result<Json<T>, ApiError>;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct ApiError(Error);
|
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 {
|
impl<'r> Responder<'r> for ApiError {
|
||||||
fn respond_to(self, req: &Request) -> response::Result<'r> {
|
fn respond_to(self, req: &Request) -> response::Result<'r> {
|
||||||
match self.0 {
|
match self.0 {
|
||||||
|
|
260
src/api/posts.rs
260
src/api/posts.rs
|
@ -1,54 +1,236 @@
|
||||||
use canapi::{Error as ApiError, Provider};
|
use chrono::NaiveDateTime;
|
||||||
use rocket::http::uri::Origin;
|
use heck::{CamelCase, KebabCase};
|
||||||
use rocket_contrib::json::Json;
|
use rocket_contrib::json::Json;
|
||||||
use serde_json;
|
|
||||||
use serde_qs;
|
|
||||||
|
|
||||||
use api::authorization::*;
|
use crate::api::{authorization::*, Api};
|
||||||
use plume_api::posts::PostEndpoint;
|
use plume_api::posts::*;
|
||||||
use plume_models::{posts::Post, users::User, PlumeRocket};
|
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>")]
|
#[get("/posts/<id>")]
|
||||||
pub fn get(
|
pub fn get(id: i32, auth: Option<Authorization<Read, Post>>, conn: DbConn) -> Api<PostData> {
|
||||||
id: i32,
|
let user = auth.and_then(|a| User::get(&conn, a.0.user_id).ok());
|
||||||
auth: Option<Authorization<Read, Post>>,
|
let post = Post::get(&conn, id)?;
|
||||||
mut rockets: PlumeRocket,
|
|
||||||
) -> Json<serde_json::Value> {
|
if !post.published
|
||||||
rockets.user = auth.and_then(|a| User::get(&*rockets.conn, a.0.user_id).ok());
|
&& !user
|
||||||
let post = <Post as Provider<PlumeRocket>>::get(&rockets, id).ok();
|
.and_then(|u| post.is_author(&conn, u.id).ok())
|
||||||
Json(json!(post))
|
.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(
|
pub fn list(
|
||||||
uri: &Origin,
|
title: Option<String>,
|
||||||
|
subtitle: Option<String>,
|
||||||
|
content: Option<String>,
|
||||||
auth: Option<Authorization<Read, Post>>,
|
auth: Option<Authorization<Read, Post>>,
|
||||||
mut rockets: PlumeRocket,
|
conn: DbConn,
|
||||||
) -> Json<serde_json::Value> {
|
) -> Api<Vec<PostData>> {
|
||||||
rockets.user = auth.and_then(|a| User::get(&*rockets.conn, a.0.user_id).ok());
|
let user = auth.and_then(|a| User::get(&conn, a.0.user_id).ok());
|
||||||
let query: PostEndpoint =
|
let user_id = user.map(|u| u.id);
|
||||||
serde_qs::from_str(uri.query().unwrap_or("")).expect("api::list: invalid query error");
|
|
||||||
let post = <Post as Provider<PlumeRocket>>::list(&rockets, query);
|
Ok(Json(
|
||||||
Json(json!(post))
|
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>")]
|
#[post("/posts", data = "<payload>")]
|
||||||
pub fn create(
|
pub fn create(
|
||||||
auth: Authorization<Write, Post>,
|
auth: Authorization<Write, Post>,
|
||||||
payload: Json<PostEndpoint>,
|
payload: Json<NewPostData>,
|
||||||
mut rockets: PlumeRocket,
|
rockets: PlumeRocket,
|
||||||
) -> Json<serde_json::Value> {
|
) -> Api<PostData> {
|
||||||
rockets.user = User::get(&*rockets.conn, auth.0.user_id).ok();
|
let conn = &*rockets.conn;
|
||||||
let new_post = <Post as Provider<PlumeRocket>>::create(&rockets, (*payload).clone());
|
let search = &rockets.searcher;
|
||||||
Json(new_post.map(|p| json!(p)).unwrap_or_else(|e| {
|
let worker = &rockets.worker;
|
||||||
json!({
|
|
||||||
"error": "Invalid data, couldn't create new post",
|
let author = User::get(conn, auth.0.user_id)?;
|
||||||
"details": match e {
|
|
||||||
ApiError::Fetch(msg) => msg,
|
let slug = &payload.title.clone().to_kebab_case();
|
||||||
ApiError::SerDe(msg) => msg,
|
let date = payload.creation_date.clone().and_then(|d| {
|
||||||
ApiError::NotFound(msg) => msg,
|
NaiveDateTime::parse_from_str(format!("{} 00:00:00", d).as_ref(), "%Y-%m-%d %H:%M:%S").ok()
|
||||||
ApiError::Authorization(msg) => msg,
|
});
|
||||||
}
|
|
||||||
})
|
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(()))
|
||||||
|
}
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
#![allow(clippy::too_many_arguments)]
|
#![allow(clippy::too_many_arguments)]
|
||||||
#![feature(decl_macro, proc_macro_hygiene)]
|
#![feature(decl_macro, proc_macro_hygiene, try_trait)]
|
||||||
|
|
||||||
extern crate activitypub;
|
extern crate activitypub;
|
||||||
extern crate askama_escape;
|
extern crate askama_escape;
|
||||||
extern crate atom_syndication;
|
extern crate atom_syndication;
|
||||||
extern crate canapi;
|
|
||||||
extern crate chrono;
|
extern crate chrono;
|
||||||
extern crate colored;
|
extern crate colored;
|
||||||
extern crate ctrlc;
|
extern crate ctrlc;
|
||||||
|
@ -237,6 +236,7 @@ Then try to restart Plume
|
||||||
api::posts::get,
|
api::posts::get,
|
||||||
api::posts::list,
|
api::posts::list,
|
||||||
api::posts::create,
|
api::posts::create,
|
||||||
|
api::posts::delete,
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
.register(catchers![
|
.register(catchers![
|
||||||
|
|
Loading…
Reference in a new issue