use activitypub::object::Article; use chrono::Utc; use heck::{CamelCase, KebabCase}; use rocket::{State, request::LenientForm}; use rocket::response::{Redirect, Flash}; use rocket_contrib::Template; use serde_json; use std::{collections::{HashMap, HashSet}, borrow::Cow}; use validator::{Validate, ValidationError, ValidationErrors}; use workerpool::{Pool, thunk::*}; use plume_common::activity_pub::{broadcast, ActivityStream, ApRequest, inbox::Deletable}; use plume_common::utils; use plume_models::{ blogs::*, db_conn::DbConn, comments::Comment, instance::Instance, mentions::Mention, post_authors::*, posts::*, safe_string::SafeString, tags::*, users::User }; #[derive(FromForm)] struct CommentQuery { responding_to: Option } // See: https://github.com/SergioBenitez/Rocket/pull/454 #[get("/~//", rank = 4)] fn details(blog: String, slug: String, conn: DbConn, user: Option) -> Template { details_response(blog, slug, conn, user, None) } #[get("/~//?")] fn details_response(blog: String, slug: String, conn: DbConn, user: Option, query: Option) -> Template { may_fail!(user.map(|u| u.to_json(&*conn)), Blog::find_by_fqn(&*conn, blog), "Couldn't find this blog", |blog| { may_fail!(user.map(|u| u.to_json(&*conn)), Post::find_by_slug(&*conn, slug, blog.id), "Couldn't find this post", |post| { if post.published || post.get_authors(&*conn).into_iter().any(|a| a.id == user.clone().map(|u| u.id).unwrap_or(0)) { let comments = Comment::list_by_post(&*conn, post.id); let comms = comments.clone(); Template::render("posts/details", json!({ "author": post.get_authors(&*conn)[0].to_json(&*conn), "article": post.to_json(&*conn), "blog": blog.to_json(&*conn), "comments": &comments.into_iter().filter_map(|c| if c.in_response_to_id.is_none() { Some(c.to_json(&*conn, &comms)) } else { None }).collect::>(), "n_likes": post.get_likes(&*conn).len(), "has_liked": user.clone().map(|u| u.has_liked(&*conn, &post)).unwrap_or(false), "n_reshares": post.get_reshares(&*conn).len(), "has_reshared": user.clone().map(|u| u.has_reshared(&*conn, &post)).unwrap_or(false), "account": &user.clone().map(|u| u.to_json(&*conn)), "date": &post.creation_date.timestamp(), "previous": query.and_then(|q| q.responding_to.map(|r| Comment::get(&*conn, r) .expect("posts::details_reponse: Error retrieving previous comment").to_json(&*conn, &vec![]))), "user_fqn": user.clone().map(|u| u.get_fqn(&*conn)).unwrap_or(String::new()), "is_author": user.clone().map(|u| post.get_authors(&*conn).into_iter().any(|a| u.id == a.id)).unwrap_or(false), "is_following": user.map(|u| u.is_following(&*conn, post.get_authors(&*conn)[0].id)).unwrap_or(false) })) } else { Template::render("errors/403", json!({ "error_message": "This post isn't published yet." })) } }) }) } #[get("/~//", rank = 3)] fn activity_details(blog: String, slug: String, conn: DbConn, _ap: ApRequest) -> Result, Option> { let blog = Blog::find_by_fqn(&*conn, blog).ok_or(None)?; let post = Post::find_by_slug(&*conn, slug, blog.id).ok_or(None)?; if post.published { Ok(ActivityStream::new(post.into_activity(&*conn))) } else { Err(Some(String::from("Not published yet."))) } } #[get("/~//new", rank = 2)] fn new_auth(blog: String) -> Flash { utils::requires_login( "You need to be logged in order to write a new post", uri!(new: blog = blog).into() ) } #[get("/~//new", rank = 1)] fn new(blog: String, user: User, conn: DbConn) -> Option