in preparation to moving userdata into app, we switched to arbitrary groups
All checks were successful
CI/CD Pipeline / test (push) Successful in 11m4s
CI/CD Pipeline / deploy-staging (push) Has been skipped
CI/CD Pipeline / deploy-main (push) Has been skipped

This commit is contained in:
2023-12-23 21:27:52 +01:00
parent e4da952a62
commit c7d7d0ca83
29 changed files with 396 additions and 256 deletions

View File

@ -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]