diff --git a/migrations/20260310140619_create_member_roles.sql b/migrations/20260310140619_create_member_roles.sql new file mode 100644 index 0000000..12e0e87 --- /dev/null +++ b/migrations/20260310140619_create_member_roles.sql @@ -0,0 +1,5 @@ +-- Add member_roles table to support multiple roles per member +CREATE TABLE MemberRoles ( + discord_id VARCHAR(255) PRIMARY KEY, -- Use the Discord ID as the key + roles TEXT[] NOT NULL -- Store roles as a PostgreSQL Array +); \ No newline at end of file diff --git a/src/graphql/mutations/member_mutations.rs b/src/graphql/mutations/member_mutations.rs index 515de22..b4fc42e 100644 --- a/src/graphql/mutations/member_mutations.rs +++ b/src/graphql/mutations/member_mutations.rs @@ -53,4 +53,30 @@ impl MemberMutations { Ok(member) } + + /// Save the roles of a member in the database + #[graphql(name = "saveMemberRoles", guard = "AuthGuard")] + async fn save_member_roles( + &self, + ctx: &Context<'_>, + #[graphql(name = "discordId")] discord_id: String, + roles: Vec, + ) -> Result { + let pool = ctx.data::>()?; + + sqlx::query( + r#" + INSERT INTO MemberRoles (discord_id, roles) + VALUES ($1, $2) + ON CONFLICT (discord_id) + DO UPDATE SET roles = EXCLUDED.roles + "#, + ) + .bind(discord_id) + .bind(roles) + .execute(pool.as_ref()) + .await?; + + Ok(true) + } } diff --git a/src/graphql/queries/member_queries.rs b/src/graphql/queries/member_queries.rs index 2b0b1af..c15e107 100644 --- a/src/graphql/queries/member_queries.rs +++ b/src/graphql/queries/member_queries.rs @@ -1,6 +1,8 @@ use crate::auth::guards::AuthGuard; use crate::auth::AuthContext; -use crate::models::{attendance::AttendanceRecord, status_update::StatusUpdateRecord}; +use crate::models::{ + attendance::AttendanceRecord, member::MemberRolesResponse, status_update::StatusUpdateRecord, +}; use async_graphql::{ComplexObject, Context, Object, Result}; use chrono::NaiveDate; use sqlx::PgPool; @@ -89,6 +91,34 @@ impl MemberQueries { // The AuthGuard ensures that the user is authenticated, so we can unwrap here. Ok(auth.user.clone().unwrap()) } + + /// Fetch the roles of a member from the database + #[graphql(guard = "AuthGuard")] + async fn member_roles( + &self, + ctx: &Context<'_>, + #[graphql(name = "discordId")] discord_id: String, + ) -> Result { + let pool = ctx.data::>()?; + + let roles: Option> = + sqlx::query_scalar("SELECT roles FROM MemberRoles WHERE discord_id = $1") + .bind(discord_id) + .fetch_optional(pool.as_ref()) + .await?; + + if let Some(r) = roles { + Ok(MemberRolesResponse { + exists: true, + roles: r, + }) + } else { + Ok(MemberRolesResponse { + exists: false, + roles: vec![], + }) + } + } } #[Object] diff --git a/src/models/member.rs b/src/models/member.rs index 21569fa..0ba6021 100644 --- a/src/models/member.rs +++ b/src/models/member.rs @@ -60,3 +60,9 @@ pub struct UpdateMemberInput { pub track: Option, pub github_user: Option, } + +#[derive(SimpleObject)] +pub struct MemberRolesResponse { + pub exists: bool, + pub roles: Vec, +}