chatthing/crates/backend/src/chat.rs

139 lines
3.9 KiB
Rust

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<String>,
headers: HeaderMap,
State(state): State<AppState>,
) -> 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<Uuid>, State(state): State<AppState>) -> 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<String>,
headers: HeaderMap,
State(state): State<AppState>,
Form(FormMessageBody { msgcontent: body }): Form<FormMessageBody>,
) -> 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,
}