Skip to content

Commit 9458678

Browse files
authored
Merge branch 'develop' into main
2 parents 9a3292e + 0eb4bae commit 9458678

File tree

8 files changed

+142
-11
lines changed

8 files changed

+142
-11
lines changed

.github/workflows/lint.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@ jobs:
88
clippy:
99
name: Clippy
1010
runs-on: ubuntu-latest
11-
env:
12-
DATABASE_URL: "sqlite::memory:"
11+
1312
steps:
1413
- name: Checkout repository
1514
uses: actions/checkout@v4

Cargo.lock

Lines changed: 54 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ edition = "2021"
77
async-graphql = { version = "7.0.15", features = ["chrono"] }
88
async-graphql-axum = "7.0.6"
99
axum = "0.8.1"
10-
chrono = "0.4.38"
10+
chrono = { version = "0.4.38", features = ["clock"] }
1111
serde = { version = "1.0.188", features = ["derive"] }
1212
sqlx = { version = "0.8.3", features = ["chrono", "postgres", "runtime-tokio"] }
1313
tokio = { version = "1.28.2", features = ["default", "macros", "rt-multi-thread"] } # For async tests
@@ -21,5 +21,6 @@ serde_json = "1.0"
2121
reqwest = { version = "0.12.12", features = ["json"] }
2222
config = "0.15"
2323
tracing = "0.1.41"
24-
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
24+
tracing-subscriber = { version = "0.3.19", features = ["env-filter", "time", "fmt", "std"] }
2525
dotenv = "0.15.0"
26+
time = { version = "0.3.37", features = ["formatting"] }

