diff --git a/docs/attendance.md b/docs/attendance.md index 54598cb..aff2d9a 100644 --- a/docs/attendance.md +++ b/docs/attendance.md @@ -19,17 +19,6 @@ struct Attendance { ``` The final two fields are not exposed in the interface for obvious reasons. -### AttendanceSummary -Monthly attendance summary for each member. -```rust -struct AttendanceSummary { - member_id: i32, - year: i32, - month: i32, - days_attended: i32, -} -``` - ## Queries ### Get Attendance @@ -85,19 +74,6 @@ mutation { } ``` -### Get Attendance Summary -Get monthly attendance summary for a member. - -```graphql -query { - attendanceSummary(memberId: 1) { - year - month - daysAttended - } -} -``` - ## Daily Task The `src/daily_task/daily_task.rs` system automatically updates attendance summaries at midnight. diff --git a/migrations/20250929191135_drop_unnecessary_tables.sql b/migrations/20250929191135_drop_unnecessary_tables.sql new file mode 100644 index 0000000..1e5428d --- /dev/null +++ b/migrations/20250929191135_drop_unnecessary_tables.sql @@ -0,0 +1,3 @@ +-- Add migration script here +DROP TABLE statusupdatestreak; +DROP TABLE project; diff --git a/migrations/20251006152634_rename_status_is_updated.sql b/migrations/20251006152634_rename_status_is_updated.sql new file mode 100644 index 0000000..c884fce --- /dev/null +++ b/migrations/20251006152634_rename_status_is_updated.sql @@ -0,0 +1,3 @@ +-- Add migration script here +ALTER table statusupdatehistory +RENAME COLUMN is_updated TO is_sent; diff --git a/src/daily_task/mod.rs b/src/daily_task/mod.rs index be21e1d..7789d4b 100644 --- a/src/daily_task/mod.rs +++ b/src/daily_task/mod.rs @@ -36,7 +36,6 @@ pub async fn run_daily_task_at_midnight(pool: Arc) { /// This function does a number of things, including: /// * Insert new attendance records everyday for [`presense`](https://www.github.com/amfoss/presense) to update them later in the day. -/// * Update the AttendanceSummary table async fn execute_daily_task(pool: Arc) { // Members is queried outside of each function to avoid repetition let members = sqlx::query_as::<_, Member>("SELECT * FROM Member") @@ -90,7 +89,6 @@ async fn update_attendance(members: &Vec, pool: &PgPool) { } } // This could have been called in `execute_daily_task()` but that would require us to loop through members twice. - // Whether or not inserting attendance failed, Root will attempt to update AttendanceSummary. This can potentially fail too since insertion failed earlier. However, these two do not depend on each other and one of them failing is no reason to avoid trying the other. } } @@ -104,7 +102,7 @@ async fn update_status_history(members: &Vec, pool: &PgPool) { for member in members { let status_update = sqlx::query( - "INSERT INTO StatusUpdateHistory (member_id, date, is_updated) + "INSERT INTO StatusUpdateHistory (member_id, date, is_sent) VALUES ($1, $2, $3) ON CONFLICT (member_id, date) DO NOTHING", ) diff --git a/src/database_seeder/seed.sql b/src/database_seeder/seed.sql index 6b8930f..e1799d1 100644 --- a/src/database_seeder/seed.sql +++ b/src/database_seeder/seed.sql @@ -58,60 +58,9 @@ WHERE (random() < 0.75) ON CONFLICT (member_id, date) DO NOTHING; --- AttendanceSummary -INSERT INTO AttendanceSummary ( - member_id, year, month, days_attended -) -SELECT - m.member_id, - 2025, - (i % 12) + 1, - FLOOR(random() * 26 + 3)::INT -FROM generate_series(1, 400) AS i -JOIN ( - SELECT generate_series(1, 60) AS idx, member_id - FROM member -) AS m ON (i % 60) + 1 = m.idx -ON CONFLICT (member_id, year, month) DO NOTHING; - - --- StatusUpdateStreak -INSERT INTO StatusUpdateStreak ( - member_id, current_streak, max_streak -) -SELECT - member_id, - FLOOR(random() * 10 + 1)::INT, - FLOOR(random() * 30 + 10)::INT -FROM member -ON CONFLICT (member_id) DO NOTHING; - - --- Project -INSERT INTO Project ( - member_id, title -) -SELECT - (i % 60) + 1, - CASE - WHEN i % 3 = 0 THEN 'Machine Learning Project ' || i - WHEN i % 3 = 1 THEN 'Web Development Project ' || i - ELSE 'Data Analysis Project ' || i - END -FROM generate_series(1, 200) AS i -WHERE NOT EXISTS ( - SELECT 1 FROM Project - WHERE member_id = (i % 60) + 1 AND title = CASE - WHEN i % 3 = 0 THEN 'Machine Learning Project ' || i - WHEN i % 3 = 1 THEN 'Web Development Project ' || i - ELSE 'Data Analysis Project ' || i - END -); - - -- StatusUpdateHistory INSERT INTO StatusUpdateHistory ( - member_id, date, is_updated + member_id, date, is_sent ) SELECT m.member_id, diff --git a/src/graphql/mod.rs b/src/graphql/mod.rs index 0d99324..a3e2615 100644 --- a/src/graphql/mod.rs +++ b/src/graphql/mod.rs @@ -1,22 +1,12 @@ use async_graphql::MergedObject; -use mutations::{AttendanceMutations, MemberMutations, ProjectMutations, StreakMutations}; -use queries::{AttendanceQueries, MemberQueries, ProjectQueries, StreakQueries}; +use mutations::{AttendanceMutations, MemberMutations, StatusMutations}; +use queries::MemberQueries; pub mod mutations; pub mod queries; #[derive(MergedObject, Default)] -pub struct Query( - MemberQueries, - AttendanceQueries, - StreakQueries, - ProjectQueries, -); +pub struct Query(MemberQueries); #[derive(MergedObject, Default)] -pub struct Mutation( - MemberMutations, - AttendanceMutations, - StreakMutations, - ProjectMutations, -); +pub struct Mutation(MemberMutations, AttendanceMutations, StatusMutations); diff --git a/src/graphql/mutations/attendance_mutations.rs b/src/graphql/mutations/attendance_mutations.rs index 75bb471..f3aea54 100644 --- a/src/graphql/mutations/attendance_mutations.rs +++ b/src/graphql/mutations/attendance_mutations.rs @@ -7,7 +7,7 @@ use hmac::{Hmac, Mac}; use sha2::Sha256; use sqlx::PgPool; -use crate::models::attendance::{Attendance, MarkAttendanceInput}; +use crate::models::attendance::{AttendanceRecord, MarkAttendanceInput}; type HmacSha256 = Hmac; @@ -21,7 +21,7 @@ impl AttendanceMutations { &self, ctx: &Context<'_>, input: MarkAttendanceInput, - ) -> Result { + ) -> Result { let pool = ctx .data::>() .expect("Pool not found in context"); @@ -43,7 +43,7 @@ impl AttendanceMutations { } let now = Local::now().with_timezone(&Kolkata).time(); - let attendance = sqlx::query_as::<_, Attendance>( + let attendance = sqlx::query_as::<_, AttendanceRecord>( "UPDATE Attendance SET time_in = CASE WHEN time_in IS NULL THEN $1 ELSE time_in END, diff --git a/src/graphql/mutations/mod.rs b/src/graphql/mutations/mod.rs index 012ed2a..8bfc1c9 100644 --- a/src/graphql/mutations/mod.rs +++ b/src/graphql/mutations/mod.rs @@ -1,9 +1,7 @@ pub mod attendance_mutations; pub mod member_mutations; -pub mod project_mutations; -pub mod streak_mutations; +pub mod status_mutations; pub use attendance_mutations::AttendanceMutations; pub use member_mutations::MemberMutations; -pub use project_mutations::ProjectMutations; -pub use streak_mutations::StreakMutations; +pub use status_mutations::StatusMutations; diff --git a/src/graphql/mutations/project_mutations.rs b/src/graphql/mutations/project_mutations.rs deleted file mode 100644 index b7a1a03..0000000 --- a/src/graphql/mutations/project_mutations.rs +++ /dev/null @@ -1,28 +0,0 @@ -use std::sync::Arc; - -use async_graphql::{Context, Object, Result}; -use sqlx::PgPool; - -use crate::models::project::{Project, SetProjectInput}; - -#[derive(Default)] -pub struct ProjectMutations; - -#[Object] -impl ProjectMutations { - #[graphql(name = "setProject")] - async fn set_project(&self, ctx: &Context<'_>, input: SetProjectInput) -> Result { - let pool = ctx - .data::>() - .expect("Pool must be found in context"); - - let project = sqlx::query_as::<_, Project>( - "INSERT INTO Project (member_id, project_title) VALUES ($1, $2) RETURNING * ", - ) - .bind(input.member_id) - .bind(input.title) - .fetch_one(pool.as_ref()) - .await?; - Ok(project) - } -} diff --git a/src/graphql/mutations/status_mutations.rs b/src/graphql/mutations/status_mutations.rs new file mode 100644 index 0000000..7cbf49a --- /dev/null +++ b/src/graphql/mutations/status_mutations.rs @@ -0,0 +1,41 @@ +use async_graphql::{Context, Object, Result}; +use chrono_tz::Asia::Kolkata; +use sqlx::PgPool; +use std::sync::Arc; + +use crate::models::status_update::StatusUpdateRecord; + +#[derive(Default)] +pub struct StatusMutations; + +#[Object] +impl StatusMutations { + async fn mark_status_update( + &self, + ctx: &Context<'_>, + emails: Vec, + ) -> Result> { + let pool = ctx.data::>().expect("Pool must be in context"); + #[allow(deprecated)] + let yesterday = chrono::Utc::now() + .with_timezone(&Kolkata) + .date() + .naive_local() + - chrono::Duration::days(1); + + let status = sqlx::query_as::<_, StatusUpdateRecord>( + "UPDATE StatusUpdateHistory SET + is_sent = true + WHERE member_id IN (SELECT member_id from Member where email = ANY($1)) + AND date = $2 + RETURNING * + ", + ) + .bind(emails) + .bind(yesterday) + .fetch_all(pool.as_ref()) + .await?; + + Ok(status) + } +} diff --git a/src/graphql/mutations/streak_mutations.rs b/src/graphql/mutations/streak_mutations.rs deleted file mode 100644 index ddaf262..0000000 --- a/src/graphql/mutations/streak_mutations.rs +++ /dev/null @@ -1,82 +0,0 @@ -use std::sync::Arc; - -use async_graphql::{Context, Object, Result}; -use sqlx::PgPool; - -use crate::models::status_update::{StatusUpdateStreak as Streak, StreakInput}; -use chrono_tz::Asia::Kolkata; - -#[derive(Default)] -pub struct StreakMutations; - -#[Object] -impl StreakMutations { - #[graphql(name = "incrementStreak")] - async fn increment_streak(&self, ctx: &Context<'_>, input: StreakInput) -> Result { - let pool = ctx.data::>().expect("Pool must be in context."); - - let query = sqlx::query_as::<_, Streak>( - " - INSERT INTO StatusUpdateStreak (member_id, current_streak, max_streak) - VALUES ($1, 1, 1) - ON CONFLICT (member_id) DO UPDATE SET - current_streak = CASE - WHEN StatusUpdateStreak.current_streak >= 0 THEN StatusUpdateStreak.current_streak + 1 - ELSE 1 - END, - max_streak = GREATEST(StatusUpdateStreak.max_streak, StatusUpdateStreak.current_streak + 1) - RETURNING *", - ) - .bind(input.member_id); - - let updated_streak = query.fetch_one(pool.as_ref()).await?; - - update_status_history(pool.as_ref(), input.member_id).await?; - - Ok(updated_streak) - } - - async fn reset_streak(&self, ctx: &Context<'_>, input: StreakInput) -> Result { - let pool = ctx.data::>().expect("Pool must be in context."); - - let query = sqlx::query_as::<_, Streak>( - " - INSERT INTO StatusUpdateStreak (member_id, current_streak, max_streak) - VALUES ($1, 0, 0) - ON CONFLICT (member_id) DO UPDATE - SET current_streak = CASE - WHEN StatusUpdateStreak.current_streak > 0 THEN 0 - ELSE StatusUpdateStreak.current_streak - 1 - END - RETURNING *", - ) - .bind(input.member_id); - - let updated_streak = query.fetch_one(pool.as_ref()).await?; - Ok(updated_streak) - } -} - -async fn update_status_history(pool: &PgPool, member_id: i32) -> Result<()> { - #[allow(deprecated)] - let yesterday = chrono::Utc::now() - .with_timezone(&Kolkata) - .date() - .naive_local() - - chrono::Duration::days(1); - - sqlx::query( - " - UPDATE StatusUpdateHistory - SET is_updated = TRUE - WHERE member_id = $1 - AND date = $2 - ", - ) - .bind(member_id) - .bind(yesterday) - .execute(pool) - .await?; - - Ok(()) -} diff --git a/src/graphql/queries/attendance_queries.rs b/src/graphql/queries/attendance_queries.rs deleted file mode 100644 index 5208a70..0000000 --- a/src/graphql/queries/attendance_queries.rs +++ /dev/null @@ -1,51 +0,0 @@ -use crate::models::attendance::Attendance; -use crate::models::member::Member; -use async_graphql::{ComplexObject, Context, Object, Result}; -use chrono::NaiveDate; -use sqlx::PgPool; -use std::sync::Arc; - -#[derive(Default)] -pub struct AttendanceQueries; - -#[ComplexObject] -impl Attendance { - async fn member(&self, ctx: &Context<'_>) -> Result { - let pool = ctx.data::>()?; - let member = sqlx::query_as::<_, Member>("SELECT * FROM Member WHERE member_id = $1") - .bind(self.member_id) - .fetch_one(pool.as_ref()) - .await?; - - Ok(member) - } -} - -#[Object] -impl AttendanceQueries { - async fn attendance(&self, ctx: &Context<'_>, member_id: i32) -> Result> { - let pool = ctx.data::>().expect("Pool must be in context."); - - Ok( - sqlx::query_as::<_, Attendance>("SELECT * FROM Attendance WHERE member_id = $1") - .bind(member_id) - .fetch_all(pool.as_ref()) - .await?, - ) - } - - async fn attendance_by_date( - &self, - ctx: &Context<'_>, - date: NaiveDate, - ) -> Result> { - let pool = ctx.data::>()?; - - let rows = sqlx::query_as::<_, Attendance>("SELECT * FROM Attendance WHERE date = $1") - .bind(date) - .fetch_all(pool.as_ref()) - .await?; - - Ok(rows) - } -} diff --git a/src/graphql/queries/member_queries.rs b/src/graphql/queries/member_queries.rs index 12f57ba..3436aad 100644 --- a/src/graphql/queries/member_queries.rs +++ b/src/graphql/queries/member_queries.rs @@ -1,25 +1,29 @@ +use crate::models::attendance::AttendanceRecord; use async_graphql::{ComplexObject, Context, Object, Result}; use chrono::NaiveDate; use sqlx::PgPool; use std::sync::Arc; -use crate::models::{ - attendance::{AttendanceInfo, AttendanceSummaryInfo}, - member::Member, - project::Project, - status_update::{StatusUpdateHistory, StatusUpdateStreakInfo}, -}; +use crate::models::{member::Member, status_update::StatusUpdateStreakRecord}; #[derive(Default)] pub struct MemberQueries; +pub struct StatusInfo { + member_id: i32, +} + +pub struct AttendanceInfo { + member_id: i32, +} + #[Object] impl MemberQueries { - pub async fn members( + pub async fn all_members( &self, ctx: &Context<'_>, year: Option, - group_id: Option, + track: Option, ) -> Result> { let pool = ctx.data::>().expect("Pool must be in context."); @@ -30,8 +34,8 @@ impl MemberQueries { query.push_bind(y); } - if let Some(g) = group_id { - query.push(" AND group_id = "); + if let Some(g) = track { + query.push(" AND track = "); query.push_bind(g); } @@ -42,74 +46,89 @@ impl MemberQueries { Ok(members) } -} -#[ComplexObject] -impl Member { - async fn attendance(&self, ctx: &Context<'_>) -> Vec { + async fn member( + &self, + ctx: &Context<'_>, + member_id: Option, + email: Option, + ) -> Result> { let pool = ctx.data::>().expect("Pool must be in context."); - sqlx::query_as::<_, AttendanceInfo>( - "SELECT date, is_present, time_in, time_out FROM Attendance WHERE member_id = $1", - ) - .bind(self.member_id) - .fetch_all(pool.as_ref()) - .await - .unwrap_or_default() + match (member_id, email) { + (Some(id), 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)) => { + 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()), + } } +} - #[graphql(name = "attendanceSummary")] - async fn attendance_summary(&self, ctx: &Context<'_>) -> Vec { +#[Object] +impl StatusInfo { + async fn streak(&self, ctx: &Context<'_>) -> Result { let pool = ctx.data::>().expect("Pool must be in context."); - sqlx::query_as::<_, AttendanceSummaryInfo>( - "SELECT - to_char(month_date, 'YYYY') AS year, - to_char(month_date, 'MM') AS month, - count(*) AS days_attended - FROM ( + // The below is based on the classic 'islands and gaps' problem, adapted to fit our needs. + // The key idea used here is in the 'streaks' CTE: for consecutive dates (a streak), the difference + // between the date value and its row number (rn) remains constant. + // All rows with the same (date - rn) value therefore belong to the same streak. + let result = sqlx::query_as::<_, StatusUpdateStreakRecord>( + "WITH numbered AS ( SELECT - date_trunc('month', date) AS month_date, - member_id - FROM - attendance - WHERE - is_present = TRUE - AND member_id = $1 - ) AS monthly_data - GROUP BY - month_date, - member_id;", - ) - .bind(self.member_id) - .fetch_all(pool.as_ref()) - .await - .unwrap_or_default() - } - - async fn streak(&self, ctx: &Context<'_>) -> Vec { - let pool = ctx.data::>().expect("Pool must be in context."); - - sqlx::query_as::<_, StatusUpdateStreakInfo>( - "SELECT current_streak, max_streak FROM StatusUpdateStreak WHERE member_id = $1", + date, + ROW_NUMBER() OVER (ORDER BY date) AS rn + FROM statusupdatehistory + WHERE member_id = $1 + AND is_sent = true + ), + streaks AS ( + SELECT + date, + date - rn * INTERVAL '1 day' AS streak_id + FROM numbered + ), + grouped AS ( + SELECT + COUNT(*) AS streak, + MAX(date) AS end_date + FROM streaks + GROUP BY streak_id + ) + SELECT + MAX(streak) AS max_streak, + ( + SELECT streak + FROM grouped + WHERE end_date = ( + SELECT MAX(end_date) + FROM grouped + ) + ) AS current_streak + FROM grouped; + ", ) .bind(self.member_id) - .fetch_all(pool.as_ref()) - .await - .unwrap_or_default() - } - - async fn projects(&self, ctx: &Context<'_>) -> Vec { - let pool = ctx.data::>().expect("Pool must be in context."); + .fetch_one(pool.as_ref()) + .await?; - sqlx::query_as::<_, Project>("SELECT project_id, title FROM Project WHERE member_id = $1") - .bind(self.member_id) - .fetch_all(pool.as_ref()) - .await - .unwrap_or_default() + Ok(result) } - async fn status_update_count_by_date( + async fn update_count( &self, ctx: &Context<'_>, start_date: NaiveDate, @@ -126,21 +145,42 @@ impl Member { Ok(result) } +} - async fn status_update_history(&self, ctx: &Context<'_>) -> Result> { - let pool = ctx.data::>().expect("Pool must be in context."); +#[Object] +impl AttendanceInfo { + async fn records( + &self, + ctx: &Context<'_>, + start_date: NaiveDate, + end_date: NaiveDate, + ) -> Result> { + let pool = ctx.data::>()?; + let rows = sqlx::query_as::<_, AttendanceRecord>("SELECT * FROM Attendance att INNER JOIN member m ON att.member_id = m.member_id where date BETWEEN $1 and $2 AND att.member_id=$3") + .bind(start_date) + .bind(end_date) + .bind(self.member_id) + .fetch_all(pool.as_ref()) + .await?; + + Ok(rows) + } + + async fn on_date(&self, ctx: &Context<'_>, date: NaiveDate) -> Result { + let pool = ctx.data::>()?; - let history = sqlx::query_as::<_, StatusUpdateHistory>( - "SELECT * FROM StatusUpdateHistory WHERE member_id = $1 AND date BETWEEN (SELECT MAX(date) FROM StatusUpdateHistory WHERE member_id = $1) - INTERVAL '6 months' AND (SELECT MAX(date) FROM StatusUpdateHistory WHERE member_id = $1);" + let rows = sqlx::query_as::<_, AttendanceRecord>( + "SELECT * FROM Attendance WHERE date = $1 AND member_id=$2", ) + .bind(date) .bind(self.member_id) - .fetch_all(pool.as_ref()) + .fetch_one(pool.as_ref()) .await?; - Ok(history) + Ok(rows) } - async fn present_count_by_date( + async fn present_count( &self, ctx: &Context<'_>, start_date: NaiveDate, @@ -170,7 +210,7 @@ impl Member { Ok(records) } - async fn absent_count_by_date( + async fn absent_count( &self, ctx: &Context<'_>, start_date: NaiveDate, @@ -215,3 +255,18 @@ impl Member { Ok(working_days - present) } } + +#[ComplexObject] +impl Member { + async fn status(&self, _ctx: &Context<'_>) -> StatusInfo { + StatusInfo { + member_id: self.member_id, + } + } + + async fn attendance(&self, _ctx: &Context<'_>) -> AttendanceInfo { + AttendanceInfo { + member_id: self.member_id, + } + } +} diff --git a/src/graphql/queries/mod.rs b/src/graphql/queries/mod.rs index 49a8263..df0fcbf 100644 --- a/src/graphql/queries/mod.rs +++ b/src/graphql/queries/mod.rs @@ -1,9 +1,3 @@ -pub mod attendance_queries; pub mod member_queries; -pub mod project_queries; -pub mod streak_queries; -pub use attendance_queries::AttendanceQueries; pub use member_queries::MemberQueries; -pub use project_queries::ProjectQueries; -pub use streak_queries::StreakQueries; diff --git a/src/graphql/queries/project_queries.rs b/src/graphql/queries/project_queries.rs deleted file mode 100644 index 08ef200..0000000 --- a/src/graphql/queries/project_queries.rs +++ /dev/null @@ -1,21 +0,0 @@ -use std::sync::Arc; - -use crate::models::project::Project; -use async_graphql::{Context, Object, Result}; -use sqlx::PgPool; - -#[derive(Default)] -pub struct ProjectQueries; - -#[Object] -impl ProjectQueries { - pub async fn projects(&self, ctx: &Context<'_>) -> Result> { - let pool = ctx.data::>().expect("Pool must be in context."); - - let projects = sqlx::query_as::<_, Project>("SELECT * FROM Project") - .fetch_all(pool.as_ref()) - .await?; - - Ok(projects) - } -} diff --git a/src/graphql/queries/streak_queries.rs b/src/graphql/queries/streak_queries.rs deleted file mode 100644 index 6c17dd4..0000000 --- a/src/graphql/queries/streak_queries.rs +++ /dev/null @@ -1,71 +0,0 @@ -use crate::models::member::Member; -use crate::models::status_update::StatusUpdateHistory; -use crate::models::status_update::StatusUpdateStreak as Streak; -use async_graphql::{ComplexObject, Context, Object, Result}; -use sqlx::PgPool; -use std::sync::Arc; - -#[derive(Default)] -pub struct StreakQueries; - -#[ComplexObject] -impl StatusUpdateHistory { - async fn member(&self, ctx: &Context<'_>) -> Result { - let pool = ctx.data::>()?; - let member = sqlx::query_as::<_, Member>("SELECT * FROM Member WHERE member_id = $1") - .bind(self.member_id) - .fetch_one(pool.as_ref()) - .await?; - - Ok(member) - } -} - -#[Object] -impl StreakQueries { - async fn streak(&self, ctx: &Context<'_>, member_id: i32) -> Result { - let pool = ctx.data::>().expect("Pool must be in context."); - - Ok(sqlx::query_as::<_, Streak>( - "SELECT current_streak, max_streak FROM StatusUpdateStreak WHERE member_id = $1", - ) - .bind(member_id) - .fetch_one(pool.as_ref()) - .await?) - } - - async fn streaks(&self, ctx: &Context<'_>) -> Result> { - let pool = ctx.data::>().expect("Pool must be in context."); - - Ok( - sqlx::query_as::<_, Streak>("SELECT * FROM StatusUpdateStreak") - .fetch_all(pool.as_ref()) - .await?, - ) - } - - async fn status_update_history_by_member_id( - &self, - ctx: &Context<'_>, - member_id: i32, - ) -> Result { - let pool = ctx.data::>().expect("Pool must be in context."); - - Ok(sqlx::query_as::<_, StatusUpdateHistory>( - "SELECT * FROM StatusUpdateHistory WHERE member_id = $1", - ) - .bind(member_id) - .fetch_one(pool.as_ref()) - .await?) - } - - async fn status_update_history(&self, ctx: &Context<'_>) -> Result> { - let pool = ctx.data::>()?; - - let rows = sqlx::query_as::<_, StatusUpdateHistory>("SELECT * FROM StatusUpdateHistory") - .fetch_all(pool.as_ref()) - .await?; - - Ok(rows) - } -} diff --git a/src/models/attendance.rs b/src/models/attendance.rs index be77548..8647774 100644 --- a/src/models/attendance.rs +++ b/src/models/attendance.rs @@ -3,11 +3,8 @@ use chrono::{NaiveDate, NaiveDateTime, NaiveTime}; use sqlx::FromRow; #[derive(SimpleObject, FromRow)] -#[graphql(complex)] -pub struct Attendance { +pub struct AttendanceRecord { pub attendance_id: i32, - #[graphql(skip)] - pub member_id: i32, pub date: NaiveDate, pub is_present: bool, pub time_in: Option, @@ -18,29 +15,6 @@ pub struct Attendance { pub updated_at: NaiveDateTime, } -#[derive(SimpleObject, FromRow)] -pub struct AttendanceSummary { - pub member_id: i32, - pub year: i32, - pub month: i32, - pub days_attended: i32, -} - -#[derive(SimpleObject, FromRow)] -pub struct AttendanceInfo { - pub date: NaiveDate, - pub is_present: bool, - pub time_in: Option, - pub time_out: Option, -} - -#[derive(SimpleObject, FromRow)] -pub struct AttendanceSummaryInfo { - pub year: i32, - pub month: i32, - pub days_attended: i32, -} - #[derive(InputObject)] pub struct MarkAttendanceInput { pub member_id: i32, diff --git a/src/models/mod.rs b/src/models/mod.rs index b728230..f0a4582 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -1,4 +1,3 @@ pub mod attendance; pub mod member; -pub mod project; pub mod status_update; diff --git a/src/models/project.rs b/src/models/project.rs deleted file mode 100644 index 8abdc50..0000000 --- a/src/models/project.rs +++ /dev/null @@ -1,15 +0,0 @@ -use async_graphql::{InputObject, SimpleObject}; -use sqlx::FromRow; - -#[derive(FromRow, SimpleObject)] -pub struct Project { - pub project_id: i32, - pub member_id: i32, - pub title: Option, -} - -#[derive(InputObject)] -pub struct SetProjectInput { - pub member_id: i32, - pub title: String, -} diff --git a/src/models/status_update.rs b/src/models/status_update.rs index 3c94114..22be945 100644 --- a/src/models/status_update.rs +++ b/src/models/status_update.rs @@ -1,31 +1,17 @@ -use async_graphql::{InputObject, SimpleObject}; +use async_graphql::SimpleObject; use chrono::NaiveDate; use sqlx::FromRow; #[derive(SimpleObject, FromRow)] -pub struct StatusUpdateStreak { - pub member_id: i32, - pub current_streak: i32, - pub max_streak: i32, -} - -#[derive(SimpleObject, FromRow)] -pub struct StatusUpdateStreakInfo { - pub current_streak: i32, - pub max_streak: i32, -} - -#[derive(InputObject)] -pub struct StreakInput { +pub struct StatusUpdateRecord { + pub update_id: i32, pub member_id: i32, + pub date: NaiveDate, + pub is_sent: bool, } #[derive(SimpleObject, FromRow)] -#[graphql(complex)] -pub struct StatusUpdateHistory { - pub update_id: i32, - #[graphql(skip)] - pub member_id: i32, - pub is_updated: bool, - pub date: NaiveDate, +pub struct StatusUpdateStreakRecord { + pub current_streak: i64, + pub max_streak: i64, }