From 19da6ec39be9e3caa5b9e2766139684fd7bbe0a0 Mon Sep 17 00:00:00 2001 From: Martin Ashby Date: Tue, 27 Dec 2022 23:24:54 +0000 Subject: Initial comments impl for my blog --- comments/src/main.rs | 143 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100644 comments/src/main.rs (limited to 'comments/src/main.rs') 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= + */ + +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, +) -> Result { + 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, +} +struct Comment { + author: String, + comment: String, + ts: OffsetDateTime, +} + +async fn get_comments( + Query(uq): Query, + State(ctx): State) -> Result { + 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 = 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, + Form(post_comment): Form) -> Result { + 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(err: E) -> (StatusCode, String) +where + E: std::error::Error, +{ + (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()) +} -- cgit v1.2.3-ZIG