diff --git a/Cargo.lock b/Cargo.lock index 41c4b8d..43849e4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -53,6 +53,21 @@ version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anstream" version = "0.6.15" @@ -208,7 +223,9 @@ dependencies = [ "anyhow", "axum", "axum-macros", + "chrono", "chrono-tz", + "http", "maud", "rand", "serde", @@ -263,6 +280,12 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + [[package]] name = "byteorder" version = "1.5.0" @@ -311,7 +334,13 @@ version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", "num-traits", + "serde", + "wasm-bindgen", + "windows-targets 0.52.6", ] [[package]] @@ -410,6 +439,12 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + [[package]] name = "cpufeatures" version = "0.2.14" @@ -842,6 +877,29 @@ dependencies = [ "tower-service", ] +[[package]] +name = "iana-time-zone" +version = "0.1.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "idna" version = "0.5.0" @@ -893,6 +951,15 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +[[package]] +name = "js-sys" +version = "0.3.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" +dependencies = [ + "wasm-bindgen", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -1701,6 +1768,7 @@ dependencies = [ "atoi", "byteorder", "bytes", + "chrono", "crc", "crossbeam-queue", "either", @@ -1785,6 +1853,7 @@ dependencies = [ "bitflags", "byteorder", "bytes", + "chrono", "crc", "digest", "dotenvy", @@ -1827,6 +1896,7 @@ dependencies = [ "base64", "bitflags", "byteorder", + "chrono", "crc", "dotenvy", "etcetera", @@ -1863,6 +1933,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5b2cf34a45953bfd3daaf3db0f7a7878ab9b7a6b91b422d24a7a9e4c857b680" dependencies = [ "atoi", + "chrono", "flume", "futures-channel", "futures-core", @@ -2256,6 +2327,61 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" +[[package]] +name = "wasm-bindgen" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" +dependencies = [ + "cfg-if", + "once_cell", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" + [[package]] name = "webpki-roots" version = "0.26.6" @@ -2297,6 +2423,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.48.0" diff --git a/crates/backend/Cargo.toml b/crates/backend/Cargo.toml index 0277485..ea51139 100644 --- a/crates/backend/Cargo.toml +++ b/crates/backend/Cargo.toml @@ -4,14 +4,16 @@ version = "0.1.0" edition = "2021" [dependencies] +http = "1" axum = { version = "0.7.5", features = [ "json", "macros" ] } axum-macros = "0.4.1" +chrono = { version = "0.4", features = [ "serde" ] } chrono-tz = "0.10.0" maud = "0.26.0" -sqlx = { version = "0.8.2", features = ["postgres", "runtime-tokio", "tls-rustls-ring", "uuid" ] } -tokio = { version = "1.40.0", features = ["full"] } +sqlx = { version = "0.8.2", features = [ "postgres", "runtime-tokio", "tls-rustls-ring", "uuid", "chrono" ] } +tokio = { version = "1.40.0", features = [ "full" ] } tokio-tungstenite = "0.24.0" -serde = { version = "1", features = ["derive"] } +serde = { version = "1", features = [ "derive" ] } anyhow = "1" -uuid = { version = "1.10.0", features = ["serde"] } +uuid = { version = "1.10.0", features = [ "serde" ] } rand = "0.8.5" diff --git a/crates/backend/migrations/20240921175315_init.sql b/crates/backend/migrations/20240921175315_init.sql index ed771c0..0afa59a 100644 --- a/crates/backend/migrations/20240921175315_init.sql +++ b/crates/backend/migrations/20240921175315_init.sql @@ -10,8 +10,10 @@ create table chats ( create table messages ( id uuid default (gen_random_uuid()) primary key, - timestamp timestamptz default (now()), + timestamp timestamptz not null default (now()), chat_id uuid not null references chats(id), content varchar(2000) not null, from_admin boolean not null -) +); + +insert into chats (url_path) values ('tstcht'); diff --git a/crates/backend/src/chat.rs b/crates/backend/src/chat.rs index 4dd8755..825a8c6 100644 --- a/crates/backend/src/chat.rs +++ b/crates/backend/src/chat.rs @@ -1,18 +1,28 @@ use axum::{ - extract::{Path, State}, - response::Html, - Json, + 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 uuid::Uuid; -use crate::model::{Chat, Message}; +use crate::{ + model::{Chat, Message}, + ADMIN_TOK, +}; pub async fn get( Path(url_path): Path, + headers: HeaderMap, State(pool): State>, -) -> Json> { +) -> impl IntoResponse { + println!("headers: {headers:#?}"); let chat = sqlx::query_as!(Chat, r#"select * from chats where url_path = $1"#, url_path) .fetch_one(&pool) .await @@ -25,5 +35,74 @@ pub async fn get( .fetch_all(&pool) .await .unwrap(); - Json(messages) + + if Some(&HeaderValue::from_static("application/json")) == headers.get(ACCEPT) { + Json(messages).into_response() + } else { + Html( + html! { + main { + div #history { + @for msg in &messages { + div.message.(if msg.from_admin { "from_admin" } else { "from_user" }) { + p { (msg.content) "(" (msg.timestamp) ")" } + } + } + } + form #send method="post"{ + textarea #msgcontent name="msgcontent" rows="1" cols="80" {} + button type="submit" { "Send!" } + } + } + } + .into_string(), + ) + .into_response() + } +} + +// TODO: +// - validation of msg length +// - fix terrible returns lmao +pub async fn post( + Path(url_path): Path, + headers: HeaderMap, + State(pool): 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(&pool) + .await + .unwrap(); + + if body.len() > 2000 { + return StatusCode::BAD_REQUEST.into_response(); + } + + if headers.get("x-admin-tok") == Some(&HeaderValue::from_static(ADMIN_TOK)) { + sqlx::query!( + r#"insert into messages (chat_id, content, from_admin) values ($1, $2, true);"#, + chat.id, + body + ) + .execute(&pool) + .await + .unwrap(); + StatusCode::OK.into_response() + } else { + sqlx::query!( + r#"insert into messages (chat_id, content, from_admin) values ($1, $2, false);"#, + chat.id, + body + ) + .execute(&pool) + .await + .unwrap(); + Redirect::to(&format!("/{url_path}")).into_response() + } +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct FormMessageBody { + msgcontent: String, } diff --git a/crates/backend/src/main.rs b/crates/backend/src/main.rs index 4b94ad7..7c6cacf 100644 --- a/crates/backend/src/main.rs +++ b/crates/backend/src/main.rs @@ -17,7 +17,7 @@ async fn main() -> anyhow::Result<()> { let app = Router::new() .route("/", get(|| async { "

gay

" })) - .route("/:path", get(chat::get)) + .route("/:path", get(chat::get).post(chat::post)) .with_state(pool.clone()) .nest("/stat", stat::router(pool.clone())) .nest("/admin", admin::router(pool.clone())); diff --git a/crates/backend/src/model.rs b/crates/backend/src/model.rs index 58e58f5..d9e6043 100644 --- a/crates/backend/src/model.rs +++ b/crates/backend/src/model.rs @@ -1,3 +1,4 @@ +use chrono::{DateTime, FixedOffset, Local, Utc}; use serde::Serialize; use sqlx::{prelude::FromRow, Decode, Encode}; use uuid::Uuid; @@ -15,6 +16,7 @@ pub struct Message { // Uuid but sqlx doesnt impl serde traits for them pub id: Uuid, pub chat_id: Uuid, + pub timestamp: DateTime, pub content: String, pub from_admin: bool, diff --git a/justfile b/justfile new file mode 100644 index 0000000..99f3175 --- /dev/null +++ b/justfile @@ -0,0 +1,5 @@ +[no-cd] +reset-db: + #!/usr/bin/env nu + cd crates/backend + sqlx database reset