use log::warn; use rocket::{ delete, get, http::CookieJar, patch, post, serde::json::{json, Json}, State, }; use serde::{Deserialize, Serialize}; use serenity::{ all::{ChannelId, GuildId}, prelude::Context, }; use crate::web::{ check_authorization, guards::transaction::Transaction, routes::{dashboard::check_channel_matches_guild, JsonResult}, string_opt, }; #[derive(Deserialize)] pub struct CreateTodo { #[serde(with = "string_opt")] channel_id: Option, value: String, } #[derive(Serialize)] pub struct GetTodo { id: u32, #[serde(with = "string_opt")] channel_id: Option, value: String, } #[derive(Deserialize)] pub struct UpdateTodo { value: String, } #[post("/api/guild//todos", data = "")] pub async fn create_todo( id: u64, todo: Json, cookies: &CookieJar<'_>, ctx: &State, mut transaction: Transaction<'_>, ) -> JsonResult { check_authorization(cookies, ctx.inner(), id).await?; let guild_id = GuildId::new(id); if todo.value.len() > 2000 { return json_err!("Value too long"); } match todo.channel_id { Some(channel_id) => { if !check_channel_matches_guild(ctx, ChannelId::new(channel_id), guild_id) { warn!("Channel {} not found for guild {}", channel_id, guild_id); return json_err!("Channel not found"); } sqlx::query!( " INSERT INTO todos (guild_id, channel_id, value) VALUES ( (SELECT id FROM guilds WHERE guild = ?), (SELECT id FROM channels WHERE channel = ?), ? ) ", id, channel_id, todo.value ) .execute(transaction.executor()) .await .map_err(|e| { warn!("Error creating todo: {:?}", e); json!({"errors": vec!["Unknown error"]}) })?; } None => { sqlx::query!( " INSERT INTO todos (guild_id, channel_id, value) VALUES ( (SELECT id FROM guilds WHERE guild = ?), NULL, ? ) ", id, todo.value ) .execute(transaction.executor()) .await .map_err(|e| { warn!("Error creating todo: {:?}", e); json!({"errors": vec!["Unknown error"]}) })?; } } if let Err(e) = transaction.commit().await { warn!("Couldn't commit transaction: {:?}", e); return json_err!("Couldn't commit transaction."); } Ok(json!({})) } #[get("/api/guild//todos")] pub async fn get_todo( id: u64, cookies: &CookieJar<'_>, ctx: &State, mut transaction: Transaction<'_>, ) -> JsonResult { check_authorization(cookies, ctx.inner(), id).await?; let todos = sqlx::query_as!( GetTodo, " SELECT todos.id, channels.channel AS channel_id, value FROM todos INNER JOIN guilds ON guilds.id = todos.guild_id LEFT JOIN channels ON channels.id = todos.channel_id WHERE guilds.guild = ? ", id ) .fetch_all(transaction.executor()) .await .map_err(|e| { warn!("Error fetching todos: {:?}", e); json!({ "errors": vec!["Unknown error"] }) })?; Ok(json!(todos)) } #[patch("/api/guild//todos/", data = "")] pub async fn update_todo( guild_id: u64, todo_id: u64, todo: Json, cookies: &CookieJar<'_>, ctx: &State, mut transaction: Transaction<'_>, ) -> JsonResult { check_authorization(cookies, ctx.inner(), guild_id).await?; if todo.value.len() > 2000 { return json_err!("Value too long"); } sqlx::query!( " UPDATE todos SET value = ? WHERE guild_id = (SELECT id FROM guilds WHERE guild = ?) AND id = ? ", todo.value, guild_id, todo_id, ) .execute(transaction.executor()) .await .map_err(|e| { warn!("Error updating todo: {:?}", e); json!({"errors": vec!["Unknown error"]}) })?; if let Err(e) = transaction.commit().await { warn!("Couldn't commit transaction: {:?}", e); return json_err!("Couldn't commit transaction."); } Ok(json!({})) } #[delete("/api/guild//todos/")] pub async fn delete_todo( guild_id: u64, todo_id: u64, cookies: &CookieJar<'_>, ctx: &State, mut transaction: Transaction<'_>, ) -> JsonResult { check_authorization(cookies, ctx.inner(), guild_id).await?; sqlx::query!( " DELETE FROM todos WHERE guild_id = (SELECT id FROM guilds WHERE guild = ?) AND id = ? ", guild_id, todo_id, ) .execute(transaction.executor()) .await .map_err(|e| { warn!("Error deleting todo: {:?}", e); json!({"errors": vec!["Unknown error"]}) })?; if let Err(e) = transaction.commit().await { warn!("Couldn't commit transaction: {:?}", e); return json_err!("Couldn't commit transaction."); } Ok(json!({})) }