Skip to content

Commit

Permalink
rewrite attendance record insertion and updation logic
Browse files Browse the repository at this point in the history
This commit unfortunately removes the leaderboard updation via leetcode and codeforces stats to simplify my work for now. Will have to add it back later.
  • Loading branch information
ivinjabraham committed Jan 15, 2025
1 parent 0ba8c00 commit 86ec4e0
Showing 1 changed file with 152 additions and 182 deletions.
334 changes: 152 additions & 182 deletions src/attendance/daily_task.rs
Original file line number Diff line number Diff line change
@@ -1,204 +1,174 @@
use chrono::{Local, NaiveTime};
use chrono::{Datelike, Local, NaiveDate, NaiveTime};
use chrono_tz::Asia::Kolkata;
use sqlx::PgPool;
use std::sync::Arc;
use tracing::{debug, error, info};
use tracing::{debug, error, info, trace};

use crate::models::member::Member;

// TODO: Abstract this down to functions for each task.
// We need to add a record for every member because otherwise Presense will only add present members to the DB, and we will have to JOIN Members and Attendance records for the day to get the absent members. In exchange for increased storage use, we get simpler queries for Home which needs the data for every member for every day so far. But as of Jan 2025, there are less than 50 members in the club and thus shouldn't really create that many rows over time.
/// 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.
/// * Fetch stats from both codeforces and leetcode
/// * Use those fetched stats to update the leaderboard
/// * Update attendance streaks for some reason?
/// * Update the AttendanceSummary table
pub async fn execute_daily_task(pool: Arc<PgPool>) {
add_daily_attendance_records(&*pool).await;
// Members is queried outside of each function to avoid repetition
let members = sqlx::query_as::<_, Member>("SELECT * FROM Member")
.fetch_all(&*pool)
.await;

//fetching the username from tables
// let leetcode_username = sqlx::query_as::<_, LeetCodeStats>(
// "SELECT * FROM leetcode_stats WHERE member_id = $1",
// )
// .bind(member.id)
// .fetch_optional(pool.as_ref())
// .await;

// if let Ok(Some(leetcode_stats)) = leetcode_username {
// let username = leetcode_stats.leetcode_username.clone();

// // Fetch and update LeetCode stats
// match fetch_leetcode_stats(pool.clone(), member.id, &username).await {
// Ok(_) => println!("LeetCode stats updated for member ID: {}", member.id),
// Err(e) => eprintln!(
// "Failed to update LeetCode stats for member ID {}: {:?}",
// member.id, e
// ),
// }
// }

// // Fetch Codeforces username
// let codeforces_username = sqlx::query_as::<_, CodeforcesStats>(
// "SELECT * FROM codeforces_stats WHERE member_id = $1",
// )
// .bind(member.id)
// .fetch_optional(pool.as_ref())
// .await;

// if let Ok(Some(codeforces_stats)) = codeforces_username {
// let username = codeforces_stats.codeforces_handle.clone();

// // Fetch and update Codeforces stats
// match fetch_codeforces_stats(pool.clone(), member.id, &username).await {
// Ok(_) => println!("Codeforces stats updated for member ID: {}", member.id),
// Err(e) => eprintln!(
// "Failed to update Codeforces stats for member ID {}: {:?}",
// member.id, e
// ),
// }
// }

// match update_leaderboard(pool.clone()).await {
// Ok(_) => println!("Leaderboard updated."),
// Err(e) => eprintln!("Failed to update leaderboard: {:?}", e),
// }

// // Update attendance streak
// update_attendance_streak(member.id, pool.as_ref()).await;
match members {
Ok(members) => update_attendance(members, &*pool).await,
// TODO: Handle this
Err(e) => (),
};
}

