aboutsummaryrefslogtreecommitdiff
path: root/comments/src/main.rs
diff options
context:
space:
mode:
Diffstat (limited to 'comments/src/main.rs')
-rw-r--r--comments/src/main.rs227
1 files changed, 0 insertions, 227 deletions
diff --git a/comments/src/main.rs b/comments/src/main.rs
deleted file mode 100644
index 50f568f..0000000
--- a/comments/src/main.rs
+++ /dev/null
@@ -1,227 +0,0 @@
-/**
- * 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,Html},
- http::StatusCode,
- routing::get,
- Router,
-};
-use lettre::{
- AsyncSmtpTransport, AsyncTransport, Message, Tokio1Executor,
- transport::smtp::authentication::Credentials
-};
-use serde::Deserialize;
-use sqlx::{
- postgres::{PgPool, PgPoolOptions},
- types::{
- time::OffsetDateTime,
- uuid::Uuid,
- },
-};
-use std::{net::SocketAddr, time::Duration};
-
-
-// Useful global thingies
-#[derive(Clone)]
-struct Ctx {
- pool: PgPool,
- base_path: String,
- mail_opts: Option<MailCtx>,
-}
-
-#[derive(Clone)]
-struct MailCtx {
- notification_address: String,
- smtp_server: String,
- smtp_credentials: Credentials,
-}
-
-#[tokio::main]
-async fn main() {
- let base_path = std::env::var("BASE_PATH")
- .unwrap_or_else(|_| "/api".to_string());
-
- let db_connection_str = std::env::var("DATABASE_URL")
- .unwrap_or_else(|_| "postgres://comments@localhost/comments".to_string());
-
- let notification_address = std::env::var("NOTIFICATION_ADDRESS").ok();
- let mail_opts = notification_address.map(|na| {
- let smtp_server = std::env::var("SMTP_SERVER").expect("SMTP_SERVER not supplied!");
- let smtp_username = std::env::var("SMTP_USERNAME").expect("SMTP_USERNAME not supplied!");
- let smtp_password = std::env::var("SMTP_PASSWORD").expect("SMTP_PASSWORD not supplied!");
- MailCtx{
- smtp_server: smtp_server,
- smtp_credentials: Credentials::new(smtp_username, smtp_password),
- notification_address: na,
- }
- });
-
- 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, base_path: base_path.clone(), mail_opts};
-
- let app = Router::new()
- .nest(&base_path, Router::new()
- .route(
- "/form",
- get(get_form),
- )
- .route(
- "/comment",
- get(get_comments).post(post_comments),
- )
- .with_state(ctx));
-
- let addr = SocketAddr::from(([127, 0, 0, 1], 5678));
- 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,
- capcha_question: String,
- capcha_id: Uuid,
- base_path: String,
-}
-
-async fn get_form(
- State(ctx): State<Ctx>,
- Query(uq): Query<UrlQuery>
-) -> Result<Html<String>, (StatusCode, String)> {
- let capcha = sqlx::query!("select id, question from capchas order by random() limit 1")
- .fetch_one(&ctx.pool)
- .await
- .map_err(internal_error)?;
- let c = CommentForm{url: uq.url, capcha_question: capcha.question, capcha_id: capcha.id, base_path: ctx.base_path};
- let res = c.render().map_err(internal_error)?;
- Ok(Html(res))
-}
-
-#[derive(Template)]
-#[template(path = "comments.html")]
-struct Comments {
- comments: Vec<Comment>,
-}
-struct Comment {
- author: String,
- comment: String,
- ts: OffsetDateTime,
-}
-
-async fn get_comments(
- State(ctx): State<Ctx>,
- Query(uq): Query<UrlQuery>) -> Result<Html<String>, (StatusCode,String)> {
- let comments = sqlx::query!("select author,comment,ts from comments where url = $1 order by ts", 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(Html(res))
-}
-
-#[derive(Deserialize)]
-struct PostComment {
- url: String,
- author: String,
- comment: String,
- capcha_id: String,
- capcha_answer: String,
-}
-
-struct CapchaAnswer {
- answer:String
-}
-
-#[derive(Template)]
-#[template(path = "notification.txt")]
-struct Notification<'a> {
- url: &'a str,
- comment: &'a str,
- author: &'a str,
-}
-
-async fn post_comments(
- State(ctx): State<Ctx>,
- Form(post_comment): Form<PostComment>) -> Result<Redirect,(StatusCode,String)> {
- let capcha_id: Uuid = post_comment.capcha_id.parse()
- .map_err(|_| {(StatusCode::BAD_REQUEST, "Invalid capcha_id".to_string())})?;
- let ans: CapchaAnswer = sqlx::query_as!(CapchaAnswer, "select answer from capchas where id = $1", capcha_id)
- .fetch_one(&ctx.pool)
- .await
- .map_err(internal_error)?;
- if post_comment.capcha_answer != ans.answer {
- return Err((StatusCode::BAD_REQUEST, "Capcha was wrong!".to_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)?;
-
- if let Some(mail_opts) = ctx.mail_opts {
- let mail_body = Notification{
- url: &post_comment.url,
- author: &post_comment.author,
- comment: &post_comment.comment
- }.render().unwrap();
- let email = Message::builder()
- // TODO should be a config value
- .from("Comments Service <comments@mfashby.net>".parse().unwrap())
- .to(mail_opts.notification_address.parse().unwrap())
- .subject(format!("New comment on {}", post_comment.url))
- .body(mail_body)
- .unwrap();
- let mailer: AsyncSmtpTransport<Tokio1Executor> =
- AsyncSmtpTransport::<Tokio1Executor>::relay(&mail_opts.smtp_server)
- .unwrap()
- .credentials(mail_opts.smtp_credentials)
- .build();
- match mailer.send(email).await {
- Ok(_) => (),
- Err(e) => eprintln!("Could not send email: {:?}", e),
- }
- };
-
- 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())
-}