More work on todo list
This commit is contained in:
		@@ -3,7 +3,7 @@ mod emojis;
 | 
			
		||||
mod reminders;
 | 
			
		||||
mod roles;
 | 
			
		||||
mod templates;
 | 
			
		||||
mod todos;
 | 
			
		||||
pub mod todos;
 | 
			
		||||
 | 
			
		||||
use std::env;
 | 
			
		||||
 | 
			
		||||
@@ -17,7 +17,6 @@ use serenity::{
 | 
			
		||||
    model::id::{GuildId, RoleId},
 | 
			
		||||
};
 | 
			
		||||
pub use templates::*;
 | 
			
		||||
pub use todos::{create_todo, delete_todo, get_todo, update_todo};
 | 
			
		||||
 | 
			
		||||
use crate::web::{check_authorization, routes::JsonResult};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -7,26 +7,38 @@ use rocket::{
 | 
			
		||||
    State,
 | 
			
		||||
};
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
use serenity::prelude::Context;
 | 
			
		||||
use serenity::{
 | 
			
		||||
    all::{ChannelId, GuildId},
 | 
			
		||||
    prelude::Context,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
use crate::web::{
 | 
			
		||||
    check_authorization, guards::transaction::Transaction, routes::JsonResult, string_opt,
 | 
			
		||||
    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<u64>,
 | 
			
		||||
    value: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Serialize)]
 | 
			
		||||
struct GetTodo {
 | 
			
		||||
pub struct GetTodo {
 | 
			
		||||
    id: u32,
 | 
			
		||||
    #[serde(with = "string_opt")]
 | 
			
		||||
    channel_id: Option<u64>,
 | 
			
		||||
    value: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Deserialize)]
 | 
			
		||||
pub struct UpdateTodo {
 | 
			
		||||
    value: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[post("/api/guild/<id>/todos", data = "<todo>")]
 | 
			
		||||
pub async fn create_todo(
 | 
			
		||||
    id: u64,
 | 
			
		||||
@@ -37,6 +49,67 @@ pub async fn create_todo(
 | 
			
		||||
) -> 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!({}))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -75,26 +148,67 @@ pub async fn get_todo(
 | 
			
		||||
    Ok(json!(todos))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[patch("/api/guild/<id>/todos")]
 | 
			
		||||
#[patch("/api/guild/<guild_id>/todos/<todo_id>", data = "<todo>")]
 | 
			
		||||
pub async fn update_todo(
 | 
			
		||||
    id: u64,
 | 
			
		||||
    guild_id: u64,
 | 
			
		||||
    todo_id: u64,
 | 
			
		||||
    todo: Json<UpdateTodo>,
 | 
			
		||||
    cookies: &CookieJar<'_>,
 | 
			
		||||
    ctx: &State<Context>,
 | 
			
		||||
    mut transaction: Transaction<'_>,
 | 
			
		||||
) -> JsonResult {
 | 
			
		||||
    check_authorization(cookies, ctx.inner(), id).await?;
 | 
			
		||||
    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 = ?
 | 
			
		||||
            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"]})
 | 
			
		||||
    })?;
 | 
			
		||||
 | 
			
		||||
    Ok(json!({}))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[delete("/api/guild/<id>/todos")]
 | 
			
		||||
#[delete("/api/guild/<guild_id>/todos/<todo_id>")]
 | 
			
		||||
pub async fn delete_todo(
 | 
			
		||||
    id: u64,
 | 
			
		||||
    guild_id: u64,
 | 
			
		||||
    todo_id: u64,
 | 
			
		||||
    cookies: &CookieJar<'_>,
 | 
			
		||||
    ctx: &State<Context>,
 | 
			
		||||
    mut transaction: Transaction<'_>,
 | 
			
		||||
) -> JsonResult {
 | 
			
		||||
    check_authorization(cookies, ctx.inner(), id).await?;
 | 
			
		||||
    check_authorization(cookies, ctx.inner(), guild_id).await?;
 | 
			
		||||
 | 
			
		||||
    sqlx::query!(
 | 
			
		||||
        "
 | 
			
		||||
        DELETE FROM todos
 | 
			
		||||
        WHERE guild_id = ?
 | 
			
		||||
            AND id = ?
 | 
			
		||||
        ",
 | 
			
		||||
        guild_id,
 | 
			
		||||
        todo_id,
 | 
			
		||||
    )
 | 
			
		||||
    .execute(transaction.executor())
 | 
			
		||||
    .await
 | 
			
		||||
    .map_err(|e| {
 | 
			
		||||
        warn!("Error deleting todo: {:?}", e);
 | 
			
		||||
        json!({"errors": vec!["Unknown error"]})
 | 
			
		||||
    })?;
 | 
			
		||||
 | 
			
		||||
    Ok(json!({}))
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -388,22 +388,13 @@ pub(crate) async fn create_reminder(
 | 
			
		||||
        _ => {}
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    {
 | 
			
		||||
        // validate channel
 | 
			
		||||
        let channel = ChannelId::new(reminder.channel).to_channel_cached(&ctx.cache);
 | 
			
		||||
        let channel_exists = channel.is_some();
 | 
			
		||||
    if !check_channel_matches_guild(ctx, ChannelId::new(reminder.channel), guild_id) {
 | 
			
		||||
        warn!(
 | 
			
		||||
            "Error in `create_reminder`: channel {} not found for guild {}",
 | 
			
		||||
            reminder.channel, guild_id
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        let channel_matches_guild =
 | 
			
		||||
            channel.map_or(false, |c| c.guild(&ctx.cache).map_or(false, |c| c.id == guild_id));
 | 
			
		||||
 | 
			
		||||
        if !channel_matches_guild || !channel_exists {
 | 
			
		||||
            warn!(
 | 
			
		||||
                "Error in `create_reminder`: channel {} not found for guild {} (channel exists: {})",
 | 
			
		||||
                reminder.channel, guild_id, channel_exists
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            return Err(json!({"error": "Channel not found"}));
 | 
			
		||||
        }
 | 
			
		||||
        return Err(json!({"error": "Channel not found"}));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let channel =
 | 
			
		||||
@@ -601,6 +592,21 @@ pub(crate) async fn create_reminder(
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn check_channel_matches_guild(ctx: &Context, channel_id: ChannelId, guild_id: GuildId) -> bool {
 | 
			
		||||
    // validate channel
 | 
			
		||||
    let channel = channel_id.to_channel_cached(&ctx.cache);
 | 
			
		||||
    let channel_exists = channel.is_some();
 | 
			
		||||
 | 
			
		||||
    if !channel_exists {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let channel_matches_guild =
 | 
			
		||||
        channel.map_or(false, |c| c.guild(&ctx.cache).map_or(false, |g| g.id == guild_id));
 | 
			
		||||
 | 
			
		||||
    channel_matches_guild
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async fn create_database_channel(
 | 
			
		||||
    ctx: impl CacheHttp,
 | 
			
		||||
    channel: ChannelId,
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user