use crate::util::gen_id; use crate::util::make_link; use crate::util::render_reject; use serde::{Deserialize, Serialize}; use sqlx::Row; use std::sync::Arc; use tera::Context; use url::Url; use warp::http::uri::InvalidUri; use warp::redirect; use warp::{ http::Uri, reject::{self, Reject}, reply, Rejection, Reply, }; use crate::{sqlargs, util::sql_reject, Brevo}; pub async fn index(brevo: Arc) -> Result, Rejection> { let rendered = brevo.tera.render("index.html", &Context::new()); match rendered { Err(_) => Err(reject::custom(BrevoReject::RenderFail)), Ok(r) => Ok(Box::new(reply::html(r))), } } pub async fn shortened(id: String, brevo: Arc) -> Result, Rejection> { make_table(Arc::clone(&brevo)).await?; let url = sqlx::query_with( " SELECT url FROM urls WHERE id = ? ", sqlargs![&id], ) .fetch_optional(&brevo.pool) .await .map_err(sql_reject)?; match url { Some(u) => Ok(Box::new(redirect::permanent( u.try_get::(0) .map_err(sql_reject)? .parse::() .map_err(|e| reject::custom(BrevoReject::UriParseError(e)))?, ))), None => Err(reject::not_found()), } } pub async fn submit(form_data: SubmitForm, brevo: Arc) -> Result, Rejection> { make_table(Arc::clone(&brevo)).await?; let url = form_data.url.as_str(); let id = sqlx::query_with( " SELECT id FROM urls WHERE url = ? ", sqlargs![url], ) .fetch_optional(&brevo.pool) .await .map_err(sql_reject)?; if let Some(id) = id { let id = id.try_get::(0).map_err(sql_reject)?; let rendered = brevo .tera .render( "success.html", &Context::from_serialize(SuccessContent { link: make_link(Arc::clone(&brevo), &id)?, }) .map_err(render_reject)?, ) .map_err(render_reject)?; Ok(Box::new(reply::html(rendered))) } else { let id = gen_id(Arc::clone(&brevo)).await; sqlx::query_with( " INSERT INTO urls ( id, url ) VALUES ( ?, ? ) ", sqlargs![&id, url], ) .execute(&brevo.pool) .await .map_err(sql_reject)?; let rendered = brevo .tera .render( "success.html", &Context::from_serialize(SuccessContent { link: make_link(Arc::clone(&brevo), &id)?, }) .map_err(render_reject)?, ) .map_err(render_reject)?; Ok(Box::new(reply::html(rendered))) } } async fn make_table(brevo: Arc) -> Result<(), Rejection> { // can't parameterize VARCHAR len, but this should be safe sqlx::query(&format!( " CREATE TABLE IF NOT EXISTS urls ( id VARCHAR({}) PRIMARY KEY, url TEXT NOT NULL UNIQUE ) ", brevo.config.link_len )) .execute(&brevo.pool) .await .map_err(sql_reject)?; Ok(()) } #[derive(Serialize)] struct SuccessContent { link: String, } #[derive(Deserialize)] pub struct SubmitForm { url: Url, } // TODO: implement reject handerls #[derive(Debug)] pub enum BrevoReject { RenderFail, SqlError(sqlx::Error), UrlParseError(url::ParseError), UriParseError(InvalidUri), } impl Reject for BrevoReject {}