this repo has no description
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

looks like it may work

+126 -29
+1 -1
Cargo.toml
··· 17 17 serde = { version = "1.0.219", features = ["derive"] } 18 18 serde_json = "1.0.141" 19 19 shared = { path = "./shared" } 20 - sqlx = { version = "0.8", features = ["runtime-tokio", "postgres", "chrono", "macros"] } 20 + sqlx = { version = "0.8", features = ["runtime-tokio", "postgres", "chrono", "macros", "json"] } 21 21 tracing = "0.1.41" 22 22 tracing-subscriber = { version = "0.3.19", features = ["env-filter"] } 23 23 bb8 = "0.9.0"
+1
migrations/20260317224144_add_additional_context.sql
··· 1 + ALTER TABLE challenges ADD COLUMN additional_context JSONB NULL;
+1 -1
shared/challenges_markdown/two/part_one.md
··· 26 26 - `app.bsky.feed.like` is the collection 27 27 - `3mhbs2cnrl22r` is the record key 28 28 29 - Using what you learned from day 1 find the following record {generate the at://uri here} and enter the verification code 29 + Using what you learned from day 1 find the following record `{{at_uri}}` and enter the verification code 30 30 found in the record 31 31 for it below 32 32
+90 -17
shared/src/advent/challenges/day_two.rs
··· 4 4 }; 5 5 use crate::atrium::safe_check_unknown_record_parse; 6 6 use crate::lexicons::codes::advent; 7 + use crate::lexicons::record::KnownRecord; 7 8 use crate::{OAuthAgentType, PasswordAgent}; 8 9 use async_trait::async_trait; 9 10 use atrium_api::types::Collection; 11 + use serde_json::json; 10 12 use sqlx::PgPool; 11 13 12 14 pub struct DayTwo { ··· 32 34 /// We are overriding the start challenge and using extra code 33 35 async fn start_challenge(&self, did: String, part: AdventPart) -> Result<String, AdventError> { 34 36 let code = get_random_token(); 37 + 38 + // For part one, create a record via the challenge agent and store the at_uri 39 + let additional_context: Option<serde_json::Value> = match part { 40 + AdventPart::One => { 41 + match &self.challenge_agent { 42 + Some(agent) => { 43 + let agent_did = agent.did().await.ok_or_else(|| { 44 + AdventError::ShouldNotHappen( 45 + "Challenge agent has no DID".to_string(), 46 + ) 47 + })?; 48 + 49 + let record_data = advent::challenge::day::RecordData { 50 + part_one: code.clone(), 51 + part_two: None, 52 + created_at: None, 53 + }; 54 + let known_record: KnownRecord = record_data.into(); 55 + let record_value: atrium_api::types::Unknown = known_record.into(); 56 + 57 + // Use a unique rkey per user based on the did to avoid collisions 58 + let rkey = did.replace(":", "_").replace(".", "_"); 59 + 60 + let put_result = agent 61 + .api 62 + .com 63 + .atproto 64 + .repo 65 + .put_record( 66 + atrium_api::com::atproto::repo::put_record::InputData { 67 + collection: advent::challenge::Day::NSID.parse().unwrap(), 68 + repo: agent_did.as_ref().parse().unwrap(), 69 + rkey: rkey.parse().unwrap(), 70 + record: record_value, 71 + swap_commit: None, 72 + swap_record: None, 73 + validate: Some(false), 74 + } 75 + .into(), 76 + ) 77 + .await; 78 + 79 + match put_result { 80 + Ok(output) => Some(json!({"at_uri": output.uri})), 81 + Err(e) => { 82 + log::error!("Failed to create challenge record via agent: {e}"); 83 + None 84 + } 85 + } 86 + } 87 + None => { 88 + log::warn!("No challenge agent configured, skipping record creation for day two"); 89 + None 90 + } 91 + } 92 + } 93 + AdventPart::Two => None, 94 + }; 95 + 35 96 match part { 36 - AdventPart::One => sqlx::query( 37 - "INSERT INTO challenges (user_did, day, time_started, verification_code_one) 38 - VALUES ($1, $2, NOW(), $3) 39 - ON CONFLICT (user_did, day) 40 - DO UPDATE SET verification_code_one = $3 41 - WHERE challenges.user_did = $1 AND challenges.day = $2", 42 - ), 97 + AdventPart::One => { 98 + sqlx::query( 99 + "INSERT INTO challenges (user_did, day, time_started, verification_code_one, additional_context) 100 + VALUES ($1, $2, NOW(), $3, $4) 101 + ON CONFLICT (user_did, day) 102 + DO UPDATE SET verification_code_one = $3, additional_context = $4 103 + WHERE challenges.user_did = $1 AND challenges.day = $2", 104 + ) 105 + .bind(&did) 106 + .bind(self.day() as i16) 107 + .bind(&code) 108 + .bind(&additional_context) 109 + .execute(self.pool()) 110 + .await?; 111 + } 43 112 //TODO just going leave these as an update. It should never ideally be an insert 44 - AdventPart::Two => sqlx::query( 45 - "UPDATE challenges 46 - SET verification_code_two = $3 47 - WHERE challenges.user_did = $1 AND challenges.day = $2", 48 - ), 113 + AdventPart::Two => { 114 + sqlx::query( 115 + "UPDATE challenges 116 + SET verification_code_two = $3 117 + WHERE challenges.user_did = $1 AND challenges.day = $2", 118 + ) 119 + .bind(&did) 120 + .bind(self.day() as i16) 121 + .bind(&code) 122 + .execute(self.pool()) 123 + .await?; 124 + } 49 125 } 50 - .bind(did) 51 - .bind(self.day() as i16) 52 - .bind(code.clone()) 53 - .execute(self.pool()) 54 - .await?; 126 + 55 127 Ok(code) 56 128 } 129 + 57 130 async fn check_part_one( 58 131 &self, 59 132 did: String,
+16 -2
shared/src/advent/mod.rs
··· 135 135 fn markdown_text_part_one( 136 136 &self, 137 137 verification_code: Option<String>, 138 + additional_context: Option<&serde_json::Value>, 138 139 ) -> Result<String, AdventError> { 139 140 let day = self.day(); 140 141 ··· 150 151 151 152 let day_one_text = std::str::from_utf8(day_one_file.data.as_ref())?; 152 153 let code = verification_code.unwrap_or_else(|| "Login to get a code".to_string()); 154 + let mut context = json!({"code": code}); 155 + if let Some(serde_json::Value::Object(map)) = additional_context { 156 + if let serde_json::Value::Object(ref mut ctx_map) = context { 157 + ctx_map.extend(map.iter().map(|(k, v)| (k.clone(), v.clone()))); 158 + } 159 + } 153 160 let handlebar_rendered = 154 - reg.render_template(day_one_text, &json!({"code": code}))?; 161 + reg.render_template(day_one_text, &context)?; 155 162 156 163 Ok( 157 164 markdown::to_html_with_options(&handlebar_rendered, &get_markdown_options()) ··· 165 172 fn markdown_text_part_two( 166 173 &self, 167 174 verification_code: Option<String>, 175 + additional_context: Option<&serde_json::Value>, 168 176 ) -> Result<Option<String>, AdventError> { 169 177 match self.get_day_markdown_file(AdventPart::Two)? { 170 178 None => Ok(None), ··· 174 182 175 183 let day_two_text = std::str::from_utf8(day_two_file.data.as_ref())?; 176 184 let code = verification_code.unwrap_or_else(|| "Login to get a code".to_string()); 185 + let mut context = json!({"code": code}); 186 + if let Some(serde_json::Value::Object(map)) = additional_context { 187 + if let serde_json::Value::Object(ref mut ctx_map) = context { 188 + ctx_map.extend(map.iter().map(|(k, v)| (k.clone(), v.clone()))); 189 + } 190 + } 177 191 let handlebar_rendered = 178 - reg.render_template(day_two_text, &json!({"code": code}))?; 192 + reg.render_template(day_two_text, &context)?; 179 193 180 194 Ok(Some( 181 195 markdown::to_html_with_options(&handlebar_rendered, &get_markdown_options())
+1
shared/src/models/db_models.rs
··· 18 18 pub time_challenge_two_completed: Option<DateTime<Utc>>, 19 19 pub verification_code_one: Option<String>, 20 20 pub verification_code_two: Option<String>, 21 + pub additional_context: Option<serde_json::Value>, 21 22 }
+16 -8
web/src/handlers/day.rs
··· 93 93 let title = format!("at://advent - Day {}", day as u8); 94 94 let part_one_text = match did_clone { 95 95 None => challenge 96 - .markdown_text_part_one(None) 96 + .markdown_text_part_one(None, None) 97 97 .map(|s| s.to_string()) 98 98 .unwrap_or_else(|_| "Error loading part one".to_string()), 99 99 Some(ref users_did) => match challenge.get_days_challenge(&users_did).await { ··· 103 103 .start_challenge(users_did.to_string(), AdventPart::One) 104 104 .await 105 105 .unwrap(); 106 + let started = challenge.get_days_challenge(users_did).await.ok().flatten(); 107 + let ctx = started.as_ref().and_then(|c| c.additional_context.as_ref()); 106 108 challenge 107 - .markdown_text_part_one(Some(new_code)) 109 + .markdown_text_part_one(Some(new_code), ctx) 108 110 .map(|s| s.to_string()) 109 111 .unwrap_or_else(|_| "Error loading part one".to_string()) 110 112 } ··· 114 116 .start_challenge(users_did.to_string(), AdventPart::One) 115 117 .await 116 118 .unwrap(); 119 + let started = challenge.get_days_challenge(users_did).await.ok().flatten(); 120 + let ctx = started.as_ref().and_then(|c| c.additional_context.as_ref()); 117 121 challenge 118 - .markdown_text_part_one(Some(new_code)) 122 + .markdown_text_part_one(Some(new_code), ctx) 119 123 .map(|s| s.to_string()) 120 124 .unwrap_or_else(|_| "Error loading part one".to_string()) 121 125 } 122 126 Some(code) => challenge 123 - .markdown_text_part_one(Some(code)) 127 + .markdown_text_part_one(Some(code), current_challenge.additional_context.as_ref()) 124 128 .map(|s| s.to_string()) 125 129 .unwrap_or_else(|_| "Error loading part one".to_string()), 126 130 }, ··· 209 213 ) -> Option<String> { 210 214 let part_two_text: Option<String> = match did_clone { 211 215 None => challenge 212 - .markdown_text_part_two(None) 216 + .markdown_text_part_two(None, None) 213 217 .map(|opt| opt.map(|s| s.to_string())) 214 218 .unwrap_or(None), 215 219 Some(users_did) => match challenge.get_days_challenge(&users_did).await { ··· 220 224 .start_challenge(users_did.to_string(), AdventPart::Two) 221 225 .await 222 226 .unwrap(); 227 + let started = challenge.get_days_challenge(&users_did).await.ok().flatten(); 228 + let ctx = started.as_ref().and_then(|c| c.additional_context.as_ref()); 223 229 challenge 224 - .markdown_text_part_two(Some(new_code)) 230 + .markdown_text_part_two(Some(new_code), ctx) 225 231 .map(|opt| opt.map(|s| s.to_string())) 226 232 .unwrap_or(None) 227 233 } else { ··· 237 243 .start_challenge(users_did.to_string(), AdventPart::Two) 238 244 .await 239 245 .unwrap(); 246 + let started = challenge.get_days_challenge(&users_did).await.ok().flatten(); 247 + let ctx = started.as_ref().and_then(|c| c.additional_context.as_ref()); 240 248 challenge 241 - .markdown_text_part_two(Some(new_code)) 249 + .markdown_text_part_two(Some(new_code), ctx) 242 250 .map(|opt| opt.map(|s| s.to_string())) 243 251 .unwrap_or(None) 244 252 } 245 253 Some(code) => challenge 246 - .markdown_text_part_two(Some(code)) 254 + .markdown_text_part_two(Some(code), current_challenge.additional_context.as_ref()) 247 255 .map(|opt| opt.map(|s| s.to_string())) 248 256 .unwrap_or(None), 249 257 }