use axum::{ extract::{FromRef, Path, State}, http::{ header::{ACCEPT, CONTENT_TYPE}, HeaderMap, HeaderValue, StatusCode, }, response::{Html, IntoResponse, Redirect}, Form, Json, }; use maud::{html, Render}; use serde::{Deserialize, Serialize}; use sqlx::{Pool, Postgres}; use tracing::error; use uuid::Uuid; use crate::{ markup_response::{simple_error_page, MarkupResponse}, model::{Chat, Message}, state::AppState, ADMIN_TOK, }; pub async fn get( Path(url_path): Path, headers: HeaderMap, State(state): State, ) -> impl IntoResponse { println!("headers: {headers:#?}"); // TODO: Error handling let chat = match state.fetch_chat_by_url_path(&url_path).await { Ok(v) => v, Err(err) => { error!("Error fetching chat: {err:?}"); return simple_error_page(err.into()).into_response(); } }; let messages = match state.fetch_messages(&chat).await { Ok(v) => v, Err(err) => { error!("Error fetching messages for chat {}: {err:?}", chat.id); return simple_error_page(err.into()).into_response(); } }; if Some(&HeaderValue::from_static("application/json")) == headers.get(ACCEPT) { Json(messages).into_response() } else { MarkupResponse::new( html! { link rel="stylesheet" href="/css"; template #chatmessage { div.message { p { } span.timestamp { } } } main { div #history { @for msg in &messages { div.message.(if msg.from_admin { "from_admin" } else { "from_user" }) #(msg.id) { p { (msg.content) } span.timestamp { (msg.timestamp) } } } } form #send method="post"{ textarea #msgcontent name="msgcontent" rows="1" cols="80" {} button type="submit" { "Send!" } } } script src="/js" {}; }, "Cursed Messenger from hell", ) .into_response() } } pub async fn poll(Path(message): Path, State(state): State) -> impl IntoResponse { let message = sqlx::query_as!(Message, r#"select * from messages where id = $1"#, message) .fetch_one(state.pool()) .await .unwrap(); let new_messages = sqlx::query_as!( Message, r#"select * from messages where chat_id = $1 and timestamp > $2;"#, message.chat_id, message.timestamp ) .fetch_all(state.pool()) .await .unwrap(); Json(new_messages) } // TODO: // - validation of msg length // - fix terrible returns lmao pub async fn post( Path(url_path): Path, headers: HeaderMap, State(state): State, Form(FormMessageBody { msgcontent: body }): Form, ) -> impl IntoResponse { let chat = sqlx::query_as!(Chat, r#"select * from chats where url_path = $1"#, url_path) .fetch_one(state.pool()) .await .unwrap(); if body.len() > 2000 { return StatusCode::BAD_REQUEST.into_response(); } let is_admin = state.check_admin_tok(&headers); sqlx::query!( r#"insert into messages (chat_id, content, from_admin) values ($1, $2, $3);"#, chat.id, body, is_admin ) .execute(state.pool()) .await .unwrap(); if is_admin { StatusCode::OK.into_response() } else { Redirect::to(&format!("/{url_path}")).into_response() } } #[derive(Debug, Serialize, Deserialize)] pub struct FormMessageBody { msgcontent: String, }