use activitypub::{activity::Create, collection::OrderedCollection, object::Article}; use atom_syndication::{Entry, FeedBuilder}; use rocket::{ http::{ContentType, Cookies}, request::LenientForm, response::{status, Content, Flash, Redirect}, }; use rocket_contrib::Template; use serde_json; use validator::{Validate, ValidationError}; use workerpool::thunk::*; use inbox::Inbox; use plume_common::activity_pub::{ broadcast, inbox::{Deletable, FromActivity, Notify}, sign::{verify_http_headers, Signable}, ActivityStream, ApRequest, Id, IntoId, }; use plume_common::utils; use plume_models::{ blogs::Blog, db_conn::DbConn, follows, headers::Headers, instance::Instance, posts::Post, reshares::Reshare, users::*, }; use routes::Page; use Worker; #[get("/me")] fn me(user: Option) -> Result> { match user { Some(user) => Ok(Redirect::to(uri!(details: name = user.username))), None => Err(utils::requires_login("", uri!(me))), } } #[get("/@/", rank = 2)] fn details( name: String, conn: DbConn, account: Option, worker: Worker, fecth_articles_conn: DbConn, fecth_followers_conn: DbConn, update_conn: DbConn, ) -> Template { may_fail!( account.map(|a| a.to_json(&*conn)), User::find_by_fqn(&*conn, &name), "Couldn't find requested user", |user| { let recents = Post::get_recents_for_author(&*conn, &user, 6); let reshares = Reshare::get_recents_for_author(&*conn, &user, 6); let user_id = user.id; let n_followers = user.get_followers(&*conn).len(); if !user.get_instance(&*conn).local { // Fetch new articles let user_clone = user.clone(); worker.execute(Thunk::of(move || { for create_act in user_clone.fetch_outbox::() { match create_act.create_props.object_object::
() { Ok(article) => { Post::from_activity( &*fecth_articles_conn, article, user_clone.clone().into_id(), ); println!("Fetched article from remote user"); } Err(e) => { println!("Error while fetching articles in background: {:?}", e) } } } })); // Fetch followers let user_clone = user.clone(); worker.execute(Thunk::of(move || { for user_id in user_clone.fetch_followers_ids() { let follower = User::find_by_ap_url(&*fecth_followers_conn, &user_id) .unwrap_or_else(|| { User::fetch_from_url(&*fecth_followers_conn, &user_id) .expect("user::details: Couldn't fetch follower") }); follows::Follow::insert( &*fecth_followers_conn, follows::NewFollow { follower_id: follower.id, following_id: user_clone.id, ap_url: format!("{}/follow/{}", follower.ap_url, user_clone.ap_url), }, ); } })); // Update profile information if needed let user_clone = user.clone(); if user.needs_update() { worker.execute(Thunk::of(move || { user_clone.refetch(&*update_conn); })) } } Template::render( "users/details", json!({ "user": user.to_json(&*conn), "instance_url": user.get_instance(&*conn).public_domain, "is_remote": user.instance_id != Instance::local_id(&*conn), "follows": account.clone().map(|x| x.is_following(&*conn, user.id)).unwrap_or(false), "account": account.clone().map(|a| a.to_json(&*conn)), "recents": recents.into_iter().map(|p| p.to_json(&*conn)).collect::>(), "reshares": reshares.into_iter().map(|r| r.get_post(&*conn).unwrap().to_json(&*conn)).collect::>(), "is_self": account.map(|a| a.id == user_id).unwrap_or(false), "n_followers": n_followers }), ) } ) } #[get("/dashboard")] fn dashboard(user: User, conn: DbConn) -> Template { let blogs = Blog::find_for_author(&*conn, &user); Template::render( "users/dashboard", json!({ "account": user.to_json(&*conn), "blogs": blogs, "drafts": Post::drafts_by_author(&*conn, &user).into_iter().map(|a| a.to_json(&*conn)).collect::>(), }), ) } #[get("/dashboard", rank = 2)] fn dashboard_auth() -> Flash { utils::requires_login( "You need to be logged in order to access your dashboard", uri!(dashboard), ) } #[post("/@//follow")] fn follow(name: String, conn: DbConn, user: User, worker: Worker) -> Option { let target = User::find_by_fqn(&*conn, &name)?; if let Some(follow) = follows::Follow::find(&*conn, user.id, target.id) { let delete_act = follow.delete(&*conn); worker.execute(Thunk::of(move || { broadcast(&user, delete_act, vec![target]) })); } else { let f = follows::Follow::insert( &*conn, follows::NewFollow { follower_id: user.id, following_id: target.id, ap_url: format!("{}/follow/{}", user.ap_url, target.ap_url), }, ); f.notify(&*conn); let act = f.to_activity(&*conn); worker.execute(Thunk::of(move || broadcast(&user, act, vec![target]))); } Some(Redirect::to(uri!(details: name = name))) } #[post("/@//follow", rank = 2)] fn follow_auth(name: String) -> Flash { utils::requires_login( "You need to be logged in order to follow someone", uri!(follow: name = name), ) } #[get("/@//followers?")] fn followers_paginated(name: String, conn: DbConn, account: Option, page: Page) -> Template { may_fail!( account.map(|a| a.to_json(&*conn)), User::find_by_fqn(&*conn, &name), "Couldn't find requested user", |user| { let user_id = user.id; let followers_count = user.get_followers(&*conn).len(); Template::render( "users/followers", json!({ "user": user.to_json(&*conn), "instance_url": user.get_instance(&*conn).public_domain, "is_remote": user.instance_id != Instance::local_id(&*conn), "follows": account.clone().map(|x| x.is_following(&*conn, user.id)).unwrap_or(false), "followers": user.get_followers_page(&*conn, page.limits()).into_iter().map(|f| f.to_json(&*conn)).collect::>(), "account": account.clone().map(|a| a.to_json(&*conn)), "is_self": account.map(|a| a.id == user_id).unwrap_or(false), "n_followers": followers_count, "page": page.page, "n_pages": Page::total(followers_count as i32) }), ) } ) } #[get("/@//followers", rank = 2)] fn followers(name: String, conn: DbConn, account: Option) -> Template { followers_paginated(name, conn, account, Page::first()) } #[get("/@/", rank = 1)] fn activity_details( name: String, conn: DbConn, _ap: ApRequest, ) -> Option> { let user = User::find_local(&*conn, &name)?; Some(ActivityStream::new(user.to_activity(&*conn))) } #[get("/users/new")] fn new(user: Option, conn: DbConn) -> Template { Template::render( "users/new", json!({ "enabled": Instance::get_local(&*conn).map(|i| i.open_registrations).unwrap_or(true), "account": user.map(|u| u.to_json(&*conn)), "errors": null, "form": null }), ) } #[get("/@//edit")] fn edit(name: String, user: User, conn: DbConn) -> Option