Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions migrations/20260214152404_create_leave_table.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
-- Leave table for tracking leaves
CREATE TABLE Leave (
leave_id SERIAL PRIMARY KEY,
discord_id VARCHAR(255) REFERENCES Member(discord_id) ON DELETE CASCADE,
Comment thread
naveensrinivas282 marked this conversation as resolved.
date DATE DEFAULT CURRENT_DATE,
duration INT DEFAULt 1,
Comment thread
naveensrinivas282 marked this conversation as resolved.
Outdated
reason TEXT NOT NULL,
approved_by VARCHAR(255) REFERENCES Member(discord_id),
Comment thread
naveensrinivas282 marked this conversation as resolved.
Outdated
CHECK (approved_by IS NULL OR approved_by <> discord_id),
Comment thread
naveensrinivas282 marked this conversation as resolved.
UNIQUE (date, discord_id)
);
32 changes: 31 additions & 1 deletion src/graphql/mutations/attendance_mutations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ use sha2::Sha256;
use sqlx::PgPool;

use crate::auth::guards::AdminOrBotGuard;
use crate::models::attendance::{AttendanceRecord, MarkAttendanceInput};
use crate::models::attendance::{
AttendanceRecord, MarkAttendanceInput, MarkLeaveInput, MarkLeaveOutput,
};

type HmacSha256 = Hmac<Sha256>;

Expand Down Expand Up @@ -61,4 +63,32 @@ impl AttendanceMutations {

Ok(attendance)
}

Comment thread
naveensrinivas282 marked this conversation as resolved.
async fn mark_leave(
&self,
ctx: &Context<'_>,
input: MarkLeaveInput,
) -> Result<MarkLeaveOutput> {
Comment thread
naveensrinivas282 marked this conversation as resolved.
Outdated
Comment thread
naveensrinivas282 marked this conversation as resolved.
let pool = ctx
.data::<Arc<PgPool>>()
.expect("Pool not found in context");
let now = chrono::Utc::now().with_timezone(&Kolkata);

let leave: MarkLeaveOutput = sqlx::query_as::<_, MarkLeaveOutput>(
"INSERT INTO Leave
(discord_id, date, duration, reason, approved_by)
VALUES ($1, $2, $3, $4, $5)
RETURNING *
",
)
.bind(input.discord_id)
.bind(now)
Comment thread
naveensrinivas282 marked this conversation as resolved.
Outdated
.bind(input.duration)
.bind(input.reason)
.bind(input.approved_by)
Comment thread
naveensrinivas282 marked this conversation as resolved.
Outdated
.fetch_one(pool.as_ref())
Comment thread
naveensrinivas282 marked this conversation as resolved.
.await?;
Comment thread
naveensrinivas282 marked this conversation as resolved.

Ok(leave)
}
}
58 changes: 51 additions & 7 deletions src/graphql/queries/member_queries.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use crate::auth::guards::AuthGuard;
use crate::auth::AuthContext;
use crate::models::{attendance::AttendanceRecord, status_update::StatusUpdateRecord};
use crate::models::{
attendance::{AttendanceRecord, LeaveCountOutput},
status_update::StatusUpdateRecord,
};
use async_graphql::{ComplexObject, Context, Object, Result};
use chrono::NaiveDate;
use sqlx::PgPool;
Expand Down Expand Up @@ -57,31 +60,39 @@ impl MemberQueries {
ctx: &Context<'_>,
member_id: Option<i32>,
email: Option<String>,
discord_id: Option<String>,
) -> Result<Option<Member>> {
let pool = ctx.data::<Arc<PgPool>>().expect("Pool must be in context.");

match (member_id, email) {
(Some(id), None) => {
match (member_id, email, discord_id) {
(Some(id), None, None) => {
let member =
sqlx::query_as::<_, Member>("SELECT * FROM Member WHERE member_id = $1")
.bind(id)
.fetch_optional(pool.as_ref())
.await?;
Ok(member)
}
(None, Some(email)) => {
(None, Some(email), None) => {
let member = sqlx::query_as::<_, Member>("SELECT * FROM Member WHERE email = $1")
.bind(email)
.fetch_optional(pool.as_ref())
.await?;
Ok(member)
}
(Some(_), Some(_)) => Err("Provide only one of member_id or email".into()),
(None, None) => Err("Provide either member_id or email".into()),
(None, None, Some(discord_id)) => {
let member =
sqlx::query_as::<_, Member>("SELECT * FROM Member WHERE discord_id = $1")
.bind(discord_id)
.fetch_optional(pool.as_ref())
.await?;
Ok(member)
}
_ => Err("Provide exactly one of member_id, email, or discord_id".into()),
}
}

/// Fetch the details of the currently logged in member
// Fetch the details of the currently logged in member
Comment thread
naveensrinivas282 marked this conversation as resolved.
Outdated
Comment thread
naveensrinivas282 marked this conversation as resolved.
Outdated
#[graphql(guard = "AuthGuard")]
async fn me(&self, ctx: &Context<'_>) -> Result<Member> {
let auth = ctx.data::<AuthContext>()?;
Expand Down Expand Up @@ -389,4 +400,37 @@ impl Member {
member_id: self.member_id,
}
}

async fn leave_count(
&self,
ctx: &Context<'_>,
start_date: NaiveDate,
end_date: NaiveDate,
) -> Result<LeaveCountOutput> {
Copy link
Copy Markdown
Member

@hrideshmg hrideshmg Feb 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

leave should be a nested object with two methods under it that return leave_count and records respectively as we need to be able to query on which days the member took a leave.

See the records and updateCount methods under StatusInfo for reference.

Copy link
Copy Markdown
Member

@hrideshmg hrideshmg Apr 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

still unresolved, i don't see a nested object or the records query

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

leave_count can be called through members and allMembers, so there should be no need for a different records query for all members leave count

let pool = ctx.data::<Arc<PgPool>>().expect("Pool must be in context.");
Comment thread
naveensrinivas282 marked this conversation as resolved.

if end_date < start_date {
return Err("end_date must be >= start_date".into());
}
let leave = sqlx::query_as::<_, LeaveCountOutput>(
r#"
SELECT discord_id, SUM(duration) AS count
FROM "Leave"
Comment thread
hrideshmg marked this conversation as resolved.
Outdated
WHERE date > $1
AND (date + duration) < $2
Comment thread
naveensrinivas282 marked this conversation as resolved.
Outdated
AND discord_id = $3
GROUP BY discord_id
"#,
)
.bind(start_date)
.bind(end_date)
.bind(
self.discord_id
.as_ref()
.expect("Leave count needs discord_id"),
)
Comment thread
naveensrinivas282 marked this conversation as resolved.
Outdated
.fetch_one(pool.as_ref())
.await?;
Comment thread
naveensrinivas282 marked this conversation as resolved.
Outdated
Ok(leave)
}
}
23 changes: 23 additions & 0 deletions src/models/attendance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,26 @@ pub struct MarkAttendanceInput {
pub date: NaiveDate,
pub hmac_signature: String,
}

#[derive(InputObject)]
pub struct MarkLeaveInput {
pub discord_id: String,
pub reason: String,
pub duration: i32,
pub approved_by: Option<String>,
}

#[derive(SimpleObject, FromRow)]
pub struct MarkLeaveOutput {
pub discord_id: String,
pub date: NaiveDate,
pub reason: String,
pub duration: i32,
pub approved_by: Option<String>,
}
Comment thread
naveensrinivas282 marked this conversation as resolved.

#[derive(SimpleObject, FromRow)]
pub struct LeaveCountOutput {
pub discord_id: String,
pub count: i32,
}