|
|
|
@ -9,7 +9,6 @@ use rocket::{
|
|
|
|
|
response::{status, Content, Flash, Redirect},
|
|
|
|
|
};
|
|
|
|
|
use rocket_i18n::I18n;
|
|
|
|
|
use serde_json;
|
|
|
|
|
use std::{borrow::Cow, collections::HashMap};
|
|
|
|
|
use validator::{Validate, ValidationError, ValidationErrors};
|
|
|
|
|
|
|
|
|
@ -42,7 +41,7 @@ pub fn me(user: Option<User>) -> RespondOrRedirect {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[get("/@/<name>", rank = 2)]
|
|
|
|
|
pub fn details(
|
|
|
|
|
pub async fn details(
|
|
|
|
|
name: String,
|
|
|
|
|
rockets: PlumeRocket,
|
|
|
|
|
fetch_rockets: PlumeRocket,
|
|
|
|
@ -50,7 +49,7 @@ pub fn details(
|
|
|
|
|
update_conn: DbConn,
|
|
|
|
|
) -> Result<Ructe, ErrorPage> {
|
|
|
|
|
let conn = &*rockets.conn;
|
|
|
|
|
let user = User::find_by_fqn(&rockets, &name)?;
|
|
|
|
|
let user = User::find_by_fqn(&rockets, &name).await?;
|
|
|
|
|
let recents = Post::get_recents_for_author(&*conn, &user, 6)?;
|
|
|
|
|
let reshares = Reshare::get_recents_for_author(&*conn, &user, 6)?;
|
|
|
|
|
let worker = &rockets.worker;
|
|
|
|
@ -61,6 +60,7 @@ pub fn details(
|
|
|
|
|
worker.execute(move || {
|
|
|
|
|
for create_act in user_clone
|
|
|
|
|
.fetch_outbox::<Create>()
|
|
|
|
|
.await
|
|
|
|
|
.expect("Remote user: outbox couldn't be fetched")
|
|
|
|
|
{
|
|
|
|
|
match create_act.create_props.object_object::<LicensedArticle>() {
|
|
|
|
@ -79,6 +79,7 @@ pub fn details(
|
|
|
|
|
worker.execute(move || {
|
|
|
|
|
for user_id in user_clone
|
|
|
|
|
.fetch_followers_ids()
|
|
|
|
|
.await
|
|
|
|
|
.expect("Remote user: fetching followers error")
|
|
|
|
|
{
|
|
|
|
|
let follower = User::from_id(&fetch_followers_rockets, &user_id, None)
|
|
|
|
@ -101,6 +102,7 @@ pub fn details(
|
|
|
|
|
worker.execute(move || {
|
|
|
|
|
user_clone
|
|
|
|
|
.refetch(&*update_conn)
|
|
|
|
|
.await
|
|
|
|
|
.expect("Couldn't update user info");
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
@ -146,13 +148,13 @@ pub fn dashboard_auth(i18n: I18n) -> Flash<Redirect> {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[post("/@/<name>/follow")]
|
|
|
|
|
pub fn follow(
|
|
|
|
|
pub async fn follow(
|
|
|
|
|
name: String,
|
|
|
|
|
user: User,
|
|
|
|
|
rockets: PlumeRocket,
|
|
|
|
|
) -> Result<Flash<Redirect>, ErrorPage> {
|
|
|
|
|
let conn = &*rockets.conn;
|
|
|
|
|
let target = User::find_by_fqn(&rockets, &name)?;
|
|
|
|
|
let target = User::find_by_fqn(&rockets, &name).await?;
|
|
|
|
|
let message = if let Ok(follow) = follows::Follow::find(&*conn, user.id, target.id) {
|
|
|
|
|
let delete_act = follow.build_undo(&*conn)?;
|
|
|
|
|
local_inbox(
|
|
|
|
@ -190,26 +192,26 @@ pub fn follow(
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[post("/@/<name>/follow", data = "<remote_form>", rank = 2)]
|
|
|
|
|
pub fn follow_not_connected(
|
|
|
|
|
pub async fn follow_not_connected(
|
|
|
|
|
rockets: PlumeRocket,
|
|
|
|
|
name: String,
|
|
|
|
|
remote_form: Option<LenientForm<RemoteForm>>,
|
|
|
|
|
i18n: I18n,
|
|
|
|
|
) -> Result<RespondOrRedirect, ErrorPage> {
|
|
|
|
|
let target = User::find_by_fqn(&rockets, &name)?;
|
|
|
|
|
let target = User::find_by_fqn(&rockets, &name).await?;
|
|
|
|
|
if let Some(remote_form) = remote_form {
|
|
|
|
|
if let Some(uri) = User::fetch_remote_interact_uri(&remote_form)
|
|
|
|
|
.await
|
|
|
|
|
.ok()
|
|
|
|
|
.and_then(|uri| {
|
|
|
|
|
rt_format!(
|
|
|
|
|
uri,
|
|
|
|
|
uri = format!(
|
|
|
|
|
Some(uri.replace(
|
|
|
|
|
"{uri}",
|
|
|
|
|
&format!(
|
|
|
|
|
"{}@{}",
|
|
|
|
|
target.fqn,
|
|
|
|
|
target.get_instance(&rockets.conn).ok()?.public_domain
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
.ok()
|
|
|
|
|
),
|
|
|
|
|
))
|
|
|
|
|
})
|
|
|
|
|
{
|
|
|
|
|
Ok(Redirect::to(uri).into())
|
|
|
|
@ -266,15 +268,18 @@ pub fn follow_auth(name: String, i18n: I18n) -> Flash<Redirect> {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[get("/@/<name>/followers?<page>", rank = 2)]
|
|
|
|
|
pub fn followers(
|
|
|
|
|
pub async fn followers(
|
|
|
|
|
name: String,
|
|
|
|
|
page: Option<Page>,
|
|
|
|
|
rockets: PlumeRocket,
|
|
|
|
|
) -> Result<Ructe, ErrorPage> {
|
|
|
|
|
let conn = &*rockets.conn;
|
|
|
|
|
let page = page.unwrap_or_default();
|
|
|
|
|
let user = User::find_by_fqn(&rockets, &name)?;
|
|
|
|
|
let followers_count = user.count_followers(&*conn)?;
|
|
|
|
|
let user: User = User::find_by_fqn(&rockets, &name).await?;
|
|
|
|
|
let followers_count = match user.count_followers(&conn) {
|
|
|
|
|
Ok(num) => num,
|
|
|
|
|
Err(_) => 0,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Ok(render!(users::followers(
|
|
|
|
|
&rockets.to_context(),
|
|
|
|
@ -293,14 +298,14 @@ pub fn followers(
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[get("/@/<name>/followed?<page>", rank = 2)]
|
|
|
|
|
pub fn followed(
|
|
|
|
|
pub async fn followed(
|
|
|
|
|
name: String,
|
|
|
|
|
page: Option<Page>,
|
|
|
|
|
rockets: PlumeRocket,
|
|
|
|
|
) -> Result<Ructe, ErrorPage> {
|
|
|
|
|
let conn = &*rockets.conn;
|
|
|
|
|
let page = page.unwrap_or_default();
|
|
|
|
|
let user = User::find_by_fqn(&rockets, &name)?;
|
|
|
|
|
let user = User::find_by_fqn(&rockets, &name).await?;
|
|
|
|
|
let followed_count = user.count_followed(conn)?;
|
|
|
|
|
|
|
|
|
|
Ok(render!(users::followed(
|
|
|
|
@ -320,12 +325,12 @@ pub fn followed(
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[get("/@/<name>", rank = 1)]
|
|
|
|
|
pub fn activity_details(
|
|
|
|
|
pub async fn activity_details(
|
|
|
|
|
name: String,
|
|
|
|
|
rockets: PlumeRocket,
|
|
|
|
|
_ap: ApRequest,
|
|
|
|
|
) -> Option<ActivityStream<CustomPerson>> {
|
|
|
|
|
let user = User::find_by_fqn(&rockets, &name).ok()?;
|
|
|
|
|
let user: User = User::find_by_fqn(&rockets, &name).await?;
|
|
|
|
|
Some(ActivityStream::new(user.to_activity(&*rockets.conn).ok()?))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -412,50 +417,49 @@ pub fn update(
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[post("/@/<name>/delete")]
|
|
|
|
|
pub fn delete(
|
|
|
|
|
pub async fn delete(
|
|
|
|
|
name: String,
|
|
|
|
|
user: User,
|
|
|
|
|
mut cookies: Cookies<'_>,
|
|
|
|
|
rockets: PlumeRocket,
|
|
|
|
|
) -> Result<Flash<Redirect>, ErrorPage> {
|
|
|
|
|
let account = User::find_by_fqn(&rockets, &name)?;
|
|
|
|
|
if user.id == account.id {
|
|
|
|
|
account.delete(&*rockets.conn, &rockets.searcher)?;
|
|
|
|
|
|
|
|
|
|
let target = User::one_by_instance(&*rockets.conn)?;
|
|
|
|
|
let delete_act = account.delete_activity(&*rockets.conn)?;
|
|
|
|
|
rockets
|
|
|
|
|
.worker
|
|
|
|
|
.execute(move || broadcast(&account, delete_act, target));
|
|
|
|
|
|
|
|
|
|
if let Some(cookie) = cookies.get_private(AUTH_COOKIE) {
|
|
|
|
|
cookies.remove_private(cookie);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(Flash::success(
|
|
|
|
|
Redirect::to(uri!(super::instance::index)),
|
|
|
|
|
i18n!(rockets.intl.catalog, "Your account has been deleted."),
|
|
|
|
|
))
|
|
|
|
|
} else {
|
|
|
|
|
Ok(Flash::error(
|
|
|
|
|
let account = User::find_by_fqn(&rockets, &name).await?;
|
|
|
|
|
if user.id != account.id {
|
|
|
|
|
return Ok(Flash::error(
|
|
|
|
|
Redirect::to(uri!(edit: name = name)),
|
|
|
|
|
i18n!(
|
|
|
|
|
rockets.intl.catalog,
|
|
|
|
|
"You can't delete someone else's account."
|
|
|
|
|
),
|
|
|
|
|
))
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
account.delete(&*rockets.conn, &rockets.searcher)?;
|
|
|
|
|
|
|
|
|
|
let target = User::one_by_instance(&*rockets.conn)?;
|
|
|
|
|
let delete_act = account.delete_activity(&*rockets.conn)?;
|
|
|
|
|
rockets
|
|
|
|
|
.worker
|
|
|
|
|
.execute(move || broadcast(&account, delete_act, target));
|
|
|
|
|
|
|
|
|
|
if let Some(cookie) = cookies.get_private(AUTH_COOKIE) {
|
|
|
|
|
cookies.remove_private(cookie);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(Flash::success(
|
|
|
|
|
Redirect::to(uri!(super::instance::index)),
|
|
|
|
|
i18n!(rockets.intl.catalog, "Your account has been deleted."),
|
|
|
|
|
))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Default, FromForm, Validate)]
|
|
|
|
|
#[validate(schema(
|
|
|
|
|
function = "passwords_match",
|
|
|
|
|
skip_on_field_errors = "false",
|
|
|
|
|
skip_on_field_errors = false,
|
|
|
|
|
message = "Passwords are not matching"
|
|
|
|
|
))]
|
|
|
|
|
pub struct NewUserForm {
|
|
|
|
|
#[validate(
|
|
|
|
|
length(min = "1", message = "Username can't be empty"),
|
|
|
|
|
length(min = 1, message = "Username can't be empty"),
|
|
|
|
|
custom(
|
|
|
|
|
function = "validate_username",
|
|
|
|
|
message = "User name is not allowed to contain any of < > & @ ' or \""
|
|
|
|
@ -464,9 +468,9 @@ pub struct NewUserForm {
|
|
|
|
|
pub username: String,
|
|
|
|
|
#[validate(email(message = "Invalid email"))]
|
|
|
|
|
pub email: String,
|
|
|
|
|
#[validate(length(min = "8", message = "Password should be at least 8 characters long"))]
|
|
|
|
|
#[validate(length(min = 8, message = "Password should be at least 8 characters long"))]
|
|
|
|
|
pub password: String,
|
|
|
|
|
#[validate(length(min = "8", message = "Password should be at least 8 characters long"))]
|
|
|
|
|
#[validate(length(min = 8, message = "Password should be at least 8 characters long"))]
|
|
|
|
|
pub password_confirmation: String,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -565,37 +569,44 @@ pub fn create(
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[get("/@/<name>/outbox")]
|
|
|
|
|
pub fn outbox(name: String, rockets: PlumeRocket) -> Option<ActivityStream<OrderedCollection>> {
|
|
|
|
|
let user = User::find_by_fqn(&rockets, &name).ok()?;
|
|
|
|
|
pub async fn outbox(
|
|
|
|
|
name: String,
|
|
|
|
|
rockets: PlumeRocket,
|
|
|
|
|
) -> Option<ActivityStream<OrderedCollection>> {
|
|
|
|
|
let user = User::find_by_fqn(&rockets, &name).await.ok()?;
|
|
|
|
|
user.outbox(&*rockets.conn).ok()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[get("/@/<name>/outbox?<page>")]
|
|
|
|
|
pub fn outbox_page(
|
|
|
|
|
pub async fn outbox_page(
|
|
|
|
|
name: String,
|
|
|
|
|
page: Page,
|
|
|
|
|
rockets: PlumeRocket,
|
|
|
|
|
) -> Option<ActivityStream<OrderedCollectionPage>> {
|
|
|
|
|
let user = User::find_by_fqn(&rockets, &name).ok()?;
|
|
|
|
|
let user = User::find_by_fqn(&rockets, &name).await.ok()?;
|
|
|
|
|
user.outbox_page(&*rockets.conn, page.limits()).ok()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[post("/@/<name>/inbox", data = "<data>")]
|
|
|
|
|
pub fn inbox(
|
|
|
|
|
pub async fn inbox(
|
|
|
|
|
name: String,
|
|
|
|
|
data: inbox::SignedJson<serde_json::Value>,
|
|
|
|
|
headers: Headers<'_>,
|
|
|
|
|
rockets: PlumeRocket,
|
|
|
|
|
) -> Result<String, status::BadRequest<&'static str>> {
|
|
|
|
|
User::find_by_fqn(&rockets, &name).map_err(|_| status::BadRequest(Some("User not found")))?;
|
|
|
|
|
inbox::handle_incoming(rockets, data, headers)
|
|
|
|
|
User::find_by_fqn(&rockets, &name)
|
|
|
|
|
.await
|
|
|
|
|
.map_err(|_| status::BadRequest(Some("User not found")))?;
|
|
|
|
|
inbox::handle_incoming(rockets, data, headers).await
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[get("/@/<name>/followers", rank = 1)]
|
|
|
|
|
pub fn ap_followers(
|
|
|
|
|
pub async fn ap_followers(
|
|
|
|
|
name: String,
|
|
|
|
|
rockets: PlumeRocket,
|
|
|
|
|
_ap: ApRequest,
|
|
|
|
|
) -> Option<ActivityStream<OrderedCollection>> {
|
|
|
|
|
let user = User::find_by_fqn(&rockets, &name).ok()?;
|
|
|
|
|
let user = User::find_by_fqn(&rockets, &name).await?;
|
|
|
|
|
let followers = user
|
|
|
|
|
.get_followers(&*rockets.conn)
|
|
|
|
|
.ok()?
|
|
|
|
@ -615,9 +626,9 @@ pub fn ap_followers(
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[get("/@/<name>/atom.xml")]
|
|
|
|
|
pub fn atom_feed(name: String, rockets: PlumeRocket) -> Option<Content<String>> {
|
|
|
|
|
pub async fn atom_feed(name: String, rockets: PlumeRocket) -> Option<Content<String>> {
|
|
|
|
|
let conn = &*rockets.conn;
|
|
|
|
|
let author = User::find_by_fqn(&rockets, &name).ok()?;
|
|
|
|
|
let author = User::find_by_fqn(&rockets, &name).await?;
|
|
|
|
|
let entries = Post::get_recents_for_author(conn, &author, 15).ok()?;
|
|
|
|
|
let uri = Instance::get_local()
|
|
|
|
|
.ok()?
|
|
|
|
|