in preparation to moving userdata into app, we switched to arbitrary groups
This commit is contained in:
@ -21,10 +21,6 @@ pub struct User {
|
||||
pub id: i64,
|
||||
pub name: String,
|
||||
pub pw: Option<String>,
|
||||
pub is_cox: bool,
|
||||
pub is_admin: bool,
|
||||
pub is_guest: bool,
|
||||
pub is_tech: bool,
|
||||
pub dob: Option<String>,
|
||||
pub weight: Option<String>,
|
||||
pub sex: Option<String>,
|
||||
@ -32,17 +28,35 @@ pub struct User {
|
||||
pub last_access: Option<chrono::NaiveDateTime>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct UserWithRoles {
|
||||
#[serde(flatten)]
|
||||
pub user: User,
|
||||
pub roles: Vec<String>,
|
||||
}
|
||||
|
||||
impl UserWithRoles {
|
||||
pub async fn from_user(user: User, db: &SqlitePool) -> Self {
|
||||
Self {
|
||||
roles: user.roles(db).await,
|
||||
user,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct UserWithWaterStatus {
|
||||
#[serde(flatten)]
|
||||
pub user: User,
|
||||
pub on_water: bool,
|
||||
pub roles: Vec<String>,
|
||||
}
|
||||
|
||||
impl UserWithWaterStatus {
|
||||
pub async fn from_user(user: User, db: &SqlitePool) -> Self {
|
||||
Self {
|
||||
on_water: user.on_water(db).await,
|
||||
roles: user.roles(db).await,
|
||||
user,
|
||||
}
|
||||
}
|
||||
@ -91,11 +105,56 @@ impl User {
|
||||
.rowed_km
|
||||
}
|
||||
|
||||
pub async fn has_role(&self, db: &SqlitePool, role: &str) -> bool {
|
||||
if sqlx::query!(
|
||||
"SELECT * FROM user_role WHERE user_id=? AND role_id = (SELECT id FROM role WHERE name = ?)",
|
||||
self.id,
|
||||
role
|
||||
)
|
||||
.fetch_optional(db)
|
||||
.await
|
||||
.unwrap()
|
||||
.is_some()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
pub async fn roles(&self, db: &SqlitePool) -> Vec<String> {
|
||||
sqlx::query!(
|
||||
"SELECT r.name FROM role r JOIN user_role ur ON r.id = ur.role_id WHERE ur.user_id = ?;",
|
||||
self.id
|
||||
)
|
||||
.fetch_all(db)
|
||||
.await
|
||||
.unwrap()
|
||||
.into_iter().map(|r| r.name).collect()
|
||||
}
|
||||
|
||||
pub async fn has_role_tx(&self, db: &mut Transaction<'_, Sqlite>, role: &str) -> bool {
|
||||
if sqlx::query!(
|
||||
"SELECT * FROM user_role WHERE user_id=? AND role_id = (SELECT id FROM role WHERE name = ?)",
|
||||
self.id,
|
||||
role
|
||||
)
|
||||
.fetch_optional(db.deref_mut())
|
||||
.await
|
||||
.unwrap()
|
||||
.is_some()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
pub async fn find_by_id(db: &SqlitePool, id: i32) -> Option<Self> {
|
||||
sqlx::query_as!(
|
||||
Self,
|
||||
"
|
||||
SELECT id, name, pw, is_cox, is_admin, is_guest, deleted, last_access, is_tech, dob, weight, sex
|
||||
SELECT id, name, pw, deleted, last_access, dob, weight, sex
|
||||
FROM user
|
||||
WHERE id like ?
|
||||
",
|
||||
@ -110,7 +169,7 @@ WHERE id like ?
|
||||
sqlx::query_as!(
|
||||
Self,
|
||||
"
|
||||
SELECT id, name, pw, is_cox, is_admin, is_guest, deleted, last_access, is_tech, dob, weight, sex
|
||||
SELECT id, name, pw, deleted, last_access, dob, weight, sex
|
||||
FROM user
|
||||
WHERE id like ?
|
||||
",
|
||||
@ -125,7 +184,7 @@ WHERE id like ?
|
||||
sqlx::query_as!(
|
||||
Self,
|
||||
"
|
||||
SELECT id, name, pw, is_cox, is_admin, is_guest, deleted, last_access, is_tech, dob, weight, sex
|
||||
SELECT id, name, pw, deleted, last_access, dob, weight, sex
|
||||
FROM user
|
||||
WHERE name like ?
|
||||
",
|
||||
@ -167,7 +226,7 @@ WHERE name like ?
|
||||
sqlx::query_as!(
|
||||
Self,
|
||||
"
|
||||
SELECT id, name, pw, is_cox, is_admin, is_guest, deleted, last_access, is_tech, dob, weight, sex
|
||||
SELECT id, name, pw, deleted, last_access, dob, weight, sex
|
||||
FROM user
|
||||
WHERE deleted = 0
|
||||
ORDER BY last_access DESC
|
||||
@ -182,7 +241,7 @@ ORDER BY last_access DESC
|
||||
sqlx::query_as!(
|
||||
Self,
|
||||
"
|
||||
SELECT id, name, pw, is_cox, is_admin, is_guest, deleted, last_access, is_tech, dob, weight, sex
|
||||
SELECT id, name, pw, deleted, last_access, dob, weight, sex
|
||||
FROM user
|
||||
WHERE deleted = 0 AND dob != '' and weight != '' and sex != ''
|
||||
ORDER BY name
|
||||
@ -197,9 +256,9 @@ ORDER BY name
|
||||
sqlx::query_as!(
|
||||
Self,
|
||||
"
|
||||
SELECT id, name, pw, is_cox, is_admin, is_guest, deleted, last_access, is_tech, dob, weight, sex
|
||||
SELECT id, name, pw, deleted, last_access, dob, weight, sex
|
||||
FROM user
|
||||
WHERE deleted = 0 AND is_cox=true
|
||||
WHERE deleted = 0 AND (SELECT COUNT(*) FROM user_role WHERE user_id=user.id AND role_id = (SELECT id FROM role WHERE name = 'cox')) > 0
|
||||
ORDER BY last_access DESC
|
||||
"
|
||||
)
|
||||
@ -208,24 +267,16 @@ ORDER BY last_access DESC
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub async fn create(db: &SqlitePool, name: &str, is_guest: bool) -> bool {
|
||||
sqlx::query!(
|
||||
"INSERT INTO USER(name, is_guest) VALUES (?,?)",
|
||||
name,
|
||||
is_guest,
|
||||
)
|
||||
.execute(db)
|
||||
.await
|
||||
.is_ok()
|
||||
pub async fn create(db: &SqlitePool, name: &str) -> bool {
|
||||
sqlx::query!("INSERT INTO USER(name) VALUES (?)", name)
|
||||
.execute(db)
|
||||
.await
|
||||
.is_ok()
|
||||
}
|
||||
|
||||
pub async fn update(&self, db: &SqlitePool, data: UserEditForm) {
|
||||
sqlx::query!(
|
||||
"UPDATE user SET is_cox = ?, is_admin = ?, is_guest = ?, is_tech = ?, dob = ?, weight = ?, sex = ? where id = ?",
|
||||
data.is_cox,
|
||||
data.is_admin,
|
||||
data.is_guest,
|
||||
data.is_tech,
|
||||
"UPDATE user SET dob = ?, weight = ?, sex = ? where id = ?",
|
||||
data.dob,
|
||||
data.weight,
|
||||
data.sex,
|
||||
@ -234,6 +285,23 @@ ORDER BY last_access DESC
|
||||
.execute(db)
|
||||
.await
|
||||
.unwrap(); //Okay, because we can only create a User of a valid id
|
||||
|
||||
// handle roles
|
||||
sqlx::query!("DELETE FROM user_role WHERE user_id = ?", self.id)
|
||||
.execute(db)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
for role_id in data.roles.into_keys() {
|
||||
sqlx::query!(
|
||||
"INSERT INTO user_role(user_id, role_id) VALUES (?, ?)",
|
||||
self.id,
|
||||
role_id
|
||||
)
|
||||
.execute(db)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn login(db: &SqlitePool, name: &str, pw: &str) -> Result<Self, LoginError> {
|
||||
@ -309,18 +377,18 @@ ORDER BY last_access DESC
|
||||
|
||||
pub async fn get_days(&self, db: &SqlitePool) -> Vec<Day> {
|
||||
let mut days = Vec::new();
|
||||
for i in 0..self.amount_days_to_show() {
|
||||
for i in 0..self.amount_days_to_show(db).await {
|
||||
let date = (Local::now() + chrono::Duration::days(i)).date_naive();
|
||||
|
||||
if self.is_guest {
|
||||
if self.has_role(db, "scheckbuch").await {
|
||||
days.push(Day::new_guest(db, date, false).await);
|
||||
} else {
|
||||
days.push(Day::new(db, date, false).await);
|
||||
}
|
||||
}
|
||||
|
||||
for date in TripDetails::pinned_days(db, self.amount_days_to_show() - 1).await {
|
||||
if self.is_guest {
|
||||
for date in TripDetails::pinned_days(db, self.amount_days_to_show(db).await - 1).await {
|
||||
if self.has_role(db, "scheckbuch").await {
|
||||
let day = Day::new_guest(db, date, true).await;
|
||||
if !day.planned_events.is_empty() {
|
||||
days.push(day);
|
||||
@ -332,8 +400,8 @@ ORDER BY last_access DESC
|
||||
days
|
||||
}
|
||||
|
||||
fn amount_days_to_show(&self) -> i64 {
|
||||
if self.is_cox {
|
||||
async fn amount_days_to_show(&self, db: &SqlitePool) -> i64 {
|
||||
if self.has_role(db, "cox").await {
|
||||
let end_of_year = NaiveDate::from_ymd_opt(Local::now().year(), 12, 31).unwrap(); //Ok,
|
||||
//december
|
||||
//has 31
|
||||
@ -393,28 +461,21 @@ impl Deref for TechUser {
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<User> for TechUser {
|
||||
type Error = LoginError;
|
||||
|
||||
fn try_from(user: User) -> Result<Self, Self::Error> {
|
||||
if user.is_tech {
|
||||
Ok(TechUser { user })
|
||||
} else {
|
||||
Err(LoginError::NotATech)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<'r> FromRequest<'r> for TechUser {
|
||||
type Error = LoginError;
|
||||
|
||||
async fn from_request(req: &'r Request<'_>) -> request::Outcome<Self, Self::Error> {
|
||||
let db = req.rocket().state::<SqlitePool>().unwrap();
|
||||
|
||||
match User::from_request(req).await {
|
||||
Outcome::Success(user) => match user.try_into() {
|
||||
Ok(user) => Outcome::Success(user),
|
||||
Err(_) => Outcome::Error((Status::Unauthorized, LoginError::NotACox)),
|
||||
},
|
||||
Outcome::Success(user) => {
|
||||
if user.has_role(db, "tech").await {
|
||||
Outcome::Success(TechUser { user })
|
||||
} else {
|
||||
Outcome::Error((Status::Unauthorized, LoginError::NotACox))
|
||||
}
|
||||
}
|
||||
Outcome::Error(f) => Outcome::Error(f),
|
||||
Outcome::Forward(f) => Outcome::Forward(f),
|
||||
}
|
||||
@ -433,14 +494,12 @@ impl Deref for CoxUser {
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<User> for CoxUser {
|
||||
type Error = LoginError;
|
||||
|
||||
fn try_from(user: User) -> Result<Self, Self::Error> {
|
||||
if user.is_cox {
|
||||
Ok(CoxUser { user })
|
||||
impl CoxUser {
|
||||
pub async fn new(db: &SqlitePool, user: User) -> Option<Self> {
|
||||
if user.has_role(db, "cox").await {
|
||||
Some(CoxUser { user })
|
||||
} else {
|
||||
Err(LoginError::NotACox)
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -450,11 +509,16 @@ impl<'r> FromRequest<'r> for CoxUser {
|
||||
type Error = LoginError;
|
||||
|
||||
async fn from_request(req: &'r Request<'_>) -> request::Outcome<Self, Self::Error> {
|
||||
let db = req.rocket().state::<SqlitePool>().unwrap();
|
||||
|
||||
match User::from_request(req).await {
|
||||
Outcome::Success(user) => match user.try_into() {
|
||||
Ok(user) => Outcome::Success(user),
|
||||
Err(_) => Outcome::Error((Status::Unauthorized, LoginError::NotACox)),
|
||||
},
|
||||
Outcome::Success(user) => {
|
||||
if user.has_role(db, "cox").await {
|
||||
Outcome::Success(CoxUser { user })
|
||||
} else {
|
||||
Outcome::Error((Status::Unauthorized, LoginError::NotACox))
|
||||
}
|
||||
}
|
||||
Outcome::Error(f) => Outcome::Error(f),
|
||||
Outcome::Forward(f) => Outcome::Forward(f),
|
||||
}
|
||||
@ -466,28 +530,20 @@ pub struct AdminUser {
|
||||
pub(crate) user: User,
|
||||
}
|
||||
|
||||
impl TryFrom<User> for AdminUser {
|
||||
type Error = LoginError;
|
||||
|
||||
fn try_from(user: User) -> Result<Self, Self::Error> {
|
||||
if user.is_admin {
|
||||
Ok(AdminUser { user })
|
||||
} else {
|
||||
Err(LoginError::NotAnAdmin)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<'r> FromRequest<'r> for AdminUser {
|
||||
type Error = LoginError;
|
||||
|
||||
async fn from_request(req: &'r Request<'_>) -> request::Outcome<Self, Self::Error> {
|
||||
let db = req.rocket().state::<SqlitePool>().unwrap();
|
||||
match User::from_request(req).await {
|
||||
Outcome::Success(user) => match user.try_into() {
|
||||
Ok(user) => Outcome::Success(user),
|
||||
Err(_) => Outcome::Error((Status::Unauthorized, LoginError::NotAnAdmin)),
|
||||
},
|
||||
Outcome::Success(user) => {
|
||||
if user.has_role(db, "admin").await {
|
||||
Outcome::Success(AdminUser { user })
|
||||
} else {
|
||||
Outcome::Error((Status::Unauthorized, LoginError::NotACox))
|
||||
}
|
||||
}
|
||||
Outcome::Error(f) => Outcome::Error(f),
|
||||
Outcome::Forward(f) => Outcome::Forward(f),
|
||||
}
|
||||
@ -499,28 +555,20 @@ pub struct NonGuestUser {
|
||||
pub(crate) user: User,
|
||||
}
|
||||
|
||||
impl TryFrom<User> for NonGuestUser {
|
||||
type Error = LoginError;
|
||||
|
||||
fn try_from(user: User) -> Result<Self, Self::Error> {
|
||||
if user.is_guest {
|
||||
Err(LoginError::GuestNotAllowed)
|
||||
} else {
|
||||
Ok(NonGuestUser { user })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<'r> FromRequest<'r> for NonGuestUser {
|
||||
type Error = LoginError;
|
||||
|
||||
async fn from_request(req: &'r Request<'_>) -> request::Outcome<Self, Self::Error> {
|
||||
let db = req.rocket().state::<SqlitePool>().unwrap();
|
||||
match User::from_request(req).await {
|
||||
Outcome::Success(user) => match user.try_into() {
|
||||
Ok(user) => Outcome::Success(user),
|
||||
Err(_) => Outcome::Error((Status::Unauthorized, LoginError::NotAnAdmin)),
|
||||
},
|
||||
Outcome::Success(user) => {
|
||||
if !user.has_role(db, "scheckbuch").await {
|
||||
Outcome::Success(NonGuestUser { user })
|
||||
} else {
|
||||
Outcome::Error((Status::Unauthorized, LoginError::NotACox))
|
||||
}
|
||||
}
|
||||
Outcome::Error(f) => Outcome::Error(f),
|
||||
Outcome::Forward(f) => Outcome::Forward(f),
|
||||
}
|
||||
@ -529,6 +577,8 @@ impl<'r> FromRequest<'r> for NonGuestUser {
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::{tera::admin::user::UserEditForm, testdb};
|
||||
|
||||
use super::User;
|
||||
@ -580,17 +630,14 @@ mod test {
|
||||
fn test_succ_create() {
|
||||
let pool = testdb!();
|
||||
|
||||
assert_eq!(
|
||||
User::create(&pool, "new-user-name".into(), false).await,
|
||||
true
|
||||
);
|
||||
assert_eq!(User::create(&pool, "new-user-name".into()).await, true);
|
||||
}
|
||||
|
||||
#[sqlx::test]
|
||||
fn test_duplicate_name_create() {
|
||||
let pool = testdb!();
|
||||
|
||||
assert_eq!(User::create(&pool, "admin".into(), false).await, false);
|
||||
assert_eq!(User::create(&pool, "admin".into()).await, false);
|
||||
}
|
||||
|
||||
#[sqlx::test]
|
||||
@ -602,19 +649,17 @@ mod test {
|
||||
&pool,
|
||||
UserEditForm {
|
||||
id: 1,
|
||||
is_guest: false,
|
||||
is_cox: false,
|
||||
is_admin: false,
|
||||
is_tech: false,
|
||||
dob: None,
|
||||
weight: None,
|
||||
sex: None,
|
||||
sex: Some("m".into()),
|
||||
roles: HashMap::new(),
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
||||
let user = User::find_by_id(&pool, 1).await.unwrap();
|
||||
assert_eq!(user.is_admin, false);
|
||||
|
||||
assert_eq!(user.sex, Some("m".into()));
|
||||
}
|
||||
|
||||
#[sqlx::test]
|
||||
|
Reference in New Issue
Block a user