diff --git a/src/bin/trainee-tracker.rs b/src/bin/trainee-tracker.rs index acbe439..5239e0c 100644 --- a/src/bin/trainee-tracker.rs +++ b/src/bin/trainee-tracker.rs @@ -100,6 +100,10 @@ async fn main() { "/groups/slack.csv", get(trainee_tracker::frontend::list_slack_groups_csv), ) + .route( + "/api/attendance", + get(trainee_tracker::endpoints::fetch_attendance), + ) .layer(session_layer) .with_state(server_state); diff --git a/src/endpoints.rs b/src/endpoints.rs index 1d5ebdb..56f3f73 100644 --- a/src/endpoints.rs +++ b/src/endpoints.rs @@ -1,3 +1,5 @@ +use std::collections::BTreeMap; + use ::octocrab::models::{teams::RequestedTeam, Author}; use anyhow::Context; use axum::{ @@ -15,6 +17,7 @@ use crate::{ newtypes::GithubLogin, octocrab::{all_pages, octocrab}, prs::{fill_in_reviewers, get_prs, PrWithReviews}, + register::{get_register, Attendance}, sheets::sheets_client, Error, ServerState, }; @@ -207,3 +210,76 @@ pub async fn get_region( .map(|trainee| trainee.region.clone()), })) } + +type SprintAttendance = BTreeMap>; +type ModuleAttendance = BTreeMap; +type BatchAttendance = BTreeMap; +type CourseAttendance = BTreeMap; + +#[derive(Serialize)] +pub struct AttendanceResponse { + courses: CourseAttendance, +} + +pub async fn fetch_attendance( + session: Session, + State(server_state): State, + OriginalUri(original_uri): OriginalUri, +) -> Result, Error> { + let all_courses = &server_state.config.courses; + let sheets_client = sheets_client(&session, server_state.clone(), original_uri.clone()).await?; + + let mut courses: CourseAttendance = BTreeMap::new(); + let mut register_futures = Vec::new(); + for (course_name, course_info) in all_courses { + for batch_name in course_info.batches.keys() { + let course_schedule = server_state + .config + .get_course_schedule_with_register_sheet_id(course_name.clone(), batch_name) + .ok_or_else(|| Error::Fatal(anyhow::anyhow!("Course not found: {course_name}")))?; + let register_future = get_register( + sheets_client.clone(), + course_schedule.register_sheet_id.clone(), + course_schedule.course_schedule.start, + course_schedule.course_schedule.end, + ); + register_futures.push(async move { + ( + course_name.clone(), + batch_name.clone(), + register_future.await, + ) + }); + } + } + let register_info = join_all(register_futures).await; + + for (course_name, batch_name, register_result) in register_info { + let register = register_result?; + let modules = register + .modules + .into_iter() + .map(|(module_name, sprint_info)| { + ( + module_name, + sprint_info + .attendance + .into_iter() + .enumerate() + .map(|(sprint_number, sprint_info)| { + ( + format!("Sprint-{}", sprint_number + 1), + sprint_info.into_values().collect(), + ) + }) + .collect(), + ) + }) + .collect(); + courses + .entry(course_name) + .or_default() + .insert(batch_name, modules); + } + Ok(Json(AttendanceResponse { courses })) +} diff --git a/src/register.rs b/src/register.rs index e904295..97918a3 100644 --- a/src/register.rs +++ b/src/register.rs @@ -4,6 +4,7 @@ use anyhow::Context; use chrono::{DateTime, NaiveDate, Utc}; use email_address::EmailAddress; use indexmap::IndexMap; +use serde::Serialize; use sheets::types::{CellData, GridData}; use tracing::warn; @@ -24,7 +25,7 @@ pub struct ModuleAttendance { pub attendance: Vec>, } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize)] pub struct Attendance { pub name: String, pub email: EmailAddress,