aboutsummaryrefslogtreecommitdiff
path: root/comments/src
diff options
context:
space:
mode:
Diffstat (limited to 'comments/src')
-rw-r--r--comments/src/main.rs143
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())
+}