mirror of
https://github.com/nitnelave/lldap.git
synced 2023-04-12 14:25:13 +00:00
graphql: Add a method to list groups
This commit is contained in:
parent
e4d6b122c5
commit
480f48f820
10
Cargo.lock
generated
10
Cargo.lock
generated
@ -1509,6 +1509,15 @@ dependencies = [
|
|||||||
"cfg-if 1.0.0",
|
"cfg-if 1.0.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itertools"
|
||||||
|
version = "0.10.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "69ddb889f9d0d08a67338271fa9b62996bc788c7796a5c18cf057420aaed5eaf"
|
||||||
|
dependencies = [
|
||||||
|
"either",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "0.4.8"
|
version = "0.4.8"
|
||||||
@ -1705,6 +1714,7 @@ dependencies = [
|
|||||||
"futures-util",
|
"futures-util",
|
||||||
"hmac 0.10.1",
|
"hmac 0.10.1",
|
||||||
"http",
|
"http",
|
||||||
|
"itertools",
|
||||||
"juniper",
|
"juniper",
|
||||||
"juniper_actix",
|
"juniper_actix",
|
||||||
"jwt",
|
"jwt",
|
||||||
|
@ -7,7 +7,7 @@ query GetUserDetails($id: String!) {
|
|||||||
lastName
|
lastName
|
||||||
creationDate
|
creationDate
|
||||||
groups {
|
groups {
|
||||||
id
|
displayName
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -219,7 +219,7 @@ impl Component for UserDetails {
|
|||||||
html! {
|
html! {
|
||||||
<tr>
|
<tr>
|
||||||
<td><button>{"-"}</button></td>
|
<td><button>{"-"}</button></td>
|
||||||
<td>{&group.id}</td>
|
<td>{&group.display_name}</td>
|
||||||
</tr>
|
</tr>
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -11,7 +11,8 @@ type Mutation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Group {
|
type Group {
|
||||||
id: String!
|
id: Int!
|
||||||
|
displayName: String!
|
||||||
"The groups to which this user belongs."
|
"The groups to which this user belongs."
|
||||||
users: [User!]!
|
users: [User!]!
|
||||||
}
|
}
|
||||||
@ -30,6 +31,13 @@ input RequestFilter {
|
|||||||
"DateTime"
|
"DateTime"
|
||||||
scalar DateTimeUtc
|
scalar DateTimeUtc
|
||||||
|
|
||||||
|
type Query {
|
||||||
|
apiVersion: String!
|
||||||
|
user(userId: String!): User!
|
||||||
|
users(filters: RequestFilter): [User!]!
|
||||||
|
groups: [Group!]!
|
||||||
|
}
|
||||||
|
|
||||||
"The details required to create a user."
|
"The details required to create a user."
|
||||||
input CreateUserInput {
|
input CreateUserInput {
|
||||||
id: String!
|
id: String!
|
||||||
@ -39,12 +47,6 @@ input CreateUserInput {
|
|||||||
lastName: String
|
lastName: String
|
||||||
}
|
}
|
||||||
|
|
||||||
type Query {
|
|
||||||
apiVersion: String!
|
|
||||||
user(userId: String!): User!
|
|
||||||
users(filters: RequestFilter): [User!]!
|
|
||||||
}
|
|
||||||
|
|
||||||
type User {
|
type User {
|
||||||
id: String!
|
id: String!
|
||||||
email: String!
|
email: String!
|
||||||
|
@ -45,6 +45,7 @@ tracing-subscriber = "*"
|
|||||||
rand = { version = "0.8", features = ["small_rng", "getrandom"] }
|
rand = { version = "0.8", features = ["small_rng", "getrandom"] }
|
||||||
juniper_actix = "0.4.0"
|
juniper_actix = "0.4.0"
|
||||||
juniper = "0.15.6"
|
juniper = "0.15.6"
|
||||||
|
itertools = "0.10.1"
|
||||||
|
|
||||||
# TODO: update to 0.6 when out.
|
# TODO: update to 0.6 when out.
|
||||||
[dependencies.opaque-ke]
|
[dependencies.opaque-ke]
|
||||||
|
@ -31,6 +31,7 @@ impl Default for User {
|
|||||||
|
|
||||||
#[derive(PartialEq, Eq, Debug, Serialize, Deserialize)]
|
#[derive(PartialEq, Eq, Debug, Serialize, Deserialize)]
|
||||||
pub struct Group {
|
pub struct Group {
|
||||||
|
pub id: GroupId,
|
||||||
pub display_name: String,
|
pub display_name: String,
|
||||||
pub users: Vec<String>,
|
pub users: Vec<String>,
|
||||||
}
|
}
|
||||||
@ -74,9 +75,12 @@ pub trait LoginHandler: Clone + Send {
|
|||||||
async fn bind(&self, request: BindRequest) -> Result<()>;
|
async fn bind(&self, request: BindRequest) -> Result<()>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
pub struct GroupId(pub i32);
|
pub struct GroupId(pub i32);
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
|
pub struct GroupIdAndName(pub GroupId, pub String);
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait BackendHandler: Clone + Send {
|
pub trait BackendHandler: Clone + Send {
|
||||||
async fn list_users(&self, filters: Option<RequestFilter>) -> Result<Vec<User>>;
|
async fn list_users(&self, filters: Option<RequestFilter>) -> Result<Vec<User>>;
|
||||||
@ -88,7 +92,7 @@ pub trait BackendHandler: Clone + Send {
|
|||||||
async fn create_group(&self, group_name: &str) -> Result<GroupId>;
|
async fn create_group(&self, group_name: &str) -> Result<GroupId>;
|
||||||
async fn add_user_to_group(&self, user_id: &str, group_id: GroupId) -> Result<()>;
|
async fn add_user_to_group(&self, user_id: &str, group_id: GroupId) -> Result<()>;
|
||||||
async fn remove_user_from_group(&self, user_id: &str, group_id: GroupId) -> Result<()>;
|
async fn remove_user_from_group(&self, user_id: &str, group_id: GroupId) -> Result<()>;
|
||||||
async fn get_user_groups(&self, user: &str) -> Result<HashSet<String>>;
|
async fn get_user_groups(&self, user: &str) -> Result<HashSet<GroupIdAndName>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@ -106,7 +110,7 @@ mockall::mock! {
|
|||||||
async fn update_user(&self, request: UpdateUserRequest) -> Result<()>;
|
async fn update_user(&self, request: UpdateUserRequest) -> Result<()>;
|
||||||
async fn delete_user(&self, user_id: &str) -> Result<()>;
|
async fn delete_user(&self, user_id: &str) -> Result<()>;
|
||||||
async fn create_group(&self, group_name: &str) -> Result<GroupId>;
|
async fn create_group(&self, group_name: &str) -> Result<GroupId>;
|
||||||
async fn get_user_groups(&self, user: &str) -> Result<HashSet<String>>;
|
async fn get_user_groups(&self, user: &str) -> Result<HashSet<GroupIdAndName>>;
|
||||||
async fn add_user_to_group(&self, user_id: &str, group_id: GroupId) -> Result<()>;
|
async fn add_user_to_group(&self, user_id: &str, group_id: GroupId) -> Result<()>;
|
||||||
async fn remove_user_from_group(&self, user_id: &str, group_id: GroupId) -> Result<()>;
|
async fn remove_user_from_group(&self, user_id: &str, group_id: GroupId) -> Result<()>;
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@ use super::{error::*, handler::*, sql_tables::*};
|
|||||||
use crate::infra::configuration::Configuration;
|
use crate::infra::configuration::Configuration;
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use futures_util::StreamExt;
|
use futures_util::StreamExt;
|
||||||
use futures_util::TryStreamExt;
|
|
||||||
use sea_query::{Expr, Iden, Order, Query, SimpleExpr};
|
use sea_query::{Expr, Iden, Order, Query, SimpleExpr};
|
||||||
use sqlx::Row;
|
use sqlx::Row;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
@ -76,6 +75,7 @@ impl BackendHandler for SqlBackendHandler {
|
|||||||
|
|
||||||
async fn list_groups(&self) -> Result<Vec<Group>> {
|
async fn list_groups(&self) -> Result<Vec<Group>> {
|
||||||
let query: String = Query::select()
|
let query: String = Query::select()
|
||||||
|
.column((Groups::Table, Groups::GroupId))
|
||||||
.column(Groups::DisplayName)
|
.column(Groups::DisplayName)
|
||||||
.column(Memberships::UserId)
|
.column(Memberships::UserId)
|
||||||
.from(Groups::Table)
|
.from(Groups::Table)
|
||||||
@ -88,32 +88,33 @@ impl BackendHandler for SqlBackendHandler {
|
|||||||
.order_by(Memberships::UserId, Order::Asc)
|
.order_by(Memberships::UserId, Order::Asc)
|
||||||
.to_string(DbQueryBuilder {});
|
.to_string(DbQueryBuilder {});
|
||||||
|
|
||||||
let mut results = sqlx::query(&query).fetch(&self.sql_pool);
|
// For group_by.
|
||||||
|
use itertools::Itertools;
|
||||||
let mut groups = Vec::new();
|
let mut groups = Vec::new();
|
||||||
// The rows are ordered by group, user, so we need to group them into vectors.
|
// The rows are returned sorted by display_name, equivalent to group_id. We group them by
|
||||||
|
// this key which gives us one element (`rows`) per group.
|
||||||
|
for ((group_id, display_name), rows) in &sqlx::query(&query)
|
||||||
|
.fetch_all(&self.sql_pool)
|
||||||
|
.await?
|
||||||
|
.into_iter()
|
||||||
|
.group_by(|row| {
|
||||||
|
(
|
||||||
|
GroupId(row.get::<i32, _>(&*Groups::GroupId.to_string())),
|
||||||
|
row.get::<String, _>(&*Groups::DisplayName.to_string()),
|
||||||
|
)
|
||||||
|
})
|
||||||
{
|
{
|
||||||
let mut current_group = String::new();
|
|
||||||
let mut current_users = Vec::new();
|
|
||||||
while let Some(row) = results.try_next().await? {
|
|
||||||
let display_name = row.get::<String, _>(&*Groups::DisplayName.to_string());
|
|
||||||
if display_name != current_group {
|
|
||||||
if !current_group.is_empty() {
|
|
||||||
groups.push(Group {
|
|
||||||
display_name: current_group,
|
|
||||||
users: current_users,
|
|
||||||
});
|
|
||||||
current_users = Vec::new();
|
|
||||||
}
|
|
||||||
current_group = display_name.clone();
|
|
||||||
}
|
|
||||||
current_users.push(row.get::<String, _>(&*Memberships::UserId.to_string()));
|
|
||||||
}
|
|
||||||
groups.push(Group {
|
groups.push(Group {
|
||||||
display_name: current_group,
|
id: group_id,
|
||||||
users: current_users,
|
display_name,
|
||||||
|
users: rows
|
||||||
|
.map(|row| row.get::<String, _>(&*Memberships::UserId.to_string()))
|
||||||
|
// If a group has no users, an empty string is returned because of the left
|
||||||
|
// join.
|
||||||
|
.filter(|s| !s.is_empty())
|
||||||
|
.collect(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(groups)
|
Ok(groups)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -135,13 +136,14 @@ impl BackendHandler for SqlBackendHandler {
|
|||||||
.await?)
|
.await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_user_groups(&self, user: &str) -> Result<HashSet<String>> {
|
async fn get_user_groups(&self, user: &str) -> Result<HashSet<GroupIdAndName>> {
|
||||||
if user == self.config.ldap_user_dn {
|
if user == self.config.ldap_user_dn {
|
||||||
let mut groups = HashSet::new();
|
let mut groups = HashSet::new();
|
||||||
groups.insert("lldap_admin".to_string());
|
groups.insert(GroupIdAndName(GroupId(1), "lldap_admin".to_string()));
|
||||||
return Ok(groups);
|
return Ok(groups);
|
||||||
}
|
}
|
||||||
let query: String = Query::select()
|
let query: String = Query::select()
|
||||||
|
.column((Groups::Table, Groups::GroupId))
|
||||||
.column(Groups::DisplayName)
|
.column(Groups::DisplayName)
|
||||||
.from(Groups::Table)
|
.from(Groups::Table)
|
||||||
.inner_join(
|
.inner_join(
|
||||||
@ -154,10 +156,15 @@ impl BackendHandler for SqlBackendHandler {
|
|||||||
|
|
||||||
sqlx::query(&query)
|
sqlx::query(&query)
|
||||||
// Extract the group id from the row.
|
// Extract the group id from the row.
|
||||||
.map(|row: DbRow| row.get::<String, _>(&*Groups::DisplayName.to_string()))
|
.map(|row: DbRow| {
|
||||||
|
GroupIdAndName(
|
||||||
|
row.get::<GroupId, _>(&*Groups::GroupId.to_string()),
|
||||||
|
row.get::<String, _>(&*Groups::DisplayName.to_string()),
|
||||||
|
)
|
||||||
|
})
|
||||||
.fetch(&self.sql_pool)
|
.fetch(&self.sql_pool)
|
||||||
// Collect the vector of rows, each potentially an error.
|
// Collect the vector of rows, each potentially an error.
|
||||||
.collect::<Vec<sqlx::Result<String>>>()
|
.collect::<Vec<sqlx::Result<GroupIdAndName>>>()
|
||||||
.await
|
.await
|
||||||
.into_iter()
|
.into_iter()
|
||||||
// Transform it into a single result (the first error if any), and group the group_ids
|
// Transform it into a single result (the first error if any), and group the group_ids
|
||||||
@ -468,6 +475,7 @@ mod tests {
|
|||||||
insert_user(&handler, "John", "Pa33w0rd!").await;
|
insert_user(&handler, "John", "Pa33w0rd!").await;
|
||||||
let group_1 = insert_group(&handler, "Best Group").await;
|
let group_1 = insert_group(&handler, "Best Group").await;
|
||||||
let group_2 = insert_group(&handler, "Worst Group").await;
|
let group_2 = insert_group(&handler, "Worst Group").await;
|
||||||
|
let group_3 = insert_group(&handler, "Empty Group").await;
|
||||||
insert_membership(&handler, group_1, "bob").await;
|
insert_membership(&handler, group_1, "bob").await;
|
||||||
insert_membership(&handler, group_1, "patrick").await;
|
insert_membership(&handler, group_1, "patrick").await;
|
||||||
insert_membership(&handler, group_2, "patrick").await;
|
insert_membership(&handler, group_2, "patrick").await;
|
||||||
@ -476,13 +484,20 @@ mod tests {
|
|||||||
handler.list_groups().await.unwrap(),
|
handler.list_groups().await.unwrap(),
|
||||||
vec![
|
vec![
|
||||||
Group {
|
Group {
|
||||||
|
id: group_1,
|
||||||
display_name: "Best Group".to_string(),
|
display_name: "Best Group".to_string(),
|
||||||
users: vec!["bob".to_string(), "patrick".to_string()]
|
users: vec!["bob".to_string(), "patrick".to_string()]
|
||||||
},
|
},
|
||||||
Group {
|
Group {
|
||||||
|
id: group_3,
|
||||||
|
display_name: "Empty Group".to_string(),
|
||||||
|
users: vec![]
|
||||||
|
},
|
||||||
|
Group {
|
||||||
|
id: group_2,
|
||||||
display_name: "Worst Group".to_string(),
|
display_name: "Worst Group".to_string(),
|
||||||
users: vec!["John".to_string(), "patrick".to_string()]
|
users: vec!["John".to_string(), "patrick".to_string()]
|
||||||
}
|
},
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -515,10 +530,10 @@ mod tests {
|
|||||||
insert_membership(&handler, group_1, "patrick").await;
|
insert_membership(&handler, group_1, "patrick").await;
|
||||||
insert_membership(&handler, group_2, "patrick").await;
|
insert_membership(&handler, group_2, "patrick").await;
|
||||||
let mut bob_groups = HashSet::new();
|
let mut bob_groups = HashSet::new();
|
||||||
bob_groups.insert("Group1".to_string());
|
bob_groups.insert(GroupIdAndName(group_1, "Group1".to_string()));
|
||||||
let mut patrick_groups = HashSet::new();
|
let mut patrick_groups = HashSet::new();
|
||||||
patrick_groups.insert("Group1".to_string());
|
patrick_groups.insert(GroupIdAndName(group_1, "Group1".to_string()));
|
||||||
patrick_groups.insert("Group2".to_string());
|
patrick_groups.insert(GroupIdAndName(group_2, "Group2".to_string()));
|
||||||
assert_eq!(handler.get_user_groups("bob").await.unwrap(), bob_groups);
|
assert_eq!(handler.get_user_groups("bob").await.unwrap(), bob_groups);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
handler.get_user_groups("patrick").await.unwrap(),
|
handler.get_user_groups("patrick").await.unwrap(),
|
||||||
|
@ -12,6 +12,31 @@ impl From<GroupId> for Value {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<DB> sqlx::Type<DB> for GroupId
|
||||||
|
where
|
||||||
|
DB: sqlx::Database,
|
||||||
|
i32: sqlx::Type<DB>,
|
||||||
|
{
|
||||||
|
fn type_info() -> <DB as sqlx::Database>::TypeInfo {
|
||||||
|
<i32 as sqlx::Type<DB>>::type_info()
|
||||||
|
}
|
||||||
|
fn compatible(ty: &<DB as sqlx::Database>::TypeInfo) -> bool {
|
||||||
|
<i32 as sqlx::Type<DB>>::compatible(ty)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'r, DB> sqlx::Decode<'r, DB> for GroupId
|
||||||
|
where
|
||||||
|
DB: sqlx::Database,
|
||||||
|
i32: sqlx::Decode<'r, DB>,
|
||||||
|
{
|
||||||
|
fn decode(
|
||||||
|
value: <DB as sqlx::database::HasValueRef<'r>>::ValueRef,
|
||||||
|
) -> Result<Self, Box<dyn std::error::Error + Sync + Send + 'static>> {
|
||||||
|
<i32 as sqlx::Decode<'r, DB>>::decode(value).map(GroupId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Iden)]
|
#[derive(Iden)]
|
||||||
pub enum Users {
|
pub enum Users {
|
||||||
Table,
|
Table,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
domain::{
|
domain::{
|
||||||
error::DomainError,
|
error::DomainError,
|
||||||
handler::{BackendHandler, BindRequest, LoginHandler},
|
handler::{BackendHandler, BindRequest, GroupIdAndName, LoginHandler},
|
||||||
opaque_handler::OpaqueHandler,
|
opaque_handler::OpaqueHandler,
|
||||||
},
|
},
|
||||||
infra::{
|
infra::{
|
||||||
@ -32,12 +32,12 @@ use time::ext::NumericalDuration;
|
|||||||
type Token<S> = jwt::Token<jwt::Header, JWTClaims, S>;
|
type Token<S> = jwt::Token<jwt::Header, JWTClaims, S>;
|
||||||
type SignedToken = Token<jwt::token::Signed>;
|
type SignedToken = Token<jwt::token::Signed>;
|
||||||
|
|
||||||
fn create_jwt(key: &Hmac<Sha512>, user: String, groups: HashSet<String>) -> SignedToken {
|
fn create_jwt(key: &Hmac<Sha512>, user: String, groups: HashSet<GroupIdAndName>) -> SignedToken {
|
||||||
let claims = JWTClaims {
|
let claims = JWTClaims {
|
||||||
exp: Utc::now() + chrono::Duration::days(1),
|
exp: Utc::now() + chrono::Duration::days(1),
|
||||||
iat: Utc::now(),
|
iat: Utc::now(),
|
||||||
user,
|
user,
|
||||||
groups,
|
groups: groups.into_iter().map(|g| g.1).collect(),
|
||||||
};
|
};
|
||||||
let header = jwt::Header {
|
let header = jwt::Header {
|
||||||
algorithm: jwt::AlgorithmType::Hs512,
|
algorithm: jwt::AlgorithmType::Hs512,
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
use crate::domain::handler::BackendHandler;
|
use crate::domain::handler::{BackendHandler, GroupIdAndName};
|
||||||
use juniper::{graphql_object, FieldResult, GraphQLInputObject};
|
use juniper::{graphql_object, FieldResult, GraphQLInputObject};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::convert::TryInto;
|
use std::convert::TryInto;
|
||||||
|
|
||||||
type DomainRequestFilter = crate::domain::handler::RequestFilter;
|
type DomainRequestFilter = crate::domain::handler::RequestFilter;
|
||||||
type DomainUser = crate::domain::handler::User;
|
type DomainUser = crate::domain::handler::User;
|
||||||
|
type DomainGroup = crate::domain::handler::Group;
|
||||||
use super::api::Context;
|
use super::api::Context;
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Debug, GraphQLInputObject)]
|
#[derive(PartialEq, Eq, Debug, GraphQLInputObject)]
|
||||||
@ -113,6 +114,17 @@ impl<Handler: BackendHandler + Sync> Query<Handler> {
|
|||||||
.await
|
.await
|
||||||
.map(|v| v.into_iter().map(Into::into).collect())?)
|
.map(|v| v.into_iter().map(Into::into).collect())?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn groups(context: &Context<Handler>) -> FieldResult<Vec<Group<Handler>>> {
|
||||||
|
if !context.validation_result.is_admin {
|
||||||
|
return Err("Unauthorized access to group list".into());
|
||||||
|
}
|
||||||
|
Ok(context
|
||||||
|
.handler
|
||||||
|
.list_groups()
|
||||||
|
.await
|
||||||
|
.map(|v| v.into_iter().map(Into::into).collect())?)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Debug, Serialize, Deserialize)]
|
#[derive(PartialEq, Eq, Debug, Serialize, Deserialize)]
|
||||||
@ -179,14 +191,19 @@ impl<Handler: BackendHandler> From<DomainUser> for User<Handler> {
|
|||||||
#[derive(PartialEq, Eq, Debug, Serialize, Deserialize)]
|
#[derive(PartialEq, Eq, Debug, Serialize, Deserialize)]
|
||||||
/// Represents a single group.
|
/// Represents a single group.
|
||||||
pub struct Group<Handler: BackendHandler> {
|
pub struct Group<Handler: BackendHandler> {
|
||||||
group_id: String,
|
group_id: i32,
|
||||||
|
display_name: String,
|
||||||
|
members: Option<Vec<String>>,
|
||||||
_phantom: std::marker::PhantomData<Box<Handler>>,
|
_phantom: std::marker::PhantomData<Box<Handler>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[graphql_object(context = Context<Handler>)]
|
#[graphql_object(context = Context<Handler>)]
|
||||||
impl<Handler: BackendHandler + Sync> Group<Handler> {
|
impl<Handler: BackendHandler + Sync> Group<Handler> {
|
||||||
fn id(&self) -> String {
|
fn id(&self) -> i32 {
|
||||||
self.group_id.clone()
|
self.group_id
|
||||||
|
}
|
||||||
|
fn display_name(&self) -> String {
|
||||||
|
self.display_name.clone()
|
||||||
}
|
}
|
||||||
/// The groups to which this user belongs.
|
/// The groups to which this user belongs.
|
||||||
async fn users(&self, context: &Context<Handler>) -> FieldResult<Vec<User<Handler>>> {
|
async fn users(&self, context: &Context<Handler>) -> FieldResult<Vec<User<Handler>>> {
|
||||||
@ -197,10 +214,23 @@ impl<Handler: BackendHandler + Sync> Group<Handler> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Handler: BackendHandler> From<String> for Group<Handler> {
|
impl<Handler: BackendHandler> From<GroupIdAndName> for Group<Handler> {
|
||||||
fn from(group_id: String) -> Self {
|
fn from(group_id_and_name: GroupIdAndName) -> Self {
|
||||||
Self {
|
Self {
|
||||||
group_id,
|
group_id: group_id_and_name.0 .0,
|
||||||
|
display_name: group_id_and_name.1,
|
||||||
|
members: None,
|
||||||
|
_phantom: std::marker::PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Handler: BackendHandler> From<DomainGroup> for Group<Handler> {
|
||||||
|
fn from(group: DomainGroup) -> Self {
|
||||||
|
Self {
|
||||||
|
group_id: group.id.0,
|
||||||
|
display_name: group.display_name,
|
||||||
|
members: Some(group.users.into_iter().map(Into::into).collect()),
|
||||||
_phantom: std::marker::PhantomData,
|
_phantom: std::marker::PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -209,7 +239,10 @@ impl<Handler: BackendHandler> From<String> for Group<Handler> {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{domain::handler::MockTestBackendHandler, infra::auth_service::ValidationResults};
|
use crate::{
|
||||||
|
domain::handler::{GroupId, GroupIdAndName, MockTestBackendHandler},
|
||||||
|
infra::auth_service::ValidationResults,
|
||||||
|
};
|
||||||
use juniper::{
|
use juniper::{
|
||||||
execute, graphql_value, DefaultScalarValue, EmptyMutation, EmptySubscription, GraphQLType,
|
execute, graphql_value, DefaultScalarValue, EmptyMutation, EmptySubscription, GraphQLType,
|
||||||
RootNode, Variables,
|
RootNode, Variables,
|
||||||
@ -250,8 +283,8 @@ mod tests {
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
let mut groups = HashSet::<String>::new();
|
let mut groups = HashSet::new();
|
||||||
groups.insert("Bobbersons".to_string());
|
groups.insert(GroupIdAndName(GroupId(3), "Bobbersons".to_string()));
|
||||||
mock.expect_get_user_groups()
|
mock.expect_get_user_groups()
|
||||||
.with(eq("bob"))
|
.with(eq("bob"))
|
||||||
.return_once(|_| Ok(groups));
|
.return_once(|_| Ok(groups));
|
||||||
@ -270,7 +303,7 @@ mod tests {
|
|||||||
"user": {
|
"user": {
|
||||||
"id": "bob",
|
"id": "bob",
|
||||||
"email": "bob@bobbers.on",
|
"email": "bob@bobbers.on",
|
||||||
"groups": [{"id": "Bobbersons"}]
|
"groups": [{"id": 3}]
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
vec![]
|
vec![]
|
||||||
|
@ -29,7 +29,7 @@ mockall::mock! {
|
|||||||
async fn list_users(&self, filters: Option<RequestFilter>) -> DomainResult<Vec<User>>;
|
async fn list_users(&self, filters: Option<RequestFilter>) -> DomainResult<Vec<User>>;
|
||||||
async fn list_groups(&self) -> DomainResult<Vec<Group>>;
|
async fn list_groups(&self) -> DomainResult<Vec<Group>>;
|
||||||
async fn get_user_details(&self, user_id: &str) -> DomainResult<User>;
|
async fn get_user_details(&self, user_id: &str) -> DomainResult<User>;
|
||||||
async fn get_user_groups(&self, user: &str) -> DomainResult<HashSet<String>>;
|
async fn get_user_groups(&self, user: &str) -> DomainResult<HashSet<GroupIdAndName>>;
|
||||||
async fn create_user(&self, request: CreateUserRequest) -> DomainResult<()>;
|
async fn create_user(&self, request: CreateUserRequest) -> DomainResult<()>;
|
||||||
async fn update_user(&self, request: UpdateUserRequest) -> DomainResult<()>;
|
async fn update_user(&self, request: UpdateUserRequest) -> DomainResult<()>;
|
||||||
async fn delete_user(&self, user_id: &str) -> DomainResult<()>;
|
async fn delete_user(&self, user_id: &str) -> DomainResult<()>;
|
||||||
|
Loading…
Reference in New Issue
Block a user