From c5d693c2bb243949cbcc0c69f730acd01dce98c0 Mon Sep 17 00:00:00 2001 From: AnnaFYZ Date: Thu, 18 Sep 2025 21:14:48 +0100 Subject: [PATCH 1/8] first working api --- config.prod.json | 6 +++--- src/bin/trainee-tracker.rs | 4 ++++ src/endpoints.rs | 32 ++++++++++++++++++++++++++------ 3 files changed, 33 insertions(+), 9 deletions(-) diff --git a/config.prod.json b/config.prod.json index b9133c4..e93030c 100644 --- a/config.prod.json +++ b/config.prod.json @@ -1,11 +1,11 @@ { "github_org": "CodeYourFuture", - "github_client_id": "Ov23liyZuouqyi9L3ZpM", + "github_client_id": "Ov23cturUbMnqQZ535bF", "github_client_secret": "$CYF_TRAINEE_TRACKER_GITHUB_CLIENT_SECRET", "addr": "0.0.0.0", "port": 3000, - "public_base_url": "https://trainee-tracker.hosting.codeyourfuture.io", - "google_apis_client_id": "403459539948-q1psg497mlfk9u6cgf0eea5qum4gp92n.apps.googleusercontent.com", + "public_base_url": "http://localhost:3000", + "google_apis_client_id": "403459539948-0af4rtk7po28idm3eiv0jj7qcq6u8vvs.apps.googleusercontent.com", "google_apis_client_secret": "$CYF_TRAINEE_TRACKER_GOOGLE_APIS_CLIENT_SECRET", "github_email_mapping_sheet_id": "1ahDEnO8odD9oLtO_EBcvcmEaF0qsX-I4iLCIY0XLjt0", "reviewer_staff_info_sheet_id": "1CKDrXtx5lkgfZ8E2mjvsDup2K8UyBsq99CIV0qOxrP0", diff --git a/src/bin/trainee-tracker.rs b/src/bin/trainee-tracker.rs index acbe439..48c72d4 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::fecth_attendance), + ) .layer(session_layer) .with_state(server_state); diff --git a/src/endpoints.rs b/src/endpoints.rs index 1d5ebdb..98edb5a 100644 --- a/src/endpoints.rs +++ b/src/endpoints.rs @@ -11,12 +11,7 @@ use serde::Serialize; use tower_sessions::Session; use crate::{ - github_accounts::get_trainees, - newtypes::GithubLogin, - octocrab::{all_pages, octocrab}, - prs::{fill_in_reviewers, get_prs, PrWithReviews}, - sheets::sheets_client, - Error, ServerState, + course, github_accounts::get_trainees, newtypes::GithubLogin, octocrab::{all_pages, octocrab}, prs::{fill_in_reviewers, get_prs, PrWithReviews}, register::get_register, sheets::sheets_client, Error, ServerState }; pub async fn health_check() -> impl IntoResponse { @@ -207,3 +202,28 @@ pub async fn get_region( .map(|trainee| trainee.region.clone()), })) } + +pub async fn fecth_attendance( session: Session, + State(server_state): State, + OriginalUri(original_uri): OriginalUri,) -> Result { + let course = "itp".to_string(); + let batch_github_slug = "itp-batch-2025-sep".to_string(); + let sheets_client = sheets_client(&session, server_state.clone(), original_uri.clone()).await?; + let github_org = &server_state.config.github_org; + let octocrab = octocrab(&session, &server_state, original_uri).await?; + let course_schedule = server_state + .config + .get_course_schedule_with_register_sheet_id(course.clone(), &batch_github_slug) + .ok_or_else(|| Error::Fatal(anyhow::anyhow!("Course not found: {course}")))?; + let course = course_schedule + .with_assignments(&octocrab, github_org) + .await?; + let register_info = get_register( + sheets_client.clone(), + course.register_sheet_id.clone(), + course.start_date, + course.end_date, + ) + .await?; + Ok(format!("Register info: {:#?}", register_info)) +} From 9e43d99a86b62e500ac2665b49d76bb5cf8aa98b Mon Sep 17 00:00:00 2001 From: AnnaFYZ Date: Fri, 26 Sep 2025 17:51:25 +0100 Subject: [PATCH 2/8] atendace --- src/endpoints.rs | 71 ++++++++++++++++++++++++++++++++++++------------ 1 file changed, 53 insertions(+), 18 deletions(-) diff --git a/src/endpoints.rs b/src/endpoints.rs index 98edb5a..76b308c 100644 --- a/src/endpoints.rs +++ b/src/endpoints.rs @@ -203,27 +203,62 @@ pub async fn get_region( })) } +#[derive(Serialize)] +pub struct AttendanceEntry { + date: String, + trainee_email: String, + time: String +} +#[derive(Serialize)] +pub struct Attendance { + courses: IndexMap>>>> +} + pub async fn fecth_attendance( session: Session, State(server_state): State, - OriginalUri(original_uri): OriginalUri,) -> Result { - let course = "itp".to_string(); - let batch_github_slug = "itp-batch-2025-sep".to_string(); + 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 github_org = &server_state.config.github_org; let octocrab = octocrab(&session, &server_state, original_uri).await?; - let course_schedule = server_state - .config - .get_course_schedule_with_register_sheet_id(course.clone(), &batch_github_slug) - .ok_or_else(|| Error::Fatal(anyhow::anyhow!("Course not found: {course}")))?; - let course = course_schedule - .with_assignments(&octocrab, github_org) - .await?; - let register_info = get_register( - sheets_client.clone(), - course.register_sheet_id.clone(), - course.start_date, - course.end_date, - ) - .await?; - Ok(format!("Register info: {:#?}", register_info)) + let mut courses: IndexMap>>>> = IndexMap::new(); + for (course_name, course_info) in all_courses { + let mut course_batches: IndexMap>>> = IndexMap::new(); + 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 mut modules: IndexMap>> = IndexMap::new(); + let course = course_schedule + .with_assignments(&octocrab, github_org) + .await?; + let register_info = get_register( + sheets_client.clone(), + course.register_sheet_id.clone(), + course.start_date, + course.end_date, + ) + .await?; + + for (module_name, sprint_info) in ®ister_info.modules { + let mut sprints: IndexMap> = IndexMap::new(); + for (sprint_number, sprint_info) in sprint_info.attendance.iter().enumerate() { + let mut entries: Vec = Vec::new(); + for (_index, entry) in sprint_info { + entries.push(AttendanceEntry { date: entry.timestamp.date_naive().to_string(), + trainee_email: entry.email.to_string(), + time: entry.timestamp.time().to_string(), }) + } + sprints.insert(sprint_number.to_string(), entries); + } + modules.insert(module_name.clone(), sprints); + } + course_batches.insert(batch_name.clone(), modules ); + } + courses.insert(course_name.clone(), course_batches ); + } + + Ok(Json(Attendance { courses })) + } From 50e6217a6301f6d10d5c50f51064687a370d7417 Mon Sep 17 00:00:00 2001 From: AnnaFYZ Date: Wed, 1 Oct 2025 16:23:29 +0100 Subject: [PATCH 3/8] register imported to endpoints --- src/endpoints.rs | 65 +++++++++++++++++++++++++++--------------------- src/register.rs | 3 ++- 2 files changed, 38 insertions(+), 30 deletions(-) diff --git a/src/endpoints.rs b/src/endpoints.rs index 76b308c..bef09dd 100644 --- a/src/endpoints.rs +++ b/src/endpoints.rs @@ -11,7 +11,14 @@ use serde::Serialize; use tower_sessions::Session; use crate::{ - course, github_accounts::get_trainees, newtypes::GithubLogin, octocrab::{all_pages, octocrab}, prs::{fill_in_reviewers, get_prs, PrWithReviews}, register::get_register, sheets::sheets_client, Error, ServerState + course, + github_accounts::get_trainees, + newtypes::GithubLogin, + octocrab::{all_pages, octocrab}, + prs::{fill_in_reviewers, get_prs, PrWithReviews}, + register::{get_register, Attendance}, + sheets::sheets_client, + Error, ServerState, }; pub async fn health_check() -> impl IntoResponse { @@ -204,32 +211,35 @@ pub async fn get_region( } #[derive(Serialize)] -pub struct AttendanceEntry { - date: String, - trainee_email: String, - time: String -} -#[derive(Serialize)] -pub struct Attendance { - courses: IndexMap>>>> +pub struct AttendanceResponse { + courses: + IndexMap>>>>, } -pub async fn fecth_attendance( session: Session, +pub async fn fecth_attendance( + session: Session, State(server_state): State, - OriginalUri(original_uri): OriginalUri,) -> Result, Error> { + 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 github_org = &server_state.config.github_org; let octocrab = octocrab(&session, &server_state, original_uri).await?; - let mut courses: IndexMap>>>> = IndexMap::new(); + let mut courses: IndexMap< + String, + IndexMap>>>, + > = IndexMap::new(); for (course_name, course_info) in all_courses { - let mut course_batches: IndexMap>>> = IndexMap::new(); + let mut course_batches: IndexMap< + String, + IndexMap>>, + > = IndexMap::new(); 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 mut modules: IndexMap>> = IndexMap::new(); + let mut modules: IndexMap>> = IndexMap::new(); let course = course_schedule .with_assignments(&octocrab, github_org) .await?; @@ -238,27 +248,24 @@ pub async fn fecth_attendance( session: Session, course.register_sheet_id.clone(), course.start_date, course.end_date, - ) - .await?; + ) + .await?; - for (module_name, sprint_info) in ®ister_info.modules { - let mut sprints: IndexMap> = IndexMap::new(); - for (sprint_number, sprint_info) in sprint_info.attendance.iter().enumerate() { - let mut entries: Vec = Vec::new(); + for (module_name, sprint_info) in register_info.modules { + let mut sprints: IndexMap> = IndexMap::new(); + for (sprint_number, sprint_info) in sprint_info.attendance.into_iter().enumerate() { + let mut entries: Vec = Vec::new(); for (_index, entry) in sprint_info { - entries.push(AttendanceEntry { date: entry.timestamp.date_naive().to_string(), - trainee_email: entry.email.to_string(), - time: entry.timestamp.time().to_string(), }) + entries.push(entry); } - sprints.insert(sprint_number.to_string(), entries); + sprints.insert(format!("Sprint {}", sprint_number + 1), entries); } modules.insert(module_name.clone(), sprints); } - course_batches.insert(batch_name.clone(), modules ); - } - courses.insert(course_name.clone(), course_batches ); + course_batches.insert(batch_name.clone(), modules); + } + courses.insert(course_name.clone(), course_batches); } - - Ok(Json(Attendance { courses })) + 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, From 5e5e7a942babcc26004052d2aaeccc68c21623c8 Mon Sep 17 00:00:00 2001 From: AnnaFYZ Date: Mon, 6 Oct 2025 20:06:11 +0100 Subject: [PATCH 4/8] types applied and IndexMap replaces with BTreeMap --- src/endpoints.rs | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/src/endpoints.rs b/src/endpoints.rs index bef09dd..5179cdc 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::{ @@ -11,7 +13,6 @@ use serde::Serialize; use tower_sessions::Session; use crate::{ - course, github_accounts::get_trainees, newtypes::GithubLogin, octocrab::{all_pages, octocrab}, @@ -210,10 +211,14 @@ pub async fn get_region( })) } +type SprintAttendance = BTreeMap>; +type ModuleAttendance = BTreeMap; +type BatchAttendance = BTreeMap; +type CourseAttendance = BTreeMap; + #[derive(Serialize)] pub struct AttendanceResponse { - courses: - IndexMap>>>>, + courses: CourseAttendance } pub async fn fecth_attendance( @@ -225,21 +230,15 @@ pub async fn fecth_attendance( let sheets_client = sheets_client(&session, server_state.clone(), original_uri.clone()).await?; let github_org = &server_state.config.github_org; let octocrab = octocrab(&session, &server_state, original_uri).await?; - let mut courses: IndexMap< - String, - IndexMap>>>, - > = IndexMap::new(); + let mut courses: CourseAttendance = BTreeMap::new(); for (course_name, course_info) in all_courses { - let mut course_batches: IndexMap< - String, - IndexMap>>, - > = IndexMap::new(); + let mut course_batches: BatchAttendance = BTreeMap::new(); 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) + .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 mut modules: IndexMap>> = IndexMap::new(); + let mut modules: ModuleAttendance = BTreeMap::new(); let course = course_schedule .with_assignments(&octocrab, github_org) .await?; @@ -252,13 +251,13 @@ pub async fn fecth_attendance( .await?; for (module_name, sprint_info) in register_info.modules { - let mut sprints: IndexMap> = IndexMap::new(); + let mut sprints: SprintAttendance = BTreeMap::new(); for (sprint_number, sprint_info) in sprint_info.attendance.into_iter().enumerate() { let mut entries: Vec = Vec::new(); - for (_index, entry) in sprint_info { + for entry in sprint_info.into_values() { entries.push(entry); } - sprints.insert(format!("Sprint {}", sprint_number + 1), entries); + sprints.insert(format!("Sprint-{}", sprint_number + 1), entries); } modules.insert(module_name.clone(), sprints); } From c5c69f52587dab35314fc76bde7fefd966f392a3 Mon Sep 17 00:00:00 2001 From: AnnaFYZ Date: Mon, 6 Oct 2025 20:11:54 +0100 Subject: [PATCH 5/8] coma added --- src/endpoints.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/endpoints.rs b/src/endpoints.rs index 5179cdc..09195c8 100644 --- a/src/endpoints.rs +++ b/src/endpoints.rs @@ -218,7 +218,7 @@ type CourseAttendance = BTreeMap; #[derive(Serialize)] pub struct AttendanceResponse { - courses: CourseAttendance + courses: CourseAttendance, } pub async fn fecth_attendance( From ba3d2c830bd492370bece75264a1f5cc35c6c227 Mon Sep 17 00:00:00 2001 From: AnnaFYZ Date: Thu, 9 Oct 2025 18:09:39 +0100 Subject: [PATCH 6/8] futures added --- src/bin/trainee-tracker.rs | 2 +- src/endpoints.rs | 63 +++++++++++++++++++++----------------- 2 files changed, 36 insertions(+), 29 deletions(-) diff --git a/src/bin/trainee-tracker.rs b/src/bin/trainee-tracker.rs index 48c72d4..5239e0c 100644 --- a/src/bin/trainee-tracker.rs +++ b/src/bin/trainee-tracker.rs @@ -102,7 +102,7 @@ async fn main() { ) .route( "/api/attendance", - get(trainee_tracker::endpoints::fecth_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 09195c8..2b7ecaf 100644 --- a/src/endpoints.rs +++ b/src/endpoints.rs @@ -221,50 +221,57 @@ pub struct AttendanceResponse { courses: CourseAttendance, } -pub async fn fecth_attendance( +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 github_org = &server_state.config.github_org; - let octocrab = octocrab(&session, &server_state, original_uri).await?; + let mut courses: CourseAttendance = BTreeMap::new(); + let mut register_futures = Vec::new(); for (course_name, course_info) in all_courses { - let mut course_batches: BatchAttendance = BTreeMap::new(); 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 mut modules: ModuleAttendance = BTreeMap::new(); - let course = course_schedule - .with_assignments(&octocrab, github_org) - .await?; - let register_info = get_register( + let register_future = get_register( sheets_client.clone(), - course.register_sheet_id.clone(), - course.start_date, - course.end_date, - ) - .await?; - - for (module_name, sprint_info) in register_info.modules { - let mut sprints: SprintAttendance = BTreeMap::new(); - for (sprint_number, sprint_info) in sprint_info.attendance.into_iter().enumerate() { - let mut entries: Vec = Vec::new(); - for entry in sprint_info.into_values() { - entries.push(entry); - } - sprints.insert(format!("Sprint-{}", sprint_number + 1), entries); - } - modules.insert(module_name.clone(), sprints); - } - course_batches.insert(batch_name.clone(), modules); + 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) }); } - courses.insert(course_name.clone(), course_batches); } + 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 })) } From 3f7196f4daf05e8921d9c6601bbdf39cfc49cf4c Mon Sep 17 00:00:00 2001 From: AnnaFYZ Date: Fri, 10 Oct 2025 13:21:44 +0100 Subject: [PATCH 7/8] config.prod.json restored --- config.prod.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/config.prod.json b/config.prod.json index e93030c..b9133c4 100644 --- a/config.prod.json +++ b/config.prod.json @@ -1,11 +1,11 @@ { "github_org": "CodeYourFuture", - "github_client_id": "Ov23cturUbMnqQZ535bF", + "github_client_id": "Ov23liyZuouqyi9L3ZpM", "github_client_secret": "$CYF_TRAINEE_TRACKER_GITHUB_CLIENT_SECRET", "addr": "0.0.0.0", "port": 3000, - "public_base_url": "http://localhost:3000", - "google_apis_client_id": "403459539948-0af4rtk7po28idm3eiv0jj7qcq6u8vvs.apps.googleusercontent.com", + "public_base_url": "https://trainee-tracker.hosting.codeyourfuture.io", + "google_apis_client_id": "403459539948-q1psg497mlfk9u6cgf0eea5qum4gp92n.apps.googleusercontent.com", "google_apis_client_secret": "$CYF_TRAINEE_TRACKER_GOOGLE_APIS_CLIENT_SECRET", "github_email_mapping_sheet_id": "1ahDEnO8odD9oLtO_EBcvcmEaF0qsX-I4iLCIY0XLjt0", "reviewer_staff_info_sheet_id": "1CKDrXtx5lkgfZ8E2mjvsDup2K8UyBsq99CIV0qOxrP0", From 299f19850cfb7067cfc994c1b4c859374f3db6c6 Mon Sep 17 00:00:00 2001 From: AnnaFYZ Date: Fri, 10 Oct 2025 13:29:36 +0100 Subject: [PATCH 8/8] fmt ran --- src/endpoints.rs | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/endpoints.rs b/src/endpoints.rs index 2b7ecaf..56f3f73 100644 --- a/src/endpoints.rs +++ b/src/endpoints.rs @@ -228,7 +228,7 @@ pub async fn fetch_attendance( ) -> 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 { @@ -243,16 +243,23 @@ pub async fn fetch_attendance( course_schedule.course_schedule.start, course_schedule.course_schedule.end, ); - register_futures.push(async move { (course_name.clone(), batch_name.clone(), register_future.await) }); + 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 + let modules = register + .modules .into_iter() - .map(|(module_name, sprint_info)|{ + .map(|(module_name, sprint_info)| { ( module_name, sprint_info @@ -264,7 +271,8 @@ pub async fn fetch_attendance( format!("Sprint-{}", sprint_number + 1), sprint_info.into_values().collect(), ) - }).collect() + }) + .collect(), ) }) .collect(); @@ -272,6 +280,6 @@ pub async fn fetch_attendance( .entry(course_name) .or_default() .insert(batch_name, modules); - } + } Ok(Json(AttendanceResponse { courses })) }