Skip to content

Commit

Permalink
frontend: allow admins to change a users role
Browse files Browse the repository at this point in the history
  • Loading branch information
flosse committed May 19, 2019
1 parent c8aab65 commit 4588b63
Show file tree
Hide file tree
Showing 17 changed files with 388 additions and 111 deletions.
1 change: 1 addition & 0 deletions src/core/db.rs
Expand Up @@ -39,6 +39,7 @@ pub trait UserGateway {
fn create_user(&self, user: User) -> Result<()>;
fn update_user(&self, user: &User) -> Result<()>;
fn get_user(&self, username: &str) -> Result<User>;
//TODO make email => user relation unique
fn get_users_by_email(&self, email: &str) -> Result<Vec<User>>;
fn all_users(&self) -> Result<Vec<User>>;
fn delete_user(&self, username: &str) -> Result<()>;
Expand Down
2 changes: 1 addition & 1 deletion src/core/entities.rs
Expand Up @@ -115,7 +115,7 @@ pub struct User {
}

#[rustfmt::skip]
#[derive(Debug, Clone, Copy, PartialEq, FromPrimitive, ToPrimitive)]
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, FromPrimitive, ToPrimitive)]
pub enum Role {
Guest = 0,
User = 1,
Expand Down
8 changes: 7 additions & 1 deletion src/core/error.rs
Expand Up @@ -32,7 +32,10 @@ quick_error! {
description("Invalid username")
}
UserExists{
description("The user already exits")
description("The user already exists")
}
UserDoesNotExist{
description("The user does not exist")
}
Password{
description("Invalid password")
Expand Down Expand Up @@ -73,6 +76,9 @@ quick_error! {
InvalidLimit{
description("Invalid limit")
}
Role{
description("Invalid role")
}
}
}

Expand Down
14 changes: 10 additions & 4 deletions src/core/usecases/archive_comments.rs
@@ -1,8 +1,14 @@
use crate::core::prelude::*;

pub fn archive_comments<D: Db>(db: &D, ids: &[&str]) -> Result<()> {
pub fn archive_comments<D: Db>(db: &D, user_email: &str, ids: &[&str]) -> Result<()> {
info!("Archiving {} comments", ids.len());
let archived = Timestamp::now();
db.archive_comments(ids, archived)?;
Ok(())
let users = db.get_users_by_email(user_email)?;
if let Some(user) = users.first() {
if user.role >= Role::Scout {
let archived = Timestamp::now();
db.archive_comments(ids, archived)?;
return Ok(());
}
}
Err(ParameterError::Forbidden.into())
}
16 changes: 11 additions & 5 deletions src/core/usecases/archive_ratings.rs
@@ -1,9 +1,15 @@
use crate::core::prelude::*;

pub fn archive_ratings<D: Db>(db: &D, ids: &[&str]) -> Result<()> {
pub fn archive_ratings<D: Db>(db: &D, user_email: &str, ids: &[&str]) -> Result<()> {
debug!("Archiving ratings {:?}", ids);
let archived = Timestamp::now();
db.archive_comments_of_ratings(ids, archived)?;
db.archive_ratings(ids, archived)?;
Ok(())
let users = db.get_users_by_email(user_email)?;
if let Some(user) = users.first() {
if user.role >= Role::Scout {
let archived = Timestamp::now();
db.archive_comments_of_ratings(ids, archived)?;
db.archive_ratings(ids, archived)?;
return Ok(());
}
}
Err(ParameterError::Forbidden.into())
}
26 changes: 26 additions & 0 deletions src/core/usecases/change_user_role.rs
@@ -0,0 +1,26 @@
use crate::core::prelude::*;

pub fn change_user_role<D: Db>(
db: &D,
account_email: &str,
user_email: &str,
role: Role,
) -> Result<()> {
info!("Changing role to {:?} for {}", role, user_email);
let accounts = db.get_users_by_email(account_email)?;
let users = db.get_users_by_email(user_email)?;
let account = accounts
.first()
.ok_or_else(|| ParameterError::UserDoesNotExist)?;
let mut user = users
.first()
.ok_or_else(|| ParameterError::UserDoesNotExist)?
.to_owned();
if account.role > user.role && role < account.role {
user.role = role;
db.update_user(&user)?;
Ok(())
} else {
Err(ParameterError::Forbidden.into())
}
}
7 changes: 4 additions & 3 deletions src/core/usecases/mod.rs
Expand Up @@ -13,6 +13,7 @@ mod archive_comments;
mod archive_entries;
mod archive_events;
mod archive_ratings;
mod change_user_role;
mod confirm_email;
mod create_new_entry;
mod create_new_event;
Expand All @@ -32,9 +33,9 @@ mod update_event;

pub use self::{
archive_comments::*, archive_entries::*, archive_events::*, archive_ratings::*,
confirm_email::*, create_new_entry::*, create_new_event::*, create_new_user::*,
delete_event::*, find_duplicates::*, indexing::*, login::*, query_events::*, rate_entry::*,
register::*, search::*, update_entry::*, update_event::*,
change_user_role::*, confirm_email::*, create_new_entry::*, create_new_event::*,
create_new_user::*, delete_event::*, find_duplicates::*, indexing::*, login::*,
query_events::*, rate_entry::*, register::*, search::*, update_entry::*, update_event::*,
};

pub fn load_ratings_with_comments<D: Db>(
Expand Down
30 changes: 25 additions & 5 deletions src/infrastructure/flows/archive_comments.rs
@@ -1,13 +1,17 @@
use super::*;

use diesel::connection::Connection;

pub fn archive_comments(connections: &sqlite::Connections, ids: &[&str]) -> Result<()> {
pub fn archive_comments(
connections: &sqlite::Connections,
account_email: &str,
ids: &[&str],
) -> Result<()> {
//TODO: check if user is allowed to archive the comments
let mut repo_err = None;
let connection = connections.exclusive()?;
Ok(connection
.transaction::<_, diesel::result::Error, _>(|| {
usecases::archive_comments(&*connection, ids).map_err(|err| {
usecases::archive_comments(&*connection, account_email, ids).map_err(|err| {
warn!("Failed to archive {} comments: {}", ids.len(), err);
repo_err = Some(err);
diesel::result::Error::RollbackTransaction
Expand All @@ -26,14 +30,27 @@ pub fn archive_comments(connections: &sqlite::Connections, ids: &[&str]) -> Resu
mod tests {
use super::super::tests::prelude::*;

fn archive_comments(fixture: &EnvFixture, ids: &[&str]) -> super::Result<()> {
super::archive_comments(&fixture.db_connections, ids)
fn archive_comments(
fixture: &EnvFixture,
account_email: &str,
ids: &[&str],
) -> super::Result<()> {
super::archive_comments(&fixture.db_connections, account_email, ids)
}

#[test]
fn should_archive_multiple_comments_only_once() {
let fixture = EnvFixture::new();

fixture.create_user(
usecases::NewUser {
email: "scout@foo.tld".into(),
password: "123456".into(),
username: "foo".into(),
},
Some(Role::Scout),
);

let entry_ids = vec![
fixture.create_entry(0.into()),
fixture.create_entry(1.into()),
Expand Down Expand Up @@ -80,6 +97,7 @@ mod tests {
// Archive comments 1 and 2
assert!(archive_comments(
&fixture,
"scout@foo.tld",
&vec![&*rating_comment_ids[1].1, &*rating_comment_ids[2].1]
)
.is_ok());
Expand All @@ -101,6 +119,7 @@ mod tests {
// Try to archive comments 0 and 1 (already archived)
assert_not_found(archive_comments(
&fixture,
"scout@foo.tld",
&vec![&*rating_comment_ids[0].1, &*rating_comment_ids[1].1],
));

Expand All @@ -119,6 +138,7 @@ mod tests {
// Archive remaining comments
assert!(archive_comments(
&fixture,
"scout@foo.tld",
&vec![&*rating_comment_ids[0].1, &*rating_comment_ids[3].1]
)
.is_ok());
Expand Down
22 changes: 19 additions & 3 deletions src/infrastructure/flows/archive_ratings.rs
Expand Up @@ -2,12 +2,17 @@ use super::*;

use diesel::connection::Connection;

pub fn exec_archive_ratings(connections: &sqlite::Connections, ids: &[&str]) -> Result<()> {
pub fn exec_archive_ratings(
connections: &sqlite::Connections,
account_email: &str,
ids: &[&str],
) -> Result<()> {
//TODO: check if user is allowed to archive the ratings
let mut repo_err = None;
let connection = connections.exclusive()?;
Ok(connection
.transaction::<_, diesel::result::Error, _>(|| {
usecases::archive_ratings(&*connection, ids).map_err(|err| {
usecases::archive_ratings(&*connection, account_email, ids).map_err(|err| {
warn!("Failed to archive {} ratings: {}", ids.len(), err);
repo_err = Some(err);
diesel::result::Error::RollbackTransaction
Expand Down Expand Up @@ -71,9 +76,10 @@ pub fn post_archive_ratings(
pub fn archive_ratings(
connections: &sqlite::Connections,
indexer: &mut EntryIndexer,
account_email: &str,
ids: &[&str],
) -> Result<()> {
exec_archive_ratings(connections, ids)?;
exec_archive_ratings(connections, account_email, ids)?;
post_archive_ratings(connections, indexer, ids)?;
Ok(())
}
Expand All @@ -86,6 +92,7 @@ mod tests {
super::archive_ratings(
&fixture.db_connections,
&mut *fixture.search_engine.borrow_mut(),
"scout@foo.tld",
ids,
)
}
Expand All @@ -94,6 +101,15 @@ mod tests {
fn should_archive_multiple_ratings_only_once() {
let fixture = EnvFixture::new();

fixture.create_user(
usecases::NewUser {
email: "scout@foo.tld".into(),
password: "123456".into(),
username: "foo".into(),
},
Some(Role::Scout),
);

let entry_ids = vec![
fixture.create_entry(0.into()),
fixture.create_entry(1.into()),
Expand Down
73 changes: 73 additions & 0 deletions src/infrastructure/flows/change_user_role.rs
@@ -0,0 +1,73 @@
use super::*;
use diesel::connection::Connection;

pub fn change_user_role(
connections: &sqlite::Connections,
account_email: &str,
user_email: &str,
role: Role,
) -> Result<()> {
let mut repo_err = None;
let connection = connections.exclusive()?;
Ok(connection
.transaction::<_, diesel::result::Error, _>(|| {
usecases::change_user_role(&*connection, account_email, user_email, role).map_err(
|err| {
warn!("Failed to chage role for email {}: {}", user_email, err);
repo_err = Some(err);
diesel::result::Error::RollbackTransaction
},
)
})
.map_err(|err| {
if let Some(repo_err) = repo_err {
repo_err
} else {
RepoError::from(err).into()
}
})?)
}

#[cfg(test)]
mod tests {
use super::super::tests::prelude::*;

fn change_user_role(
fixture: &EnvFixture,
account_email: &str,
user_email: &str,
role: Role,
) -> super::Result<()> {
super::change_user_role(&fixture.db_connections, account_email, user_email, role)
}

#[test]
fn should_change_the_role_to_scout_if_its_done_by_an_admin() {
let fixture = EnvFixture::new();
fixture.create_user(
usecases::NewUser {
email: "user@bar.tld".into(),
password: "123456".into(),
username: "user".into(),
},
None,
);
fixture.create_user(
usecases::NewUser {
email: "admin@foo.tld".into(),
password: "123456".into(),
username: "admin".into(),
},
Some(Role::Admin),
);
assert_eq!(
fixture.try_get_user("user@bar.tld").unwrap().role,
Role::Guest
);
assert!(change_user_role(&fixture, "admin@foo.tld", "user@bar.tld", Role::Scout).is_ok());
assert_eq!(
fixture.try_get_user("user@bar.tld").unwrap().role,
Role::Scout
);
}
}
32 changes: 30 additions & 2 deletions src/infrastructure/flows/mod.rs
Expand Up @@ -2,21 +2,21 @@ mod archive_comments;
mod archive_entries;
mod archive_events;
mod archive_ratings;
mod change_user_role;
mod create_entry;
mod create_rating;
mod update_entry;

pub mod prelude {
pub use super::{
archive_comments::*, archive_entries::*, archive_events::*, archive_ratings::*,
create_entry::*, create_rating::*, update_entry::*,
change_user_role::*, create_entry::*, create_rating::*, update_entry::*,
};
}

pub type Result<T> = std::result::Result<T, error::AppError>;

pub(crate) use super::{db::sqlite, error, notify};

pub(crate) use crate::core::{prelude::*, usecases};

#[cfg(test)]
Expand Down Expand Up @@ -90,6 +90,34 @@ mod tests {
.unwrap()
}

pub fn create_user(self: &EnvFixture, new_user: usecases::NewUser, role: Option<Role>) {
let email = {
let mut db = self.db_connections.exclusive().unwrap();
let email = new_user.email.clone();
usecases::create_new_user(&mut *db, new_user).unwrap();
email
};
if let Some(role) = role {
let mut u = self.try_get_user(&email).unwrap();
u.role = role;
let db = self.db_connections.exclusive().unwrap();
db.update_user(&u).unwrap();
}
}

pub fn try_get_user(self: &EnvFixture, email: &str) -> Option<User> {
match self
.db_connections
.shared()
.unwrap()
.get_users_by_email(email)
{
Ok(users) => users.first().cloned(),
Err(RepoError::NotFound) => None,
x => x.map(|_| None).unwrap(),
}
}

pub fn try_get_entry(self: &EnvFixture, id: &str) -> Option<Entry> {
match self.db_connections.shared().unwrap().get_entry(id) {
Ok(entry) => Some(entry),
Expand Down

0 comments on commit 4588b63

Please sign in to comment.