Plume/src/routes/user.rs

594 lines
18 KiB
Rust
Raw Normal View History

use activitystreams::{
collection::{OrderedCollection, OrderedCollectionPage},
iri_string::types::IriString,
prelude::*,
};
use diesel::SaveChangesDsl;
2018-07-26 17:51:41 +02:00
use rocket::{
2021-02-14 14:59:01 +01:00
http::{uri::Uri, ContentType, Cookies},
2018-07-26 17:51:41 +02:00
request::LenientForm,
response::{status, Content, Flash, Redirect},
};
use rocket_i18n::I18n;
use std::{borrow::Cow, collections::HashMap};
use validator::{Validate, ValidationError, ValidationErrors};
2018-04-22 20:13:12 +02:00
2020-01-21 07:02:03 +01:00
use crate::inbox;
2022-01-06 12:18:20 +01:00
use crate::routes::{
email_signups::EmailSignupForm, errors::ErrorPage, Page, RemoteForm, RespondOrRedirect,
};
2020-01-21 07:02:03 +01:00
use crate::template_utils::{IntoContext, Ructe};
use crate::utils::requires_login;
use plume_common::activity_pub::{broadcast, ActivityStream, ApRequest, CustomPerson};
use plume_common::utils::md_to_html;
use plume_models::{
blogs::Blog,
db_conn::DbConn,
follows,
headers::Headers,
inbox::inbox as local_inbox,
instance::Instance,
medias::Media,
posts::Post,
reshares::Reshare,
safe_string::SafeString,
signups::{self, Strategy as SignupStrategy},
users::*,
Error, PlumeRocket, CONFIG,
2018-05-19 09:39:59 +02:00
};
2018-04-22 20:13:12 +02:00
2018-04-23 11:52:44 +02:00
#[get("/me")]
pub fn me(user: Option<User>) -> RespondOrRedirect {
match user {
Some(user) => Redirect::to(uri!(details: name = user.username)).into(),
None => requires_login("", uri!(me)).into(),
}
2018-04-23 11:52:44 +02:00
}
2018-04-22 20:13:12 +02:00
2018-04-23 17:09:05 +02:00
#[get("/@/<name>", rank = 2)]
pub fn details(name: String, rockets: PlumeRocket, conn: DbConn) -> Result<Ructe, ErrorPage> {
2021-01-30 13:44:29 +01:00
let user = User::find_by_fqn(&conn, &name)?;
let recents = Post::get_recents_for_author(&*conn, &user, 6)?;
let reshares = Reshare::get_recents_for_author(&*conn, &user, 6)?;
if !user.get_instance(&*conn)?.local {
tracing::trace!("remote user found");
user.remote_user_found(); // Doesn't block
}
Ok(render!(users::details(
2021-01-30 13:44:29 +01:00
&(&conn, &rockets).to_context(),
user.clone(),
rockets
.user
.clone()
2019-03-20 17:56:17 +01:00
.and_then(|x| x.is_following(&*conn, user.id).ok())
.unwrap_or(false),
user.instance_id != Instance::get_local()?.id,
user.get_instance(&*conn)?.public_domain,
recents,
2019-03-20 17:56:17 +01:00
reshares
.into_iter()
.filter_map(|r| r.get_post(&*conn).ok())
.collect()
)))
2018-04-22 20:13:12 +02:00
}
2018-06-10 19:55:08 +02:00
#[get("/dashboard")]
2021-01-30 13:44:29 +01:00
pub fn dashboard(user: User, conn: DbConn, rockets: PlumeRocket) -> Result<Ructe, ErrorPage> {
let blogs = Blog::find_for_author(&conn, &user)?;
Ok(render!(users::dashboard(
2021-01-30 13:44:29 +01:00
&(&conn, &rockets).to_context(),
blogs,
2021-01-30 13:44:29 +01:00
Post::drafts_by_author(&conn, &user)?
)))
2018-06-10 19:55:08 +02:00
}
#[get("/dashboard", rank = 2)]
pub fn dashboard_auth(i18n: I18n) -> Flash<Redirect> {
requires_login(
2019-03-20 17:56:17 +01:00
&i18n!(
i18n.catalog,
"To access your dashboard, you need to be logged in"
2019-03-20 17:56:17 +01:00
),
uri!(dashboard),
)
2018-06-10 19:55:08 +02:00
}
#[post("/@/<name>/follow")]
pub fn follow(
name: String,
user: User,
2021-01-30 13:44:29 +01:00
conn: DbConn,
rockets: PlumeRocket,
) -> Result<Flash<Redirect>, ErrorPage> {
2021-01-30 13:44:29 +01:00
let target = User::find_by_fqn(&conn, &name)?;
let message = if let Ok(follow) = follows::Follow::find(&conn, user.id, target.id) {
2022-05-02 19:11:46 +02:00
let delete_act = follow.build_undo(&conn)?;
Big refactoring of the Inbox (#443) * Big refactoring of the Inbox We now have a type that routes an activity through the registered handlers until one of them matches. Each Actor/Activity/Object combination is represented by an implementation of AsObject These combinations are then registered on the Inbox type, which will try to deserialize the incoming activity in the requested types. Advantages: - nicer syntax: the final API is clearer and more idiomatic - more generic: only two traits (`AsActor` and `AsObject`) instead of one for each kind of activity - it is easier to see which activities we handle and which one we don't * Small fixes - Avoid panics - Don't search for AP ID infinitely - Code style issues * Fix tests * Introduce a new trait: FromId It should be implemented for any AP object. It allows to look for an object in database using its AP ID, or to dereference it if it was not present in database Also moves the inbox code to plume-models to test it (and write a basic test for each activity type we handle) * Use if let instead of match * Don't require PlumeRocket::intl for tests * Return early and remove a forgotten dbg! * Add more tests to try to understand where the issues come from * Also add a test for comment federation * Don't check creation_date is the same for blogs * Make user and blog federation more tolerant to errors/missing fields * Make clippy happy * Use the correct Accept header when dereferencing * Fix follow approval with Mastodon * Add spaces to characters that should not be in usernames And validate blog names too * Smarter dereferencing: only do it once for each actor/object * Forgot some files * Cargo fmt * Delete plume_test * Delete plume_tests * Update get_id docs + Remove useless : Sized * Appease cargo fmt * Remove dbg! + Use as_ref instead of clone when possible + Use and_then instead of map when possible * Remove .po~ * send unfollow to local instance * read cover from update activity * Make sure "cc" and "to" are never empty and fix a typo in a constant name * Cargo fmt
2019-04-17 19:31:47 +02:00
local_inbox(
2021-01-30 13:44:29 +01:00
&conn,
Big refactoring of the Inbox (#443) * Big refactoring of the Inbox We now have a type that routes an activity through the registered handlers until one of them matches. Each Actor/Activity/Object combination is represented by an implementation of AsObject These combinations are then registered on the Inbox type, which will try to deserialize the incoming activity in the requested types. Advantages: - nicer syntax: the final API is clearer and more idiomatic - more generic: only two traits (`AsActor` and `AsObject`) instead of one for each kind of activity - it is easier to see which activities we handle and which one we don't * Small fixes - Avoid panics - Don't search for AP ID infinitely - Code style issues * Fix tests * Introduce a new trait: FromId It should be implemented for any AP object. It allows to look for an object in database using its AP ID, or to dereference it if it was not present in database Also moves the inbox code to plume-models to test it (and write a basic test for each activity type we handle) * Use if let instead of match * Don't require PlumeRocket::intl for tests * Return early and remove a forgotten dbg! * Add more tests to try to understand where the issues come from * Also add a test for comment federation * Don't check creation_date is the same for blogs * Make user and blog federation more tolerant to errors/missing fields * Make clippy happy * Use the correct Accept header when dereferencing * Fix follow approval with Mastodon * Add spaces to characters that should not be in usernames And validate blog names too * Smarter dereferencing: only do it once for each actor/object * Forgot some files * Cargo fmt * Delete plume_test * Delete plume_tests * Update get_id docs + Remove useless : Sized * Appease cargo fmt * Remove dbg! + Use as_ref instead of clone when possible + Use and_then instead of map when possible * Remove .po~ * send unfollow to local instance * read cover from update activity * Make sure "cc" and "to" are never empty and fix a typo in a constant name * Cargo fmt
2019-04-17 19:31:47 +02:00
serde_json::to_value(&delete_act).map_err(Error::from)?,
)?;
let msg = i18n!(rockets.intl.catalog, "You are no longer following {}."; target.name());
Big refactoring of the Inbox (#443) * Big refactoring of the Inbox We now have a type that routes an activity through the registered handlers until one of them matches. Each Actor/Activity/Object combination is represented by an implementation of AsObject These combinations are then registered on the Inbox type, which will try to deserialize the incoming activity in the requested types. Advantages: - nicer syntax: the final API is clearer and more idiomatic - more generic: only two traits (`AsActor` and `AsObject`) instead of one for each kind of activity - it is easier to see which activities we handle and which one we don't * Small fixes - Avoid panics - Don't search for AP ID infinitely - Code style issues * Fix tests * Introduce a new trait: FromId It should be implemented for any AP object. It allows to look for an object in database using its AP ID, or to dereference it if it was not present in database Also moves the inbox code to plume-models to test it (and write a basic test for each activity type we handle) * Use if let instead of match * Don't require PlumeRocket::intl for tests * Return early and remove a forgotten dbg! * Add more tests to try to understand where the issues come from * Also add a test for comment federation * Don't check creation_date is the same for blogs * Make user and blog federation more tolerant to errors/missing fields * Make clippy happy * Use the correct Accept header when dereferencing * Fix follow approval with Mastodon * Add spaces to characters that should not be in usernames And validate blog names too * Smarter dereferencing: only do it once for each actor/object * Forgot some files * Cargo fmt * Delete plume_test * Delete plume_tests * Update get_id docs + Remove useless : Sized * Appease cargo fmt * Remove dbg! + Use as_ref instead of clone when possible + Use and_then instead of map when possible * Remove .po~ * send unfollow to local instance * read cover from update activity * Make sure "cc" and "to" are never empty and fix a typo in a constant name * Cargo fmt
2019-04-17 19:31:47 +02:00
rockets
.worker
2022-05-02 18:12:39 +02:00
.execute(move || broadcast(&user, delete_act, vec![target], CONFIG.proxy().cloned()));
msg
} else {
let f = follows::Follow::insert(
2021-01-30 13:44:29 +01:00
&conn,
follows::NewFollow {
follower_id: user.id,
following_id: target.id,
ap_url: String::new(),
},
)?;
2021-01-30 13:44:29 +01:00
f.notify(&conn)?;
2022-05-02 18:26:15 +02:00
let act = f.to_activity(&conn)?;
let msg = i18n!(rockets.intl.catalog, "You are now following {}."; target.name());
Big refactoring of the Inbox (#443) * Big refactoring of the Inbox We now have a type that routes an activity through the registered handlers until one of them matches. Each Actor/Activity/Object combination is represented by an implementation of AsObject These combinations are then registered on the Inbox type, which will try to deserialize the incoming activity in the requested types. Advantages: - nicer syntax: the final API is clearer and more idiomatic - more generic: only two traits (`AsActor` and `AsObject`) instead of one for each kind of activity - it is easier to see which activities we handle and which one we don't * Small fixes - Avoid panics - Don't search for AP ID infinitely - Code style issues * Fix tests * Introduce a new trait: FromId It should be implemented for any AP object. It allows to look for an object in database using its AP ID, or to dereference it if it was not present in database Also moves the inbox code to plume-models to test it (and write a basic test for each activity type we handle) * Use if let instead of match * Don't require PlumeRocket::intl for tests * Return early and remove a forgotten dbg! * Add more tests to try to understand where the issues come from * Also add a test for comment federation * Don't check creation_date is the same for blogs * Make user and blog federation more tolerant to errors/missing fields * Make clippy happy * Use the correct Accept header when dereferencing * Fix follow approval with Mastodon * Add spaces to characters that should not be in usernames And validate blog names too * Smarter dereferencing: only do it once for each actor/object * Forgot some files * Cargo fmt * Delete plume_test * Delete plume_tests * Update get_id docs + Remove useless : Sized * Appease cargo fmt * Remove dbg! + Use as_ref instead of clone when possible + Use and_then instead of map when possible * Remove .po~ * send unfollow to local instance * read cover from update activity * Make sure "cc" and "to" are never empty and fix a typo in a constant name * Cargo fmt
2019-04-17 19:31:47 +02:00
rockets
.worker
2022-05-02 18:12:39 +02:00
.execute(move || broadcast(&user, act, vec![target], CONFIG.proxy().cloned()));
msg
};
Ok(Flash::success(
Redirect::to(uri!(details: name = name)),
message,
))
2018-05-01 21:57:30 +02:00
}
#[post("/@/<name>/follow", data = "<remote_form>", rank = 2)]
pub fn follow_not_connected(
2021-01-30 13:44:29 +01:00
conn: DbConn,
rockets: PlumeRocket,
name: String,
remote_form: Option<LenientForm<RemoteForm>>,
i18n: I18n,
) -> Result<RespondOrRedirect, ErrorPage> {
2021-01-30 13:44:29 +01:00
let target = User::find_by_fqn(&conn, &name)?;
if let Some(remote_form) = remote_form {
if let Some(uri) = User::fetch_remote_interact_uri(&remote_form)
.ok()
2021-02-14 14:59:01 +01:00
.and_then(|uri| {
Some(uri.replace(
"{uri}",
&Uri::percent_encode(&target.acct_authority(&conn).ok()?),
))
})
{
Ok(Redirect::to(uri).into())
} else {
let mut err = ValidationErrors::default();
err.add("remote",
ValidationError {
code: Cow::from("invalid_remote"),
message: Some(Cow::from(i18n!(&i18n.catalog, "Couldn't obtain enough information about your account. Please make sure your username is correct."))),
params: HashMap::new(),
},
);
Ok(Flash::new(
render!(users::follow_remote(
2021-01-30 13:44:29 +01:00
&(&conn, &rockets).to_context(),
target,
super::session::LoginForm::default(),
ValidationErrors::default(),
remote_form.clone(),
err
)),
"callback",
uri!(follow: name = name).to_string(),
)
.into())
}
} else {
Ok(Flash::new(
render!(users::follow_remote(
2021-01-30 13:44:29 +01:00
&(&conn, &rockets).to_context(),
target,
super::session::LoginForm::default(),
ValidationErrors::default(),
#[allow(clippy::map_clone)]
remote_form.map(|x| x.clone()).unwrap_or_default(),
ValidationErrors::default()
)),
"callback",
uri!(follow: name = name).to_string(),
)
.into())
}
}
#[get("/@/<name>/follow?local", rank = 2)]
pub fn follow_auth(name: String, i18n: I18n) -> Flash<Redirect> {
requires_login(
2019-03-20 17:56:17 +01:00
&i18n!(
i18n.catalog,
"To subscribe to someone, you need to be logged in"
2019-03-20 17:56:17 +01:00
),
uri!(follow: name = name),
)
}
#[get("/@/<name>/followers?<page>", rank = 2)]
2019-03-20 17:56:17 +01:00
pub fn followers(
name: String,
page: Option<Page>,
2021-01-30 13:44:29 +01:00
conn: DbConn,
Big refactoring of the Inbox (#443) * Big refactoring of the Inbox We now have a type that routes an activity through the registered handlers until one of them matches. Each Actor/Activity/Object combination is represented by an implementation of AsObject These combinations are then registered on the Inbox type, which will try to deserialize the incoming activity in the requested types. Advantages: - nicer syntax: the final API is clearer and more idiomatic - more generic: only two traits (`AsActor` and `AsObject`) instead of one for each kind of activity - it is easier to see which activities we handle and which one we don't * Small fixes - Avoid panics - Don't search for AP ID infinitely - Code style issues * Fix tests * Introduce a new trait: FromId It should be implemented for any AP object. It allows to look for an object in database using its AP ID, or to dereference it if it was not present in database Also moves the inbox code to plume-models to test it (and write a basic test for each activity type we handle) * Use if let instead of match * Don't require PlumeRocket::intl for tests * Return early and remove a forgotten dbg! * Add more tests to try to understand where the issues come from * Also add a test for comment federation * Don't check creation_date is the same for blogs * Make user and blog federation more tolerant to errors/missing fields * Make clippy happy * Use the correct Accept header when dereferencing * Fix follow approval with Mastodon * Add spaces to characters that should not be in usernames And validate blog names too * Smarter dereferencing: only do it once for each actor/object * Forgot some files * Cargo fmt * Delete plume_test * Delete plume_tests * Update get_id docs + Remove useless : Sized * Appease cargo fmt * Remove dbg! + Use as_ref instead of clone when possible + Use and_then instead of map when possible * Remove .po~ * send unfollow to local instance * read cover from update activity * Make sure "cc" and "to" are never empty and fix a typo in a constant name * Cargo fmt
2019-04-17 19:31:47 +02:00
rockets: PlumeRocket,
2019-03-20 17:56:17 +01:00
) -> Result<Ructe, ErrorPage> {
let page = page.unwrap_or_default();
2021-01-30 13:44:29 +01:00
let user = User::find_by_fqn(&conn, &name)?;
let followers_count = user.count_followers(&conn)?;
Ok(render!(users::followers(
2021-01-30 13:44:29 +01:00
&(&conn, &rockets).to_context(),
user.clone(),
Big refactoring of the Inbox (#443) * Big refactoring of the Inbox We now have a type that routes an activity through the registered handlers until one of them matches. Each Actor/Activity/Object combination is represented by an implementation of AsObject These combinations are then registered on the Inbox type, which will try to deserialize the incoming activity in the requested types. Advantages: - nicer syntax: the final API is clearer and more idiomatic - more generic: only two traits (`AsActor` and `AsObject`) instead of one for each kind of activity - it is easier to see which activities we handle and which one we don't * Small fixes - Avoid panics - Don't search for AP ID infinitely - Code style issues * Fix tests * Introduce a new trait: FromId It should be implemented for any AP object. It allows to look for an object in database using its AP ID, or to dereference it if it was not present in database Also moves the inbox code to plume-models to test it (and write a basic test for each activity type we handle) * Use if let instead of match * Don't require PlumeRocket::intl for tests * Return early and remove a forgotten dbg! * Add more tests to try to understand where the issues come from * Also add a test for comment federation * Don't check creation_date is the same for blogs * Make user and blog federation more tolerant to errors/missing fields * Make clippy happy * Use the correct Accept header when dereferencing * Fix follow approval with Mastodon * Add spaces to characters that should not be in usernames And validate blog names too * Smarter dereferencing: only do it once for each actor/object * Forgot some files * Cargo fmt * Delete plume_test * Delete plume_tests * Update get_id docs + Remove useless : Sized * Appease cargo fmt * Remove dbg! + Use as_ref instead of clone when possible + Use and_then instead of map when possible * Remove .po~ * send unfollow to local instance * read cover from update activity * Make sure "cc" and "to" are never empty and fix a typo in a constant name * Cargo fmt
2019-04-17 19:31:47 +02:00
rockets
.user
.clone()
2021-01-30 13:44:29 +01:00
.and_then(|x| x.is_following(&conn, user.id).ok())
2019-03-20 17:56:17 +01:00
.unwrap_or(false),
user.instance_id != Instance::get_local()?.id,
2021-01-30 13:44:29 +01:00
user.get_instance(&conn)?.public_domain,
user.get_followers_page(&conn, page.limits())?,
page.0,
Page::total(followers_count as i32)
)))
}
#[get("/@/<name>/followed?<page>", rank = 2)]
2019-03-20 17:56:17 +01:00
pub fn followed(
name: String,
page: Option<Page>,
2021-01-30 13:44:29 +01:00
conn: DbConn,
Big refactoring of the Inbox (#443) * Big refactoring of the Inbox We now have a type that routes an activity through the registered handlers until one of them matches. Each Actor/Activity/Object combination is represented by an implementation of AsObject These combinations are then registered on the Inbox type, which will try to deserialize the incoming activity in the requested types. Advantages: - nicer syntax: the final API is clearer and more idiomatic - more generic: only two traits (`AsActor` and `AsObject`) instead of one for each kind of activity - it is easier to see which activities we handle and which one we don't * Small fixes - Avoid panics - Don't search for AP ID infinitely - Code style issues * Fix tests * Introduce a new trait: FromId It should be implemented for any AP object. It allows to look for an object in database using its AP ID, or to dereference it if it was not present in database Also moves the inbox code to plume-models to test it (and write a basic test for each activity type we handle) * Use if let instead of match * Don't require PlumeRocket::intl for tests * Return early and remove a forgotten dbg! * Add more tests to try to understand where the issues come from * Also add a test for comment federation * Don't check creation_date is the same for blogs * Make user and blog federation more tolerant to errors/missing fields * Make clippy happy * Use the correct Accept header when dereferencing * Fix follow approval with Mastodon * Add spaces to characters that should not be in usernames And validate blog names too * Smarter dereferencing: only do it once for each actor/object * Forgot some files * Cargo fmt * Delete plume_test * Delete plume_tests * Update get_id docs + Remove useless : Sized * Appease cargo fmt * Remove dbg! + Use as_ref instead of clone when possible + Use and_then instead of map when possible * Remove .po~ * send unfollow to local instance * read cover from update activity * Make sure "cc" and "to" are never empty and fix a typo in a constant name * Cargo fmt
2019-04-17 19:31:47 +02:00
rockets: PlumeRocket,
2019-03-20 17:56:17 +01:00
) -> Result<Ructe, ErrorPage> {
let page = page.unwrap_or_default();
2021-01-30 13:44:29 +01:00
let user = User::find_by_fqn(&conn, &name)?;
let followed_count = user.count_followed(&conn)?;
Ok(render!(users::followed(
2021-01-30 13:44:29 +01:00
&(&conn, &rockets).to_context(),
user.clone(),
Big refactoring of the Inbox (#443) * Big refactoring of the Inbox We now have a type that routes an activity through the registered handlers until one of them matches. Each Actor/Activity/Object combination is represented by an implementation of AsObject These combinations are then registered on the Inbox type, which will try to deserialize the incoming activity in the requested types. Advantages: - nicer syntax: the final API is clearer and more idiomatic - more generic: only two traits (`AsActor` and `AsObject`) instead of one for each kind of activity - it is easier to see which activities we handle and which one we don't * Small fixes - Avoid panics - Don't search for AP ID infinitely - Code style issues * Fix tests * Introduce a new trait: FromId It should be implemented for any AP object. It allows to look for an object in database using its AP ID, or to dereference it if it was not present in database Also moves the inbox code to plume-models to test it (and write a basic test for each activity type we handle) * Use if let instead of match * Don't require PlumeRocket::intl for tests * Return early and remove a forgotten dbg! * Add more tests to try to understand where the issues come from * Also add a test for comment federation * Don't check creation_date is the same for blogs * Make user and blog federation more tolerant to errors/missing fields * Make clippy happy * Use the correct Accept header when dereferencing * Fix follow approval with Mastodon * Add spaces to characters that should not be in usernames And validate blog names too * Smarter dereferencing: only do it once for each actor/object * Forgot some files * Cargo fmt * Delete plume_test * Delete plume_tests * Update get_id docs + Remove useless : Sized * Appease cargo fmt * Remove dbg! + Use as_ref instead of clone when possible + Use and_then instead of map when possible * Remove .po~ * send unfollow to local instance * read cover from update activity * Make sure "cc" and "to" are never empty and fix a typo in a constant name * Cargo fmt
2019-04-17 19:31:47 +02:00
rockets
.user
.clone()
2021-01-30 13:44:29 +01:00
.and_then(|x| x.is_following(&conn, user.id).ok())
2019-03-20 17:56:17 +01:00
.unwrap_or(false),
user.instance_id != Instance::get_local()?.id,
2021-01-30 13:44:29 +01:00
user.get_instance(&conn)?.public_domain,
user.get_followed_page(&conn, page.limits())?,
page.0,
Page::total(followed_count as i32)
)))
}
#[get("/@/<name>", rank = 1)]
pub fn activity_details(
name: String,
2021-01-30 13:44:29 +01:00
conn: DbConn,
_ap: ApRequest,
) -> Option<ActivityStream<CustomPerson>> {
2021-01-30 13:44:29 +01:00
let user = User::find_by_fqn(&conn, &name).ok()?;
2022-05-02 18:26:15 +02:00
Some(ActivityStream::new(user.to_activity(&conn).ok()?))
2018-04-23 17:09:05 +02:00
}
2018-04-22 20:13:12 +02:00
#[get("/users/new")]
2021-01-30 13:44:29 +01:00
pub fn new(conn: DbConn, rockets: PlumeRocket) -> Result<Ructe, ErrorPage> {
2022-01-06 12:18:20 +01:00
use SignupStrategy::*;
let rendered = match CONFIG.signup {
Password => render!(users::new(
&(&conn, &rockets).to_context(),
Instance::get_local()?.open_registrations,
&NewUserForm::default(),
ValidationErrors::default()
)),
Email => render!(email_signups::new(
&(&conn, &rockets).to_context(),
Instance::get_local()?.open_registrations,
&EmailSignupForm::default(),
ValidationErrors::default()
)),
};
Ok(rendered)
2018-04-22 20:13:12 +02:00
}
2018-05-12 17:30:14 +02:00
#[get("/@/<name>/edit")]
2021-01-30 13:44:29 +01:00
pub fn edit(
name: String,
user: User,
conn: DbConn,
rockets: PlumeRocket,
) -> Result<Ructe, ErrorPage> {
if user.username == name && !name.contains('@') {
Ok(render!(users::edit(
2021-01-30 13:44:29 +01:00
&(&conn, &rockets).to_context(),
UpdateUserForm {
display_name: user.display_name.clone(),
email: user.email.clone().unwrap_or_default(),
summary: user.summary.clone(),
theme: user.preferred_theme,
hide_custom_css: user.hide_custom_css,
},
ValidationErrors::default()
)))
2018-05-12 17:30:14 +02:00
} else {
Err(Error::Unauthorized.into())
2018-05-12 17:30:14 +02:00
}
}
#[get("/@/<name>/edit", rank = 2)]
pub fn edit_auth(name: String, i18n: I18n) -> Flash<Redirect> {
requires_login(
2019-03-20 17:56:17 +01:00
&i18n!(
i18n.catalog,
"To edit your profile, you need to be logged in"
2019-03-20 17:56:17 +01:00
),
uri!(edit: name = name),
)
}
2018-05-12 17:30:14 +02:00
#[derive(FromForm)]
pub struct UpdateUserForm {
pub display_name: String,
pub email: String,
pub summary: String,
pub theme: Option<String>,
pub hide_custom_css: bool,
2018-05-12 17:30:14 +02:00
}
#[allow(unused_variables)]
#[put("/@/<name>/edit", data = "<form>")]
2019-03-20 17:56:17 +01:00
pub fn update(
name: String,
2019-03-20 17:56:17 +01:00
conn: DbConn,
mut user: User,
2019-03-20 17:56:17 +01:00
form: LenientForm<UpdateUserForm>,
intl: I18n,
) -> Result<Flash<Redirect>, ErrorPage> {
user.display_name = form.display_name.clone();
user.email = Some(form.email.clone());
user.summary = form.summary.clone();
user.summary_html = SafeString::new(
&md_to_html(
&form.summary,
None,
false,
Some(Media::get_media_processor(&conn, vec![&user])),
)
.0,
);
user.preferred_theme = form
.theme
.clone()
.and_then(|t| if t.is_empty() { None } else { Some(t) });
user.hide_custom_css = form.hide_custom_css;
let _: User = user.save_changes(&*conn).map_err(Error::from)?;
Ok(Flash::success(
Redirect::to(uri!(me)),
i18n!(intl.catalog, "Your profile has been updated."),
))
2018-05-12 17:30:14 +02:00
}
#[post("/@/<name>/delete")]
2019-03-20 17:56:17 +01:00
pub fn delete(
name: String,
user: User,
2020-01-21 07:02:03 +01:00
mut cookies: Cookies<'_>,
2021-01-30 13:44:29 +01:00
conn: DbConn,
Big refactoring of the Inbox (#443) * Big refactoring of the Inbox We now have a type that routes an activity through the registered handlers until one of them matches. Each Actor/Activity/Object combination is represented by an implementation of AsObject These combinations are then registered on the Inbox type, which will try to deserialize the incoming activity in the requested types. Advantages: - nicer syntax: the final API is clearer and more idiomatic - more generic: only two traits (`AsActor` and `AsObject`) instead of one for each kind of activity - it is easier to see which activities we handle and which one we don't * Small fixes - Avoid panics - Don't search for AP ID infinitely - Code style issues * Fix tests * Introduce a new trait: FromId It should be implemented for any AP object. It allows to look for an object in database using its AP ID, or to dereference it if it was not present in database Also moves the inbox code to plume-models to test it (and write a basic test for each activity type we handle) * Use if let instead of match * Don't require PlumeRocket::intl for tests * Return early and remove a forgotten dbg! * Add more tests to try to understand where the issues come from * Also add a test for comment federation * Don't check creation_date is the same for blogs * Make user and blog federation more tolerant to errors/missing fields * Make clippy happy * Use the correct Accept header when dereferencing * Fix follow approval with Mastodon * Add spaces to characters that should not be in usernames And validate blog names too * Smarter dereferencing: only do it once for each actor/object * Forgot some files * Cargo fmt * Delete plume_test * Delete plume_tests * Update get_id docs + Remove useless : Sized * Appease cargo fmt * Remove dbg! + Use as_ref instead of clone when possible + Use and_then instead of map when possible * Remove .po~ * send unfollow to local instance * read cover from update activity * Make sure "cc" and "to" are never empty and fix a typo in a constant name * Cargo fmt
2019-04-17 19:31:47 +02:00
rockets: PlumeRocket,
) -> Result<Flash<Redirect>, ErrorPage> {
2021-01-30 13:44:29 +01:00
let account = User::find_by_fqn(&conn, &name)?;
2018-09-09 21:49:24 +02:00
if user.id == account.id {
2021-01-30 13:44:29 +01:00
account.delete(&conn)?;
2018-09-09 21:49:24 +02:00
2021-01-30 13:44:29 +01:00
let target = User::one_by_instance(&conn)?;
2022-05-02 18:26:15 +02:00
let delete_act = account.delete_activity(&conn)?;
rockets
.worker
2022-05-02 18:12:39 +02:00
.execute(move || broadcast(&account, delete_act, target, CONFIG.proxy().cloned()));
if let Some(cookie) = cookies.get_private(AUTH_COOKIE) {
cookies.remove_private(cookie);
}
2018-09-09 21:49:24 +02:00
Ok(Flash::success(
Redirect::to(uri!(super::instance::index)),
i18n!(rockets.intl.catalog, "Your account has been deleted."),
))
2018-09-09 21:49:24 +02:00
} else {
Ok(Flash::error(
Redirect::to(uri!(edit: name = name)),
i18n!(
rockets.intl.catalog,
"You can't delete someone else's account."
),
))
2018-09-09 21:49:24 +02:00
}
}
#[derive(Default, FromForm, Validate)]
2019-03-20 17:56:17 +01:00
#[validate(schema(
function = "passwords_match",
2022-01-06 20:55:49 +01:00
skip_on_field_errors = false,
2019-03-20 17:56:17 +01:00
message = "Passwords are not matching"
))]
pub struct NewUserForm {
#[validate(
2022-01-06 20:55:49 +01:00
length(min = 1, message = "Username can't be empty"),
2019-03-20 17:56:17 +01:00
custom(
function = "validate_username",
message = "User name is not allowed to contain any of < > & @ ' or \""
)
)]
2019-03-20 17:56:17 +01:00
pub username: String,
#[validate(email(message = "Invalid email"))]
pub email: String,
2022-01-06 20:55:49 +01:00
#[validate(length(min = 8, message = "Password should be at least 8 characters long"))]
pub password: String,
2022-01-06 20:55:49 +01:00
#[validate(length(min = 8, message = "Password should be at least 8 characters long"))]
pub password_confirmation: String,
2018-04-22 20:13:12 +02:00
}
pub fn passwords_match(form: &NewUserForm) -> Result<(), ValidationError> {
2018-06-29 14:56:00 +02:00
if form.password != form.password_confirmation {
Err(ValidationError::new("password_match"))
} else {
Ok(())
}
}
pub fn validate_username(username: &str) -> Result<(), ValidationError> {
if username.contains(&['<', '>', '&', '@', '\'', '"', ' ', '\n', '\t'][..]) {
Err(ValidationError::new("username_illegal_char"))
} else {
Ok(())
}
}
fn to_validation(x: Error) -> ValidationErrors {
let mut errors = ValidationErrors::new();
if let Error::Blocklisted(show, msg) = x {
if show {
errors.add(
"email",
ValidationError {
code: Cow::from("blocklisted"),
message: Some(Cow::from(msg)),
params: HashMap::new(),
},
);
}
}
2019-03-20 17:56:17 +01:00
errors.add(
"",
ValidationError {
code: Cow::from("server_error"),
message: Some(Cow::from("An unknown error occured")),
params: HashMap::new(),
},
);
errors
}
#[post("/users/new", data = "<form>")]
pub fn create(
form: LenientForm<NewUserForm>,
2021-01-30 13:44:29 +01:00
conn: DbConn,
rockets: PlumeRocket,
_enabled: signups::Password,
) -> Result<Flash<Redirect>, Ructe> {
if !Instance::get_local()
.map(|i| i.open_registrations)
.unwrap_or(true)
{
return Ok(Flash::error(
Redirect::to(uri!(new)),
i18n!(
rockets.intl.catalog,
"Registrations are closed on this instance."
),
)); // Actually, it is an error
}
let mut form = form.into_inner();
form.username = form.username.trim().to_owned();
form.email = form.email.trim().to_owned();
2018-07-06 11:51:19 +02:00
form.validate()
.and_then(|_| {
NewUser::new_local(
2021-01-30 13:44:29 +01:00
&conn,
2018-07-06 11:51:19 +02:00
form.username.to_string(),
form.username.to_string(),
Role::Normal,
"",
2018-07-06 11:51:19 +02:00
form.email.to_string(),
2020-10-04 12:18:22 +02:00
Some(User::hash_pass(&form.password).map_err(to_validation)?),
).map_err(to_validation)?;
Ok(Flash::success(
Redirect::to(uri!(super::session::new: m = _)),
i18n!(
rockets.intl.catalog,
"Your account has been created. Now you just need to log in, before you can use it."
),
))
2018-07-06 11:51:19 +02:00
})
2019-03-20 17:56:17 +01:00
.map_err(|err| {
render!(users::new(
2021-01-30 13:44:29 +01:00
&(&conn, &rockets).to_context(),
Instance::get_local()
2019-03-20 17:56:17 +01:00
.map(|i| i.open_registrations)
.unwrap_or(true),
&form,
err
))
})
2018-04-22 20:13:12 +02:00
}
2018-04-29 20:01:42 +02:00
#[get("/@/<name>/outbox")]
pub fn outbox(name: String, conn: DbConn) -> Option<ActivityStream<OrderedCollection>> {
2021-01-30 13:44:29 +01:00
let user = User::find_by_fqn(&conn, &name).ok()?;
2022-05-02 19:11:46 +02:00
user.outbox(&conn).ok()
2018-04-29 20:01:42 +02:00
}
#[get("/@/<name>/outbox?<page>")]
pub fn outbox_page(
name: String,
page: Page,
2021-01-30 13:44:29 +01:00
conn: DbConn,
) -> Option<ActivityStream<OrderedCollectionPage>> {
2021-01-30 13:44:29 +01:00
let user = User::find_by_fqn(&conn, &name).ok()?;
2022-05-02 19:11:46 +02:00
user.outbox_page(&conn, page.limits()).ok()
}
2018-05-01 16:00:29 +02:00
#[post("/@/<name>/inbox", data = "<data>")]
pub fn inbox(
name: String,
Big refactoring of the Inbox (#443) * Big refactoring of the Inbox We now have a type that routes an activity through the registered handlers until one of them matches. Each Actor/Activity/Object combination is represented by an implementation of AsObject These combinations are then registered on the Inbox type, which will try to deserialize the incoming activity in the requested types. Advantages: - nicer syntax: the final API is clearer and more idiomatic - more generic: only two traits (`AsActor` and `AsObject`) instead of one for each kind of activity - it is easier to see which activities we handle and which one we don't * Small fixes - Avoid panics - Don't search for AP ID infinitely - Code style issues * Fix tests * Introduce a new trait: FromId It should be implemented for any AP object. It allows to look for an object in database using its AP ID, or to dereference it if it was not present in database Also moves the inbox code to plume-models to test it (and write a basic test for each activity type we handle) * Use if let instead of match * Don't require PlumeRocket::intl for tests * Return early and remove a forgotten dbg! * Add more tests to try to understand where the issues come from * Also add a test for comment federation * Don't check creation_date is the same for blogs * Make user and blog federation more tolerant to errors/missing fields * Make clippy happy * Use the correct Accept header when dereferencing * Fix follow approval with Mastodon * Add spaces to characters that should not be in usernames And validate blog names too * Smarter dereferencing: only do it once for each actor/object * Forgot some files * Cargo fmt * Delete plume_test * Delete plume_tests * Update get_id docs + Remove useless : Sized * Appease cargo fmt * Remove dbg! + Use as_ref instead of clone when possible + Use and_then instead of map when possible * Remove .po~ * send unfollow to local instance * read cover from update activity * Make sure "cc" and "to" are never empty and fix a typo in a constant name * Cargo fmt
2019-04-17 19:31:47 +02:00
data: inbox::SignedJson<serde_json::Value>,
2020-01-21 07:02:03 +01:00
headers: Headers<'_>,
2021-01-30 13:44:29 +01:00
conn: DbConn,
Big refactoring of the Inbox (#443) * Big refactoring of the Inbox We now have a type that routes an activity through the registered handlers until one of them matches. Each Actor/Activity/Object combination is represented by an implementation of AsObject These combinations are then registered on the Inbox type, which will try to deserialize the incoming activity in the requested types. Advantages: - nicer syntax: the final API is clearer and more idiomatic - more generic: only two traits (`AsActor` and `AsObject`) instead of one for each kind of activity - it is easier to see which activities we handle and which one we don't * Small fixes - Avoid panics - Don't search for AP ID infinitely - Code style issues * Fix tests * Introduce a new trait: FromId It should be implemented for any AP object. It allows to look for an object in database using its AP ID, or to dereference it if it was not present in database Also moves the inbox code to plume-models to test it (and write a basic test for each activity type we handle) * Use if let instead of match * Don't require PlumeRocket::intl for tests * Return early and remove a forgotten dbg! * Add more tests to try to understand where the issues come from * Also add a test for comment federation * Don't check creation_date is the same for blogs * Make user and blog federation more tolerant to errors/missing fields * Make clippy happy * Use the correct Accept header when dereferencing * Fix follow approval with Mastodon * Add spaces to characters that should not be in usernames And validate blog names too * Smarter dereferencing: only do it once for each actor/object * Forgot some files * Cargo fmt * Delete plume_test * Delete plume_tests * Update get_id docs + Remove useless : Sized * Appease cargo fmt * Remove dbg! + Use as_ref instead of clone when possible + Use and_then instead of map when possible * Remove .po~ * send unfollow to local instance * read cover from update activity * Make sure "cc" and "to" are never empty and fix a typo in a constant name * Cargo fmt
2019-04-17 19:31:47 +02:00
) -> Result<String, status::BadRequest<&'static str>> {
2021-01-30 13:44:29 +01:00
User::find_by_fqn(&conn, &name).map_err(|_| status::BadRequest(Some("User not found")))?;
inbox::handle_incoming(conn, data, headers)
2018-05-01 16:00:29 +02:00
}
2018-05-04 15:13:55 +02:00
#[get("/@/<name>/followers", rank = 1)]
pub fn ap_followers(
name: String,
2021-01-30 13:44:29 +01:00
conn: DbConn,
_ap: ApRequest,
) -> Option<ActivityStream<OrderedCollection>> {
2021-01-30 13:44:29 +01:00
let user = User::find_by_fqn(&conn, &name).ok()?;
let followers = user
2021-01-30 13:44:29 +01:00
.get_followers(&conn)
2019-03-20 17:56:17 +01:00
.ok()?
.into_iter()
.filter_map(|f| f.ap_url.parse::<IriString>().ok())
.collect::<Vec<IriString>>();
let mut coll = OrderedCollection::new();
coll.set_id(user.followers_endpoint.parse::<IriString>().ok()?);
coll.set_total_items(followers.len() as u64);
coll.set_many_items(followers);
Some(ActivityStream::new(coll))
2018-05-04 15:13:55 +02:00
}
2018-09-01 22:08:26 +02:00
#[get("/@/<name>/atom.xml")]
2021-01-30 13:44:29 +01:00
pub fn atom_feed(name: String, conn: DbConn) -> Option<Content<String>> {
let conn = &conn;
2021-11-27 23:53:13 +01:00
let author = User::find_by_fqn(conn, &name).ok()?;
let entries = Post::get_recents_for_author(conn, &author, 15).ok()?;
let uri = Instance::get_local()
.ok()?
.compute_box("@", &name, "atom.xml");
let title = &author.display_name;
let default_updated = &author.creation_date;
2021-11-27 23:53:13 +01:00
let feed = super::build_atom_feed(entries, &uri, title, default_updated, conn);
Some(Content(
ContentType::new("application", "atom+xml"),
feed.to_string(),
))
2018-09-01 22:08:26 +02:00
}