user-role-cluster #760
@ -27,7 +27,8 @@ CREATE TABLE IF NOT EXISTS "family" (
|
|||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS "role" (
|
CREATE TABLE IF NOT EXISTS "role" (
|
||||||
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
|
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||||
"name" text NOT NULL UNIQUE
|
"name" text NOT NULL UNIQUE,
|
||||||
|
"cluster" text
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS "user_role" (
|
CREATE TABLE IF NOT EXISTS "user_role" (
|
||||||
@ -220,3 +221,16 @@ CREATE TABLE IF NOT EXISTS "distance" (
|
|||||||
"distance_in_km" integer NOT NULL
|
"distance_in_km" integer NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX one_role_per_group_per_user ON user_role
|
||||||
|
WHERE EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM role r1
|
||||||
|
JOIN role r2 ON r1.id = user_role.role_id
|
||||||
|
WHERE r1."group" = r2."group"
|
||||||
|
AND r2.id IN (
|
||||||
|
SELECT role_id
|
||||||
|
FROM user_role ur2
|
||||||
|
WHERE ur2.user_id = user_role.user_id
|
||||||
|
)
|
||||||
|
);
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
|
use std::ops::DerefMut;
|
||||||
|
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use sqlx::{sqlite::SqliteQueryResult, FromRow, SqlitePool};
|
use sqlx::{sqlite::SqliteQueryResult, FromRow, Sqlite, SqlitePool, Transaction};
|
||||||
|
|
||||||
use super::user::User;
|
use super::user::User;
|
||||||
|
|
||||||
@ -22,6 +24,15 @@ impl Family {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn insert_tx(db: &mut Transaction<'_, Sqlite>) -> i64 {
|
||||||
|
let result: SqliteQueryResult = sqlx::query("INSERT INTO family DEFAULT VALUES")
|
||||||
|
.execute(db.deref_mut())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
result.last_insert_rowid()
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn insert(db: &SqlitePool) -> i64 {
|
pub async fn insert(db: &SqlitePool) -> i64 {
|
||||||
let result: SqliteQueryResult = sqlx::query("INSERT INTO family DEFAULT VALUES")
|
let result: SqliteQueryResult = sqlx::query("INSERT INTO family DEFAULT VALUES")
|
||||||
.execute(db)
|
.execute(db)
|
||||||
|
@ -7,11 +7,12 @@ use sqlx::{FromRow, Sqlite, SqlitePool, Transaction};
|
|||||||
pub struct Role {
|
pub struct Role {
|
||||||
pub(crate) id: i64,
|
pub(crate) id: i64,
|
||||||
pub(crate) name: String,
|
pub(crate) name: String,
|
||||||
|
pub(crate) cluster: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Role {
|
impl Role {
|
||||||
pub async fn all(db: &SqlitePool) -> Vec<Role> {
|
pub async fn all(db: &SqlitePool) -> Vec<Role> {
|
||||||
sqlx::query_as!(Role, "SELECT id, name FROM role")
|
sqlx::query_as!(Role, "SELECT id, name, cluster FROM role")
|
||||||
.fetch_all(db)
|
.fetch_all(db)
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
@ -21,7 +22,7 @@ impl Role {
|
|||||||
sqlx::query_as!(
|
sqlx::query_as!(
|
||||||
Self,
|
Self,
|
||||||
"
|
"
|
||||||
SELECT id, name
|
SELECT id, name, cluster
|
||||||
FROM role
|
FROM role
|
||||||
WHERE id like ?
|
WHERE id like ?
|
||||||
",
|
",
|
||||||
@ -31,12 +32,41 @@ WHERE id like ?
|
|||||||
.await
|
.await
|
||||||
.ok()
|
.ok()
|
||||||
}
|
}
|
||||||
|
pub async fn find_by_id_tx(db: &mut Transaction<'_, Sqlite>, name: i32) -> Option<Self> {
|
||||||
|
sqlx::query_as!(
|
||||||
|
Self,
|
||||||
|
"
|
||||||
|
SELECT id, name, cluster
|
||||||
|
FROM role
|
||||||
|
WHERE id like ?
|
||||||
|
",
|
||||||
|
name
|
||||||
|
)
|
||||||
|
.fetch_one(db.deref_mut())
|
||||||
|
.await
|
||||||
|
.ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn find_by_cluster_tx(db: &mut Transaction<'_, Sqlite>, name: i32) -> Option<Self> {
|
||||||
|
sqlx::query_as!(
|
||||||
|
Self,
|
||||||
|
"
|
||||||
|
SELECT id, name, cluster
|
||||||
|
FROM role
|
||||||
|
WHERE cluster = ?
|
||||||
|
",
|
||||||
|
name
|
||||||
|
)
|
||||||
|
.fetch_one(db.deref_mut())
|
||||||
|
.await
|
||||||
|
.ok()
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn find_by_name(db: &SqlitePool, name: &str) -> Option<Self> {
|
pub async fn find_by_name(db: &SqlitePool, name: &str) -> Option<Self> {
|
||||||
sqlx::query_as!(
|
sqlx::query_as!(
|
||||||
Self,
|
Self,
|
||||||
"
|
"
|
||||||
SELECT id, name
|
SELECT id, name, cluster
|
||||||
FROM role
|
FROM role
|
||||||
WHERE name like ?
|
WHERE name like ?
|
||||||
",
|
",
|
||||||
@ -51,7 +81,7 @@ WHERE name like ?
|
|||||||
sqlx::query_as!(
|
sqlx::query_as!(
|
||||||
Self,
|
Self,
|
||||||
"
|
"
|
||||||
SELECT id, name
|
SELECT id, name, cluster
|
||||||
FROM role
|
FROM role
|
||||||
WHERE name like ?
|
WHERE name like ?
|
||||||
",
|
",
|
||||||
|
@ -490,6 +490,17 @@ ASKÖ Ruderverein Donau Linz", self.name),
|
|||||||
_ => true,
|
_ => true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub async fn has_membership_pdf_tx(&self, db: &mut Transaction<'_, Sqlite>) -> bool {
|
||||||
|
match sqlx::query_scalar!("SELECT membership_pdf FROM user WHERE id = ?", self.id)
|
||||||
|
.fetch_one(db.deref_mut())
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
{
|
||||||
|
Some(a) if a.is_empty() => false,
|
||||||
|
None => false,
|
||||||
|
_ => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn roles(&self, db: &SqlitePool) -> Vec<String> {
|
pub async fn roles(&self, db: &SqlitePool) -> Vec<String> {
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
@ -697,14 +708,16 @@ ORDER BY last_access DESC
|
|||||||
.is_ok()
|
.is_ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn update(&self, db: &SqlitePool, data: UserEditForm<'_>) {
|
pub async fn update(&self, db: &SqlitePool, data: UserEditForm<'_>) -> Result<(), String> {
|
||||||
|
let mut db = db.begin().await.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
let mut family_id = data.family_id;
|
let mut family_id = data.family_id;
|
||||||
|
|
||||||
if family_id.is_some_and(|x| x == -1) {
|
if family_id.is_some_and(|x| x == -1) {
|
||||||
family_id = Some(Family::insert(db).await)
|
family_id = Some(Family::insert_tx(&mut db).await)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !self.has_membership_pdf(db).await {
|
if !self.has_membership_pdf_tx(&mut db).await {
|
||||||
if let Some(membership_pdf) = data.membership_pdf {
|
if let Some(membership_pdf) = data.membership_pdf {
|
||||||
let mut stream = membership_pdf.open().await.unwrap();
|
let mut stream = membership_pdf.open().await.unwrap();
|
||||||
let mut buffer = Vec::new();
|
let mut buffer = Vec::new();
|
||||||
@ -714,7 +727,7 @@ ORDER BY last_access DESC
|
|||||||
buffer,
|
buffer,
|
||||||
self.id
|
self.id
|
||||||
)
|
)
|
||||||
.execute(db)
|
.execute(db.deref_mut())
|
||||||
.await
|
.await
|
||||||
.unwrap(); //Okay, because we can only create a User of a valid id
|
.unwrap(); //Okay, because we can only create a User of a valid id
|
||||||
}
|
}
|
||||||
@ -735,28 +748,29 @@ ORDER BY last_access DESC
|
|||||||
family_id,
|
family_id,
|
||||||
self.id
|
self.id
|
||||||
)
|
)
|
||||||
.execute(db)
|
.execute(db.deref_mut())
|
||||||
.await
|
.await
|
||||||
.unwrap(); //Okay, because we can only create a User of a valid id
|
.unwrap(); //Okay, because we can only create a User of a valid id
|
||||||
|
|
||||||
// handle roles
|
// handle roles
|
||||||
sqlx::query!("DELETE FROM user_role WHERE user_id = ?", self.id)
|
sqlx::query!("DELETE FROM user_role WHERE user_id = ?", self.id)
|
||||||
.execute(db)
|
.execute(db.deref_mut())
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
for role_id in data.roles.into_keys() {
|
for role_id in data.roles.into_keys() {
|
||||||
self.add_role(
|
let role = Role::find_by_id_tx(&mut db, role_id.parse::<i32>().unwrap())
|
||||||
db,
|
|
||||||
&Role::find_by_id(db, role_id.parse::<i32>().unwrap())
|
|
||||||
.await
|
.await
|
||||||
.unwrap(),
|
.unwrap();
|
||||||
)
|
self.add_role_tx(&mut db, &role).await?;
|
||||||
.await;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn add_role(&self, db: &SqlitePool, role: &Role) {
|
db.commit().await.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn add_role(&self, db: &SqlitePool, role: &Role) -> Result<(), String> {
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
"INSERT INTO user_role(user_id, role_id) VALUES (?, ?)",
|
"INSERT INTO user_role(user_id, role_id) VALUES (?, ?)",
|
||||||
self.id,
|
self.id,
|
||||||
@ -764,7 +778,40 @@ ORDER BY last_access DESC
|
|||||||
)
|
)
|
||||||
.execute(db)
|
.execute(db)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.map_err(|_| {
|
||||||
|
format!(
|
||||||
|
"User already has a role in the cluster '{}'",
|
||||||
|
role.cluster
|
||||||
|
.clone()
|
||||||
|
.expect("db trigger can't activate on empty string")
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn add_role_tx(
|
||||||
|
&self,
|
||||||
|
db: &mut Transaction<'_, Sqlite>,
|
||||||
|
role: &Role,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
sqlx::query!(
|
||||||
|
"INSERT INTO user_role(user_id, role_id) VALUES (?, ?)",
|
||||||
|
self.id,
|
||||||
|
role.id
|
||||||
|
)
|
||||||
|
.execute(db.deref_mut())
|
||||||
|
.await
|
||||||
|
.map_err(|_| {
|
||||||
|
format!(
|
||||||
|
"User already has a role in the cluster '{}'",
|
||||||
|
role.cluster
|
||||||
|
.clone()
|
||||||
|
.expect("db trigger can't activate on empty string")
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn remove_role(&self, db: &SqlitePool, role: &Role) {
|
pub async fn remove_role(&self, db: &SqlitePool, role: &Role) {
|
||||||
|
@ -200,7 +200,8 @@ async fn fees_paid(
|
|||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
user.add_role(db, &Role::find_by_name(db, "paid").await.unwrap())
|
user.add_role(db, &Role::find_by_name(db, "paid").await.unwrap())
|
||||||
.await;
|
.await
|
||||||
|
.expect("paid role has no group");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -305,9 +306,10 @@ async fn update(
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
user.update(db, data.into_inner()).await;
|
match user.update(db, data.into_inner()).await {
|
||||||
|
Ok(_) => Flash::success(Redirect::to("/admin/user"), "Successfully updated user"),
|
||||||
Flash::success(Redirect::to("/admin/user"), "Successfully updated user")
|
Err(e) => Flash::error(Redirect::to("/admin/user"), e),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/user/<user>/membership")]
|
#[get("/user/<user>/membership")]
|
||||||
@ -394,7 +396,9 @@ async fn create_scheckbuch(
|
|||||||
|
|
||||||
// 4. Add 'scheckbuch' role
|
// 4. Add 'scheckbuch' role
|
||||||
let scheckbuch = Role::find_by_name(db, "scheckbuch").await.unwrap();
|
let scheckbuch = Role::find_by_name(db, "scheckbuch").await.unwrap();
|
||||||
user.add_role(db, &scheckbuch).await;
|
user.add_role(db, &scheckbuch)
|
||||||
|
.await
|
||||||
|
.expect("new user has no roles yet");
|
||||||
|
|
||||||
// 4. Send welcome mail (+ notification)
|
// 4. Send welcome mail (+ notification)
|
||||||
user.send_welcome_email(db, &config.smtp_pw).await.unwrap();
|
user.send_welcome_email(db, &config.smtp_pw).await.unwrap();
|
||||||
@ -434,10 +438,14 @@ async fn schnupper_to_scheckbuch(
|
|||||||
user.remove_role(db, &paid).await;
|
user.remove_role(db, &paid).await;
|
||||||
|
|
||||||
let scheckbuch = Role::find_by_name(db, "scheckbuch").await.unwrap();
|
let scheckbuch = Role::find_by_name(db, "scheckbuch").await.unwrap();
|
||||||
user.add_role(db, &scheckbuch).await;
|
user.add_role(db, &scheckbuch)
|
||||||
|
.await
|
||||||
|
.expect("just removed 'schnupperant' thus can't have a role with that group");
|
||||||
|
|
||||||
if let Some(no_einschreibgebuehr) = Role::find_by_name(db, "no-einschreibgebuehr").await {
|
if let Some(no_einschreibgebuehr) = Role::find_by_name(db, "no-einschreibgebuehr").await {
|
||||||
user.add_role(db, &no_einschreibgebuehr).await;
|
user.add_role(db, &no_einschreibgebuehr)
|
||||||
|
.await
|
||||||
|
.expect("role doesn't have a group");
|
||||||
}
|
}
|
||||||
|
|
||||||
user.send_welcome_email(db, &config.smtp_pw).await.unwrap();
|
user.send_welcome_email(db, &config.smtp_pw).await.unwrap();
|
||||||
|
@ -3,3 +3,35 @@ INSERT INTO user(name) VALUES('Marie');
|
|||||||
INSERT INTO "user_role" (user_id, role_id) VALUES((SELECT id from user where name = 'Marie'),(SELECT id FROM role where name = 'Donau Linz'));
|
INSERT INTO "user_role" (user_id, role_id) VALUES((SELECT id from user where name = 'Marie'),(SELECT id FROM role where name = 'Donau Linz'));
|
||||||
INSERT INTO user(name) VALUES('Philipp');
|
INSERT INTO user(name) VALUES('Philipp');
|
||||||
INSERT INTO "user_role" (user_id, role_id) VALUES((SELECT id from user where name = 'Philipp'),(SELECT id FROM role where name = 'Donau Linz'));
|
INSERT INTO "user_role" (user_id, role_id) VALUES((SELECT id from user where name = 'Philipp'),(SELECT id FROM role where name = 'Donau Linz'));
|
||||||
|
|
||||||
|
ALTER TABLE "role" ADD COLUMN "cluster" text;
|
||||||
|
CREATE TRIGGER prevent_multiple_roles_same_cluster
|
||||||
|
BEFORE INSERT ON user_role
|
||||||
|
BEGIN
|
||||||
|
SELECT CASE
|
||||||
|
WHEN EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM user_role ur
|
||||||
|
JOIN role r1 ON ur.role_id = r1.id
|
||||||
|
JOIN role r2 ON r1."cluster" = r2."cluster"
|
||||||
|
WHERE ur.user_id = NEW.user_id
|
||||||
|
AND r2.id = NEW.role_id
|
||||||
|
AND r1.id != NEW.role_id
|
||||||
|
)
|
||||||
|
THEN RAISE(ABORT, 'User already has a role in this cluster')
|
||||||
|
END;
|
||||||
|
END;
|
||||||
|
|
||||||
|
|
||||||
|
UPDATE role SET 'cluster'='skill' WHERE id=2;
|
||||||
|
UPDATE role SET 'cluster'='membership_type' WHERE id=3;
|
||||||
|
UPDATE role SET 'cluster'='skill' WHERE id=5;
|
||||||
|
UPDATE role SET 'cluster'='skill' WHERE id=6;
|
||||||
|
UPDATE role SET 'cluster'='membership_type' WHERE id=7;
|
||||||
|
UPDATE role SET 'cluster'='financial' WHERE id=8;
|
||||||
|
UPDATE role SET 'cluster'='membership_type' WHERE id=9;
|
||||||
|
UPDATE role SET 'cluster'='membership_type' WHERE id=14;
|
||||||
|
UPDATE role SET 'cluster'='financial' WHERE id=17;
|
||||||
|
UPDATE role SET 'cluster'='financial' WHERE id=18;
|
||||||
|
UPDATE role SET 'cluster'='membership_type' WHERE id=20;
|
||||||
|
UPDATE role SET 'cluster'='membership_type' WHERE id=22;
|
||||||
|
Loading…
Reference in New Issue
Block a user