server: Add graphql support for creating/deleting attributes

This commit is contained in:
Valentin Tolmer 2023-10-04 01:39:08 +02:00 committed by nitnelave
parent 2a5fd01439
commit 439fde434b
7 changed files with 208 additions and 57 deletions

77
schema.graphql generated
View file

@ -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

View file

@ -235,6 +235,7 @@ pub trait BackendHandler:
+ UserListerBackendHandler
+ GroupListerBackendHandler
+ ReadSchemaBackendHandler
+ SchemaBackendHandler
{
}

View file

@ -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,

View file

@ -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<GroupId>;
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<Handler: BackendHandler> AdminBackendHandler for Handler {
async fn delete_group(&self, group_id: GroupId) -> Result<()> {
<Handler as GroupBackendHandler>::delete_group(self, group_id).await
}
async fn add_user_attribute(&self, request: CreateAttributeRequest) -> Result<()> {
<Handler as SchemaBackendHandler>::add_user_attribute(self, request).await
}
async fn add_group_attribute(&self, request: CreateAttributeRequest) -> Result<()> {
<Handler as SchemaBackendHandler>::add_group_attribute(self, request).await
}
async fn delete_user_attribute(&self, name: &str) -> Result<()> {
<Handler as SchemaBackendHandler>::delete_user_attribute(self, name).await
}
async fn delete_group_attribute(&self, name: &str) -> Result<()> {
<Handler as SchemaBackendHandler>::delete_group_attribute(self, name).await
}
}
pub struct AccessControlledBackendHandler<Handler> {

View file

@ -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<Handler: BackendHandler> Mutation<Handler> {
.await?;
Ok(Success::new())
}
async fn add_user_attribute(
context: &Context<Handler>,
name: String,
attribute_type: AttributeType,
is_list: bool,
is_visible: bool,
is_editable: bool,
) -> FieldResult<Success> {
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<Handler>,
name: String,
attribute_type: AttributeType,
is_list: bool,
is_visible: bool,
is_editable: bool,
) -> FieldResult<Success> {
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<Handler>,
name: String,
) -> FieldResult<Success> {
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<Handler>,
name: String,
) -> FieldResult<Success> {
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())
}
}

View file

@ -438,9 +438,8 @@ impl<Handler: BackendHandler> AttributeSchema<Handler> {
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,

View file

@ -42,6 +42,13 @@ mockall::mock! {
async fn get_schema(&self) -> Result<Schema>;
}
#[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 {