1- use crate :: api:: types:: EventResponse ;
1+ use crate :: api:: types:: { CalendarStatsResponse , EventResponse } ;
22use crate :: db:: { Calendar , DbState } ;
33use crate :: processing:: rule:: Rule ;
44use crate :: upstream;
5+ use axum:: Json ;
56use axum:: body:: Body ;
67use axum:: extract:: { Path , State } ;
7- use axum:: http:: { header , HeaderMap , StatusCode } ;
8+ use axum:: http:: { HeaderMap , StatusCode , header } ;
89use axum:: response:: IntoResponse ;
9- use axum:: Json ;
1010use serde:: { Deserialize , Serialize } ;
11+ use std:: time:: UNIX_EPOCH ;
1112use tokio_util:: io:: ReaderStream ;
1213use utoipa:: ToSchema ;
1314use utoipa_axum:: router:: OpenApiRouter ;
@@ -32,6 +33,7 @@ pub(crate) fn router() -> axum::Router<DbState> {
3233 . routes ( routes ! ( allowlist_add) )
3334 . routes ( routes ! ( allowlist_remove) )
3435 . routes ( routes ! ( allowlist_list) )
36+ . routes ( routes ! ( get_stats) )
3537 . split_for_parts ( ) ;
3638 app_router
3739 . route ( "/calendars/{id}/feed" , axum:: routing:: get ( get_feed) )
@@ -99,7 +101,7 @@ pub async fn get_events(
99101 State ( db) : State < DbState > ,
100102 Path ( id) : Path < String > ,
101103) -> Result < Json < Vec < EventResponse > > , StatusCode > {
102- let db_lock = db. lock ( ) . await ;
104+ let mut db_lock = db. lock ( ) . await ;
103105 let cal = db_lock
104106 . get_calendar ( & id)
105107 . await
@@ -234,13 +236,19 @@ pub async fn get_feed(
234236 State ( db) : State < DbState > ,
235237 Path ( id) : Path < String > ,
236238) -> Result < ( HeaderMap , Body ) , StatusCode > {
237- let db_lock = db. lock ( ) . await ;
238- let calendar = db_lock
239+ let mut db_lock = db. lock ( ) . await ;
240+ let mut calendar = db_lock
239241 . get_calendar ( & id)
240242 . await
241243 . ok_or ( StatusCode :: NOT_FOUND ) ?;
242244 drop ( db_lock) ;
243245
246+ calendar. last_accessed = Some (
247+ std:: time:: SystemTime :: now ( )
248+ . duration_since ( std:: time:: UNIX_EPOCH )
249+ . map_err ( |_| StatusCode :: INTERNAL_SERVER_ERROR ) ?
250+ . as_secs ( ) ,
251+ ) ;
244252 let ical = calendar. get_filtered_icalendar ( ) ;
245253
246254 let reader = std:: io:: Cursor :: new ( ical. to_string ( ) . into_bytes ( ) ) ;
@@ -557,3 +565,41 @@ pub async fn allowlist_list(
557565
558566 Ok ( Json ( set) )
559567}
568+
569+ #[ utoipa:: path(
570+ get,
571+ path = "/stats" ,
572+ responses(
573+ ( status = 200 , description = "Retrieved calendar statistics" , body = CalendarStatsResponse ) ,
574+ )
575+ ) ]
576+ pub async fn get_stats (
577+ State ( db) : State < DbState > ,
578+ ) -> Result < Json < CalendarStatsResponse > , StatusCode > {
579+ let db_lock = db. lock ( ) . await ;
580+ let mut active_calendars = 0 ;
581+ let calendars = db_lock. list_calendars ( ) . await ;
582+
583+ for calendar in calendars {
584+ let Some ( last_accessed) = calendar. last_accessed else {
585+ continue ;
586+ } ;
587+ let Ok ( duration_since_last_access) = std:: time:: SystemTime :: now ( )
588+ . duration_since ( UNIX_EPOCH + std:: time:: Duration :: from_secs ( last_accessed) )
589+ . map_err ( |err| {
590+ eprintln ! ( "Error calculating duration since last access: {}" , err) ;
591+ ( )
592+ } )
593+ else {
594+ continue ;
595+ } ;
596+
597+ if duration_since_last_access < std:: time:: Duration :: from_secs ( 2 * 24 * 60 * 60 ) {
598+ active_calendars += 1 ;
599+ }
600+ }
601+
602+ let stats = CalendarStatsResponse { active_calendars } ;
603+
604+ Ok ( Json ( stats) )
605+ }
0 commit comments