diff --git a/Cargo.lock b/Cargo.lock index aecc078..9f7fb23 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -22,6 +22,7 @@ name = "at-advent" version = "0.1.0" dependencies = [ "axum", + "serde_json", "tokio", ] diff --git a/Cargo.toml b/Cargo.toml index 98470ca..2102c9c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,4 +5,5 @@ edition = "2024" [dependencies] axum = "0.8.4" +serde_json = "1.0.141" tokio = { version = "1.46.1", features = ["full"] } diff --git a/src/main.rs b/src/main.rs index cbd650f..e8f4a2f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,15 +1,31 @@ -use axum::{Router, routing::get}; +use axum::extract::Path; +use axum::{Router, middleware, routing::get}; use std::net::{IpAddr, Ipv4Addr, SocketAddr}; +use std::time; + +mod unlock; #[tokio::main] async fn main() { - let app = Router::new().route("/", get(handler)); let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 7878); let listener = tokio::net::TcpListener::bind(addr).await.unwrap(); println!("listening on {}", addr); + + let now = time::Instant::now(); + let daily = time::Duration::from_secs(24 * 60 * 60); + let state = unlock::Unlock::new(now, daily); + + let app = + Router::new() + .route("/day/{id}", get(handler)) + .route_layer(middleware::from_fn_with_state( + state.clone(), + unlock::unlock, + )); + axum::serve(listener, app).await.unwrap(); } -async fn handler() -> &'static str { - "hello, world!" +async fn handler(Path(id): Path) -> String { + format!("hello day {id}") } diff --git a/src/unlock.rs b/src/unlock.rs new file mode 100644 index 0000000..c60aebe --- /dev/null +++ b/src/unlock.rs @@ -0,0 +1,40 @@ +use axum::extract::{Path, Request, State}; +use axum::http; +use axum::{ + middleware, + response::{self, IntoResponse}, +}; +use std::time; + +#[derive(Clone)] +pub struct Unlock { + start: time::Instant, + interval: time::Duration, +} + +impl Unlock { + pub fn new(start: time::Instant, interval: time::Duration) -> Self { + Self { start, interval } + } +} + +pub async fn unlock( + Path(day): Path, + State(unlocker): State, + request: Request, + next: middleware::Next, +) -> response::Response { + let deadline = unlocker.start + unlocker.interval * day; + let now = time::Instant::now(); + if now >= deadline { + return next.run(request).await; + } + + let time_remaining = deadline.saturating_duration_since(now); + let error_response = axum::Json(serde_json::json!({ + "error": "Route Locked", + "time_remaining_seconds": time_remaining.as_secs(), + })); + + (http::StatusCode::FORBIDDEN, error_response).into_response() +}