···
1
+
use crate::error_response;
2
+
use crate::session::{AxumSessionStore, FlashMessage, get_flash_message, set_flash_message};
3
+
use crate::templates::{HtmlTemplate, day::DayTemplate};
4
+
use atrium_api::agent::Agent;
5
+
use atrium_api::types::string::Did;
7
+
extract::{Form, Path, State},
9
+
response::{IntoResponse, Redirect, Response},
11
+
use shared::advent::ChallengeCheckResponse;
13
+
OAuthAgentType, OAuthClientType,
14
+
advent::challenges::day_one::DayOne,
15
+
advent::challenges::day_two::DayTwo,
17
+
advent::{AdventChallenge, AdventError},
18
+
advent::{AdventPart, CompletionStatus},
25
+
oauth_client: Option<OAuthAgentType>,
26
+
) -> Result<Box<dyn AdventChallenge + Send + Sync>, AdventError> {
28
+
Day::One => Ok(Box::new(DayOne { pool, oauth_client })),
29
+
Day::Two => Ok(Box::new(DayTwo { pool, oauth_client })),
30
+
_ => Err(AdventError::InvalidDay(0)), // Day::Three => {}
38
+
// Day::Eleven => {}
39
+
// Day::Twelve => {}
40
+
// Day::Thirteen => {}
41
+
// Day::Fourteen => {}
42
+
// Day::Fifteen => {}
43
+
// Day::Sixteen => {}
44
+
// Day::Seventeen => {}
45
+
// Day::Eighteen => {}
46
+
// Day::Nineteen => {}
47
+
// Day::Twenty => {}
48
+
// Day::TwentyOne => {}
49
+
// Day::TwentyTwo => {}
50
+
// Day::TwentyThree => {}
51
+
// Day::TwentyFour => {}
52
+
// Day::TwentyFive => {}
56
+
fn log_and_respond<E: std::fmt::Display>(
58
+
context: &'static str,
59
+
) -> impl FnOnce(E) -> Response {
61
+
log::error!("{context}: {err}");
62
+
error_response(status, context)
66
+
pub async fn view_day_handler(
68
+
State(pool): State<PgPool>,
69
+
session: AxumSessionStore,
70
+
) -> Result<impl IntoResponse, Response> {
71
+
let day = Day::from(id);
73
+
let did = session.get_did();
74
+
let did_clone = did.clone();
75
+
let challenge = pick_day(day, pool, None).map_err(|err| {
76
+
log::error!("Error picking day: {err}");
77
+
error_response(StatusCode::INTERNAL_SERVER_ERROR, "Error picking day")
80
+
let title = format!("at://advent - Day {}", day as u8);
81
+
let part_one_text = match did_clone {
83
+
.markdown_text_part_one(None)
84
+
.map(|s| s.to_string())
85
+
.unwrap_or_else(|_| "Error loading part one".to_string()),
86
+
Some(ref users_did) => match challenge.get_days_challenge(users_did.clone()).await {
87
+
Ok(current_challenge) => match current_challenge {
89
+
let new_code = challenge
90
+
.start_challenge(users_did.to_string(), AdventPart::One)
94
+
.markdown_text_part_one(Some(new_code))
95
+
.map(|s| s.to_string())
96
+
.unwrap_or_else(|_| "Error loading part one".to_string())
98
+
Some(current_challenge) => match current_challenge.verification_code_one {
100
+
let new_code = challenge
101
+
.start_challenge(users_did.to_string(), AdventPart::One)
105
+
.markdown_text_part_one(Some(new_code))
106
+
.map(|s| s.to_string())
107
+
.unwrap_or_else(|_| "Error loading part one".to_string())
109
+
Some(code) => challenge
110
+
.markdown_text_part_one(Some(code))
111
+
.map(|s| s.to_string())
112
+
.unwrap_or_else(|_| "Error loading part one".to_string()),
117
+
log::error!("Error loading today's challenge for the user: {users_did} \n {err}");
118
+
"There was an error loading the challenge...sorry about that".to_string()
123
+
let status = challenge.get_completed_status(did).await.map_err(|err| {
124
+
log::error!("Error getting completed status: {err}");
126
+
StatusCode::INTERNAL_SERVER_ERROR,
127
+
"Error getting completed status",
131
+
let mut session = session;
132
+
let part_one_flash = get_flash_message(&mut session, "part_one_result").await?;
133
+
let part_two_flash = get_flash_message(&mut session, "part_two_result").await?;
135
+
let template = match status {
136
+
CompletionStatus::None => DayTemplate {
139
+
challenge_one_text: part_one_text,
140
+
challenge_one_completed: false,
141
+
challenge_two_text: None,
142
+
challenge_two_completed: false,
143
+
part_one_submit_message: part_one_flash,
144
+
part_two_submit_message: part_two_flash,
146
+
CompletionStatus::PartOne => {
147
+
let part_two_text = get_part_two_text(did_clone, &challenge).await;
148
+
let completed = part_two_text.is_none();
152
+
challenge_one_text: part_one_text,
153
+
challenge_one_completed: true,
154
+
challenge_two_text: part_two_text,
155
+
challenge_two_completed: completed,
156
+
part_one_submit_message: part_one_flash,
157
+
part_two_submit_message: part_two_flash,
160
+
CompletionStatus::Both => {
161
+
let part_two_text = get_part_two_text(did_clone, &challenge).await;
165
+
challenge_one_text: part_one_text,
166
+
challenge_one_completed: true,
167
+
challenge_two_text: part_two_text,
168
+
challenge_two_completed: true,
169
+
part_one_submit_message: part_one_flash,
170
+
part_two_submit_message: part_two_flash,
175
+
Ok(HtmlTemplate(template))
178
+
///TODO prob look and see if this can be shared between part one since it is similar logic...
179
+
/// Also this is in a function since PartOne and Both load the partwo text
180
+
async fn get_part_two_text(
181
+
did_clone: Option<String>,
182
+
challenge: &Box<dyn AdventChallenge + Send + Sync>,
183
+
) -> Option<String> {
184
+
let part_two_text: Option<String> = match did_clone {
186
+
.markdown_text_part_two(None)
187
+
.map(|opt| opt.map(|s| s.to_string()))
189
+
Some(users_did) => match challenge.get_days_challenge(users_did.clone()).await {
190
+
Ok(current_challenge) => match current_challenge {
192
+
if challenge.has_part_two() {
193
+
let new_code = challenge
194
+
.start_challenge(users_did.to_string(), AdventPart::Two)
198
+
.markdown_text_part_two(Some(new_code))
199
+
.map(|opt| opt.map(|s| s.to_string()))
205
+
Some(current_challenge) => {
206
+
// If there is no code yet for part two, start it; otherwise use the existing code
207
+
if challenge.has_part_two() {
208
+
match current_challenge.verification_code_two {
210
+
let new_code = challenge
211
+
.start_challenge(users_did.to_string(), AdventPart::Two)
215
+
.markdown_text_part_two(Some(new_code))
216
+
.map(|opt| opt.map(|s| s.to_string()))
219
+
Some(code) => challenge
220
+
.markdown_text_part_two(Some(code))
221
+
.map(|opt| opt.map(|s| s.to_string()))
225
+
let day = current_challenge.day;
227
+
"There is no part two for day: {day}. Developer may of forgotten to set the has_part_two flag to true."
234
+
log::error!("Error loading today's challenge for the user: {users_did} \n {err}");
242
+
/// This can be used to verify the day's challenge. Empty if it's up to the backend to grab the verification code
243
+
/// from somewhere like a lexicon record
244
+
#[derive(Debug, serde::Deserialize, Clone)]
245
+
pub struct PostDayForm {
247
+
pub verification_code_one: Option<String>,
249
+
pub verification_code_two: Option<String>,
252
+
///This is the endpoint to verify the day's challenge
253
+
pub async fn post_day_handler(
254
+
Path(day): Path<u8>,
255
+
State(pool): State<PgPool>,
256
+
State(oauth_client): State<OAuthClientType>,
257
+
mut session: AxumSessionStore,
258
+
Form(form): Form<PostDayForm>,
259
+
) -> Result<impl IntoResponse, Response> {
260
+
match &session.get_did() {
261
+
None => Err(error_response(
262
+
StatusCode::FORBIDDEN,
263
+
"You need to be logged in to submit an answer",
266
+
let did_as_string = did.clone();
267
+
let did = Did::new(did.to_string())
268
+
.map_err(log_and_respond(StatusCode::BAD_REQUEST, "Invalid DID"))?;
270
+
let client = oauth_client.restore(&did).await.map_err(log_and_respond(
271
+
StatusCode::INTERNAL_SERVER_ERROR,
272
+
"There was an error restoring the oauth client",
275
+
let agent = Agent::new(client);
276
+
let day = Day::from(day);
278
+
let challenge = pick_day(day, pool, Some(agent)).map_err(log_and_respond(
279
+
StatusCode::INTERNAL_SERVER_ERROR,
280
+
"Error picking the day",
283
+
let status = challenge
284
+
.get_completed_status(Some(did_as_string.clone()))
286
+
.map_err(log_and_respond(
287
+
StatusCode::INTERNAL_SERVER_ERROR,
288
+
"Error getting the completed status",
292
+
CompletionStatus::None => {
293
+
let result = challenge
294
+
.check_part_one(did_as_string.clone(), form.verification_code_one)
296
+
.map_err(log_and_respond(
297
+
StatusCode::INTERNAL_SERVER_ERROR,
298
+
"Error checking part one",
301
+
ChallengeCheckResponse::Correct => {
302
+
challenge.complete_part_one(did_as_string).await.map_err(
304
+
StatusCode::INTERNAL_SERVER_ERROR,
305
+
"Error completing part one",
311
+
FlashMessage::Success(
312
+
"Good job, you've completed Part 1".to_string(),
316
+
Ok(Redirect::to(format!("/day/{}", day as u8).as_str()))
318
+
ChallengeCheckResponse::Incorrect(message) => {
322
+
FlashMessage::Error(message),
325
+
Ok(Redirect::to(format!("/day/{}", day as u8).as_str()))
329
+
CompletionStatus::PartOne => {
330
+
if !challenge.has_part_two() {
332
+
"Someone tried to check for part two on day:{day}, when there was not one"
334
+
return Ok(Redirect::to(format!("/day/{}", day as u8).as_str()));
337
+
let result = challenge
338
+
.check_part_two(did_as_string.clone(), form.verification_code_two)
340
+
.map_err(log_and_respond(
341
+
StatusCode::INTERNAL_SERVER_ERROR,
342
+
"Error checking part two",
346
+
ChallengeCheckResponse::Correct => {
347
+
challenge.complete_part_two(did_as_string).await.map_err(
349
+
StatusCode::INTERNAL_SERVER_ERROR,
350
+
"Error completing part two",
356
+
FlashMessage::Success(
357
+
"Good job, you've completed Part 2".to_string(),
361
+
Ok(Redirect::to(format!("/day/{}", day as u8).as_str()))
363
+
ChallengeCheckResponse::Incorrect(message) => {
367
+
FlashMessage::Error(message),
370
+
Ok(Redirect::to(format!("/day/{}", day as u8).as_str()))
374
+
CompletionStatus::Both => Ok(Redirect::to(format!("/day/{}", day as u8).as_str())),