// We need to add a record for every member because otherwise [`Presense`](https://www.github.com/presense) will only add present members to the DB, and we will have to JOIN Members and Attendance records for the day to get the absent members. In exchange for increased storage use, we get simpler queries for Home which needs the data for every member for every day so far. But as of Jan 2025, there are less than 50 members in the club and thus storage really shouldn't be an issue.
/// Inserts new attendance records everyday for [`presense`](https://www.github.com/amfoss/presense) to update them later in the day.
async fn add_daily_attendance_records(pool: &PgPool) {
info!("Adding daily attendance records...");

let members: Result<Vec<Member>, sqlx::Error> =
sqlx::query_as::<_, Member>("SELECT * FROM Member")
.fetch_all(pool)
.await;

match members {
Ok(members) => {
let today = Local::now().with_timezone(&Kolkata).date_naive();

for member in members {
let attendance = sqlx::query(
"INSERT INTO Attendance (member_id, date, is_present, time_in, time_out)
/// Inserts new attendance records everyday for [`presense`](https://www.github.com/amfoss/presense) to update them later in the day and updates the AttendanceSummary table to keep track of monthly streaks.
async fn update_attendance(members: Vec<Member>, pool: &PgPool) {
info!("Updating attendance...");
let today = Local::now().with_timezone(&Kolkata).date_naive();

for member in members {
let attendance = sqlx::query(
"INSERT INTO Attendance (member_id, date, is_present, time_in, time_out)
VALUES ($1, $2, $3, $4, $5)
ON CONFLICT (member_id, date) DO NOTHING",
)
.bind(member.member_id)
.bind(today)
.bind(false) // Default `is_present` is False
.bind(None::<NaiveTime>) // Default `time_in` is NULL
.bind(None::<NaiveTime>) // Default `time_out` is NULL
.execute(pool)
.await;

match attendance {
Ok(_) => {
debug!(
"Attendance record added for member ID: {}",
member.member_id
);
}
Err(e) => {
error!(
"Failed to insert attendance for member ID: {}: {:?}",
member.member_id, e
);
}
}
)
.bind(member.member_id)
.bind(today)
.bind(false) // Default `is_present` is False
.bind(None::<NaiveTime>) // Default `time_in` is NULL
.bind(None::<NaiveTime>) // Default `time_out` is NULL
.execute(pool)
.await;

match attendance {
Ok(_) => {
debug!(
"Attendance record added for member ID: {}",
member.member_id
);
}
Err(e) => {
error!(
"Failed to insert attendance for member ID: {}: {:?}",
member.member_id, e
);
}
}
update_attendance_summary(member.member_id, pool).await;
}
}

/// Checks if the member was present yesterday, and if so, increments the `days_attended` value. Otherwise, do
/// nothing.
async fn update_attendance_summary(member_id: i32, pool: &PgPool) {
trace!("Updating summary for member #{}", member_id);
let today = chrono::Local::now().with_timezone(&Kolkata).date_naive();
let yesterday = today.checked_sub_signed(chrono::Duration::days(1)).unwrap(); // Get yesterday's date

// Check if the member was present yesterday
let was_present_yesterday = sqlx::query_scalar::<_, bool>(
r#"
SELECT is_present
FROM Attendance
WHERE member_id = $1 AND date = $2
"#,
)
.bind(member_id)
.bind(yesterday)
.fetch_one(pool)
.await;

match was_present_yesterday {
// Member was present yesterday, update the summary
Ok(true) => {
update_days_attended(member_id, today, pool).await;
}
// Member was absent
Ok(false) => {
debug!(
"Member ID: {} was absent yesterday, days_attended remains the same.",
member_id
);
}
Err(e) => {
error!("Failed to fetch members: {:?}", e);
error!("Could not fetch records from DB. Error: {}", e);
}
}
}

// Function to update attendance streak
// async fn update_attendance_streak(member_id: i32, pool: &sqlx::PgPool) {
// let today = chrono::Local::now()
// .with_timezone(&chrono_tz::Asia::Kolkata)
// .naive_local();
// let yesterday = today
// .checked_sub_signed(chrono::Duration::hours(12))
// .unwrap()
// .date();
//
// if today.day() == 1 {
// let _ = sqlx::query(
// r#"
// INSERT INTO AttendanceStreak (member_id, month, streak)
// VALUES ($1, date_trunc('month', $2::date AT TIME ZONE 'Asia/Kolkata'), 0)
// "#,
// )
// .bind(member_id)
// .bind(today)
// .execute(pool)
// .await;
// println!("Attendance streak created for member ID: {}", member_id);
// }
//
// let present_attendance = sqlx::query_scalar::<_, i64>(
// r#"
// SELECT COUNT(*)
// FROM Attendance
// WHERE id = $1
// AND is_present = true
// AND date = $2
// "#,
// )
// .bind(member_id)
// .bind(yesterday)
// .fetch_one(pool)
// .await;
//
// match present_attendance {
// Ok(1) => {
// let existing_streak = sqlx::query_scalar::<_, i32>(
// r#"
// SELECT streak
// FROM AttendanceStreak
// WHERE member_id = $1
// AND month = date_trunc('month', $2::date AT TIME ZONE 'Asia/Kolkata')
// "#,
// )
// .bind(member_id)
// .bind(today)
// .fetch_optional(pool)
// .await;
//
// match existing_streak {
// Ok(Some(streak)) => {
// let _ = sqlx::query(
// r#"
// UPDATE AttendanceStreak
// SET streak = $1
// WHERE member_id = $2
// AND month = date_trunc('month', $3::date AT TIME ZONE 'Asia/Kolkata')
// "#,
// )
// .bind(streak + 1)
// .bind(member_id)
// .bind(today)
// .execute(pool)
// .await;
// }
// Ok(None) => {
// println!("No streak found for member ID: {}", member_id);
// }
// Err(e) => eprintln!("Error checking streak for member ID {}: {:?}", member_id, e),
// }
// }
// Ok(0) => {
// println!("Sreak not incremented for member ID: {}", member_id);
// }
// Ok(_) => eprintln!("Unexpected attendance value for member ID: {}", member_id),
// Err(e) => eprintln!(
// "Error checking attendance for member ID {}: {:?}",
// member_id, e
// ),
// }
//}
/// Increments the `days_attended` value for the given member in the given month.
async fn update_days_attended(member_id: i32, today: NaiveDate, pool: &PgPool) {
// Convert year and month into i32 cause SQLx cannot encode u32 into database types
let month: i32 = (today.month0() + 1) as i32;
let year: i32 = today.year_ce().1 as i32;

// Check if there’s an existing summary for the current month
let existing_days_attended = sqlx::query_scalar::<_, i32>(
r#"
SELECT days_attended
FROM AttendanceSummary
WHERE member_id = $1
AND year = $2
AND month = $3
"#,
)
.bind(member_id)
.bind(year)
.bind(month)
.fetch_optional(pool)
.await;

match existing_days_attended {
Ok(Some(days_attended)) => {
sqlx::query(
r#"
UPDATE AttendanceSummary
SET days_attended = days_attended + 1
WHERE member_id = $2
AND year = $3
AND month = $4
"#,
)
.bind(member_id)
.bind(year)
.bind(month)
.execute(pool)
.await
.unwrap();

debug!(
"Updated days_attended for member ID: {}. New days_attended: {}",
member_id,
days_attended + 1
);
}
// No summary exists for this month, create a new one
Ok(None) => {
sqlx::query(
r#"
INSERT INTO AttendanceSummary (member_id, year, month, days_attended)
VALUES ($1, $2, 1)
"#,
)
.bind(member_id)
.bind(year)
.bind(month)
.execute(pool)
.await
.unwrap();

debug!(
"Created new streak for member ID: {} for the month.",
member_id
);
}
Err(e) => {
error!(
"Error checking or updating streak for member ID {}: {:?}",
member_id, e
);
}
}
}

0 comments on commit 86ec4e0

Please sign in to comment.