use crate::advent::day::Day; use crate::advent::{AdventChallenge, AdventError, AdventPart, ChallengeCheckResponse}; use crate::atrium::safe_check_unknown_record_parse; use crate::lexicons::codes::advent; use crate::lexicons::record::KnownRecord; use crate::{OAuthAgentType, PasswordAgent}; use async_trait::async_trait; use atrium_api::types::Collection; use serde_json::json; use sqlx::PgPool; pub struct DayTwo { pub pool: PgPool, pub oauth_client: Option, pub challenge_agent: Option, } #[async_trait] impl AdventChallenge for DayTwo { fn pool(&self) -> &PgPool { &self.pool } fn day(&self) -> Day { Day::Two } fn has_part_two(&self) -> bool { true } fn requires_manual_verification_part_one(&self) -> bool { true } /// Create a record via the challenge agent and return the at_uri as additional context async fn build_additional_context( &self, _did: &str, part: &AdventPart, code: &str, ) -> Result, AdventError> { match part { AdventPart::One => { let Some(agent) = &self.challenge_agent else { log::warn!( "No challenge agent configured, skipping record creation for day two" ); return Ok(None); }; let agent_did = agent.did().await.ok_or_else(|| { AdventError::ShouldNotHappen("Challenge agent has no DID".to_string()) })?; let record_data = advent::challenge::day::RecordData { part_one: Some(code.to_string()), part_two: None, created_at: None, }; let known_record: KnownRecord = record_data.into(); let record_value: atrium_api::types::Unknown = known_record.into(); let create_record_result = agent .api .com .atproto .repo .create_record( atrium_api::com::atproto::repo::create_record::InputData { collection: advent::challenge::Day::NSID.parse().unwrap(), repo: agent_did.as_ref().parse().unwrap(), rkey: None, record: record_value, swap_commit: None, validate: Some(false), } .into(), ) .await; match create_record_result { Ok(output) => Ok(Some(json!({"at_uri": output.uri}))), Err(e) => { log::error!("Failed to create challenge record via agent: {e}"); Ok(None) } } } AdventPart::Two => Ok(None), } } async fn check_part_one( &self, did: String, verification_code: Option, ) -> Result { let submitted_code = match verification_code { Some(code) if !code.is_empty() => code, _ => { return Ok(ChallengeCheckResponse::Incorrect( "Please enter a verification code".to_string(), )); } }; let Some(challenge) = self.get_days_challenge(&did).await? else { log::error!("Could not find a challenge record for day: 2 for the user: {did:?}"); return Err(AdventError::ShouldNotHappen( "Could not find challenge record".to_string(), )); }; let expected_code = challenge .verification_code_one .ok_or(AdventError::ShouldNotHappen( "no verification code for day 2 challenge".to_string(), ))?; Ok(if submitted_code == expected_code { ChallengeCheckResponse::Correct } else { ChallengeCheckResponse::Incorrect(format!("The code {} is incorrect", submitted_code)) }) } ///TODO this is just a straight copy and paste of part one since it's a proof of concept needs to share code better between the two async fn check_part_two( &self, did: String, _verification_code: Option, ) -> Result { match &self.oauth_client { None => Err(AdventError::ShouldNotHappen( "No oauth client. This should not happen".to_string(), )), Some(client) => { match client .api .com .atproto .repo .get_record( atrium_api::com::atproto::repo::get_record::ParametersData { cid: None, collection: advent::challenge::Day::NSID.parse().unwrap(), repo: did.parse().unwrap(), rkey: "2".parse().unwrap(), } .into(), ) .await { Ok(record) => { //TODO trouble, and make it double let challenge = self.get_days_challenge(&did).await?; match challenge { None => { log::error!( "Could not find a challenge record for day: {} for the user: {}", self.day(), did.clone() ); Err(AdventError::ShouldNotHappen( "Could not find a challenge record".to_string(), )) } Some(challenge) => { let parse_record_result = safe_check_unknown_record_parse::< advent::challenge::day::RecordData, >(record.value.clone()); match parse_record_result { Ok(record_data) => match record_data.part_two { None => { Ok(ChallengeCheckResponse::Incorrect("The record is there, it's the right kind. But aren't you forgetting something?".to_string())) } Some(part_two_code) => { match part_two_code == challenge .verification_code_two .unwrap_or("".to_string()) { true => Ok(ChallengeCheckResponse::Correct), false => { Ok(ChallengeCheckResponse::Incorrect(format!( "The code {} is incorrect", record_data.part_one.unwrap_or("".to_string()) ))) } } } }, Err(err) => { log::error!("Error parsing record: {}", err); Ok(ChallengeCheckResponse::Incorrect(format!( "There is a record at the correct location, but it does not seem like it is correct. Try again:\n{err}" ))) } } } } } Err(err) => { log::error!("Error getting record: {}", err); Ok(ChallengeCheckResponse::Incorrect("Does not appear to be a record in your repo in the collection codes.advent.challenge.day with the record key of 1".to_string())) } } } } } }