1107 lines
31 KiB
Rust

use std::ops::{Deref, DerefMut};
use argon2::{password_hash::SaltString, Argon2, PasswordHasher};
use chrono::{Datelike, Local, NaiveDate};
use log::info;
use rocket::{
async_trait,
http::{Cookie, Status},
request::{self, FromRequest, Outcome},
time::{Duration, OffsetDateTime},
Request,
};
use serde::{Deserialize, Serialize};
use sqlx::{FromRow, Sqlite, SqlitePool, Transaction};
use super::{family::Family, log::Log, role::Role, tripdetails::TripDetails, Day};
use crate::tera::admin::user::UserEditForm;
const RENNRUDERBEITRAG: i32 = 11000;
const BOAT_STORAGE: i32 = 4500;
const FAMILY_TWO: i32 = 30000;
const FAMILY_THREE_OR_MORE: i32 = 35000;
const STUDENT_OR_PUPIL: i32 = 8000;
const REGULAR: i32 = 22000;
const UNTERSTUETZEND: i32 = 2500;
const FOERDERND: i32 = 8500;
#[derive(FromRow, Debug, Serialize, Deserialize, Clone)]
pub struct User {
pub id: i64,
pub name: String,
pub pw: Option<String>,
pub dob: Option<String>,
pub weight: Option<String>,
pub sex: Option<String>,
pub deleted: bool,
pub last_access: Option<chrono::NaiveDateTime>,
pub member_since_date: Option<String>,
pub birthdate: Option<String>,
pub mail: Option<String>,
pub nickname: Option<String>,
pub notes: Option<String>,
pub phone: Option<String>,
pub address: Option<String>,
pub family_id: Option<i64>,
}
#[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,
}
}
}
impl PartialEq for User {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
#[derive(Debug)]
pub enum LoginError {
InvalidAuthenticationCombo,
UserNotFound,
UserDeleted,
NotLoggedIn,
NotAnAdmin,
NotACox,
NotATech,
GuestNotAllowed,
NoPasswordSet(User),
DeserializationError,
}
#[derive(Debug, Serialize)]
pub struct Fee {
pub sum_in_cents: i32,
pub parts: Vec<(String, i32)>,
pub name: String,
pub user_ids: String,
pub paid: bool,
pub users: Vec<User>,
}
impl Default for Fee {
fn default() -> Self {
Self::new()
}
}
impl Fee {
pub fn new() -> Self {
Self {
sum_in_cents: 0,
name: "".into(),
parts: Vec::new(),
user_ids: "".into(),
users: Vec::new(),
paid: false,
}
}
pub fn add(&mut self, desc: String, price_in_cents: i32) {
self.sum_in_cents += price_in_cents;
self.parts.push((desc, price_in_cents));
}
pub fn add_person(&mut self, user: &User) {
if !self.name.is_empty() {
self.name.push_str(" + ");
self.user_ids.push('&');
}
self.name.push_str(&user.name);
self.user_ids.push_str(&format!("user_ids[]={}", user.id));
self.users.push(user.clone());
}
pub fn paid(&mut self) {
self.paid = true;
}
pub fn merge(&mut self, fee: Fee) {
for (desc, price_in_cents) in fee.parts {
self.add(desc, price_in_cents);
}
}
}
impl User {
pub async fn fee(&self, db: &SqlitePool) -> Option<Fee> {
if !self.has_role(db, "Donau Linz").await {
return None;
}
if self.deleted {
return None;
}
let mut fee = Fee::new();
if let Some(family) = Family::find_by_opt_id(db, self.family_id).await {
for member in family.members(db).await {
fee.add_person(&member);
if member.has_role(db, "paid").await {
fee.paid();
}
fee.merge(member.fee_without_families(db).await);
}
if family.amount_family_members(db).await > 2 {
fee.add("Familie 3+ Personen".into(), FAMILY_THREE_OR_MORE);
} else {
fee.add("Familie 2 Personen".into(), FAMILY_TWO);
}
} else {
fee.add_person(self);
if self.has_role(db, "paid").await {
fee.paid();
}
fee.merge(self.fee_without_families(db).await);
}
Some(fee)
}
async fn fee_without_families(&self, db: &SqlitePool) -> Fee {
let mut fee = Fee::new();
if !self.has_role(db, "Donau Linz").await {
return fee;
}
if self.has_role(db, "Rennrudern").await {
fee.add("Rennruderbeitrag".into(), RENNRUDERBEITRAG);
}
let amount_boats = self.amount_boats(db).await;
if amount_boats > 0 {
fee.add(
format!("{}x Bootsplatz", amount_boats),
amount_boats * BOAT_STORAGE,
);
}
if self.has_role(db, "Unterstützend").await {
fee.add("Unterstützendes Mitglied".into(), UNTERSTUETZEND);
} else if self.has_role(db, "Förderndes Mitglied").await {
fee.add("Förderndes Mitglied".into(), FOERDERND);
} else if Family::find_by_opt_id(db, self.family_id).await.is_none() {
if self.has_role(db, "Student").await || self.has_role(db, "Schüler").await {
fee.add("Schüler/Student".into(), STUDENT_OR_PUPIL);
} else if self.has_role(db, "Ehrenmitglied").await {
fee.add("Ehrenmitglied".into(), 0);
} else {
fee.add("Mitgliedsbeitrag".into(), REGULAR);
}
}
fee
}
pub async fn amount_boats(&self, db: &SqlitePool) -> i32 {
sqlx::query!(
"SELECT COUNT(*) as count FROM boat WHERE owner = ?",
self.id
)
.fetch_one(db)
.await
.unwrap()
.count
}
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 JOIN user u ON u.id = ur.user_id WHERE ur.user_id = ? AND u.deleted = 0;",
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, deleted, last_access, dob, weight, sex, member_since_date, birthdate, mail, nickname, notes, phone, address, family_id
FROM user
WHERE id like ?
",
id
)
.fetch_one(db)
.await
.ok()
}
pub async fn find_by_id_tx(db: &mut Transaction<'_, Sqlite>, id: i32) -> Option<Self> {
sqlx::query_as!(
Self,
"
SELECT id, name, pw, deleted, last_access, dob, weight, sex, member_since_date, birthdate, mail, nickname, notes, phone, address, family_id
FROM user
WHERE id like ?
",
id
)
.fetch_one(db.deref_mut())
.await
.ok()
}
pub async fn find_by_name(db: &SqlitePool, name: &str) -> Option<Self> {
sqlx::query_as!(
Self,
"
SELECT id, name, pw, deleted, last_access, dob, weight, sex, member_since_date, birthdate, mail, nickname, notes, phone, address, family_id
FROM user
WHERE name like ?
",
name
)
.fetch_one(db)
.await
.ok()
}
pub async fn on_water(&self, db: &SqlitePool) -> bool {
if sqlx::query!(
"SELECT * FROM logbook WHERE shipmaster=? AND arrival is null",
self.id
)
.fetch_optional(db)
.await
.unwrap()
.is_some()
{
return true;
}
if sqlx::query!(
"SELECT * FROM logbook JOIN rower ON rower.logbook_id=logbook.id WHERE rower_id=? AND arrival is null",
self.id
)
.fetch_optional(db)
.await
.unwrap()
.is_some()
{
return true;
}
false
}
pub async fn all(db: &SqlitePool) -> Vec<Self> {
sqlx::query_as!(
Self,
"
SELECT id, name, pw, deleted, last_access, dob, weight, sex, member_since_date, birthdate, mail, nickname, notes, phone, address, family_id
FROM user
WHERE deleted = 0
ORDER BY last_access DESC
"
)
.fetch_all(db)
.await
.unwrap()
}
pub async fn all_with_role(db: &SqlitePool, role: &Role) -> Vec<Self> {
sqlx::query_as!(
Self,
"
SELECT id, name, pw, deleted, last_access, dob, weight, sex, member_since_date, birthdate, mail, nickname, notes, phone, address, family_id
FROM user u
JOIN user_role ur ON u.id = ur.user_id
WHERE ur.role_id = ? AND deleted = 0
ORDER BY name;
", role.id
)
.fetch_all(db)
.await
.unwrap()
}
pub async fn all_payer_groups(db: &SqlitePool) -> Vec<Self> {
sqlx::query_as!(
Self,
"
SELECT id, name, pw, deleted, last_access, dob, weight, sex, member_since_date, birthdate, mail, nickname, notes, phone, address, family_id FROM user
WHERE family_id IS NOT NULL
GROUP BY family_id
UNION
-- Select users with a null family_id, without grouping
SELECT id, name, pw, deleted, last_access, dob, weight, sex, member_since_date, birthdate, mail, nickname, notes, phone, address, family_id FROM user
WHERE family_id IS NULL;
"
)
.fetch_all(db)
.await
.unwrap()
}
pub async fn ergo(db: &SqlitePool) -> Vec<Self> {
sqlx::query_as!(
Self,
"
SELECT id, name, pw, deleted, last_access, dob, weight, sex, member_since_date, birthdate, mail, nickname, notes, phone, address, family_id
FROM user
WHERE deleted = 0 AND dob != '' and weight != '' and sex != ''
ORDER BY name
"
)
.fetch_all(db)
.await
.unwrap()
}
pub async fn cox(db: &SqlitePool) -> Vec<Self> {
sqlx::query_as!(
Self,
"
SELECT id, name, pw, deleted, last_access, dob, weight, sex, member_since_date, birthdate, mail, nickname, notes, phone, address, family_id
FROM user
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
"
)
.fetch_all(db)
.await
.unwrap()
}
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) {
let mut family_id = data.family_id;
if family_id.is_some_and(|x| x == -1) {
family_id = Some(Family::insert(db).await)
}
sqlx::query!(
"UPDATE user SET dob = ?, weight = ?, sex = ?, member_since_date=?, birthdate=?, mail=?, nickname=?, notes=?, phone=?, address=?, family_id = ? where id = ?",
data.dob,
data.weight,
data.sex,
data.member_since_date,
data.birthdate,
data.mail,
data.nickname,
data.notes,
data.phone,
data.address,
family_id,
self.id
)
.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() {
self.add_role(
db,
&Role::find_by_id(db, role_id.parse::<i32>().unwrap())
.await
.unwrap(),
)
.await;
}
}
pub async fn add_role(&self, db: &SqlitePool, role: &Role) {
sqlx::query!(
"INSERT INTO user_role(user_id, role_id) VALUES (?, ?)",
self.id,
role.id
)
.execute(db)
.await
.unwrap();
}
pub async fn remove_role(&self, db: &SqlitePool, role: &Role) {
sqlx::query!(
"DELETE FROM user_role WHERE user_id = ? and role_id = ?",
self.id,
role.id
)
.execute(db)
.await
.unwrap();
}
pub async fn login(db: &SqlitePool, name: &str, pw: &str) -> Result<Self, LoginError> {
let name = name.trim(); // just to make sure...
let Some(user) = User::find_by_name(db, name).await else {
if ![
"n-sageder",
"p-hofer",
"m-birner",
"s-sollberger",
"d-kortschak",
"wwwadmin",
"wadminw",
"admin",
"m sageder",
"d kortschak",
"a almousa",
"p hofer",
"s sollberger",
"n sageder",
"wp-system",
"s.sollberger",
"m.birner",
"m-sageder",
"a-almousa",
"m.sageder",
"n.sageder",
"a.almousa",
"p.hofer",
"d.kortschak",
"[login]",
]
.contains(&name)
{
Log::create(db, format!("Username ({name}) not found (tried to login)")).await;
}
return Err(LoginError::InvalidAuthenticationCombo); // Username not found
};
if user.deleted {
Log::create(
db,
format!("User ({name}) already deleted (tried to login)."),
)
.await;
return Err(LoginError::InvalidAuthenticationCombo); //User existed sometime ago; has
//been deleted
}
if let Some(user_pw) = user.pw.as_ref() {
let password_hash = &Self::get_hashed_pw(pw);
if password_hash == user_pw {
return Ok(user);
}
Log::create(db, format!("User {name} supplied the wrong PW")).await;
Err(LoginError::InvalidAuthenticationCombo)
} else {
info!("User {name} has no PW set");
Err(LoginError::NoPasswordSet(user))
}
}
pub async fn reset_pw(&self, db: &SqlitePool) {
sqlx::query!("UPDATE user SET pw = null where id = ?", self.id)
.execute(db)
.await
.unwrap(); //Okay, because we can only create a User of a valid id
}
pub async fn update_pw(&self, db: &SqlitePool, pw: &str) {
let pw = Self::get_hashed_pw(pw);
sqlx::query!("UPDATE user SET pw = ? where id = ?", pw, self.id)
.execute(db)
.await
.unwrap(); //Okay, because we can only create a User of a valid id
}
fn get_hashed_pw(pw: &str) -> String {
let salt = SaltString::from_b64("dS/X5/sPEKTj4Rzs/CuvzQ").unwrap();
let argon2 = Argon2::default();
argon2
.hash_password(pw.as_bytes(), &salt)
.unwrap()
.to_string()
}
pub async fn logged_in(&self, db: &SqlitePool) {
sqlx::query!(
"UPDATE user SET last_access = CURRENT_TIMESTAMP where id = ?",
self.id
)
.execute(db)
.await
.unwrap(); //Okay, because we can only create a User of a valid id
}
pub async fn delete(&self, db: &SqlitePool) {
sqlx::query!("UPDATE user SET deleted=1 WHERE id=?", self.id)
.execute(db)
.await
.unwrap(); //Okay, because we can only create a User of a valid id
}
pub async fn get_days(&self, db: &SqlitePool) -> Vec<Day> {
let mut days = Vec::new();
for i in 0..self.amount_days_to_show(db).await {
let date = (Local::now() + chrono::Duration::days(i)).date_naive();
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(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);
}
} else {
days.push(Day::new(db, date, true).await);
}
}
days
}
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
//days
end_of_year
.signed_duration_since(Local::now().date_naive())
.num_days()
+ 1
} else {
6
}
}
}
#[async_trait]
impl<'r> FromRequest<'r> for User {
type Error = LoginError;
async fn from_request(req: &'r Request<'_>) -> request::Outcome<Self, Self::Error> {
match req.cookies().get_private("loggedin_user") {
Some(user_id) => match user_id.value().parse::<i32>() {
Ok(user_id) => {
let db = req.rocket().state::<SqlitePool>().unwrap();
let Some(user) = User::find_by_id(db, user_id).await else {
return Outcome::Error((Status::Forbidden, LoginError::UserNotFound));
};
if user.deleted {
return Outcome::Error((Status::Forbidden, LoginError::UserDeleted));
}
user.logged_in(db).await;
let mut cookie = Cookie::new("loggedin_user", format!("{}", user.id));
cookie.set_expires(OffsetDateTime::now_utc() + Duration::weeks(2));
req.cookies().add_private(cookie);
Outcome::Success(user)
}
Err(_) => Outcome::Error((Status::Unauthorized, LoginError::DeserializationError)),
},
None => Outcome::Error((Status::Unauthorized, LoginError::NotLoggedIn)),
}
}
}
pub struct TechUser {
pub(crate) user: User,
}
impl Deref for TechUser {
type Target = User;
fn deref(&self) -> &Self::Target {
&self.user
}
}
#[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) => {
if user.has_role(db, "tech").await {
Outcome::Success(TechUser { user })
} else {
Outcome::Error((Status::Forbidden, LoginError::NotACox))
}
}
Outcome::Error(f) => Outcome::Error(f),
Outcome::Forward(f) => Outcome::Forward(f),
}
}
}
pub struct CoxUser {
user: User,
}
impl Deref for CoxUser {
type Target = User;
fn deref(&self) -> &Self::Target {
&self.user
}
}
impl CoxUser {
pub async fn new(db: &SqlitePool, user: User) -> Option<Self> {
if user.has_role(db, "cox").await {
Some(CoxUser { user })
} else {
None
}
}
}
#[async_trait]
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) => {
if user.has_role(db, "cox").await {
Outcome::Success(CoxUser { user })
} else {
Outcome::Error((Status::Forbidden, LoginError::NotACox))
}
}
Outcome::Error(f) => Outcome::Error(f),
Outcome::Forward(f) => Outcome::Forward(f),
}
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct AdminUser {
pub(crate) user: User,
}
#[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) => {
if user.has_role(db, "admin").await {
Outcome::Success(AdminUser { user })
} else {
Outcome::Forward(Status::Forbidden)
}
}
Outcome::Error(f) => Outcome::Error(f),
Outcome::Forward(f) => Outcome::Forward(f),
}
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct AllowedForPlannedTripsUser(pub(crate) User);
#[async_trait]
impl<'r> FromRequest<'r> for AllowedForPlannedTripsUser {
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) => {
if user.has_role(db, "Donau Linz").await | user.has_role(db, "scheckbuch").await {
Outcome::Success(AllowedForPlannedTripsUser(user))
} else {
Outcome::Error((Status::Forbidden, LoginError::NotACox))
}
}
Outcome::Error(f) => Outcome::Error(f),
Outcome::Forward(f) => Outcome::Forward(f),
}
}
}
impl From<AllowedForPlannedTripsUser> for User {
fn from(val: AllowedForPlannedTripsUser) -> Self {
val.0
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct DonauLinzUser(pub(crate) User);
impl From<DonauLinzUser> for User {
fn from(val: DonauLinzUser) -> Self {
val.0
}
}
impl Deref for DonauLinzUser {
type Target = User;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[async_trait]
impl<'r> FromRequest<'r> for DonauLinzUser {
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) => {
if user.has_role(db, "Donau Linz").await
&& !user.has_role(db, "Unterstützend").await
&& !user.has_role(db, "Förderndes Mitglied").await
{
Outcome::Success(DonauLinzUser(user))
} else {
Outcome::Error((Status::Forbidden, LoginError::NotACox))
}
}
Outcome::Error(f) => Outcome::Error(f),
Outcome::Forward(f) => Outcome::Forward(f),
}
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct SchnupperBetreuerUser(pub(crate) User);
impl From<SchnupperBetreuerUser> for User {
fn from(val: SchnupperBetreuerUser) -> Self {
val.0
}
}
impl Deref for SchnupperBetreuerUser {
type Target = User;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[async_trait]
impl<'r> FromRequest<'r> for SchnupperBetreuerUser {
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) => {
if user.has_role(db, "schnupper-betreuer").await {
Outcome::Success(SchnupperBetreuerUser(user))
} else {
Outcome::Forward(Status::Forbidden)
}
}
Outcome::Error(f) => Outcome::Error(f),
Outcome::Forward(f) => Outcome::Forward(f),
}
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct VorstandUser(pub(crate) User);
impl From<VorstandUser> for User {
fn from(val: VorstandUser) -> Self {
val.0
}
}
impl Deref for VorstandUser {
type Target = User;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[async_trait]
impl<'r> FromRequest<'r> for VorstandUser {
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) => {
if user.has_role(db, "Vorstand").await {
Outcome::Success(VorstandUser(user))
} else {
Outcome::Forward(Status::Forbidden)
}
}
Outcome::Error(f) => Outcome::Error(f),
Outcome::Forward(f) => Outcome::Forward(f),
}
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct PlannedEventUser(pub(crate) User);
impl From<PlannedEventUser> for User {
fn from(val: PlannedEventUser) -> Self {
val.0
}
}
impl Deref for PlannedEventUser {
type Target = User;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[async_trait]
impl<'r> FromRequest<'r> for PlannedEventUser {
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) => {
if user.has_role(db, "planned_event").await {
Outcome::Success(PlannedEventUser(user))
} else {
Outcome::Error((Status::Forbidden, LoginError::NotACox))
}
}
Outcome::Error(f) => Outcome::Error(f),
Outcome::Forward(f) => Outcome::Forward(f),
}
}
}
#[cfg(test)]
mod test {
use std::collections::HashMap;
use crate::{tera::admin::user::UserEditForm, testdb};
use super::User;
use sqlx::SqlitePool;
#[sqlx::test]
fn test_find_correct_id() {
let pool = testdb!();
let user = User::find_by_id(&pool, 1).await.unwrap();
assert_eq!(user.id, 1);
}
#[sqlx::test]
fn test_find_wrong_id() {
let pool = testdb!();
let user = User::find_by_id(&pool, 1337).await;
assert!(user.is_none());
}
#[sqlx::test]
fn test_find_correct_name() {
let pool = testdb!();
let user = User::find_by_name(&pool, "admin".into()).await.unwrap();
assert_eq!(user.id, 1);
}
#[sqlx::test]
fn test_find_wrong_name() {
let pool = testdb!();
let user = User::find_by_name(&pool, "name-does-not-exist".into()).await;
assert!(user.is_none());
}
#[sqlx::test]
fn test_all() {
let pool = testdb!();
let res = User::all(&pool).await;
assert!(res.len() > 3);
}
#[sqlx::test]
fn test_cox() {
let pool = testdb!();
let res = User::cox(&pool).await;
assert_eq!(res.len(), 3);
}
#[sqlx::test]
fn test_succ_create() {
let pool = testdb!();
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()).await, false);
}
#[sqlx::test]
fn test_update() {
let pool = testdb!();
let user = User::find_by_id(&pool, 1).await.unwrap();
user.update(
&pool,
UserEditForm {
id: 1,
dob: None,
weight: None,
sex: Some("m".into()),
roles: HashMap::new(),
member_since_date: None,
birthdate: None,
mail: None,
nickname: None,
notes: None,
phone: None,
address: None,
family_id: None,
},
)
.await;
let user = User::find_by_id(&pool, 1).await.unwrap();
assert_eq!(user.sex, Some("m".into()));
}
#[sqlx::test]
fn succ_login_with_test_db() {
let pool = testdb!();
User::login(&pool, "admin".into(), "admin".into())
.await
.unwrap();
}
#[sqlx::test]
fn wrong_pw() {
let pool = testdb!();
assert!(User::login(&pool, "admin".into(), "admi".into())
.await
.is_err());
}
#[sqlx::test]
fn wrong_username() {
let pool = testdb!();
assert!(User::login(&pool, "admi".into(), "admin".into())
.await
.is_err());
}
#[sqlx::test]
fn reset() {
let pool = testdb!();
let user = User::find_by_id(&pool, 1).await.unwrap();
user.reset_pw(&pool).await;
let user = User::find_by_id(&pool, 1).await.unwrap();
assert_eq!(user.pw, None);
}
#[sqlx::test]
fn update_pw() {
let pool = testdb!();
let user = User::find_by_id(&pool, 1).await.unwrap();
assert!(User::login(&pool, "admin".into(), "abc".into())
.await
.is_err());
user.update_pw(&pool, "abc".into()).await;
User::login(&pool, "admin".into(), "abc".into())
.await
.unwrap();
}
}