/* * tmtd - Suckless To Do list * Copyright (C) 2022 C4TG1RL5 * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation, either version 3 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along with * this program. If not, see . */ use std::sync::Arc; use async_sqlx_session::PostgresSessionStore; use chrono::Duration; use sqlx::types::chrono::Utc; use tera::{Context, Tera}; use tokio::sync::broadcast; use tracing::info; use warp::Filter; use warp_sessions::{CookieOptions, SameSiteCookieOption, SessionWithStore}; use std::time::SystemTime; use crate::{task, Config, Database}; macro_rules! warp_try { ($expr:expr) => { match $expr { Ok(o) => o, Err(e) => { warn!("Error in a request handler: {}", e); return reply::with_status( format!("Error: {}", e), StatusCode::INTERNAL_SERVER_ERROR, ) .into_response(); } } }; ($store:expr, $expr:expr) => { match $expr { Ok(o) => o, Err(e) => { warn!("Error in a request handler: {}", e); return ( reply::with_status(format!("Error: {}", e), StatusCode::INTERNAL_SERVER_ERROR) .into_response(), $store, ); } } }; } macro_rules! warp_session { ($filter:expr, $store:expr, $then:expr) => { $filter .and( warp_sessions::request::with_session( $store.clone(), Some(CookieOptions { cookie_name: "tmtd_sid", cookie_value: None, max_age: Some(86400 * 7), domain: None, path: None, secure: true, http_only: true, same_site: Some(SameSiteCookieOption::Strict), }), ) .map(|mut s: SessionWithStore| { s.session.set_expiry(Utc::now() + Duration::days(7)); s }), ) .then($then) .untuple_one() .and_then(warp_sessions::reply::with_session) }; } pub struct App { config: Arc, db: Arc, session_store: PostgresSessionStore, tera: Arc, } impl App { pub fn new( config: Arc, db: Arc, session_store: PostgresSessionStore, ) -> Result { Ok(Self { config, db, session_store, tera: Arc::new(Tera::new("templates/*.html")?), }) } pub async fn run(self, mut cancel: broadcast::Receiver<()>) { let route = warp_session!( warp::path::end(), self.session_store, move |mut s: SessionWithStore| async move { s.session.insert_raw("test", "something".into()); ( warp::reply::html(format!("session id: {}", s.session.id())), s, ) } ) // example route, remove later or something .or(warp::path!("hello" / String).map({ let tera = self.tera.clone(); move |name: String| { let mut ctx = Context::new(); ctx.insert("name", &name); warp::reply::html(tera.render("hello.html", &ctx).unwrap()) } })) .or(warp::path("tmtd").map({ let tera = self.tera.clone(); move || { let mut ctx = Context::new(); let mut tasks = Vec::new(); for _ in 0..10 { let task_ctx = task::Task { date: "TODO".to_string(), assignee: "TODO".to_string(), description: "TODO".to_string(), status: "TODO".to_string(), title: "TODO".to_string(), }; tasks.push(task_ctx); } ctx.insert("tasks", &tasks); warp::reply::html(tera.render("task_page.html", &ctx).unwrap()) } })) .or(warp::path("task") .and(warp::path("create")) .map({ let tera = self.tera.clone(); move || { let ctx = Context::new(); warp::reply::html(tera.render("create_task.html", &ctx).unwrap()) } })) .or(warp::post() .and(warp::path("api")) .and(warp::path("task")) .and(warp::path("move")) .and(warp::body::form::()) .then(handle_post) ) .or(warp::post() .and(warp::path("api")) .and(warp::path("task")) .and(warp::path("create")) .and(warp::body::form::()) .then(handle_post_create) ); let (_, server) = warp::serve(route).bind_with_graceful_shutdown(self.config.listen_addr, async move { cancel.recv().await.unwrap(); }); server.await; info!("Web app has been shut down"); } } pub async fn handle_post(form: task::Request) -> impl warp::Reply { // TODO: intelligent SQL code here (or maybe in another function idk, it's up to the reader to // decide whats more intelligent) tracing::debug!("Got POST on /api/task/move: {}", form.r#move); warp::redirect(warp::http::Uri::from_static("/")) } pub async fn handle_post_create(form: task::Create)-> impl warp::Reply { let date = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap(); tracing::debug!("Got POST on /api/task/create: {:?}, date: {:?}", form, date); warp::redirect(warp::http::Uri::from_static("/")) }