personal settings: update username, name, email
This commit is contained in:
parent
1346b57b30
commit
a47e4c204a
14 changed files with 247 additions and 5 deletions
5
crates/database/queries/users/set_email.sql
Normal file
5
crates/database/queries/users/set_email.sql
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
update users
|
||||
|
||||
set email = ?
|
||||
|
||||
where id is ?
|
||||
5
crates/database/queries/users/set_name.sql
Normal file
5
crates/database/queries/users/set_name.sql
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
update users
|
||||
|
||||
set name = ?
|
||||
|
||||
where id is ?
|
||||
5
crates/database/queries/users/set_username.sql
Normal file
5
crates/database/queries/users/set_username.sql
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
update users
|
||||
|
||||
set username = ?
|
||||
|
||||
where id is ?
|
||||
|
|
@ -268,6 +268,16 @@
|
|||
},
|
||||
"query": "insert into users (id, is_admin, username, password)\nvalues (?, ?, ?, ?)\n"
|
||||
},
|
||||
"545f19b0373c7ffe16864eb242c15a0092355e120c5cbe006c877afdfc4a4e8c": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"nullable": [],
|
||||
"parameters": {
|
||||
"Right": 2
|
||||
}
|
||||
},
|
||||
"query": "update users\n\nset username = ?\n\nwhere id is ?"
|
||||
},
|
||||
"56a9c0dff010858189a95087d014c7d0ce930da5d841b9d788a9c0e84b580bc6": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
|
|
@ -518,6 +528,16 @@
|
|||
},
|
||||
"query": "update settings\n\nset url = ?\n\nwhere id is 0\n"
|
||||
},
|
||||
"8c131e1f73ffa01fc3e5e08071a786b85f23b9638d1c7eaa7b633c052703c911": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"nullable": [],
|
||||
"parameters": {
|
||||
"Right": 2
|
||||
}
|
||||
},
|
||||
"query": "update users\n\nset name = ?\n\nwhere id is ?"
|
||||
},
|
||||
"93b15a942a6c7db595990f00e14fde26d6d36b8c8de9935179d41f6c7c755978": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
|
|
@ -656,6 +676,16 @@
|
|||
},
|
||||
"query": "update refresh_tokens\n\nset revoked_at = CURRENT_TIMESTAMP\n\nwhere user is ?\n and revoked_at is null"
|
||||
},
|
||||
"c28c88869239edc02c073f461645eca82d816650fabe65464e2059d5908d8a28": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"nullable": [],
|
||||
"parameters": {
|
||||
"Right": 2
|
||||
}
|
||||
},
|
||||
"query": "update users\n\nset email = ?\n\nwhere id is ?"
|
||||
},
|
||||
"c5a57c971d07532ec0cc897b5ac06e0814e506f9c24647d1eaf44174dc0a5954": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
|
|
|
|||
|
|
@ -95,4 +95,44 @@ impl Users {
|
|||
.await
|
||||
.map_err(handle_error)
|
||||
}
|
||||
|
||||
pub async fn set_username(
|
||||
conn: impl SqliteExecutor<'_>,
|
||||
id: &str,
|
||||
username: &str,
|
||||
) -> Result<Option<()>, Error> {
|
||||
let query: SqliteQueryResult =
|
||||
sqlx::query_file!("queries/users/set_username.sql", username, id)
|
||||
.execute(conn)
|
||||
.await
|
||||
.map_err(handle_error)?;
|
||||
|
||||
Ok((query.rows_affected() == 1).then_some(()))
|
||||
}
|
||||
|
||||
pub async fn set_name(
|
||||
conn: impl SqliteExecutor<'_>,
|
||||
id: &str,
|
||||
name: &str,
|
||||
) -> Result<Option<()>, Error> {
|
||||
let query: SqliteQueryResult = sqlx::query_file!("queries/users/set_name.sql", name, id)
|
||||
.execute(conn)
|
||||
.await
|
||||
.map_err(handle_error)?;
|
||||
|
||||
Ok((query.rows_affected() == 1).then_some(()))
|
||||
}
|
||||
|
||||
pub async fn set_email(
|
||||
conn: impl SqliteExecutor<'_>,
|
||||
id: &str,
|
||||
email: &str,
|
||||
) -> Result<Option<()>, Error> {
|
||||
let query: SqliteQueryResult = sqlx::query_file!("queries/users/set_email.sql", email, id)
|
||||
.execute(conn)
|
||||
.await
|
||||
.map_err(handle_error)?;
|
||||
|
||||
Ok((query.rows_affected() == 1).then_some(()))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ identicon-rs = "4.0"
|
|||
futures = "0.3"
|
||||
base64 = "0.21.0"
|
||||
rocket_cors = "0.6.0-alpha2"
|
||||
email_address = { workspace = true }
|
||||
|
||||
# local crates
|
||||
database_pool = { path = "../database_pool" }
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ impl UserMenu {
|
|||
icon: Icon::Settings.svg,
|
||||
sub: Some(vec![SubItem {
|
||||
label: "Personal",
|
||||
link: uri!(routes::settings::user_settings_personal).to_string(),
|
||||
link: uri!(routes::settings::personal::user_settings_personal).to_string(),
|
||||
}]),
|
||||
},
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,11 +1,15 @@
|
|||
use super::prelude::*;
|
||||
pub use personal::*;
|
||||
use personal::*;
|
||||
use rocket::get;
|
||||
|
||||
pub mod personal;
|
||||
|
||||
pub fn routes() -> Vec<Route> {
|
||||
routes![user_settings, user_settings_personal]
|
||||
routes![
|
||||
user_settings,
|
||||
user_settings_personal,
|
||||
user_settings_personal_form,
|
||||
]
|
||||
}
|
||||
|
||||
#[get("/settings")]
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
use crate::routes::prelude::*;
|
||||
use rocket::get;
|
||||
use crate::tokens::JWT_DURATION_MINUTES;
|
||||
use email_address::EmailAddress;
|
||||
use rocket::{get, post};
|
||||
use std::str::FromStr;
|
||||
use users::User;
|
||||
|
||||
#[get("/settings/personal")]
|
||||
|
|
@ -23,3 +26,91 @@ pub async fn user_settings_personal(
|
|||
.map(|flash| Page::with_flash(page.clone(), flash))
|
||||
.unwrap_or_else(|| page.into()))
|
||||
}
|
||||
|
||||
#[derive(Debug, FromForm)]
|
||||
pub struct UpdatePersonalSettings<'r> {
|
||||
pub username: &'r str,
|
||||
pub name: &'r str,
|
||||
pub email: &'r str,
|
||||
}
|
||||
|
||||
#[post("/settings/personal", data = "<form>")]
|
||||
pub async fn user_settings_personal_form(
|
||||
mut db: Connection<Database>,
|
||||
jwt_user: JwtUser,
|
||||
form: Form<UpdatePersonalSettings<'_>>,
|
||||
) -> Result<Flash<Redirect>> {
|
||||
let mut transaction = db.begin().await?;
|
||||
|
||||
let user = User::get_by_login(&mut transaction, &jwt_user.0.subject)
|
||||
.await?
|
||||
.ok_or_else(|| Error::not_found(jwt_user.0.subject.to_string()))?;
|
||||
|
||||
if user.is_archived() {
|
||||
return Err(Error::forbidden("User is archived"));
|
||||
}
|
||||
|
||||
// Update username
|
||||
if user.username() != form.username {
|
||||
if let Err(e) = user.set_username(&mut transaction, form.username).await {
|
||||
return Ok(Flash::new(
|
||||
Redirect::to(uri!(user_settings_personal)),
|
||||
FlashKind::Danger,
|
||||
e.to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// Update name
|
||||
if !form.name.is_empty()
|
||||
&& user
|
||||
.name()
|
||||
// If it exists in database, check if provided value is different
|
||||
.map(|current| current != form.name)
|
||||
// If it does not exist, use provided value
|
||||
.unwrap_or(true)
|
||||
{
|
||||
user.set_name(&mut transaction, form.name).await?;
|
||||
}
|
||||
|
||||
// Update email
|
||||
if !form.email.is_empty()
|
||||
&& user
|
||||
.email()
|
||||
// If it exists in database, check if provided value is different
|
||||
.map(|current| current != form.email)
|
||||
// If it does not exist, use provided value
|
||||
.unwrap_or(true)
|
||||
{
|
||||
// Parse email address
|
||||
let email = match EmailAddress::from_str(form.email) {
|
||||
Ok(email) => email,
|
||||
Err(e) => {
|
||||
return Ok(Flash::new(
|
||||
Redirect::to(uri!(user_settings_personal)),
|
||||
FlashKind::Danger,
|
||||
e.to_string(),
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(e) = user.set_email(&mut transaction, email).await {
|
||||
return Ok(Flash::new(
|
||||
Redirect::to(uri!(user_settings_personal)),
|
||||
FlashKind::Danger,
|
||||
e.to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
transaction.commit().await?;
|
||||
|
||||
Ok(Flash::new(
|
||||
Redirect::to(uri!(user_settings_personal)),
|
||||
FlashKind::Success,
|
||||
format!(
|
||||
"Personal settings have been saved.\
|
||||
<br>Some changes can take up to {JWT_DURATION_MINUTES} minutes to appear."
|
||||
),
|
||||
))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,4 +9,4 @@ hash = { path = "../hash" }
|
|||
id = { path = "../id" }
|
||||
thiserror = { workspace = true }
|
||||
chrono = { workspace = true }
|
||||
email_address = { version = "0.2", default-features = false }
|
||||
email_address = { workspace = true }
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use crate::error::Error;
|
||||
use crate::User;
|
||||
use database::sqlx::SqliteExecutor;
|
||||
use database::Error as DatabaseError;
|
||||
use database::Users as DatabaseUsers;
|
||||
use email_address::EmailAddress;
|
||||
use hash::Password;
|
||||
|
|
@ -105,4 +106,53 @@ impl User {
|
|||
.await?
|
||||
.map(Self::from))
|
||||
}
|
||||
|
||||
pub async fn set_username(
|
||||
&self,
|
||||
conn: impl SqliteExecutor<'_>,
|
||||
username: &str,
|
||||
) -> Result<(), Error> {
|
||||
DatabaseUsers::set_username(conn, self.id.as_ref(), username)
|
||||
.await
|
||||
.map_err(|e| match e {
|
||||
DatabaseError::UniqueConstraint(column) => {
|
||||
if &column == "username" {
|
||||
Error::UsernameNotAvailable(username.into())
|
||||
} else {
|
||||
Error::ColumnNotAvailable(column)
|
||||
}
|
||||
}
|
||||
_ => e.into(),
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn set_name(&self, conn: impl SqliteExecutor<'_>, name: &str) -> Result<(), Error> {
|
||||
DatabaseUsers::set_name(conn, self.id.as_ref(), name).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn set_email(
|
||||
&self,
|
||||
conn: impl SqliteExecutor<'_>,
|
||||
email: EmailAddress,
|
||||
) -> Result<(), Error> {
|
||||
let email = email.as_str();
|
||||
DatabaseUsers::set_email(conn, self.id.as_ref(), email)
|
||||
.await
|
||||
.map_err(|e| match e {
|
||||
DatabaseError::UniqueConstraint(column) => {
|
||||
if &column == "email" {
|
||||
Error::EmailNotAvailable(email.into())
|
||||
} else {
|
||||
Error::ColumnNotAvailable(column)
|
||||
}
|
||||
}
|
||||
_ => e.into(),
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,4 +5,13 @@
|
|||
pub enum Error {
|
||||
#[error("Database: {0}")]
|
||||
Database(#[from] database::Error),
|
||||
|
||||
#[error("The database column \"{0}\" is not available.")]
|
||||
ColumnNotAvailable(String),
|
||||
|
||||
#[error("The username \"{0}\" is not available.")]
|
||||
UsernameNotAvailable(String),
|
||||
|
||||
#[error("The email \"{0}\" is linked to another user.")]
|
||||
EmailNotAvailable(String),
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue