use activitypub::{ activity::Create, collection::OrderedCollection, object::Article }; use atom_syndication::{Entry, FeedBuilder}; use rocket::{ request::LenientForm, response::{Redirect, Flash, Content}, http::{ContentType, Cookies} }; use rocket_contrib::Template; use serde_json; use validator::{Validate, ValidationError}; use workerpool::thunk::*; use plume_common::activity_pub::{ ActivityStream, broadcast, Id, IntoId, ApRequest, inbox::{FromActivity, Notify, Deletable}, sign::{Signable, verify_http_headers} }; use plume_common::utils; use plume_models::{ blogs::Blog, db_conn::DbConn, follows, headers::Headers, instance::Instance, posts::Post, reshares::Reshare, users::* }; use inbox::Inbox; 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).into())) } } #[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.clone(); 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.clone()) .unwrap_or_else(|| User::fetch_from_url(&*fecth_followers_conn, user_id).expect("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.id); 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).into() ) } #[post("/@//follow")] fn follow(name: String, conn: DbConn, user: User, worker: Worker) -> Redirect { let target = User::find_by_fqn(&*conn, name.clone()).unwrap(); 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.into_activity(&*conn); worker.execute(Thunk::of(move || broadcast(&user, act, vec![target]))); } 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).into() ) } #[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.clone()), "Couldn't find requested user", |user| { let user_id = user.id.clone(); 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) -> ActivityStream { let user = User::find_local(&*conn, name).unwrap(); ActivityStream::new(user.into_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