diff options
author | Martin Ashby <martin@ashbysoft.com> | 2022-12-27 23:24:54 +0000 |
---|---|---|
committer | Martin Ashby <martin@ashbysoft.com> | 2022-12-27 23:24:54 +0000 |
commit | 19da6ec39be9e3caa5b9e2766139684fd7bbe0a0 (patch) | |
tree | bb92494a161c661cd085621db309d7c7fa77a1e3 /comments/src | |
parent | d8d7a2a5ff43f913322473a9714a63f1fd8d2303 (diff) | |
download | mfashby.net-19da6ec39be9e3caa5b9e2766139684fd7bbe0a0.tar.gz mfashby.net-19da6ec39be9e3caa5b9e2766139684fd7bbe0a0.tar.bz2 mfashby.net-19da6ec39be9e3caa5b9e2766139684fd7bbe0a0.tar.xz mfashby.net-19da6ec39be9e3caa5b9e2766139684fd7bbe0a0.zip |
Initial comments impl for my blog
Diffstat (limited to 'comments/src')
-rw-r--r-- | comments/src/main.rs | 143 |
1 files changed, 143 insertions, 0 deletions
diff --git a/comments/src/main.rs b/comments/src/main.rs new file mode 100644 index 0000000..312dafa --- /dev/null +++ b/comments/src/main.rs @@ -0,0 +1,143 @@ +/** + * Comments generator + * POST /comment - submits a comment + * + * GET /form - generate html comment form + * GET /comment - gets html fragment with comments + * both accept a query parameter ?page=<url> + */ + +use askama::Template; +use axum::{ + extract::{Query, Form, State}, + response::Redirect, + http::StatusCode, + routing::get, + Router, + debug_handler, +}; +use serde::Deserialize; +use sqlx::{ + postgres::{PgPool, PgPoolOptions}, + types::time::OffsetDateTime, +}; +use std::{net::SocketAddr, time::Duration}; + +// Useful global thingies +#[derive(Clone)] +struct Ctx { + pool: PgPool, +} + +#[tokio::main] +async fn main() { + let db_connection_str = std::env::var("DATABASE_URL") + .unwrap_or_else(|_| "postgres://postgres:password@localhost".to_string()); + + // setup connection pool + let pool = PgPoolOptions::new() + .max_connections(5) + .connect_timeout(Duration::from_secs(3)) + .connect(&db_connection_str) + .await + .expect("can't connect to database"); + sqlx::migrate!() + .run(&pool) + .await + .expect("failed to run migrations"); + + let ctx = Ctx {pool}; + + // build our application with some routes + let app = Router::new() + .route( + "/form", + get(get_form), + ) + .route( + "/comment", + get(get_comments).post(post_comments), + ) + .with_state(ctx); + + // run it with hyper + let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); + axum::Server::bind(&addr) + .serve(app.into_make_service()) + .await + .unwrap(); +} + +#[derive(Deserialize)] +struct UrlQuery { + url: String +} + +#[derive(Template)] +#[template(path = "form.html")] +struct CommentForm { + url: String +} + +async fn get_form( + Query(uq): Query<UrlQuery>, +) -> Result<String, (StatusCode, String)> { + let c = CommentForm{url: uq.url}; + let res = c.render().map_err(internal_error)?; + Ok(res) +} + +#[derive(Template)] +#[template(path = "comments.html")] +struct Comments { + comments: Vec<Comment>, +} +struct Comment { + author: String, + comment: String, + ts: OffsetDateTime, +} + +async fn get_comments( + Query(uq): Query<UrlQuery>, + State(ctx): State<Ctx>) -> Result<String, (StatusCode,String)> { + let comments = sqlx::query!("select author,comment,ts from comments where url = $1", uq.url) + .fetch_all(&ctx.pool) + .await + .map_err(internal_error)?; + let render_comments: Vec<Comment> = comments.into_iter().map(|comment| { + Comment { + author: comment.author, + comment: comment.comment, + ts: comment.ts, + } + }).collect(); + let c = Comments{comments: render_comments}; + let res = c.render().map_err(internal_error)?; + Ok(res) +} + +#[derive(Deserialize)] +struct PostComment { + url: String, + author: String, + comment: String, +} + +#[debug_handler] +async fn post_comments( + State(ctx): State<Ctx>, + Form(post_comment): Form<PostComment>) -> Result<Redirect,(StatusCode,String)> { + sqlx::query!("insert into comments(url,author,comment) values($1, $2, $3)", post_comment.url, post_comment.author, post_comment.comment) + .execute(&ctx.pool) + .await + .map_err(internal_error)?; + Ok(Redirect::temporary(&post_comment.url)) +} + +fn internal_error<E>(err: E) -> (StatusCode, String) +where + E: std::error::Error, +{ + (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()) +} |