graphql: Add a method to update a user

This commit is contained in:
Valentin Tolmer 2021-09-01 10:00:51 +02:00 committed by nitnelave
parent 0ac9e134de
commit 2954109d96
7 changed files with 101 additions and 8 deletions

View File

@ -1,4 +1,4 @@
mutation CreateUser($user: UserInput!) { mutation CreateUser($user: CreateUserInput!) {
createUser(user: $user) { createUser(user: $user) {
id id
creationDate creationDate

View File

@ -46,7 +46,7 @@ impl CreateUserForm {
match msg { match msg {
Msg::SubmitForm => { Msg::SubmitForm => {
let req = create_user::Variables { let req = create_user::Variables {
user: create_user::UserInput { user: create_user::CreateUserInput {
id: get_element("username") id: get_element("username")
.filter(not_empty) .filter(not_empty)
.ok_or_else(|| anyhow!("Missing username"))?, .ok_or_else(|| anyhow!("Missing username"))?,

View File

@ -4,7 +4,8 @@ input EqualityConstraint {
} }
type Mutation { type Mutation {
createUser(user: UserInput!): User! createUser(user: CreateUserInput!): User!
updateUser(user: UpdateUserInput!): Success!
} }
type Group { type Group {
@ -28,7 +29,7 @@ input RequestFilter {
scalar DateTimeUtc scalar DateTimeUtc
"The details required to create a user." "The details required to create a user."
input UserInput { input CreateUserInput {
id: String! id: String!
email: String! email: String!
displayName: String displayName: String
@ -53,6 +54,19 @@ type User {
groups: [Group!]! groups: [Group!]!
} }
type Success {
ok: Boolean!
}
"The fields that can be updated for a user."
input UpdateUserInput {
id: String!
email: String
displayName: String
firstName: String
lastName: String
}
schema { schema {
query: Query query: Query
mutation: Mutation mutation: Mutation

View File

@ -59,6 +59,16 @@ pub struct CreateUserRequest {
pub last_name: Option<String>, pub last_name: Option<String>,
} }
#[derive(PartialEq, Eq, Debug, Serialize, Deserialize, Clone, Default)]
pub struct UpdateUserRequest {
// Same fields as CreateUserRequest, but no with an extra layer of Option.
pub user_id: String,
pub email: Option<String>,
pub display_name: Option<String>,
pub first_name: Option<String>,
pub last_name: Option<String>,
}
#[async_trait] #[async_trait]
pub trait LoginHandler: Clone + Send { pub trait LoginHandler: Clone + Send {
async fn bind(&self, request: BindRequest) -> Result<()>; async fn bind(&self, request: BindRequest) -> Result<()>;
@ -73,6 +83,7 @@ pub trait BackendHandler: Clone + Send {
async fn list_groups(&self) -> Result<Vec<Group>>; async fn list_groups(&self) -> Result<Vec<Group>>;
async fn get_user_details(&self, user_id: &str) -> Result<User>; async fn get_user_details(&self, user_id: &str) -> Result<User>;
async fn create_user(&self, request: CreateUserRequest) -> Result<()>; async fn create_user(&self, request: CreateUserRequest) -> 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 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<()>;
@ -91,6 +102,7 @@ mockall::mock! {
async fn list_groups(&self) -> Result<Vec<Group>>; async fn list_groups(&self) -> Result<Vec<Group>>;
async fn get_user_details(&self, user_id: &str) -> Result<User>; async fn get_user_details(&self, user_id: &str) -> Result<User>;
async fn create_user(&self, request: CreateUserRequest) -> Result<()>; async fn create_user(&self, request: CreateUserRequest) -> 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<String>>;

View File

@ -193,6 +193,31 @@ impl BackendHandler for SqlBackendHandler {
Ok(()) Ok(())
} }
async fn update_user(&self, request: UpdateUserRequest) -> Result<()> {
let mut values = Vec::new();
if let Some(email) = request.email {
values.push((Users::Email, email.into()));
}
if let Some(display_name) = request.display_name {
values.push((Users::DisplayName, display_name.into()));
}
if let Some(first_name) = request.first_name {
values.push((Users::FirstName, first_name.into()));
}
if let Some(last_name) = request.last_name {
values.push((Users::LastName, last_name.into()));
}
if values.is_empty() {
return Ok(());
}
let query = Query::update()
.table(Users::Table)
.values(values)
.to_string(DbQueryBuilder {});
sqlx::query(&query).execute(&self.sql_pool).await?;
Ok(())
}
async fn delete_user(&self, user_id: &str) -> Result<()> { async fn delete_user(&self, user_id: &str) -> Result<()> {
let delete_query = Query::delete() let delete_query = Query::delete()
.from_table(Users::Table) .from_table(Users::Table)

View File

@ -1,5 +1,5 @@
use crate::domain::handler::{BackendHandler, CreateUserRequest}; use crate::domain::handler::{BackendHandler, CreateUserRequest, UpdateUserRequest};
use juniper::{graphql_object, FieldResult, GraphQLInputObject}; use juniper::{graphql_object, FieldResult, GraphQLInputObject, GraphQLObject};
use super::api::Context; use super::api::Context;
@ -19,7 +19,7 @@ impl<Handler: BackendHandler> Mutation<Handler> {
#[derive(PartialEq, Eq, Debug, GraphQLInputObject)] #[derive(PartialEq, Eq, Debug, GraphQLInputObject)]
/// The details required to create a user. /// The details required to create a user.
pub struct UserInput { pub struct CreateUserInput {
id: String, id: String,
email: String, email: String,
display_name: Option<String>, display_name: Option<String>,
@ -27,11 +27,32 @@ pub struct UserInput {
last_name: Option<String>, last_name: Option<String>,
} }
#[derive(PartialEq, Eq, Debug, GraphQLInputObject)]
/// The fields that can be updated for a user.
pub struct UpdateUserInput {
id: String,
email: Option<String>,
display_name: Option<String>,
first_name: Option<String>,
last_name: Option<String>,
}
#[derive(PartialEq, Eq, Debug, GraphQLObject)]
pub struct Success {
ok: bool,
}
impl Success {
fn new() -> Self {
Self { ok: true }
}
}
#[graphql_object(context = Context<Handler>)] #[graphql_object(context = Context<Handler>)]
impl<Handler: BackendHandler + Sync> Mutation<Handler> { impl<Handler: BackendHandler + Sync> Mutation<Handler> {
async fn create_user( async fn create_user(
context: &Context<Handler>, context: &Context<Handler>,
user: UserInput, user: CreateUserInput,
) -> FieldResult<super::query::User<Handler>> { ) -> FieldResult<super::query::User<Handler>> {
if !context.validation_result.is_admin { if !context.validation_result.is_admin {
return Err("Unauthorized user creation".into()); return Err("Unauthorized user creation".into());
@ -52,4 +73,24 @@ impl<Handler: BackendHandler + Sync> Mutation<Handler> {
.await .await
.map(Into::into)?) .map(Into::into)?)
} }
async fn update_user(
context: &Context<Handler>,
user: UpdateUserInput,
) -> FieldResult<Success> {
if !context.validation_result.can_access(&user.id) {
return Err("Unauthorized user update".into());
}
context
.handler
.update_user(UpdateUserRequest {
user_id: user.id,
email: user.email,
display_name: user.display_name,
first_name: user.first_name,
last_name: user.last_name,
})
.await?;
Ok(Success::new())
}
} }

View File

@ -31,6 +31,7 @@ mockall::mock! {
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<String>>;
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 delete_user(&self, user_id: &str) -> DomainResult<()>; async fn delete_user(&self, user_id: &str) -> DomainResult<()>;
async fn create_group(&self, group_name: &str) -> DomainResult<GroupId>; async fn create_group(&self, group_name: &str) -> DomainResult<GroupId>;
async fn add_user_to_group(&self, user_id: &str, group_id: GroupId) -> DomainResult<()>; async fn add_user_to_group(&self, user_id: &str, group_id: GroupId) -> DomainResult<()>;