docs/attendance.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,38 @@ struct AttendanceSummary {
3232

3333
## Queries
3434

35+
### Get Attendance
36+
Retrieve attendance records by member ID or date.
37+
38+
```graphql
39+
# Get attendance by member ID
40+
query {
41+
attendance(memberId: 1) {
42+
attendanceId
43+
date
44+
isPresent
45+
timeIn
46+
timeOut
47+
}
48+
}
49+
```
50+
51+
Get all attendance for a specific date
52+
53+
```graphql
54+
query {
55+
attendanceByDate(date: "2025-02-27") {
56+
attendanceId
57+
memberId
58+
name
59+
year
60+
isPresent
61+
timeIn
62+
timeOut
63+
}
64+
}
65+
```
66+
3567
### Mark Attendance
3668
Record a member's attendance for the day.
3769

src/daily_task/mod.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ async fn execute_daily_task(pool: Arc<PgPool>) {
5555
// 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.
5656
/// 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.
5757
async fn update_attendance(members: Vec<Member>, pool: &PgPool) {
58-
let today = chrono::Utc::now().with_timezone(&Kolkata).date_naive();
58+
let today = chrono::Utc::now().with_timezone(&Kolkata).date().naive_local();
5959
debug!("Updating attendance on {}", today);
6060

6161
for member in members {
@@ -96,8 +96,8 @@ async fn update_attendance(members: Vec<Member>, pool: &PgPool) {
9696
/// Checks if the member was present yesterday, and if so, increments the `days_attended` value. Otherwise, do nothing.
9797
async fn update_attendance_summary(member_id: i32, pool: &PgPool) {
9898
debug!("Updating summary for member #{}", member_id);
99-
let today = chrono::Utc::now().with_timezone(&Kolkata).date_naive();
100-
let yesterday = today.pred_opt().expect("Time must be valid");
99+
let today = chrono::Utc::now().with_timezone(&Kolkata).date().naive_local();
100+
let yesterday = today - chrono::Duration::days(1);
101101

102102
// Check if the member was present yesterday
103103
let was_present_yesterday = sqlx::query_scalar::<_, bool>(

src/graphql/queries/attendance_queries.rs

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
use std::sync::Arc;
22

3+
use crate::models::{
4+
attendance::{Attendance, AttendanceWithMember},
5+
member::Member,
6+
};
37
use async_graphql::{Context, Object, Result};
8+
use chrono::NaiveDate;
49
use sqlx::PgPool;
510

6-
use crate::models::{attendance::Attendance, member::Member};
7-
811
/// Sub-query for the [`Attendance`] table. The queries are:
9-
/// * attendance - get a specific member's attendance details using their member_id, roll_no or discord_id
12+
/// * attendance - get a specific member's attendance details using their member_id, roll_no or discord_id, or by date for all members.
1013
#[derive(Default)]
1114
pub struct AttendanceQueries;
1215

@@ -66,4 +69,26 @@ impl AttendanceQueries {
6669

6770
Ok(attendance_query)
6871
}
72+
73+
// Query to get attendance by date
74+
async fn attendance_by_date(
75+
&self,
76+
ctx: &Context<'_>,
77+
date: NaiveDate,
78+
) -> Result<Vec<AttendanceWithMember>> {
79+
let pool = ctx.data::<Arc<PgPool>>().expect("Pool must be in context.");
80+
81+
let records = sqlx::query_as::<_, AttendanceWithMember>(
82+
"SELECT a.attendance_id, a.member_id, a.date, a.is_present,
83+
a.time_in, a.time_out, m.name, m.year
84+
FROM Attendance a
85+
JOIN Member m ON a.member_id = m.member_id
86+
WHERE a.date = $1",
87+
)
88+
.bind(date)
89+
.fetch_all(pool.as_ref())
90+
.await?;
91+
92+
Ok(records)
93+
}
6994
}

src/main.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
use async_graphql::EmptySubscription;
22
use axum::http::{HeaderValue, Method};
3+
use chrono::FixedOffset;
34
use sqlx::PgPool;
5+
use time::UtcOffset;
46
use std::sync::Arc;
57
use tower_http::cors::CorsLayer;
68
use tracing::info;
@@ -68,11 +70,14 @@ async fn main() {
6870

6971
/// Abstraction over initializing the global subscriber for tracing depending on whether it's in production or dev.
7072
fn setup_tracing(env: &str) {
73+
let kolkata_offset = UtcOffset::from_hms(5, 30, 0).expect("Hardcoded offset must be correct");
74+
let timer = fmt::time::OffsetTime::new(kolkata_offset, time::format_description::well_known::Rfc2822);
7175
if env == "production" {
7276
tracing_subscriber::registry()
7377
// In production, no need to write to stdout, write directly to file.
7478
.with(
7579
fmt::layer()
80+
.event_format(fmt::format().with_timer(timer.clone()))
7681
.pretty()
7782
.with_ansi(false) // ANSI encodings make it pretty but unreadable in the raw file.
7883
.with_writer(std::fs::File::create("root.log").unwrap()),
@@ -84,9 +89,10 @@ fn setup_tracing(env: &str) {
8489
} else {
8590
tracing_subscriber::registry()
8691
// Write to both stdout and file in development.
87-
.with(fmt::layer().pretty().with_writer(std::io::stdout))
92+
.with(fmt::layer().event_format(fmt::format().with_timer(timer.clone())).pretty().with_writer(std::io::stdout))
8893
.with(
8994
fmt::layer()
95+
.event_format(fmt::format().with_timer(timer.clone()))
9096
.pretty()
9197
.with_ansi(false)
9298
.with_writer(std::fs::File::create("root.log").unwrap()),

src/models/attendance.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,17 @@ pub struct MarkAttendanceInput {
4848
pub date: NaiveDate,
4949
pub hmac_signature: String,
5050
}
51+
52+
/// This struct combines attendance data with member name for queries that need both.
53+
/// It joins the Attendance table with Member to include the member's name.
54+
#[derive(SimpleObject, FromRow)]
55+
pub struct AttendanceWithMember {
56+
pub attendance_id: i32,
57+
pub member_id: i32,
58+
pub date: NaiveDate,
59+
pub is_present: bool,
60+
pub time_in: Option<NaiveTime>,
61+
pub time_out: Option<NaiveTime>,
62+
pub name: String,
63+
pub year: i32,
64+
}

0 commit comments

Comments
 (0)