736 lines
20 KiB
Rust
736 lines
20 KiB
Rust
use crate::{
|
|
error::{Error, OptionExt},
|
|
extensions::ProfileExt,
|
|
middleware::{ProfileData, Referer, UserProfile},
|
|
nav::NavState,
|
|
views::OwnedProfileView,
|
|
ActixLoader, State,
|
|
};
|
|
use actix_session::Session;
|
|
use actix_web::{web, HttpRequest, HttpResponse, Scope};
|
|
use hyaenidae_accounts::User;
|
|
use hyaenidae_profiles::store::{File, Profile};
|
|
use hyaenidae_toolkit::TextInput;
|
|
use i18n_embed_fl::fl;
|
|
use std::collections::HashMap;
|
|
use uuid::Uuid;
|
|
|
|
mod settings;
|
|
mod state;
|
|
mod update;
|
|
|
|
pub(crate) use settings::{SettingStore, Settings};
|
|
pub use state::{EditProfileState, ViewProfileState};
|
|
pub use update::HandleState;
|
|
|
|
pub(super) fn scope() -> Scope {
|
|
web::scope("/profiles")
|
|
.service(web::resource("").route(web::get().to(to_current_profile)))
|
|
.service(web::resource("/current").route(web::get().to(current_profile)))
|
|
.service(
|
|
web::resource("/delete")
|
|
.route(web::get().to(delete_page))
|
|
.route(web::post().to(delete_profile)),
|
|
)
|
|
.service(
|
|
web::resource("/change")
|
|
.route(web::get().to(change_profile_page))
|
|
.route(web::post().to(change_profile)),
|
|
)
|
|
.service(update::create_scope())
|
|
.service(update::update_scope())
|
|
.route("/drafts", web::get().to(drafts_page))
|
|
.service(
|
|
web::scope("/@{handle}@{domain}")
|
|
.route("", web::get().to(handle_view))
|
|
.service(
|
|
web::resource("/follow")
|
|
.route(web::get().to(route_to_profile_page))
|
|
.route(web::post().to(follow)),
|
|
)
|
|
.service(
|
|
web::resource("/unfollow")
|
|
.route(web::get().to(route_to_profile_page))
|
|
.route(web::post().to(unfollow)),
|
|
)
|
|
.service(
|
|
web::resource("/block")
|
|
.route(web::get().to(route_to_profile_page))
|
|
.route(web::post().to(block)),
|
|
)
|
|
.service(
|
|
web::resource("/unblock")
|
|
.route(web::get().to(route_to_profile_page))
|
|
.route(web::post().to(unblock)),
|
|
)
|
|
.service(
|
|
web::resource("/report")
|
|
.route(web::get().to(report_page))
|
|
.route(web::post().to(report)),
|
|
)
|
|
.route("/report-success", web::get().to(report_success_page)),
|
|
)
|
|
.route("/id/{id}", web::get().to(id_view))
|
|
}
|
|
|
|
fn to_profile_page(handle: &str, domain: &str) -> HttpResponse {
|
|
crate::redirect(&format!("/profiles/@{}@{}", handle, domain))
|
|
}
|
|
|
|
async fn route_to_profile_page(handle: web::Path<(String, String)>) -> HttpResponse {
|
|
let (handle, domain) = handle.into_inner();
|
|
to_profile_page(&handle, &domain)
|
|
}
|
|
|
|
async fn follow(
|
|
referer: Option<Referer>,
|
|
handle: web::Path<(String, String)>,
|
|
self_profile: UserProfile,
|
|
state: web::Data<State>,
|
|
) -> Result<HttpResponse, Error> {
|
|
let self_profile = self_profile.into_inner();
|
|
use hyaenidae_profiles::apub::actions::CreateFollowRequest;
|
|
|
|
let (handle, domain) = handle.into_inner();
|
|
let profile = profile_from_handle(handle.clone(), domain.clone(), &state).await?;
|
|
|
|
let self_id = self_profile.id();
|
|
let profile_id = profile.id();
|
|
|
|
let blocks = state.profiles.store.view.blocks.clone();
|
|
let is_blocked = web::block(move || {
|
|
Ok(blocks.by_forward(self_id, profile_id)?.is_some()) as Result<bool, Error>
|
|
})
|
|
.await??;
|
|
if is_blocked {
|
|
return Ok(crate::to_404());
|
|
}
|
|
|
|
let follow_requests = state.profiles.store.view.follow_requests.clone();
|
|
let follows = state.profiles.store.view.follows.clone();
|
|
|
|
let follow_exists = web::block(move || {
|
|
let exists = follow_requests.by_forward(profile_id, self_id)?.is_some()
|
|
|| follows.by_forward(profile_id, self_id)?.is_some();
|
|
|
|
Ok(exists) as Result<bool, Error>
|
|
})
|
|
.await??;
|
|
|
|
if follow_exists {
|
|
return Ok(to_profile_page(&handle, &domain));
|
|
}
|
|
|
|
state
|
|
.profiles
|
|
.run(CreateFollowRequest::from_profiles(
|
|
profile.id(),
|
|
self_profile.id(),
|
|
))
|
|
.await?;
|
|
|
|
if let Some(referer) = referer {
|
|
Ok(crate::redirect(&referer.0))
|
|
} else {
|
|
Ok(to_profile_page(&handle, &domain))
|
|
}
|
|
}
|
|
|
|
async fn unfollow(
|
|
referer: Option<Referer>,
|
|
handle: web::Path<(String, String)>,
|
|
self_profile: UserProfile,
|
|
state: web::Data<State>,
|
|
) -> Result<HttpResponse, Error> {
|
|
let self_profile = self_profile.0;
|
|
use hyaenidae_profiles::apub::actions::{UndoFollow, UndoFollowRequest};
|
|
|
|
let (handle, domain) = handle.into_inner();
|
|
let profile = profile_from_handle(handle.clone(), domain.clone(), &state).await?;
|
|
|
|
let self_id = self_profile.id();
|
|
let profile_id = profile.id();
|
|
|
|
let blocks = state.profiles.store.view.blocks.clone();
|
|
let is_blocked = web::block(move || {
|
|
Ok(blocks.by_forward(self_id, profile_id)?.is_some()) as Result<bool, Error>
|
|
})
|
|
.await??;
|
|
if is_blocked {
|
|
return Ok(crate::to_404());
|
|
}
|
|
|
|
let follow_requests = state.profiles.store.view.follow_requests.clone();
|
|
let follows = state.profiles.store.view.follows.clone();
|
|
|
|
let (follow_request, follow) = web::block(move || {
|
|
let follow_request = follow_requests.by_forward(profile_id, self_id)?;
|
|
let follow = follows.by_forward(profile_id, self_id)?;
|
|
|
|
Ok((follow_request, follow)) as Result<_, Error>
|
|
})
|
|
.await??;
|
|
|
|
if let Some(follow_request) = follow_request {
|
|
state
|
|
.profiles
|
|
.run(UndoFollowRequest::from_id(follow_request))
|
|
.await?;
|
|
}
|
|
if let Some(follow) = follow {
|
|
state.profiles.run(UndoFollow::from_id(follow)).await?;
|
|
}
|
|
|
|
if let Some(referer) = referer {
|
|
Ok(crate::redirect(&referer.0))
|
|
} else {
|
|
Ok(to_profile_page(&handle, &domain))
|
|
}
|
|
}
|
|
|
|
async fn block(
|
|
handle: web::Path<(String, String)>,
|
|
self_profile: UserProfile,
|
|
state: web::Data<State>,
|
|
) -> Result<HttpResponse, Error> {
|
|
let self_profile = self_profile.0;
|
|
use hyaenidae_profiles::apub::actions::CreateBlock;
|
|
|
|
let (handle, domain) = handle.into_inner();
|
|
let profile = profile_from_handle(handle.clone(), domain.clone(), &state).await?;
|
|
|
|
let self_id = self_profile.id();
|
|
let profile_id = profile.id();
|
|
|
|
let blocks = state.profiles.store.view.blocks.clone();
|
|
let is_blocked = web::block(move || {
|
|
Ok(blocks.by_forward(self_id, profile_id)?.is_some()) as Result<bool, Error>
|
|
})
|
|
.await??;
|
|
if is_blocked {
|
|
return Ok(crate::to_404());
|
|
}
|
|
|
|
let blocks = state.profiles.store.view.blocks.clone();
|
|
let block_exists = web::block(move || {
|
|
Ok(blocks.by_forward(profile_id, self_id)?.is_some()) as Result<bool, Error>
|
|
})
|
|
.await??;
|
|
if block_exists {
|
|
return Ok(to_profile_page(&handle, &domain));
|
|
}
|
|
|
|
state
|
|
.profiles
|
|
.run(CreateBlock::from_profiles(profile.id(), self_profile.id()))
|
|
.await?;
|
|
|
|
Ok(to_profile_page(&handle, &domain))
|
|
}
|
|
|
|
async fn unblock(
|
|
handle: web::Path<(String, String)>,
|
|
self_profile: UserProfile,
|
|
state: web::Data<State>,
|
|
) -> Result<HttpResponse, Error> {
|
|
let self_profile = self_profile.0;
|
|
use hyaenidae_profiles::apub::actions::DeleteBlock;
|
|
|
|
let (handle, domain) = handle.into_inner();
|
|
let profile = profile_from_handle(handle.clone(), domain.clone(), &state).await?;
|
|
|
|
let self_id = self_profile.id();
|
|
let profile_id = profile.id();
|
|
|
|
let blocks = state.profiles.store.view.blocks.clone();
|
|
let block = web::block(move || blocks.by_forward(profile_id, self_id)).await??;
|
|
if let Some(block) = block {
|
|
state.profiles.run(DeleteBlock::from_id(block)).await?;
|
|
}
|
|
|
|
Ok(to_profile_page(&handle, &domain))
|
|
}
|
|
|
|
pub struct ReportView {
|
|
pub(crate) files: HashMap<Uuid, File>,
|
|
pub(crate) profile: Profile,
|
|
input_value: Option<String>,
|
|
input_error: Option<String>,
|
|
}
|
|
|
|
impl ReportView {
|
|
fn new(profile: Profile, files: HashMap<Uuid, File>) -> Self {
|
|
ReportView {
|
|
files,
|
|
profile,
|
|
input_value: None,
|
|
input_error: None,
|
|
}
|
|
}
|
|
|
|
pub(crate) fn input(&self, loader: &ActixLoader) -> TextInput {
|
|
let input = TextInput::new("body")
|
|
.title(&fl!(loader, "report-input"))
|
|
.placeholder(&fl!(loader, "report-placeholder"))
|
|
.textarea()
|
|
.error_opt(self.input_error.clone());
|
|
|
|
if let Some(value) = &self.input_value {
|
|
input.value(value)
|
|
} else {
|
|
input
|
|
}
|
|
}
|
|
|
|
pub(crate) fn profile(&self) -> OwnedProfileView {
|
|
OwnedProfileView {
|
|
profile: self.profile.clone(),
|
|
icon: self
|
|
.profile
|
|
.icon()
|
|
.and_then(|i| self.files.get(&i))
|
|
.map(|i| i.clone()),
|
|
banner: self
|
|
.profile
|
|
.banner()
|
|
.and_then(|b| self.files.get(&b))
|
|
.map(|b| b.clone()),
|
|
}
|
|
}
|
|
|
|
fn error_opt(mut self, error: Option<String>) -> Self {
|
|
self.input_error = error;
|
|
self
|
|
}
|
|
|
|
fn value(mut self, value: &str) -> Self {
|
|
self.input_value = Some(value.to_owned());
|
|
self
|
|
}
|
|
}
|
|
|
|
async fn profile_from_id(id: Uuid, state: &State) -> Result<Profile, Error> {
|
|
let store = state.profiles.clone();
|
|
|
|
let profile = web::block(move || store.store.profiles.by_id(id)?.req()).await??;
|
|
|
|
Ok(profile)
|
|
}
|
|
|
|
async fn profile_from_handle(
|
|
handle: String,
|
|
domain: String,
|
|
state: &State,
|
|
) -> Result<Profile, Error> {
|
|
let store = state.profiles.clone();
|
|
|
|
let profile = web::block(move || {
|
|
let id = store.store.profiles.by_handle(&handle, &domain)?.req()?;
|
|
let profile = store.store.profiles.by_id(id)?.req()?;
|
|
Ok(profile) as Result<_, Error>
|
|
})
|
|
.await??;
|
|
|
|
Ok(profile)
|
|
}
|
|
|
|
async fn get_files_for_profile(
|
|
profile: &Profile,
|
|
mut files: HashMap<Uuid, File>,
|
|
state: &State,
|
|
) -> Result<HashMap<Uuid, File>, Error> {
|
|
let mut file_ids = vec![];
|
|
file_ids.extend(profile.icon());
|
|
file_ids.extend(profile.banner());
|
|
|
|
let store = state.profiles.clone();
|
|
let files = web::block(move || {
|
|
for file_id in file_ids {
|
|
if !files.contains_key(&file_id) {
|
|
let file = store.store.files.by_id(file_id)?.req()?;
|
|
files.insert(file.id(), file);
|
|
}
|
|
}
|
|
|
|
Ok(files) as Result<_, Error>
|
|
})
|
|
.await??;
|
|
|
|
Ok(files)
|
|
}
|
|
|
|
async fn report_page(
|
|
loader: ActixLoader,
|
|
self_profile: UserProfile,
|
|
handle: web::Path<(String, String)>,
|
|
nav_state: NavState,
|
|
state: web::Data<State>,
|
|
) -> Result<HttpResponse, Error> {
|
|
let self_profile = self_profile.0;
|
|
let (handle, domain) = handle.into_inner();
|
|
let profile = profile_from_handle(handle, domain, &state).await?;
|
|
|
|
let blocks = state.profiles.store.view.blocks.clone();
|
|
let self_id = self_profile.id();
|
|
let profile_id = profile.id();
|
|
let is_blocked = web::block(move || {
|
|
Ok(blocks.by_forward(self_id, profile_id)?.is_some()) as Result<bool, Error>
|
|
})
|
|
.await??;
|
|
|
|
if is_blocked {
|
|
return Ok(crate::to_404());
|
|
}
|
|
|
|
let files = get_files_for_profile(&profile, HashMap::new(), &state).await?;
|
|
|
|
let view = ReportView::new(profile, files);
|
|
|
|
crate::rendered(HttpResponse::Ok(), |cursor| {
|
|
crate::templates::profiles::report(cursor, &loader, &view, &nav_state)
|
|
})
|
|
}
|
|
|
|
const MAX_REPORT_LEN: usize = 1000;
|
|
|
|
#[derive(Clone, Debug, serde::Deserialize)]
|
|
struct ReportForm {
|
|
body: String,
|
|
}
|
|
|
|
async fn report(
|
|
loader: ActixLoader,
|
|
self_profile: UserProfile,
|
|
handle: web::Path<(String, String)>,
|
|
form: web::Form<ReportForm>,
|
|
nav_state: NavState,
|
|
state: web::Data<State>,
|
|
) -> Result<HttpResponse, Error> {
|
|
let self_profile = self_profile.0;
|
|
use hyaenidae_profiles::apub::actions::CreateReport;
|
|
|
|
let (handle, domain) = handle.into_inner();
|
|
let profile = profile_from_handle(handle, domain, &state).await?;
|
|
|
|
let form = form.into_inner();
|
|
|
|
let error = if form.body.len() > MAX_REPORT_LEN {
|
|
format!("Must be shorter than {} characters", MAX_REPORT_LEN)
|
|
} else if form.body.trim().is_empty() {
|
|
format!("Must be present")
|
|
} else {
|
|
let res = state
|
|
.profiles
|
|
.run(CreateReport::from_profile(
|
|
profile.id(),
|
|
self_profile.id(),
|
|
Some(form.body.clone()),
|
|
))
|
|
.await;
|
|
|
|
match res {
|
|
Ok(_) => return Ok(to_report_success_page(profile.full_handle())),
|
|
Err(e) => e.to_string(),
|
|
}
|
|
};
|
|
|
|
let files = get_files_for_profile(&profile, HashMap::new(), &state).await?;
|
|
|
|
let view = ReportView::new(profile, files)
|
|
.error_opt(Some(error))
|
|
.value(&form.body);
|
|
|
|
crate::rendered(HttpResponse::BadRequest(), |cursor| {
|
|
crate::templates::profiles::report(cursor, &loader, &view, &nav_state)
|
|
})
|
|
}
|
|
|
|
async fn report_success_page(
|
|
loader: ActixLoader,
|
|
self_profile: UserProfile,
|
|
handle: web::Path<(String, String)>,
|
|
nav_state: NavState,
|
|
state: web::Data<State>,
|
|
) -> Result<HttpResponse, Error> {
|
|
let self_profile = self_profile.0;
|
|
let (handle, domain) = handle.into_inner();
|
|
let profile = profile_from_handle(handle, domain, &state).await?;
|
|
|
|
let blocks = state.profiles.store.view.blocks.clone();
|
|
let self_id = self_profile.id();
|
|
let profile_id = profile.id();
|
|
let is_blocked = web::block(move || {
|
|
Ok(blocks.by_forward(self_id, profile_id)?.is_some()) as Result<bool, Error>
|
|
})
|
|
.await??;
|
|
|
|
if is_blocked {
|
|
return Ok(crate::to_404());
|
|
}
|
|
|
|
let files = get_files_for_profile(&profile, HashMap::new(), &state).await?;
|
|
|
|
let view = ReportView::new(profile, files);
|
|
|
|
crate::rendered(HttpResponse::Ok(), |cursor| {
|
|
crate::templates::profiles::report_success(cursor, &loader, &view, &nav_state)
|
|
})
|
|
}
|
|
|
|
fn to_report_success_page(full_handle: String) -> HttpResponse {
|
|
redirect(&format!("/profiles/{}/report-success", full_handle))
|
|
}
|
|
|
|
fn to_current_profile() -> HttpResponse {
|
|
redirect("/profiles/current")
|
|
}
|
|
|
|
pub(super) fn to_change_profile_page() -> HttpResponse {
|
|
redirect("/profiles/change")
|
|
}
|
|
|
|
fn redirect(path: &str) -> HttpResponse {
|
|
HttpResponse::SeeOther()
|
|
.insert_header(("Location", path))
|
|
.finish()
|
|
}
|
|
|
|
#[derive(Clone, Debug, serde::Deserialize)]
|
|
#[serde(untagged)]
|
|
pub(crate) enum SubmissionPage {
|
|
Max { max: Uuid },
|
|
Min { min: Uuid },
|
|
}
|
|
|
|
impl From<SubmissionPage> for crate::pagination::PageSource {
|
|
fn from(s: SubmissionPage) -> Self {
|
|
match s {
|
|
SubmissionPage::Max { max } => Self::OlderThan(max),
|
|
SubmissionPage::Min { min } => Self::NewerThan(min),
|
|
}
|
|
}
|
|
}
|
|
|
|
async fn id_view(id: web::Path<Uuid>, state: web::Data<State>) -> Result<HttpResponse, Error> {
|
|
let profile = profile_from_id(id.into_inner(), &state).await?;
|
|
|
|
Ok(crate::redirect(&profile.view_path()))
|
|
}
|
|
|
|
async fn handle_view(
|
|
loader: ActixLoader,
|
|
req: HttpRequest,
|
|
user: Option<User>,
|
|
self_profile: Option<UserProfile>,
|
|
handle: web::Path<(String, String)>,
|
|
page: Option<web::Query<SubmissionPage>>,
|
|
nav_state: NavState,
|
|
state: web::Data<State>,
|
|
) -> Result<HttpResponse, Error> {
|
|
let (handle, domain) = handle.into_inner();
|
|
let profile = profile_from_handle(handle, domain, &state).await?;
|
|
if profile.is_suspended() {
|
|
return Ok(crate::to_404());
|
|
}
|
|
|
|
do_public_view(
|
|
loader,
|
|
req.uri().path(),
|
|
user,
|
|
self_profile.map(|p| p.into_inner()),
|
|
profile,
|
|
page.map(|q| q.into_inner()),
|
|
nav_state,
|
|
&state,
|
|
)
|
|
.await
|
|
}
|
|
|
|
async fn drafts_page(
|
|
loader: ActixLoader,
|
|
req: HttpRequest,
|
|
profile: UserProfile,
|
|
page: Option<web::Query<SubmissionPage>>,
|
|
nav_state: NavState,
|
|
state: web::Data<State>,
|
|
) -> Result<HttpResponse, Error> {
|
|
let profile = profile.into_inner();
|
|
let viewed_by = Some(profile.id());
|
|
|
|
let view = ViewProfileState::for_profile(
|
|
req.path().to_owned(),
|
|
profile,
|
|
viewed_by,
|
|
true,
|
|
page.map(|p| p.into_inner().into()),
|
|
&state,
|
|
)
|
|
.await?;
|
|
|
|
crate::rendered(HttpResponse::Ok(), |cursor| {
|
|
crate::templates::profiles::drafts(cursor, &loader, &view, &nav_state)
|
|
})
|
|
}
|
|
|
|
async fn do_public_view(
|
|
loader: ActixLoader,
|
|
base_url: &str,
|
|
user: Option<User>,
|
|
self_profile: Option<Profile>,
|
|
profile: Profile,
|
|
page: Option<SubmissionPage>,
|
|
nav_state: NavState,
|
|
state: &State,
|
|
) -> Result<HttpResponse, Error> {
|
|
if profile.login_required() && user.is_none() {
|
|
return Ok(crate::to_404());
|
|
}
|
|
|
|
let is_blocked = if let Some(self_profile) = &self_profile {
|
|
let store = state.profiles.clone();
|
|
let self_id = self_profile.id();
|
|
let profile_id = profile.id();
|
|
|
|
web::block(move || {
|
|
Ok(store
|
|
.store
|
|
.view
|
|
.blocks
|
|
.by_forward(self_id, profile_id)?
|
|
.is_some()) as Result<bool, Error>
|
|
})
|
|
.await??
|
|
} else {
|
|
false
|
|
};
|
|
|
|
if is_blocked {
|
|
return Ok(crate::to_404());
|
|
}
|
|
|
|
let viewed_by = self_profile.as_ref().map(|p| p.id());
|
|
|
|
let view = ViewProfileState::for_profile(
|
|
base_url.to_owned(),
|
|
profile,
|
|
viewed_by,
|
|
false,
|
|
page.map(|p| p.into()),
|
|
state,
|
|
)
|
|
.await?;
|
|
|
|
crate::rendered(HttpResponse::Ok(), |cursor| {
|
|
crate::templates::profiles::public(cursor, &loader, &view, &nav_state)
|
|
})
|
|
}
|
|
|
|
async fn delete_page(
|
|
loader: ActixLoader,
|
|
profile: UserProfile,
|
|
nav_state: NavState,
|
|
state: web::Data<State>,
|
|
) -> Result<HttpResponse, Error> {
|
|
let profile = profile.0;
|
|
|
|
let files = get_files_for_profile(&profile, HashMap::new(), &state).await?;
|
|
|
|
let view = OwnedProfileView {
|
|
banner: profile
|
|
.banner()
|
|
.and_then(|b| files.get(&b))
|
|
.map(|b| b.clone()),
|
|
icon: profile
|
|
.icon()
|
|
.and_then(|i| files.get(&i))
|
|
.map(|i| i.clone()),
|
|
profile,
|
|
};
|
|
|
|
crate::rendered(HttpResponse::Ok(), |cursor| {
|
|
crate::templates::profiles::delete(cursor, &loader, &view, &nav_state)
|
|
})
|
|
}
|
|
|
|
async fn delete_profile(
|
|
profile: UserProfile,
|
|
state: web::Data<State>,
|
|
) -> Result<HttpResponse, Error> {
|
|
use hyaenidae_profiles::apub::actions::DeleteProfile;
|
|
|
|
state
|
|
.profiles
|
|
.run(DeleteProfile::from_id(profile.0.id()))
|
|
.await?;
|
|
|
|
Ok(to_change_profile_page())
|
|
}
|
|
|
|
async fn current_profile(
|
|
loader: ActixLoader,
|
|
profile: UserProfile,
|
|
nav_state: NavState,
|
|
state: web::Data<State>,
|
|
) -> Result<HttpResponse, Error> {
|
|
let profile = profile.into_inner();
|
|
|
|
let profile_state = EditProfileState::for_profile(profile, &state).await?;
|
|
|
|
crate::rendered(HttpResponse::Ok(), |cursor| {
|
|
crate::templates::profiles::current(cursor, &loader, &profile_state, &nav_state)
|
|
})
|
|
}
|
|
|
|
#[derive(Clone, Debug, serde::Deserialize)]
|
|
struct ChangeProfileForm {
|
|
profile_id: Uuid,
|
|
}
|
|
|
|
async fn change_profile_page(
|
|
loader: ActixLoader,
|
|
user: User,
|
|
nav_state: NavState,
|
|
state: web::Data<State>,
|
|
) -> Result<HttpResponse, Error> {
|
|
let store = state.profiles.clone();
|
|
|
|
let profiles = web::block(move || {
|
|
let profiles = store
|
|
.store
|
|
.profiles
|
|
.for_local(user.id())
|
|
.filter_map(|profile_id| OwnedProfileView::from_id(profile_id, &store.store).ok())
|
|
.filter(|view| !view.profile.is_suspended())
|
|
.collect::<Vec<_>>();
|
|
|
|
Ok(profiles) as Result<Vec<_>, Error>
|
|
})
|
|
.await??;
|
|
|
|
if profiles.len() == 0 {
|
|
return Ok(update::to_create());
|
|
}
|
|
|
|
crate::rendered(HttpResponse::Ok(), |cursor| {
|
|
crate::templates::profiles::list(cursor, &loader, &profiles, &nav_state)
|
|
})
|
|
}
|
|
|
|
async fn change_profile(
|
|
user: User,
|
|
session: Session,
|
|
form: web::Form<ChangeProfileForm>,
|
|
state: web::Data<State>,
|
|
) -> Result<HttpResponse, Error> {
|
|
let profile = profile_from_id(form.profile_id, &state).await?;
|
|
if profile.local_owner() != Some(user.id()) {
|
|
return Ok(to_change_profile_page());
|
|
}
|
|
|
|
ProfileData::set_data(form.profile_id, &session)
|
|
.ok()
|
|
.req()?;
|
|
|
|
Ok(crate::to_home())
|
|
}
|