From 439fde434bca023a938f41717188d35ad401494d Mon Sep 17 00:00:00 2001 From: Valentin Tolmer Date: Wed, 4 Oct 2023 01:39:08 +0200 Subject: [PATCH] server: Add graphql support for creating/deleting attributes --- schema.graphql | 77 +++++++++++-------- server/src/domain/handler.rs | 1 + server/src/domain/types.rs | 12 ++- server/src/infra/access_control.rs | 29 +++++-- server/src/infra/graphql/mutation.rs | 110 ++++++++++++++++++++++++++- server/src/infra/graphql/query.rs | 29 ++++--- server/src/infra/test_utils.rs | 7 ++ 7 files changed, 208 insertions(+), 57 deletions(-) diff --git a/schema.graphql b/schema.graphql index e2ac084..6e34296 100644 --- a/schema.graphql +++ b/schema.graphql @@ -3,11 +3,6 @@ type AttributeValue { value: [String!]! } -input EqualityConstraint { - field: String! - value: String! -} - type Mutation { createUser(user: CreateUserInput!): User! createGroup(name: String!): Group! @@ -17,6 +12,10 @@ type Mutation { removeUserFromGroup(userId: String!, groupId: Int!): Success! deleteUser(userId: String!): Success! deleteGroup(groupId: Int!): Success! + addUserAttribute(name: String!, attributeType: AttributeType!, isList: Boolean!, isVisible: Boolean!, isEditable: Boolean!): Success! + addGroupAttribute(name: String!, attributeType: AttributeType!, isList: Boolean!, isVisible: Boolean!, isEditable: Boolean!): Success! + deleteUserAttribute(name: String!): Success! + deleteGroupAttribute(name: String!): Success! } type Group { @@ -46,17 +45,6 @@ input RequestFilter { "DateTime" scalar DateTimeUtc -type Schema { - userSchema: AttributeList! - groupSchema: AttributeList! -} - -"The fields that can be updated for a group." -input UpdateGroupInput { - id: Int! - displayName: String -} - type Query { apiVersion: String! user(userId: String!): User! @@ -76,6 +64,41 @@ input CreateUserInput { avatar: String } +type AttributeSchema { + name: String! + attributeType: AttributeType! + isList: Boolean! + isVisible: Boolean! + isEditable: Boolean! + isHardcoded: Boolean! +} + +"The fields that can be updated for a user." +input UpdateUserInput { + id: String! + email: String + displayName: String + firstName: String + lastName: String + avatar: String +} + +input EqualityConstraint { + field: String! + value: String! +} + +type Schema { + userSchema: AttributeList! + groupSchema: AttributeList! +} + +"The fields that can be updated for a group." +input UpdateGroupInput { + id: Int! + displayName: String +} + type User { id: String! email: String! @@ -95,29 +118,17 @@ type AttributeList { attributes: [AttributeSchema!]! } -type AttributeSchema { - name: String! - attributeType: String! - isList: Boolean! - isVisible: Boolean! - isEditable: Boolean! - isHardcoded: Boolean! +enum AttributeType { + STRING + INTEGER + JPEG_PHOTO + DATE_TIME } 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 - avatar: String -} - schema { query: Query mutation: Mutation diff --git a/server/src/domain/handler.rs b/server/src/domain/handler.rs index d8afc1e..d0ca4e3 100644 --- a/server/src/domain/handler.rs +++ b/server/src/domain/handler.rs @@ -235,6 +235,7 @@ pub trait BackendHandler: + UserListerBackendHandler + GroupListerBackendHandler + ReadSchemaBackendHandler + + SchemaBackendHandler { } diff --git a/server/src/domain/types.rs b/server/src/domain/types.rs index 0e1eddb..02fad38 100644 --- a/server/src/domain/types.rs +++ b/server/src/domain/types.rs @@ -342,7 +342,17 @@ impl From<&GroupId> for Value { } #[derive( - Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, EnumString, IntoStaticStr, + Debug, + Copy, + Clone, + PartialEq, + Eq, + Hash, + Serialize, + Deserialize, + EnumString, + IntoStaticStr, + juniper::GraphQLEnum, )] pub enum AttributeType { String, diff --git a/server/src/infra/access_control.rs b/server/src/infra/access_control.rs index 0dfaf6c..8d449b7 100644 --- a/server/src/infra/access_control.rs +++ b/server/src/infra/access_control.rs @@ -6,10 +6,10 @@ use tracing::info; use crate::domain::{ error::Result, handler::{ - AttributeSchema, BackendHandler, CreateGroupRequest, CreateUserRequest, - GroupBackendHandler, GroupListerBackendHandler, GroupRequestFilter, - ReadSchemaBackendHandler, Schema, UpdateGroupRequest, UpdateUserRequest, - UserBackendHandler, UserListerBackendHandler, UserRequestFilter, + AttributeSchema, BackendHandler, CreateAttributeRequest, CreateGroupRequest, + CreateUserRequest, GroupBackendHandler, GroupListerBackendHandler, GroupRequestFilter, + ReadSchemaBackendHandler, Schema, SchemaBackendHandler, UpdateGroupRequest, + UpdateUserRequest, UserBackendHandler, UserListerBackendHandler, UserRequestFilter, }, types::{Group, GroupDetails, GroupId, User, UserAndGroups, UserId}, }; @@ -94,7 +94,10 @@ pub trait UserWriteableBackendHandler: UserReadableBackendHandler { #[async_trait] pub trait AdminBackendHandler: - UserWriteableBackendHandler + ReadonlyBackendHandler + UserWriteableBackendHandler + UserWriteableBackendHandler + + ReadonlyBackendHandler + + UserWriteableBackendHandler + + SchemaBackendHandler { async fn create_user(&self, request: CreateUserRequest) -> Result<()>; async fn delete_user(&self, user_id: &UserId) -> Result<()>; @@ -103,6 +106,10 @@ pub trait AdminBackendHandler: async fn update_group(&self, request: UpdateGroupRequest) -> Result<()>; async fn create_group(&self, request: CreateGroupRequest) -> Result; async fn delete_group(&self, group_id: GroupId) -> Result<()>; + async fn add_user_attribute(&self, request: CreateAttributeRequest) -> Result<()>; + async fn add_group_attribute(&self, request: CreateAttributeRequest) -> Result<()>; + async fn delete_user_attribute(&self, name: &str) -> Result<()>; + async fn delete_group_attribute(&self, name: &str) -> Result<()>; } #[async_trait] @@ -161,6 +168,18 @@ impl AdminBackendHandler for Handler { async fn delete_group(&self, group_id: GroupId) -> Result<()> { ::delete_group(self, group_id).await } + async fn add_user_attribute(&self, request: CreateAttributeRequest) -> Result<()> { + ::add_user_attribute(self, request).await + } + async fn add_group_attribute(&self, request: CreateAttributeRequest) -> Result<()> { + ::add_group_attribute(self, request).await + } + async fn delete_user_attribute(&self, name: &str) -> Result<()> { + ::delete_user_attribute(self, name).await + } + async fn delete_group_attribute(&self, name: &str) -> Result<()> { + ::delete_group_attribute(self, name).await + } } pub struct AccessControlledBackendHandler { diff --git a/server/src/infra/graphql/mutation.rs b/server/src/infra/graphql/mutation.rs index 9bf28a8..eed5653 100644 --- a/server/src/infra/graphql/mutation.rs +++ b/server/src/infra/graphql/mutation.rs @@ -1,10 +1,10 @@ use crate::{ domain::{ handler::{ - BackendHandler, CreateGroupRequest, CreateUserRequest, UpdateGroupRequest, - UpdateUserRequest, + BackendHandler, CreateAttributeRequest, CreateGroupRequest, CreateUserRequest, + UpdateGroupRequest, UpdateUserRequest, }, - types::{GroupId, JpegPhoto, UserId}, + types::{AttributeType, GroupId, JpegPhoto, UserId}, }, infra::{ access_control::{ @@ -285,4 +285,108 @@ impl Mutation { .await?; Ok(Success::new()) } + + async fn add_user_attribute( + context: &Context, + name: String, + attribute_type: AttributeType, + is_list: bool, + is_visible: bool, + is_editable: bool, + ) -> FieldResult { + let span = debug_span!("[GraphQL mutation] add_user_attribute"); + span.in_scope(|| { + debug!(?name, ?attribute_type, is_list, is_visible, is_editable); + }); + let handler = context + .get_admin_handler() + .ok_or_else(field_error_callback( + &span, + "Unauthorized attribute creation", + ))?; + handler + .add_user_attribute(CreateAttributeRequest { + name, + attribute_type, + is_list, + is_visible, + is_editable, + }) + .instrument(span) + .await?; + Ok(Success::new()) + } + + async fn add_group_attribute( + context: &Context, + name: String, + attribute_type: AttributeType, + is_list: bool, + is_visible: bool, + is_editable: bool, + ) -> FieldResult { + let span = debug_span!("[GraphQL mutation] add_group_attribute"); + span.in_scope(|| { + debug!(?name, ?attribute_type, is_list, is_visible, is_editable); + }); + let handler = context + .get_admin_handler() + .ok_or_else(field_error_callback( + &span, + "Unauthorized attribute creation", + ))?; + handler + .add_group_attribute(CreateAttributeRequest { + name, + attribute_type, + is_list, + is_visible, + is_editable, + }) + .instrument(span) + .await?; + Ok(Success::new()) + } + + async fn delete_user_attribute( + context: &Context, + name: String, + ) -> FieldResult { + let span = debug_span!("[GraphQL mutation] delete_user_attribute"); + span.in_scope(|| { + debug!(?name); + }); + let handler = context + .get_admin_handler() + .ok_or_else(field_error_callback( + &span, + "Unauthorized attribute deletion", + ))?; + handler + .delete_user_attribute(&name) + .instrument(span) + .await?; + Ok(Success::new()) + } + + async fn delete_group_attribute( + context: &Context, + name: String, + ) -> FieldResult { + let span = debug_span!("[GraphQL mutation] delete_group_attribute"); + span.in_scope(|| { + debug!(?name); + }); + let handler = context + .get_admin_handler() + .ok_or_else(field_error_callback( + &span, + "Unauthorized attribute deletion", + ))?; + handler + .delete_group_attribute(&name) + .instrument(span) + .await?; + Ok(Success::new()) + } } diff --git a/server/src/infra/graphql/query.rs b/server/src/infra/graphql/query.rs index 3b80795..63d4278 100644 --- a/server/src/infra/graphql/query.rs +++ b/server/src/infra/graphql/query.rs @@ -438,9 +438,8 @@ impl AttributeSchema { fn name(&self) -> String { self.schema.name.clone() } - fn attribute_type(&self) -> String { - let name: &'static str = self.schema.attribute_type.into(); - name.to_owned() + fn attribute_type(&self) -> AttributeType { + self.schema.attribute_type } fn is_list(&self) -> bool { self.schema.is_list @@ -917,7 +916,7 @@ mod tests { "attributes": [ { "name": "avatar", - "attributeType": "JpegPhoto", + "attributeType": "JPEG_PHOTO", "isList": false, "isVisible": true, "isEditable": true, @@ -925,7 +924,7 @@ mod tests { }, { "name": "creation_date", - "attributeType": "DateTime", + "attributeType": "DATE_TIME", "isList": false, "isVisible": true, "isEditable": false, @@ -933,7 +932,7 @@ mod tests { }, { "name": "display_name", - "attributeType": "String", + "attributeType": "STRING", "isList": false, "isVisible": true, "isEditable": true, @@ -941,7 +940,7 @@ mod tests { }, { "name": "first_name", - "attributeType": "String", + "attributeType": "STRING", "isList": false, "isVisible": true, "isEditable": true, @@ -949,7 +948,7 @@ mod tests { }, { "name": "last_name", - "attributeType": "String", + "attributeType": "STRING", "isList": false, "isVisible": true, "isEditable": true, @@ -957,7 +956,7 @@ mod tests { }, { "name": "mail", - "attributeType": "String", + "attributeType": "STRING", "isList": false, "isVisible": true, "isEditable": true, @@ -965,7 +964,7 @@ mod tests { }, { "name": "user_id", - "attributeType": "String", + "attributeType": "STRING", "isList": false, "isVisible": true, "isEditable": false, @@ -973,7 +972,7 @@ mod tests { }, { "name": "uuid", - "attributeType": "String", + "attributeType": "STRING", "isList": false, "isVisible": true, "isEditable": false, @@ -985,7 +984,7 @@ mod tests { "attributes": [ { "name": "creation_date", - "attributeType": "DateTime", + "attributeType": "DATE_TIME", "isList": false, "isVisible": true, "isEditable": false, @@ -993,7 +992,7 @@ mod tests { }, { "name": "display_name", - "attributeType": "String", + "attributeType": "STRING", "isList": false, "isVisible": true, "isEditable": true, @@ -1001,7 +1000,7 @@ mod tests { }, { "name": "group_id", - "attributeType": "Integer", + "attributeType": "INTEGER", "isList": false, "isVisible": true, "isEditable": false, @@ -1009,7 +1008,7 @@ mod tests { }, { "name": "uuid", - "attributeType": "String", + "attributeType": "STRING", "isList": false, "isVisible": true, "isEditable": false, diff --git a/server/src/infra/test_utils.rs b/server/src/infra/test_utils.rs index b8d3f67..820ab03 100644 --- a/server/src/infra/test_utils.rs +++ b/server/src/infra/test_utils.rs @@ -42,6 +42,13 @@ mockall::mock! { async fn get_schema(&self) -> Result; } #[async_trait] + impl SchemaBackendHandler for TestBackendHandler { + async fn add_user_attribute(&self, request: CreateAttributeRequest) -> Result<()>; + async fn add_group_attribute(&self, request: CreateAttributeRequest) -> Result<()>; + async fn delete_user_attribute(&self, name: &str) -> Result<()>; + async fn delete_group_attribute(&self, name: &str) -> Result<()>; + } + #[async_trait] impl BackendHandler for TestBackendHandler {} #[async_trait] impl OpaqueHandler for TestBackendHandler {