Skip to content

Implements a scheduled task that migrates all members to attendance table at midnight #3

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Aug 13, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 2 additions & 0 deletions migrations/20240701161049_add_default_attendance_column.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ALTER TABLE Attendance
ADD COLUMN present BOOLEAN DEFAULT FALSE;
1 change: 1 addition & 0 deletions migrations/20240711150547_add_mac.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE Member ADD COLUMN macaddress TEXT;
1 change: 1 addition & 0 deletions migrations/20240813125650_rename_present_to_ispresent.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE Attendance RENAME COLUMN present TO ispresent;
1 change: 1 addition & 0 deletions migrations/20240813132052_rename_present_to_is_present.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE Attendance RENAME COLUMN ispresent TO is_present;
1 change: 1 addition & 0 deletions src/db/attendance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ pub struct Attendance {
pub date: NaiveDate,
pub timein: NaiveTime,
pub timeout: NaiveTime,
pub is_present: bool,
}
1 change: 1 addition & 0 deletions src/db/member.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use async_graphql::SimpleObject;

//Struct for the Member table
#[derive(FromRow, SimpleObject)]

pub struct Member {
pub id: i32,
pub rollno: String,
Expand Down
25 changes: 25 additions & 0 deletions src/graphql/mutations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ pub struct MutationRoot;

#[Object]
impl MutationRoot {

//Mutation for adding members to the Member table
async fn add_member(
&self,
ctx: &Context<'_>,
Expand Down Expand Up @@ -37,6 +39,8 @@ impl MutationRoot {
Ok(member)
}


//Mutation for adding attendance to the Attendance table
async fn add_attendance(
&self,
ctx: &Context<'_>,
Expand All @@ -59,4 +63,25 @@ impl MutationRoot {

Ok(attendance)
}

async fn mark_attendance(
&self,
ctx: &Context<'_>,
id: i32,
date: NaiveDate,
is_present: bool,
) -> Result<Attendance,sqlx::Error> {
let pool = ctx.data::<Arc<PgPool>>().expect("Pool not found in context");

let attendance = sqlx::query_as::<_, Attendance>(
"UPDATE Attendance SET is_present = $1 WHERE id = $2 AND date = $3 RETURNING *"
)
.bind(is_present)
.bind(id)
.bind(date)
.fetch_one(pool.as_ref())
.await?;

Ok(attendance)
}
}
32 changes: 17 additions & 15 deletions src/graphql/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,28 +10,30 @@ pub struct QueryRoot;

#[Object]
impl QueryRoot {
async fn get_users(&self, ctx: &Context<'_>) -> Result<Vec<Member>, sqlx::Error> {

//Query for retrieving the members
async fn get_member(&self, ctx: &Context<'_>) -> Result<Vec<Member>, sqlx::Error> {
let pool = ctx.data::<Arc<PgPool>>().expect("Pool not found in context");
let users = sqlx::query_as::<_, Member>("SELECT * FROM Member")
.fetch_all(pool.as_ref())
.await?;
Ok(users)
}

async fn get_attendance(
&self,
ctx: &Context<'_>,
date: NaiveDate,
) -> Result<Vec<Attendance>, sqlx::Error> {
let pool = ctx.data::<Arc<PgPool>>().expect("Pool not found in context");

let attendance_list = sqlx::query_as::<_, Attendance>(
"SELECT id, date, timein, timeout FROM Attendance WHERE date = $1"
)
.bind(date)
.fetch_all(pool.as_ref())
.await?;
//Query for retrieving the attendance based on date
async fn get_attendance(
&self,
ctx: &Context<'_>,
date: NaiveDate,
) -> Result<Vec<Attendance>, sqlx::Error> {
let pool = ctx.data::<Arc<PgPool>>().expect("Pool not found in context");

Ok(attendance_list)
let attendance_list = sqlx::query_as::<_, Attendance>(
"SELECT id, date, timein, timeout FROM Attendance WHERE date = $1"
)
.bind(date)
.fetch_all(pool.as_ref())
.await?;
Ok(attendance_list)
}
}
74 changes: 71 additions & 3 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
use std::{env, sync::Arc};

use std::{env, sync::Arc};
use tokio::task;
use tokio::time::{sleep, sleep_until, Instant};
use std::time::Duration;
use async_graphql_axum::GraphQL;
use axum::{routing::get, Router};
use chrono::{ Local, NaiveTime};
use db::attendance::Attendance;
use db::member::Member;
use sqlx::PgPool;
use async_graphql::{ Schema, EmptySubscription};

Expand All @@ -18,7 +24,7 @@ struct MyState {
pool: Arc<PgPool>,
}

//This is the main method
//Main method
#[shuttle_runtime::main]
async fn main(#[shuttle_shared_db::Postgres] pool: PgPool) -> shuttle_axum::ShuttleAxum {
env::set_var("PGOPTIONS", "-c ignore_version=true");
Expand All @@ -30,10 +36,72 @@ async fn main(#[shuttle_shared_db::Postgres] pool: PgPool) -> shuttle_axum::Shut
.data(pool.clone())
.finish();

let state = MyState { pool };
let state = MyState { pool: pool.clone() };

let router = Router::new()
.route("/", get(graphiql).post_service(GraphQL::new(schema.clone())))
.with_state(state);
task::spawn(async move {

schedule_task_at_midnight(pool.clone()).await; // Call the function after 10 seconds
});


Ok(router.into())
}



//Scheduled task for moving all members to Attendance table at midnight.
async fn scheduled_task(pool: Arc<PgPool>) {
let members: Result<Vec<Member>, sqlx::Error> = sqlx::query_as::<_, Member>("SELECT * FROM Member")
.fetch_all(pool.as_ref())
.await;

match members {
Ok(members) => {
let today = Local::now().naive_local();

for member in members {
let timein = NaiveTime::from_hms_opt(0, 0, 0);
let timeout = NaiveTime::from_hms_opt(0, 0, 0); // Default time, can be modified as needed

let attendance = sqlx::query(
"INSERT INTO Attendance (id, date, timein, timeout, present) VALUES ($1, $2, $3, $4, $5) ON CONFLICT (id, date) DO NOTHING RETURNING *"
)
.bind(member.id)
.bind(today)
.bind(timein)
.bind(timeout)
.bind(false)
.execute(pool.as_ref())
.await;

match attendance {
Ok(_) => println!("Attendance record added for member ID: {}", member.id),
Err(e) => eprintln!("Failed to insert attendance for member ID: {}: {:?}", member.id, e),
}
}
},
Err(e) => eprintln!("Failed to fetch members: {:?}", e),
}
}

//Ticker for calling the scheduled task
async fn schedule_task_at_midnight(pool: Arc<PgPool>) {
loop {
let now = Local::now();

let tomorrow = now.date_naive().succ_opt().unwrap();
let midnight = NaiveTime::from_hms_opt(0, 0, 0).unwrap();
let next_midnight = tomorrow.and_time(midnight);

let now_naive = now.naive_local();
let duration_until_midnight = next_midnight.signed_duration_since(now_naive);
let sleep_duration = Duration::from_secs(duration_until_midnight.num_seconds() as u64 + 60);

sleep_until(Instant::now() + sleep_duration).await;
scheduled_task(pool.clone()).await;
print!("done");
}
}