···5252 Two,
5353}
54545555-#[derive(Clone, Copy, Debug)]
5555+#[derive(Clone, Copy, Debug, PartialEq)]
5656pub enum CompletionStatus {
5757 ///None of the day's challenges have been completed
5858 None,
+24-15
web/src/main.rs
···258258 "/day/{id}",
259259 match prod {
260260 true => get(handlers::day::view_day_handler)
261261- .route_layer(middleware::from_fn(unlock::unlock)),
261261+ .route_layer(middleware::from_fn_with_state(app_state.postgres_pool.clone(), unlock::unlock)),
262262 false => get(handlers::day::view_day_handler),
263263 },
264264 )
···266266 "/day/{id}",
267267 match prod {
268268 true => post(handlers::day::post_day_handler)
269269- .route_layer(middleware::from_fn(unlock::unlock)),
269269+ .route_layer(middleware::from_fn_with_state(app_state.postgres_pool.clone(), unlock::unlock)),
270270 false => post(handlers::day::post_day_handler),
271271 },
272272 )
···296296297297/// The default handler that will be used during the advent month, but not during amtosphere conf
298298async fn home_handler(State(pool): State<PgPool>, session: AxumSessionStore) -> impl IntoResponse {
299299- //TODO make a helper function for this since it is similar to the middleware
300300- let now = chrono::Utc::now();
301299 let mut unlocked: Vec<u8> = Vec::new();
302300301301+ let did = session.get_did();
302302+ let is_logged_in = session.logged_in();
303303+ let all_statuses = get_all_days_completion_status(&pool, did.as_ref())
304304+ .await
305305+ .unwrap_or_else(|_| (1..=25).map(|day| (day, CompletionStatus::None)).collect());
306306+303307 //HACK Yeah I don't like it either - bt
304308 let prod: bool = env::var("PROD")
305309 .map(|val| val == "true")
306310 .unwrap_or_else(|_| true);
307311 if prod {
308308- if now.month() == 12 {
309309- let today = now.day().min(25);
310310- for d in 1..=today {
311311- unlocked.push(d as u8);
312312+ // Only show implemented days, progressively unlocked as users complete them
313313+ let implemented_days: Vec<u8> = vec![1, 2, 3];
314314+ if let Some(&first) = implemented_days.first() {
315315+ unlocked.push(first);
316316+ }
317317+ for window in implemented_days.windows(2) {
318318+ let prev = window[0];
319319+ let prev_status = all_statuses
320320+ .iter()
321321+ .find(|(d, _)| *d == prev)
322322+ .map(|(_, s)| s)
323323+ .unwrap_or(&CompletionStatus::None);
324324+ if *prev_status == CompletionStatus::Both {
325325+ unlocked.push(window[1]);
326326+ } else {
327327+ break;
312328 }
313329 }
314330 } else {
···316332 unlocked.push(d as u8);
317333 }
318334 }
319319-320320- // Get completion status for all days at once
321321- let did = session.get_did();
322322- let is_logged_in = session.logged_in();
323323- let all_statuses = get_all_days_completion_status(&pool, did.as_ref())
324324- .await
325325- .unwrap_or_else(|_| (1..=25).map(|day| (day, CompletionStatus::None)).collect());
326335327336 // Filter to only include unlocked days
328337 let unlocked_with_status: Vec<DayStatus> = all_statuses
+32-22
web/src/unlock.rs
···11-use axum::extract::{Path, Request};
11+use crate::session::AxumSessionStore;
22+use axum::extract::{Path, Request, State};
23use axum::http;
34use axum::{
45 middleware,
56 response::{self, IntoResponse},
67};
77-use chrono::Datelike;
88+use shared::advent::{CompletionStatus, get_all_days_completion_status};
99+use sqlx::PgPool;
810911pub async fn unlock(
1212+ State(pool): State<PgPool>,
1013 Path(mut day): Path<u8>,
1414+ session: AxumSessionStore,
1115 request: Request,
1216 next: middleware::Next,
1317) -> response::Response {
···3539 .into_response();
3640 }
37413838- let now = chrono::Utc::now();
3939- let current_day = now.day();
4040- let month = now.month();
4242+ let implemented_days: Vec<u8> = vec![1, 2, 3];
41434242- if month != 12 {
4444+ if !implemented_days.contains(&day) {
4345 return (
4446 http::StatusCode::FORBIDDEN,
4545- "It's not December yet! NO PEAKING",
4747+ "This day hasn't been created yet!",
4648 )
4749 .into_response();
4850 }
49515050- //Show any day previous to the current day and current day
5151- if day as u32 <= current_day {
5252- return next.run(request).await;
5353- }
5252+ // First implemented day is always accessible
5353+ let pos = implemented_days.iter().position(|&d| d == day).unwrap();
5454+ if pos > 0 {
5555+ let prev_day = implemented_days[pos - 1];
5656+ let did = session.get_did();
54575555- (
5656- http::StatusCode::FORBIDDEN,
5757- "Now just hold on a minute. It ain't time yet.",
5858- )
5959- .into_response()
5858+ let all_statuses = get_all_days_completion_status(&pool, did.as_ref())
5959+ .await
6060+ .unwrap_or_else(|_| (1..=25).map(|d| (d, CompletionStatus::None)).collect());
60616161- // Just commenting out for now if we do want a json endpoint and i forgot easiest way to return it
6262- // let error_response = axum::Json(serde_json::json!({
6363- // "error": "Route Locked",
6464- // "time_remaining_seconds": time_remaining.as_secs(),
6565- // }));
6262+ let prev_status = all_statuses
6363+ .iter()
6464+ .find(|(d, _)| *d == prev_day)
6565+ .map(|(_, s)| s)
6666+ .unwrap_or(&CompletionStatus::None);
6767+6868+ if *prev_status != CompletionStatus::Both {
6969+ return (
7070+ http::StatusCode::FORBIDDEN,
7171+ "Now just hold on a minute. It ain't time yet.",
7272+ )
7373+ .into_response();
7474+ }
7575+ }
66766767- // (http::StatusCode::FORBIDDEN, error_response).into_response()
7777+ next.run(request).await
6878}