check joke refs to questions

This commit is contained in:
David Westgate 2024-06-11 22:12:21 -07:00
parent 538c55fb2a
commit e3c384ea5a
4 changed files with 72 additions and 51 deletions

View File

@ -5,8 +5,9 @@ mod question;
mod question_tag; mod question_tag;
mod tag; mod tag;
use axum::{ use axum::{
extract::{Path, Query, State}, extract::{Path, Query, Request, State},
http::{StatusCode, Uri}, http::{header, StatusCode, Uri},
middleware::Next,
response::{IntoResponse, Response}, response::{IntoResponse, Response},
routing::{delete, get, post, put}, routing::{delete, get, post, put},
Form, Json, Router, Form, Json, Router,
@ -57,6 +58,27 @@ fn db_string() -> String {
) )
} }
// Simple cors middleware to avoid pulling in tower just for this
async fn add_cors_headers(
req: Request,
next: Next,
) -> Result<impl IntoResponse, (StatusCode, String)> {
let mut response = next.run(req).await;
response.headers_mut().insert(
header::ACCESS_CONTROL_ALLOW_ORIGIN,
header::HeaderValue::from_static("*"),
);
response.headers_mut().insert(
header::ACCESS_CONTROL_ALLOW_METHODS,
header::HeaderValue::from_static("GET, POST, PUT, DELETE"),
);
response.headers_mut().insert(
header::ACCESS_CONTROL_ALLOW_HEADERS,
header::HeaderValue::from_static("Content-Type, Authorization"),
);
Ok(response)
}
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {
let db_url: String = db_string(); let db_url: String = db_string();
@ -76,6 +98,7 @@ async fn main() {
.nest("/api/v1", apis) .nest("/api/v1", apis)
.route("/", get(serve_file)) .route("/", get(serve_file))
.route("/*path", get(serve_file)) .route("/*path", get(serve_file))
.layer(axum::middleware::from_fn(add_cors_headers))
.fallback(handler_404); .fallback(handler_404);
axum::serve(listener, app).await.unwrap(); axum::serve(listener, app).await.unwrap();

View File

@ -27,9 +27,9 @@ pub fn Finder(props: &FinderProps) -> Html {
let props = props.clone(); let props = props.clone();
html! { <> html! { <>
<div> <div>
<input type="text" placeholder="joke id" oninput={change_key}/> <input type="text" placeholder="question id" oninput={change_key}/>
<button onclick={move |_| props.on_find.emit((*key).clone())}> <button onclick={move |_| props.on_find.emit((*key).clone())}>
{"Find this joke"} {"Find this question"}
</button> </button>
</div> </div>
</> } </> }

View File

@ -1,8 +1,8 @@
mod finder; mod finder;
mod joke; mod question;
use finder::*; use finder::*;
use joke::*; use question::*;
use std::collections::HashSet; use std::collections::HashSet;
@ -13,21 +13,21 @@ extern crate wasm_bindgen_futures;
use web_sys::HtmlTextAreaElement; use web_sys::HtmlTextAreaElement;
use yew::prelude::*; use yew::prelude::*;
pub type JokeResult = Result<JokeStruct, gloo_net::Error>; pub type QuestionResult = Result<QuestionStruct, gloo_net::Error>;
struct App { struct App {
joke: JokeResult, question: QuestionResult,
} }
pub enum Msg { pub enum Msg {
GotJoke(JokeResult), GotQuestion(QuestionResult),
GetJoke(Option<String>), GetQuestion(Option<String>),
} }
impl App { impl App {
fn refresh_joke(ctx: &Context<Self>, key: Option<String>) { fn refresh_question(ctx: &Context<Self>, key: Option<String>) {
let got_joke = JokeStruct::get_joke(key); let got_question = QuestionStruct::get_question(key);
ctx.link().send_future(got_joke); ctx.link().send_future(got_question);
} }
} }
@ -36,42 +36,42 @@ impl Component for App {
type Properties = (); type Properties = ();
fn create(ctx: &Context<Self>) -> Self { fn create(ctx: &Context<Self>) -> Self {
App::refresh_joke(ctx, None); App::refresh_question(ctx, None);
let joke = Err(gloo_net::Error::GlooError("Loading Joke".to_string())); let question = Err(gloo_net::Error::GlooError("Loading Question".to_string()));
Self { joke } Self { question }
} }
fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool { fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
match msg { match msg {
Msg::GotJoke(joke) => { Msg::GotQuestion(question) => {
self.joke = joke; self.question = question;
true true
} }
Msg::GetJoke(key) => { Msg::GetQuestion(key) => {
// log!(format!("GetJoke: {:?}", key)); // log!(format!("GetQuestion: {:?}", key));
App::refresh_joke(ctx, key); App::refresh_question(ctx, key);
false false
} }
} }
} }
fn view(&self, ctx: &Context<Self>) -> Html { fn view(&self, ctx: &Context<Self>) -> Html {
let joke = &self.joke; let question = &self.question;
html! { html! {
<> <>
<h1>{ "Knock-Knock" }</h1> <h1>{ "Question and Answer" }</h1>
if let Ok(ref joke) = joke { if let Ok(ref question) = question {
<Joke joke={joke.clone()}/> <Question question={question.clone()}/>
} }
if let Err(ref error) = joke { if let Err(ref error) = question {
<div> <div>
<span class="error">{format!("Server Error: {error}")}</span> <span class="error">{format!("Server Error: {error}")}</span>
</div> </div>
} }
<div> <div>
<button onclick={ctx.link().callback(|_| Msg::GetJoke(None))}>{"Tell me another!"}</button> <button onclick={ctx.link().callback(|_| Msg::GetQuestion(None))}>{"Ask me another!"}</button>
</div> </div>
<Finder on_find={ctx.link().callback(Msg::GetJoke)}/> <Finder on_find={ctx.link().callback(Msg::GetQuestion)}/>
</> </>
} }
} }

View File

@ -1,24 +1,24 @@
use crate::*; use crate::*;
#[derive(Properties, Clone, PartialEq, serde::Deserialize)] #[derive(Properties, Clone, PartialEq, serde::Deserialize)]
pub struct JokeStruct { pub struct QuestionStruct {
pub id: String, pub id: String,
pub whos_there: String, pub title: String,
pub answer_who: String, pub content: String,
pub tags: Option<HashSet<String>>, pub tags: Option<HashSet<String>>,
pub source: Option<String>, pub source: Option<String>,
} }
impl JokeStruct { impl QuestionStruct {
pub async fn get_joke(key: Option<String>) -> Msg { pub async fn get_question(key: Option<String>) -> Msg {
let request = match &key { let request = match &key {
None => "http://localhost:3000/api/v1/joke".to_string(), None => "http://localhost:3000/api/v1/question/2".to_string(),
Some(ref key) => format!("http://localhost:3000/api/v1/joke/{}", key,), Some(ref key) => format!("http://localhost:3000/api/v1/question/{}", key,),
}; };
let response = http::Request::get(&request).send().await; let response = http::Request::get(&request).send().await;
match response { match response {
Err(e) => Msg::GotJoke(Err(e)), Err(e) => Msg::GotQuestion(Err(e)),
Ok(data) => Msg::GotJoke(data.json().await), Ok(data) => Msg::GotQuestion(data.json().await),
} }
} }
} }
@ -28,27 +28,25 @@ pub fn format_tags(tags: &HashSet<String>) -> String {
} }
#[derive(Properties, Clone, PartialEq, serde::Deserialize)] #[derive(Properties, Clone, PartialEq, serde::Deserialize)]
pub struct JokeProps { pub struct QuestionProps {
pub joke: JokeStruct, pub question: QuestionStruct,
} }
#[function_component(Joke)] #[function_component(Question)]
pub fn joke(joke: &JokeProps) -> Html { pub fn question(question: &QuestionProps) -> Html {
let joke = &joke.joke; let question = &question.question;
html! { <> html! { <>
<div class="joke"> <div class="question">
<span class="teller">{"Knock-Knock!"}</span><br/> <span class="teller">{"Question time!"}</span><br/>
<span class="tellee">{"Who's there?"}</span><br/> <span class="teller">{question.title.clone()}</span><br/>
<span class="teller">{joke.whos_there.clone()}</span><br/> <span class="teller">{question.content.clone()}</span>
<span class="tellee">{format!("{} who?", &joke.whos_there)}</span><br/>
<span class="teller">{joke.answer_who.clone()}</span>
</div> </div>
<span class="annotation"> <span class="annotation">
{format!("[id: {}", &joke.id)} {format!("[id: {}", &question.id)}
if let Some(ref tags) = joke.tags { if let Some(ref tags) = question.tags {
{format!("; tags: {}", &format_tags(tags))} {format!("; tags: {}", &format_tags(tags))}
} }
if let Some(ref source) = joke.source { if let Some(ref source) = question.source {
{format!("; source: {}", source)} {format!("; source: {}", source)}
} }
{"]"} {"]"}