657 lines
18 KiB
Rust
657 lines
18 KiB
Rust
use crate::{
|
|
error::{Error, OptionExt, ResultExt, StateError},
|
|
State,
|
|
};
|
|
use actix_session::Session;
|
|
use actix_web::{dev::Payload, web, HttpRequest, HttpResponse, Scope};
|
|
use hyaenidae_accounts::Authenticated;
|
|
use uuid::Uuid;
|
|
|
|
pub(super) fn scope() -> Scope {
|
|
web::scope("/profiles")
|
|
.service(
|
|
web::resource("")
|
|
.route(web::get().to(profile))
|
|
.route(web::post().to(update_profile)),
|
|
)
|
|
.service(
|
|
web::resource("/change")
|
|
.route(web::get().to(change_profile_page))
|
|
.route(web::post().to(change_profile)),
|
|
)
|
|
.service(
|
|
web::scope("/create")
|
|
.service(
|
|
web::resource("/handle")
|
|
.route(web::get().to(new_handle))
|
|
.route(web::post().to(create_handle)),
|
|
)
|
|
.service(
|
|
web::resource("/bio")
|
|
.route(web::get().to(new_bio))
|
|
.route(web::post().to(create_bio)),
|
|
)
|
|
.service(
|
|
web::resource("/icon")
|
|
.route(web::get().to(new_icon))
|
|
.route(web::post().to(create_icon)),
|
|
)
|
|
.service(
|
|
web::resource("/banner")
|
|
.route(web::get().to(new_banner))
|
|
.route(web::post().to(create_banner)),
|
|
)
|
|
.service(
|
|
web::resource("/require-login")
|
|
.route(web::get().to(new_require_login))
|
|
.route(web::post().to(create_require_login)),
|
|
)
|
|
.service(web::resource("/done").route(web::get().to(done))),
|
|
)
|
|
}
|
|
|
|
fn to_create() -> HttpResponse {
|
|
redirect("/profiles/create/handle")
|
|
}
|
|
|
|
fn to_bio() -> HttpResponse {
|
|
redirect("/profiles/create/bio")
|
|
}
|
|
|
|
fn to_icon() -> HttpResponse {
|
|
redirect("/profiles/create/icon")
|
|
}
|
|
|
|
fn to_banner() -> HttpResponse {
|
|
redirect("/profiles/create/banner")
|
|
}
|
|
|
|
fn to_require_login() -> HttpResponse {
|
|
redirect("/profiles/create/require-login")
|
|
}
|
|
|
|
fn to_done() -> HttpResponse {
|
|
redirect("/profiles/create/done")
|
|
}
|
|
|
|
fn redirect(path: &str) -> HttpResponse {
|
|
HttpResponse::SeeOther().header("Location", path).finish()
|
|
}
|
|
|
|
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
|
struct CurrentProfile {
|
|
id: Uuid,
|
|
}
|
|
|
|
pub struct Profile {
|
|
inner: hyaenidae_profiles::store::Profile,
|
|
banner: Option<String>,
|
|
icon: Option<String>,
|
|
}
|
|
|
|
impl Profile {
|
|
fn from_id(profile_id: Uuid, state: &State) -> Result<Self, Error> {
|
|
let inner = state.profiles.store.profiles.by_id(profile_id)?.req()?;
|
|
let banner = match inner.banner() {
|
|
Some(banner_id) => {
|
|
let file = state.profiles.store.files.by_id(banner_id)?.req()?;
|
|
let hyaenidae_profiles::store::FileSource::PictRs(file) = file.source();
|
|
Some(file.key().to_owned())
|
|
}
|
|
None => None,
|
|
};
|
|
let icon = match inner.icon() {
|
|
Some(icon_id) => {
|
|
let file = state.profiles.store.files.by_id(icon_id)?.req()?;
|
|
let hyaenidae_profiles::store::FileSource::PictRs(file) = file.source();
|
|
Some(file.key().to_owned())
|
|
}
|
|
None => None,
|
|
};
|
|
|
|
Ok(Profile {
|
|
inner,
|
|
banner,
|
|
icon,
|
|
})
|
|
}
|
|
|
|
pub(crate) fn full_handle(&self) -> String {
|
|
format!("{}@{}", self.inner.handle(), self.inner.domain())
|
|
}
|
|
|
|
pub(crate) fn display_name(&self) -> Option<&str> {
|
|
self.inner.display_name()
|
|
}
|
|
|
|
pub(crate) fn description(&self) -> Option<&str> {
|
|
self.inner.description()
|
|
}
|
|
|
|
pub(crate) fn name(&self) -> &str {
|
|
self.inner.display_name().unwrap_or(self.inner.handle())
|
|
}
|
|
|
|
pub(crate) fn icon_key(&self) -> Option<&str> {
|
|
self.icon.as_deref()
|
|
}
|
|
|
|
pub(crate) fn banner_key(&self) -> Option<&str> {
|
|
self.banner.as_deref()
|
|
}
|
|
}
|
|
|
|
async fn profile(
|
|
_: Authenticated,
|
|
session: Session,
|
|
state: web::Data<State>,
|
|
) -> Result<HttpResponse, StateError> {
|
|
do_profile(session, &state).await.state(&state)
|
|
}
|
|
|
|
async fn do_profile(session: Session, state: &State) -> Result<HttpResponse, Error> {
|
|
let profile_id = match session
|
|
.get::<CurrentProfile>("current-profile")
|
|
.ok()
|
|
.req()?
|
|
{
|
|
Some(id) => id.id,
|
|
None => return Ok(to_create()),
|
|
};
|
|
|
|
let profile = match state.profiles.store.profiles.by_id(profile_id)? {
|
|
Some(profile) => profile,
|
|
None => return Ok(to_create()),
|
|
};
|
|
|
|
Ok(HttpResponse::Ok().finish())
|
|
}
|
|
|
|
async fn update_profile(_: Authenticated) -> &'static str {
|
|
"Hewwo, Mr Obama"
|
|
}
|
|
|
|
async fn change_profile(_: Authenticated) -> &'static str {
|
|
"Hewwo, Mr Obama"
|
|
}
|
|
|
|
async fn change_profile_page(_: Authenticated) -> &'static str {
|
|
"Hewwo, Mr Obama"
|
|
}
|
|
|
|
#[derive(Clone, Debug, serde::Deserialize)]
|
|
pub struct HandleForm {
|
|
pub(crate) handle: String,
|
|
}
|
|
|
|
async fn new_handle(_: Authenticated, state: web::Data<State>) -> Result<HttpResponse, StateError> {
|
|
let mut handle_input = hyaenidae_toolkit::TextInput::new("handle");
|
|
handle_input.placeholder("Handle").title("Handle");
|
|
|
|
crate::rendered(HttpResponse::Ok(), |cursor| {
|
|
crate::templates::profiles::create::handle(cursor, &handle_input)
|
|
})
|
|
.state(&state)
|
|
}
|
|
|
|
async fn create_handle(
|
|
auth: Authenticated,
|
|
form: web::Form<HandleForm>,
|
|
session: Session,
|
|
state: web::Data<State>,
|
|
) -> Result<HttpResponse, StateError> {
|
|
do_create_handle(auth, form.into_inner(), session, &state)
|
|
.await
|
|
.state(&state)
|
|
}
|
|
|
|
async fn do_create_handle(
|
|
auth: Authenticated,
|
|
form: HandleForm,
|
|
session: Session,
|
|
state: &State,
|
|
) -> Result<HttpResponse, Error> {
|
|
let user_id = auth.user().id();
|
|
let domain = state.domain.clone();
|
|
let handle = form.handle.clone();
|
|
|
|
let exists = state
|
|
.profiles
|
|
.store
|
|
.profiles
|
|
.by_handle(&handle, &domain)?
|
|
.is_some();
|
|
|
|
let error = if !exists {
|
|
use hyaenidae_profiles::apub::actions::CreateProfile;
|
|
|
|
let res = state
|
|
.profiles
|
|
.run(&CreateProfile::from_local(user_id, handle, domain))
|
|
.await;
|
|
|
|
match res {
|
|
Ok(Some(id)) => {
|
|
session
|
|
.set("current-profile", CurrentProfile { id })
|
|
.ok()
|
|
.req()?;
|
|
return Ok(to_bio());
|
|
}
|
|
Ok(None) => None,
|
|
Err(e) => Some(e.to_string()),
|
|
}
|
|
} else {
|
|
Some("Handle already in use".to_owned())
|
|
};
|
|
|
|
let mut handle_input = hyaenidae_toolkit::TextInput::new("handle");
|
|
handle_input
|
|
.placeholder("Handle")
|
|
.title("Handle")
|
|
.value(&form.handle)
|
|
.error_opt(error);
|
|
|
|
crate::rendered(HttpResponse::BadRequest(), |cursor| {
|
|
crate::templates::profiles::create::handle(cursor, &handle_input)
|
|
})
|
|
}
|
|
|
|
#[derive(Clone, Debug, serde::Deserialize)]
|
|
pub struct BioForm {
|
|
pub(crate) display_name: String,
|
|
pub(crate) description: String,
|
|
}
|
|
|
|
async fn new_bio(
|
|
_: Authenticated,
|
|
session: Session,
|
|
state: web::Data<State>,
|
|
) -> Result<HttpResponse, StateError> {
|
|
do_new_bio(session, &state).await.state(&state)
|
|
}
|
|
|
|
async fn do_new_bio(session: Session, state: &State) -> Result<HttpResponse, Error> {
|
|
let profile_id = match session
|
|
.get::<CurrentProfile>("current-profile")
|
|
.ok()
|
|
.req()?
|
|
{
|
|
Some(id) => id.id,
|
|
None => return Ok(to_create()),
|
|
};
|
|
|
|
let profile = Profile::from_id(profile_id, state)?;
|
|
|
|
let mut display_name = hyaenidae_toolkit::TextInput::new("display_name");
|
|
display_name
|
|
.title("Display Name")
|
|
.placeholder("Display Name");
|
|
if let Some(text) = profile.display_name() {
|
|
display_name.value(text);
|
|
}
|
|
|
|
let mut description = hyaenidae_toolkit::TextInput::new("description");
|
|
description
|
|
.title("Description")
|
|
.placeholder("Description")
|
|
.textarea();
|
|
if let Some(text) = profile.description() {
|
|
description.value(text);
|
|
}
|
|
|
|
crate::rendered(HttpResponse::Ok(), |cursor| {
|
|
crate::templates::profiles::create::bio(cursor, &display_name, &description, &profile)
|
|
})
|
|
}
|
|
|
|
async fn create_bio(
|
|
_: Authenticated,
|
|
form: web::Form<BioForm>,
|
|
session: Session,
|
|
state: web::Data<State>,
|
|
) -> Result<HttpResponse, StateError> {
|
|
do_create_bio(form.into_inner(), session, &state)
|
|
.await
|
|
.state(&state)
|
|
}
|
|
|
|
async fn do_create_bio(
|
|
form: BioForm,
|
|
session: Session,
|
|
state: &State,
|
|
) -> Result<HttpResponse, Error> {
|
|
let profile_id = match session
|
|
.get::<CurrentProfile>("current-profile")
|
|
.ok()
|
|
.req()?
|
|
{
|
|
Some(id) => id.id,
|
|
None => return Ok(to_create()),
|
|
};
|
|
|
|
let display_name = form.display_name.clone();
|
|
let description = form.description.clone();
|
|
|
|
use hyaenidae_profiles::apub::actions::UpdateProfile;
|
|
|
|
let res = state
|
|
.profiles
|
|
.run(&UpdateProfile::from_text(
|
|
profile_id,
|
|
display_name,
|
|
description,
|
|
))
|
|
.await;
|
|
|
|
let error = match res {
|
|
Ok(_) => return Ok(to_icon()),
|
|
Err(e) => Some(e.to_string()),
|
|
};
|
|
|
|
let mut display_name = hyaenidae_toolkit::TextInput::new("display_name");
|
|
display_name
|
|
.title("Display Name")
|
|
.placeholder("Display Name")
|
|
.value(&form.display_name)
|
|
.error_opt(error);
|
|
|
|
let mut description = hyaenidae_toolkit::TextInput::new("description");
|
|
description
|
|
.title("Description")
|
|
.placeholder("Description")
|
|
.value(&form.description);
|
|
|
|
let profile = Profile::from_id(profile_id, state)?;
|
|
|
|
crate::rendered(HttpResponse::BadRequest(), |cursor| {
|
|
crate::templates::profiles::create::bio(cursor, &display_name, &description, &profile)
|
|
})
|
|
}
|
|
|
|
async fn new_icon(
|
|
_: Authenticated,
|
|
session: Session,
|
|
state: web::Data<State>,
|
|
) -> Result<HttpResponse, StateError> {
|
|
do_new_icon(session, &state).await.state(&state)
|
|
}
|
|
|
|
async fn do_new_icon(session: Session, state: &State) -> Result<HttpResponse, Error> {
|
|
let profile_id = match session
|
|
.get::<CurrentProfile>("current-profile")
|
|
.ok()
|
|
.req()?
|
|
{
|
|
Some(id) => id.id,
|
|
None => return Ok(to_create()),
|
|
};
|
|
|
|
let mut icon_input =
|
|
hyaenidae_toolkit::FileInput::secondary("images[]", "Select Icon", "icon-input");
|
|
icon_input.accept("image/png,image/webp,image/jpeg,image/gif,.png,.webp,.jpg,.jpeg,.gif");
|
|
|
|
let profile = Profile::from_id(profile_id, state)?;
|
|
|
|
crate::rendered(HttpResponse::Ok(), |cursor| {
|
|
crate::templates::profiles::create::icon(cursor, &icon_input, None, &profile)
|
|
})
|
|
}
|
|
|
|
async fn create_icon(
|
|
_: Authenticated,
|
|
request: HttpRequest,
|
|
payload: web::Payload,
|
|
session: Session,
|
|
state: web::Data<State>,
|
|
) -> Result<HttpResponse, StateError> {
|
|
do_create_icon(request, payload.into_inner(), session, &state)
|
|
.await
|
|
.state(&state)
|
|
}
|
|
|
|
async fn do_create_icon(
|
|
request: HttpRequest,
|
|
payload: Payload,
|
|
session: Session,
|
|
state: &State,
|
|
) -> Result<HttpResponse, Error> {
|
|
let profile_id = match session
|
|
.get::<CurrentProfile>("current-profile")
|
|
.ok()
|
|
.req()?
|
|
{
|
|
Some(id) => id.id,
|
|
None => return Ok(to_create()),
|
|
};
|
|
|
|
let res = state.profiles.upload_image(request, payload).await;
|
|
|
|
let error = match res {
|
|
Ok(file_ids) if file_ids.len() == 1 => {
|
|
use hyaenidae_profiles::apub::actions::UpdateProfile;
|
|
|
|
let res = state
|
|
.profiles
|
|
.run(&UpdateProfile::from_icon(profile_id, file_ids[0]))
|
|
.await;
|
|
|
|
match res {
|
|
Ok(_) => return Ok(to_banner()),
|
|
Err(e) => Some(e.to_string()),
|
|
}
|
|
}
|
|
Ok(_) => Some("Incorrect number of files".to_owned()),
|
|
Err(e) => Some(e.to_string()),
|
|
};
|
|
|
|
let mut icon_input =
|
|
hyaenidae_toolkit::FileInput::secondary("images[]", "Select Icon", "icon-input");
|
|
icon_input.accept("image/png,image/webp,image/jpeg,image/gif,.png,.webp,.jpg,.jpeg,.gif");
|
|
|
|
let profile = Profile::from_id(profile_id, state)?;
|
|
|
|
crate::rendered(HttpResponse::BadRequest(), |cursor| {
|
|
crate::templates::profiles::create::icon(cursor, &icon_input, error, &profile)
|
|
})
|
|
}
|
|
|
|
async fn new_banner(
|
|
_: Authenticated,
|
|
session: Session,
|
|
state: web::Data<State>,
|
|
) -> Result<HttpResponse, StateError> {
|
|
do_new_banner(session, &state).await.state(&state)
|
|
}
|
|
|
|
async fn do_new_banner(session: Session, state: &State) -> Result<HttpResponse, Error> {
|
|
let profile_id = match session
|
|
.get::<CurrentProfile>("current-profile")
|
|
.ok()
|
|
.req()?
|
|
{
|
|
Some(id) => id.id,
|
|
None => return Ok(to_create()),
|
|
};
|
|
|
|
let mut banner_input =
|
|
hyaenidae_toolkit::FileInput::secondary("images[]", "Select Banner", "banner-input");
|
|
banner_input.accept("image/png,image/webp,image/jpeg,image/gif,.png,.webp,.jpg,.jpeg,.gif");
|
|
|
|
let profile = Profile::from_id(profile_id, state)?;
|
|
|
|
crate::rendered(HttpResponse::Ok(), |cursor| {
|
|
crate::templates::profiles::create::banner(cursor, &banner_input, None, &profile)
|
|
})
|
|
}
|
|
|
|
async fn create_banner(
|
|
_: Authenticated,
|
|
request: HttpRequest,
|
|
payload: web::Payload,
|
|
session: Session,
|
|
state: web::Data<State>,
|
|
) -> Result<HttpResponse, StateError> {
|
|
do_create_banner(request, payload.into_inner(), session, &state)
|
|
.await
|
|
.state(&state)
|
|
}
|
|
|
|
async fn do_create_banner(
|
|
request: HttpRequest,
|
|
payload: Payload,
|
|
session: Session,
|
|
state: &State,
|
|
) -> Result<HttpResponse, Error> {
|
|
let profile_id = match session
|
|
.get::<CurrentProfile>("current-profile")
|
|
.ok()
|
|
.req()?
|
|
{
|
|
Some(id) => id.id,
|
|
None => return Ok(to_create()),
|
|
};
|
|
|
|
let res = state.profiles.upload_image(request, payload).await;
|
|
|
|
let error = match res {
|
|
Ok(file_ids) if file_ids.len() == 1 => {
|
|
use hyaenidae_profiles::apub::actions::UpdateProfile;
|
|
|
|
let res = state
|
|
.profiles
|
|
.run(&UpdateProfile::from_banner(profile_id, file_ids[0]))
|
|
.await;
|
|
|
|
match res {
|
|
Ok(_) => return Ok(to_require_login()),
|
|
Err(e) => Some(e.to_string()),
|
|
}
|
|
}
|
|
Ok(_) => Some("Incorrect number of files".to_owned()),
|
|
Err(e) => Some(e.to_string()),
|
|
};
|
|
|
|
let mut banner_input =
|
|
hyaenidae_toolkit::FileInput::secondary("images[]", "Select Banner", "banner-input");
|
|
banner_input.accept("image/png,image/webp,image/jpeg,image/gif,.png,.webp,.jpg,.jpeg,.gif");
|
|
|
|
let profile = Profile::from_id(profile_id, state)?;
|
|
|
|
crate::rendered(HttpResponse::BadRequest(), |cursor| {
|
|
crate::templates::profiles::create::banner(cursor, &banner_input, error, &profile)
|
|
})
|
|
}
|
|
|
|
#[derive(Clone, Debug, serde::Deserialize)]
|
|
struct RequireLoginForm {
|
|
require_login: Option<String>,
|
|
}
|
|
|
|
async fn new_require_login(
|
|
_: Authenticated,
|
|
session: Session,
|
|
state: web::Data<State>,
|
|
) -> Result<HttpResponse, StateError> {
|
|
do_new_require_login(session, &state).await.state(&state)
|
|
}
|
|
|
|
async fn do_new_require_login(session: Session, state: &State) -> Result<HttpResponse, Error> {
|
|
let profile_id = match session
|
|
.get::<CurrentProfile>("current-profile")
|
|
.ok()
|
|
.req()?
|
|
{
|
|
Some(id) => id.id,
|
|
None => return Ok(to_create()),
|
|
};
|
|
|
|
let profile = Profile::from_id(profile_id, state)?;
|
|
|
|
crate::rendered(HttpResponse::Ok(), |cursor| {
|
|
crate::templates::profiles::create::require_login(
|
|
cursor,
|
|
profile.inner.login_required(),
|
|
None,
|
|
&profile,
|
|
)
|
|
})
|
|
}
|
|
|
|
async fn create_require_login(
|
|
_: Authenticated,
|
|
form: web::Form<RequireLoginForm>,
|
|
session: Session,
|
|
state: web::Data<State>,
|
|
) -> Result<HttpResponse, StateError> {
|
|
do_create_require_login(form.into_inner(), session, &state)
|
|
.await
|
|
.state(&state)
|
|
}
|
|
|
|
async fn do_create_require_login(
|
|
form: RequireLoginForm,
|
|
session: Session,
|
|
state: &State,
|
|
) -> Result<HttpResponse, Error> {
|
|
let profile_id = match session
|
|
.get::<CurrentProfile>("current-profile")
|
|
.ok()
|
|
.req()?
|
|
{
|
|
Some(id) => id.id,
|
|
None => return Ok(to_create()),
|
|
};
|
|
|
|
use hyaenidae_profiles::apub::actions::UpdateProfile;
|
|
|
|
let res = state
|
|
.profiles
|
|
.run(&UpdateProfile::from_login_required(
|
|
profile_id,
|
|
form.require_login.is_some(),
|
|
))
|
|
.await;
|
|
|
|
let error = match res {
|
|
Ok(_) => return Ok(to_done()),
|
|
Err(e) => Some(e.to_string()),
|
|
};
|
|
|
|
let profile = Profile::from_id(profile_id, state)?;
|
|
|
|
crate::rendered(HttpResponse::Ok(), |cursor| {
|
|
crate::templates::profiles::create::require_login(
|
|
cursor,
|
|
form.require_login.is_some(),
|
|
error,
|
|
&profile,
|
|
)
|
|
})
|
|
}
|
|
|
|
async fn done(
|
|
_: Authenticated,
|
|
session: Session,
|
|
state: web::Data<State>,
|
|
) -> Result<HttpResponse, StateError> {
|
|
do_done(session, &state).await.state(&state)
|
|
}
|
|
|
|
async fn do_done(session: Session, state: &State) -> Result<HttpResponse, Error> {
|
|
let profile_id = match session
|
|
.get::<CurrentProfile>("current-profile")
|
|
.ok()
|
|
.req()?
|
|
{
|
|
Some(id) => id.id,
|
|
None => return Ok(to_create()),
|
|
};
|
|
|
|
let profile = Profile::from_id(profile_id, state)?;
|
|
|
|
crate::rendered(HttpResponse::Ok(), |cursor| {
|
|
crate::templates::profiles::create::done(cursor, &profile)
|
|
})
|
|
}
|