Move postman and web inside src
This commit is contained in:
		
							
								
								
									
										83
									
								
								src/web/routes/dashboard/api/user/guilds.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								src/web/routes/dashboard/api/user/guilds.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,83 @@
 | 
			
		||||
use log::warn;
 | 
			
		||||
use reqwest::Client;
 | 
			
		||||
use rocket::{
 | 
			
		||||
    get,
 | 
			
		||||
    http::CookieJar,
 | 
			
		||||
    serde::json::{json, Value as JsonValue},
 | 
			
		||||
    State,
 | 
			
		||||
};
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
use serenity::model::{id::GuildId, permissions::Permissions};
 | 
			
		||||
 | 
			
		||||
use crate::web::consts::DISCORD_API;
 | 
			
		||||
 | 
			
		||||
#[derive(Serialize)]
 | 
			
		||||
struct GuildInfo {
 | 
			
		||||
    id: String,
 | 
			
		||||
    name: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Deserialize)]
 | 
			
		||||
struct PartialGuild {
 | 
			
		||||
    pub id: GuildId,
 | 
			
		||||
    pub name: String,
 | 
			
		||||
    #[serde(default)]
 | 
			
		||||
    pub owner: bool,
 | 
			
		||||
    #[serde(rename = "permissions_new")]
 | 
			
		||||
    pub permissions: Option<String>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[get("/api/user/guilds")]
 | 
			
		||||
pub async fn get_user_guilds(cookies: &CookieJar<'_>, reqwest_client: &State<Client>) -> JsonValue {
 | 
			
		||||
    offline!(json!(vec![GuildInfo { id: "1".to_string(), name: "Guild".to_string() }]));
 | 
			
		||||
 | 
			
		||||
    if let Some(access_token) = cookies.get_private("access_token") {
 | 
			
		||||
        let request_res = reqwest_client
 | 
			
		||||
            .get(format!("{}/users/@me/guilds", DISCORD_API))
 | 
			
		||||
            .bearer_auth(access_token.value())
 | 
			
		||||
            .send()
 | 
			
		||||
            .await;
 | 
			
		||||
 | 
			
		||||
        match request_res {
 | 
			
		||||
            Ok(response) => {
 | 
			
		||||
                let guilds_res = response.json::<Vec<PartialGuild>>().await;
 | 
			
		||||
 | 
			
		||||
                match guilds_res {
 | 
			
		||||
                    Ok(guilds) => {
 | 
			
		||||
                        let reduced_guilds = guilds
 | 
			
		||||
                            .iter()
 | 
			
		||||
                            .filter(|g| {
 | 
			
		||||
                                g.owner
 | 
			
		||||
                                    || g.permissions.as_ref().map_or(false, |p| {
 | 
			
		||||
                                        let permissions =
 | 
			
		||||
                                            Permissions::from_bits_truncate(p.parse().unwrap());
 | 
			
		||||
 | 
			
		||||
                                        permissions.manage_messages()
 | 
			
		||||
                                            || permissions.manage_guild()
 | 
			
		||||
                                            || permissions.administrator()
 | 
			
		||||
                                    })
 | 
			
		||||
                            })
 | 
			
		||||
                            .map(|g| GuildInfo { id: g.id.to_string(), name: g.name.to_string() })
 | 
			
		||||
                            .collect::<Vec<GuildInfo>>();
 | 
			
		||||
 | 
			
		||||
                        json!(reduced_guilds)
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    Err(e) => {
 | 
			
		||||
                        warn!("Error constructing user from request: {:?}", e);
 | 
			
		||||
 | 
			
		||||
                        json!({"error": "Could not get user details"})
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            Err(e) => {
 | 
			
		||||
                warn!("Error getting user guilds: {:?}", e);
 | 
			
		||||
 | 
			
		||||
                json!({"error": "Could not reach Discord"})
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    } else {
 | 
			
		||||
        json!({"error": "Not authorized"})
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										102
									
								
								src/web/routes/dashboard/api/user/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										102
									
								
								src/web/routes/dashboard/api/user/mod.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,102 @@
 | 
			
		||||
mod guilds;
 | 
			
		||||
mod models;
 | 
			
		||||
mod reminders;
 | 
			
		||||
 | 
			
		||||
use std::env;
 | 
			
		||||
 | 
			
		||||
use chrono_tz::Tz;
 | 
			
		||||
pub use guilds::*;
 | 
			
		||||
pub use reminders::*;
 | 
			
		||||
use rocket::{
 | 
			
		||||
    get,
 | 
			
		||||
    http::CookieJar,
 | 
			
		||||
    patch,
 | 
			
		||||
    serde::json::{json, Json, Value as JsonValue},
 | 
			
		||||
    State,
 | 
			
		||||
};
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
use serenity::{
 | 
			
		||||
    client::Context,
 | 
			
		||||
    model::id::{GuildId, RoleId},
 | 
			
		||||
};
 | 
			
		||||
use sqlx::{MySql, Pool};
 | 
			
		||||
 | 
			
		||||
#[derive(Serialize)]
 | 
			
		||||
struct UserInfo {
 | 
			
		||||
    name: String,
 | 
			
		||||
    patreon: bool,
 | 
			
		||||
    timezone: Option<String>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Deserialize)]
 | 
			
		||||
pub struct UpdateUser {
 | 
			
		||||
    timezone: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[get("/api/user")]
 | 
			
		||||
pub async fn get_user_info(
 | 
			
		||||
    cookies: &CookieJar<'_>,
 | 
			
		||||
    ctx: &State<Context>,
 | 
			
		||||
    pool: &State<Pool<MySql>>,
 | 
			
		||||
) -> JsonValue {
 | 
			
		||||
    offline!(json!(UserInfo { name: "Discord".to_string(), patreon: true, timezone: None }));
 | 
			
		||||
 | 
			
		||||
    if let Some(user_id) =
 | 
			
		||||
        cookies.get_private("userid").map(|u| u.value().parse::<u64>().ok()).flatten()
 | 
			
		||||
    {
 | 
			
		||||
        let member_res = GuildId::new(env::var("PATREON_GUILD_ID").unwrap().parse().unwrap())
 | 
			
		||||
            .member(&ctx.inner(), user_id)
 | 
			
		||||
            .await;
 | 
			
		||||
 | 
			
		||||
        let timezone = sqlx::query!(
 | 
			
		||||
            "SELECT IFNULL(timezone, 'UTC') AS timezone FROM users WHERE user = ?",
 | 
			
		||||
            user_id
 | 
			
		||||
        )
 | 
			
		||||
        .fetch_one(pool.inner())
 | 
			
		||||
        .await
 | 
			
		||||
        .map_or(None, |q| Some(q.timezone));
 | 
			
		||||
 | 
			
		||||
        let user_info = UserInfo {
 | 
			
		||||
            name: cookies
 | 
			
		||||
                .get_private("username")
 | 
			
		||||
                .map_or("Discord User".to_string(), |c| c.value().to_string()),
 | 
			
		||||
            patreon: member_res.map_or(false, |member| {
 | 
			
		||||
                member
 | 
			
		||||
                    .roles
 | 
			
		||||
                    .contains(&RoleId::new(env::var("PATREON_ROLE_ID").unwrap().parse().unwrap()))
 | 
			
		||||
            }),
 | 
			
		||||
            timezone,
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        json!(user_info)
 | 
			
		||||
    } else {
 | 
			
		||||
        json!({"error": "Not authorized"})
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[patch("/api/user", data = "<user>")]
 | 
			
		||||
pub async fn update_user_info(
 | 
			
		||||
    cookies: &CookieJar<'_>,
 | 
			
		||||
    user: Json<UpdateUser>,
 | 
			
		||||
    pool: &State<Pool<MySql>>,
 | 
			
		||||
) -> JsonValue {
 | 
			
		||||
    if let Some(user_id) =
 | 
			
		||||
        cookies.get_private("userid").map(|u| u.value().parse::<u64>().ok()).flatten()
 | 
			
		||||
    {
 | 
			
		||||
        if user.timezone.parse::<Tz>().is_ok() {
 | 
			
		||||
            let _ = sqlx::query!(
 | 
			
		||||
                "UPDATE users SET timezone = ? WHERE user = ?",
 | 
			
		||||
                user.timezone,
 | 
			
		||||
                user_id,
 | 
			
		||||
            )
 | 
			
		||||
            .execute(pool.inner())
 | 
			
		||||
            .await;
 | 
			
		||||
 | 
			
		||||
            json!({})
 | 
			
		||||
        } else {
 | 
			
		||||
            json!({"error": "Timezone not recognized"})
 | 
			
		||||
        }
 | 
			
		||||
    } else {
 | 
			
		||||
        json!({"error": "Not authorized"})
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										231
									
								
								src/web/routes/dashboard/api/user/models.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										231
									
								
								src/web/routes/dashboard/api/user/models.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,231 @@
 | 
			
		||||
use chrono::{naive::NaiveDateTime, Utc};
 | 
			
		||||
use futures::TryFutureExt;
 | 
			
		||||
use log::warn;
 | 
			
		||||
use rocket::serde::json::json;
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
use serenity::{client::Context, model::id::UserId};
 | 
			
		||||
use sqlx::types::Json;
 | 
			
		||||
 | 
			
		||||
use crate::web::{
 | 
			
		||||
    check_subscription,
 | 
			
		||||
    consts::{
 | 
			
		||||
        DAY, MAX_CONTENT_LENGTH, MAX_EMBED_AUTHOR_LENGTH, MAX_EMBED_DESCRIPTION_LENGTH,
 | 
			
		||||
        MAX_EMBED_FIELDS, MAX_EMBED_FIELD_TITLE_LENGTH, MAX_EMBED_FIELD_VALUE_LENGTH,
 | 
			
		||||
        MAX_EMBED_FOOTER_LENGTH, MAX_EMBED_TITLE_LENGTH, MAX_NAME_LENGTH, MAX_URL_LENGTH,
 | 
			
		||||
        MIN_INTERVAL,
 | 
			
		||||
    },
 | 
			
		||||
    guards::transaction::Transaction,
 | 
			
		||||
    routes::{
 | 
			
		||||
        dashboard::{create_database_channel, generate_uid, name_default, Attachment, EmbedField},
 | 
			
		||||
        JsonResult,
 | 
			
		||||
    },
 | 
			
		||||
    Error,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#[derive(Serialize, Deserialize)]
 | 
			
		||||
pub struct Reminder {
 | 
			
		||||
    pub attachment: Option<Attachment>,
 | 
			
		||||
    pub attachment_name: Option<String>,
 | 
			
		||||
    pub content: String,
 | 
			
		||||
    pub embed_author: String,
 | 
			
		||||
    pub embed_author_url: Option<String>,
 | 
			
		||||
    pub embed_color: u32,
 | 
			
		||||
    pub embed_description: String,
 | 
			
		||||
    pub embed_footer: String,
 | 
			
		||||
    pub embed_footer_url: Option<String>,
 | 
			
		||||
    pub embed_image_url: Option<String>,
 | 
			
		||||
    pub embed_thumbnail_url: Option<String>,
 | 
			
		||||
    pub embed_title: String,
 | 
			
		||||
    pub embed_fields: Option<Json<Vec<EmbedField>>>,
 | 
			
		||||
    pub enabled: bool,
 | 
			
		||||
    pub expires: Option<NaiveDateTime>,
 | 
			
		||||
    pub interval_seconds: Option<u32>,
 | 
			
		||||
    pub interval_days: Option<u32>,
 | 
			
		||||
    pub interval_months: Option<u32>,
 | 
			
		||||
    #[serde(default = "name_default")]
 | 
			
		||||
    pub name: String,
 | 
			
		||||
    pub tts: bool,
 | 
			
		||||
    #[serde(default)]
 | 
			
		||||
    pub uid: String,
 | 
			
		||||
    pub utc_time: NaiveDateTime,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub async fn create_reminder(
 | 
			
		||||
    ctx: &Context,
 | 
			
		||||
    transaction: &mut Transaction<'_>,
 | 
			
		||||
    user_id: UserId,
 | 
			
		||||
    reminder: Reminder,
 | 
			
		||||
) -> JsonResult {
 | 
			
		||||
    let channel = user_id
 | 
			
		||||
        .create_dm_channel(&ctx)
 | 
			
		||||
        .map_err(|e| Error::Serenity(e))
 | 
			
		||||
        .and_then(|dm_channel| create_database_channel(&ctx, dm_channel.id, transaction))
 | 
			
		||||
        .await;
 | 
			
		||||
 | 
			
		||||
    if let Err(e) = channel {
 | 
			
		||||
        warn!("`create_database_channel` returned an error code: {:?}", e);
 | 
			
		||||
 | 
			
		||||
        return Err(json!({"error": "Failed to configure channel for reminders."}));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let channel = channel.unwrap();
 | 
			
		||||
 | 
			
		||||
    // validate lengths
 | 
			
		||||
    check_length!(MAX_NAME_LENGTH, reminder.name);
 | 
			
		||||
    check_length!(MAX_CONTENT_LENGTH, reminder.content);
 | 
			
		||||
    check_length!(MAX_EMBED_DESCRIPTION_LENGTH, reminder.embed_description);
 | 
			
		||||
    check_length!(MAX_EMBED_TITLE_LENGTH, reminder.embed_title);
 | 
			
		||||
    check_length!(MAX_EMBED_AUTHOR_LENGTH, reminder.embed_author);
 | 
			
		||||
    check_length!(MAX_EMBED_FOOTER_LENGTH, reminder.embed_footer);
 | 
			
		||||
    check_length_opt!(MAX_EMBED_FIELDS, reminder.embed_fields);
 | 
			
		||||
    if let Some(fields) = &reminder.embed_fields {
 | 
			
		||||
        for field in &fields.0 {
 | 
			
		||||
            check_length!(MAX_EMBED_FIELD_VALUE_LENGTH, field.value);
 | 
			
		||||
            check_length!(MAX_EMBED_FIELD_TITLE_LENGTH, field.title);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    check_length_opt!(
 | 
			
		||||
        MAX_URL_LENGTH,
 | 
			
		||||
        reminder.embed_footer_url,
 | 
			
		||||
        reminder.embed_thumbnail_url,
 | 
			
		||||
        reminder.embed_author_url,
 | 
			
		||||
        reminder.embed_image_url
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    // validate urls
 | 
			
		||||
    check_url_opt!(
 | 
			
		||||
        reminder.embed_footer_url,
 | 
			
		||||
        reminder.embed_thumbnail_url,
 | 
			
		||||
        reminder.embed_author_url,
 | 
			
		||||
        reminder.embed_image_url
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    // validate time and interval
 | 
			
		||||
    if reminder.utc_time < Utc::now().naive_utc() {
 | 
			
		||||
        return Err(json!({"error": "Time must be in the future"}));
 | 
			
		||||
    }
 | 
			
		||||
    if reminder.interval_seconds.is_some()
 | 
			
		||||
        || reminder.interval_days.is_some()
 | 
			
		||||
        || reminder.interval_months.is_some()
 | 
			
		||||
    {
 | 
			
		||||
        if reminder.interval_months.unwrap_or(0) * 30 * DAY as u32
 | 
			
		||||
            + reminder.interval_days.unwrap_or(0) * DAY as u32
 | 
			
		||||
            + reminder.interval_seconds.unwrap_or(0)
 | 
			
		||||
            < *MIN_INTERVAL
 | 
			
		||||
        {
 | 
			
		||||
            return Err(json!({"error": "Interval too short"}));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // check patreon if necessary
 | 
			
		||||
    if reminder.interval_seconds.is_some()
 | 
			
		||||
        || reminder.interval_days.is_some()
 | 
			
		||||
        || reminder.interval_months.is_some()
 | 
			
		||||
    {
 | 
			
		||||
        if !check_subscription(&ctx, user_id).await {
 | 
			
		||||
            return Err(json!({"error": "Patreon is required to set intervals"}));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let name = if reminder.name.is_empty() { name_default() } else { reminder.name.clone() };
 | 
			
		||||
    let new_uid = generate_uid();
 | 
			
		||||
 | 
			
		||||
    // write to db
 | 
			
		||||
    match sqlx::query!(
 | 
			
		||||
        "INSERT INTO reminders (
 | 
			
		||||
         uid,
 | 
			
		||||
         attachment,
 | 
			
		||||
         attachment_name,
 | 
			
		||||
         channel_id,
 | 
			
		||||
         content,
 | 
			
		||||
         embed_author,
 | 
			
		||||
         embed_author_url,
 | 
			
		||||
         embed_color,
 | 
			
		||||
         embed_description,
 | 
			
		||||
         embed_footer,
 | 
			
		||||
         embed_footer_url,
 | 
			
		||||
         embed_image_url,
 | 
			
		||||
         embed_thumbnail_url,
 | 
			
		||||
         embed_title,
 | 
			
		||||
         embed_fields,
 | 
			
		||||
         enabled,
 | 
			
		||||
         expires,
 | 
			
		||||
         interval_seconds,
 | 
			
		||||
         interval_days,
 | 
			
		||||
         interval_months,
 | 
			
		||||
         name,
 | 
			
		||||
         tts,
 | 
			
		||||
         `utc_time`
 | 
			
		||||
        ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
 | 
			
		||||
        new_uid,
 | 
			
		||||
        reminder.attachment,
 | 
			
		||||
        reminder.attachment_name,
 | 
			
		||||
        channel,
 | 
			
		||||
        reminder.content,
 | 
			
		||||
        reminder.embed_author,
 | 
			
		||||
        reminder.embed_author_url,
 | 
			
		||||
        reminder.embed_color,
 | 
			
		||||
        reminder.embed_description,
 | 
			
		||||
        reminder.embed_footer,
 | 
			
		||||
        reminder.embed_footer_url,
 | 
			
		||||
        reminder.embed_image_url,
 | 
			
		||||
        reminder.embed_thumbnail_url,
 | 
			
		||||
        reminder.embed_title,
 | 
			
		||||
        reminder.embed_fields,
 | 
			
		||||
        reminder.enabled,
 | 
			
		||||
        reminder.expires,
 | 
			
		||||
        reminder.interval_seconds,
 | 
			
		||||
        reminder.interval_days,
 | 
			
		||||
        reminder.interval_months,
 | 
			
		||||
        name,
 | 
			
		||||
        reminder.tts,
 | 
			
		||||
        reminder.utc_time,
 | 
			
		||||
    )
 | 
			
		||||
    .execute(transaction.executor())
 | 
			
		||||
    .await
 | 
			
		||||
    {
 | 
			
		||||
        Ok(_) => sqlx::query_as_unchecked!(
 | 
			
		||||
            Reminder,
 | 
			
		||||
            "SELECT
 | 
			
		||||
             reminders.attachment,
 | 
			
		||||
             reminders.attachment_name,
 | 
			
		||||
             reminders.content,
 | 
			
		||||
             reminders.embed_author,
 | 
			
		||||
             reminders.embed_author_url,
 | 
			
		||||
             reminders.embed_color,
 | 
			
		||||
             reminders.embed_description,
 | 
			
		||||
             reminders.embed_footer,
 | 
			
		||||
             reminders.embed_footer_url,
 | 
			
		||||
             reminders.embed_image_url,
 | 
			
		||||
             reminders.embed_thumbnail_url,
 | 
			
		||||
             reminders.embed_title,
 | 
			
		||||
             reminders.embed_fields,
 | 
			
		||||
             reminders.enabled,
 | 
			
		||||
             reminders.expires,
 | 
			
		||||
             reminders.interval_seconds,
 | 
			
		||||
             reminders.interval_days,
 | 
			
		||||
             reminders.interval_months,
 | 
			
		||||
             reminders.name,
 | 
			
		||||
             reminders.tts,
 | 
			
		||||
             reminders.uid,
 | 
			
		||||
             reminders.utc_time
 | 
			
		||||
            FROM reminders
 | 
			
		||||
            WHERE uid = ?",
 | 
			
		||||
            new_uid
 | 
			
		||||
        )
 | 
			
		||||
        .fetch_one(transaction.executor())
 | 
			
		||||
        .await
 | 
			
		||||
        .map(|r| Ok(json!(r)))
 | 
			
		||||
        .unwrap_or_else(|e| {
 | 
			
		||||
            warn!("Failed to complete SQL query: {:?}", e);
 | 
			
		||||
 | 
			
		||||
            Err(json!({"error": "Could not load reminder"}))
 | 
			
		||||
        }),
 | 
			
		||||
 | 
			
		||||
        Err(e) => {
 | 
			
		||||
            warn!("Error in `create_reminder`: Could not execute query: {:?}", e);
 | 
			
		||||
 | 
			
		||||
            Err(json!({"error": "Unknown error"}))
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										284
									
								
								src/web/routes/dashboard/api/user/reminders.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										284
									
								
								src/web/routes/dashboard/api/user/reminders.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,284 @@
 | 
			
		||||
use log::warn;
 | 
			
		||||
use rocket::{
 | 
			
		||||
    get,
 | 
			
		||||
    http::CookieJar,
 | 
			
		||||
    patch, post,
 | 
			
		||||
    serde::json::{json, Json},
 | 
			
		||||
    State,
 | 
			
		||||
};
 | 
			
		||||
use serenity::{client::Context, model::id::UserId};
 | 
			
		||||
use sqlx::{MySql, Pool};
 | 
			
		||||
 | 
			
		||||
use crate::web::{
 | 
			
		||||
    check_subscription,
 | 
			
		||||
    guards::transaction::Transaction,
 | 
			
		||||
    routes::{
 | 
			
		||||
        dashboard::{
 | 
			
		||||
            api::user::models::{create_reminder, Reminder},
 | 
			
		||||
            PatchReminder, MIN_INTERVAL,
 | 
			
		||||
        },
 | 
			
		||||
        JsonResult,
 | 
			
		||||
    },
 | 
			
		||||
    Database,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#[post("/api/user/reminders", data = "<reminder>")]
 | 
			
		||||
pub async fn create_user_reminder(
 | 
			
		||||
    reminder: Json<Reminder>,
 | 
			
		||||
    cookies: &CookieJar<'_>,
 | 
			
		||||
    ctx: &State<Context>,
 | 
			
		||||
    mut transaction: Transaction<'_>,
 | 
			
		||||
) -> JsonResult {
 | 
			
		||||
    let user_id =
 | 
			
		||||
        cookies.get_private("userid").map(|c| c.value().parse::<u64>().ok()).flatten().unwrap();
 | 
			
		||||
 | 
			
		||||
    match create_reminder(
 | 
			
		||||
        ctx.inner(),
 | 
			
		||||
        &mut transaction,
 | 
			
		||||
        UserId::new(user_id),
 | 
			
		||||
        reminder.into_inner(),
 | 
			
		||||
    )
 | 
			
		||||
    .await
 | 
			
		||||
    {
 | 
			
		||||
        Ok(r) => match transaction.commit().await {
 | 
			
		||||
            Ok(_) => Ok(r),
 | 
			
		||||
            Err(e) => {
 | 
			
		||||
                warn!("Couldn't commit transaction: {:?}", e);
 | 
			
		||||
                json_err!("Couldn't commit transaction.")
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        Err(e) => Err(e),
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[get("/api/user/reminders")]
 | 
			
		||||
pub async fn get_reminders(
 | 
			
		||||
    cookies: &CookieJar<'_>,
 | 
			
		||||
    ctx: &State<Context>,
 | 
			
		||||
    pool: &State<Pool<MySql>>,
 | 
			
		||||
) -> JsonResult {
 | 
			
		||||
    let user_id =
 | 
			
		||||
        cookies.get_private("userid").map(|c| c.value().parse::<u64>().ok()).flatten().unwrap();
 | 
			
		||||
    let channel = UserId::new(user_id).create_dm_channel(ctx.inner()).await;
 | 
			
		||||
 | 
			
		||||
    match channel {
 | 
			
		||||
        Ok(channel) => sqlx::query_as_unchecked!(
 | 
			
		||||
            Reminder,
 | 
			
		||||
            "
 | 
			
		||||
            SELECT
 | 
			
		||||
                reminders.attachment,
 | 
			
		||||
                reminders.attachment_name,
 | 
			
		||||
                reminders.content,
 | 
			
		||||
                reminders.embed_author,
 | 
			
		||||
                reminders.embed_author_url,
 | 
			
		||||
                reminders.embed_color,
 | 
			
		||||
                reminders.embed_description,
 | 
			
		||||
                reminders.embed_footer,
 | 
			
		||||
                reminders.embed_footer_url,
 | 
			
		||||
                reminders.embed_image_url,
 | 
			
		||||
                reminders.embed_thumbnail_url,
 | 
			
		||||
                reminders.embed_title,
 | 
			
		||||
                IFNULL(reminders.embed_fields, '[]') AS embed_fields,
 | 
			
		||||
                reminders.enabled,
 | 
			
		||||
                reminders.expires,
 | 
			
		||||
                reminders.interval_seconds,
 | 
			
		||||
                reminders.interval_days,
 | 
			
		||||
                reminders.interval_months,
 | 
			
		||||
                reminders.name,
 | 
			
		||||
                reminders.tts,
 | 
			
		||||
                reminders.uid,
 | 
			
		||||
                reminders.utc_time
 | 
			
		||||
            FROM reminders
 | 
			
		||||
            INNER JOIN channels ON channels.id = reminders.channel_id
 | 
			
		||||
            WHERE `status` = 'pending' AND channels.channel = ?
 | 
			
		||||
            ",
 | 
			
		||||
            channel.id.get()
 | 
			
		||||
        )
 | 
			
		||||
        .fetch_all(pool.inner())
 | 
			
		||||
        .await
 | 
			
		||||
        .map(|r| Ok(json!(r)))
 | 
			
		||||
        .unwrap_or_else(|e| {
 | 
			
		||||
            warn!("Failed to complete SQL query: {:?}", e);
 | 
			
		||||
 | 
			
		||||
            json_err!("Could not load reminders")
 | 
			
		||||
        }),
 | 
			
		||||
 | 
			
		||||
        Err(e) => {
 | 
			
		||||
            warn!("Couldn't get DM channel: {:?}", e);
 | 
			
		||||
 | 
			
		||||
            json_err!("Could not find a DM channel")
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[patch("/api/user/reminders", data = "<reminder>")]
 | 
			
		||||
pub async fn edit_reminder(
 | 
			
		||||
    reminder: Json<PatchReminder>,
 | 
			
		||||
    ctx: &State<Context>,
 | 
			
		||||
    mut transaction: Transaction<'_>,
 | 
			
		||||
    pool: &State<Pool<Database>>,
 | 
			
		||||
    cookies: &CookieJar<'_>,
 | 
			
		||||
) -> JsonResult {
 | 
			
		||||
    let user_id_cookie =
 | 
			
		||||
        cookies.get_private("userid").map(|c| c.value().parse::<u64>().ok()).flatten();
 | 
			
		||||
 | 
			
		||||
    if user_id_cookie.is_none() {
 | 
			
		||||
        return Err(json!({"error": "User not authorized"}));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let mut error = vec![];
 | 
			
		||||
    let user_id = user_id_cookie.unwrap();
 | 
			
		||||
 | 
			
		||||
    if reminder.message_ok() {
 | 
			
		||||
        update_field!(transaction.executor(), error, reminder.[
 | 
			
		||||
            content,
 | 
			
		||||
            embed_author,
 | 
			
		||||
            embed_description,
 | 
			
		||||
            embed_footer,
 | 
			
		||||
            embed_title,
 | 
			
		||||
            embed_fields
 | 
			
		||||
        ]);
 | 
			
		||||
    } else {
 | 
			
		||||
        error.push("Message exceeds limits.".to_string());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    update_field!(transaction.executor(), error, reminder.[
 | 
			
		||||
        attachment,
 | 
			
		||||
        attachment_name,
 | 
			
		||||
        embed_author_url,
 | 
			
		||||
        embed_color,
 | 
			
		||||
        embed_footer_url,
 | 
			
		||||
        embed_image_url,
 | 
			
		||||
        embed_thumbnail_url,
 | 
			
		||||
        enabled,
 | 
			
		||||
        expires,
 | 
			
		||||
        name,
 | 
			
		||||
        tts,
 | 
			
		||||
        utc_time
 | 
			
		||||
    ]);
 | 
			
		||||
 | 
			
		||||
    if reminder.interval_days.flatten().is_some()
 | 
			
		||||
        || reminder.interval_months.flatten().is_some()
 | 
			
		||||
        || reminder.interval_seconds.flatten().is_some()
 | 
			
		||||
    {
 | 
			
		||||
        if check_subscription(&ctx.inner(), user_id).await {
 | 
			
		||||
            let new_interval_length = match reminder.interval_days {
 | 
			
		||||
                Some(interval) => interval.unwrap_or(0),
 | 
			
		||||
                None => sqlx::query!(
 | 
			
		||||
                    "SELECT interval_days AS days FROM reminders WHERE uid = ?",
 | 
			
		||||
                    reminder.uid
 | 
			
		||||
                )
 | 
			
		||||
                .fetch_one(transaction.executor())
 | 
			
		||||
                .await
 | 
			
		||||
                .map_err(|e| {
 | 
			
		||||
                    warn!("Error updating reminder interval: {:?}", e);
 | 
			
		||||
                    json!({ "reminder": Option::<Reminder>::None, "errors": vec!["Unknown error"] })
 | 
			
		||||
                })?
 | 
			
		||||
                .days
 | 
			
		||||
                .unwrap_or(0),
 | 
			
		||||
            } * 86400 + match reminder.interval_months {
 | 
			
		||||
                Some(interval) => interval.unwrap_or(0),
 | 
			
		||||
                None => sqlx::query!(
 | 
			
		||||
                    "SELECT interval_months AS months FROM reminders WHERE uid = ?",
 | 
			
		||||
                    reminder.uid
 | 
			
		||||
                )
 | 
			
		||||
                .fetch_one(transaction.executor())
 | 
			
		||||
                .await
 | 
			
		||||
                .map_err(|e| {
 | 
			
		||||
                    warn!("Error updating reminder interval: {:?}", e);
 | 
			
		||||
                    json!({ "reminder": Option::<Reminder>::None, "errors": vec!["Unknown error"] })
 | 
			
		||||
                })?
 | 
			
		||||
                .months
 | 
			
		||||
                .unwrap_or(0),
 | 
			
		||||
            } * 2592000 + match reminder.interval_seconds {
 | 
			
		||||
                Some(interval) => interval.unwrap_or(0),
 | 
			
		||||
                None => sqlx::query!(
 | 
			
		||||
                    "SELECT interval_seconds AS seconds FROM reminders WHERE uid = ?",
 | 
			
		||||
                    reminder.uid
 | 
			
		||||
                )
 | 
			
		||||
                .fetch_one(transaction.executor())
 | 
			
		||||
                .await
 | 
			
		||||
                .map_err(|e| {
 | 
			
		||||
                    warn!("Error updating reminder interval: {:?}", e);
 | 
			
		||||
                    json!({ "reminder": Option::<Reminder>::None, "errors": vec!["Unknown error"] })
 | 
			
		||||
                })?
 | 
			
		||||
                .seconds
 | 
			
		||||
                .unwrap_or(0),
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            if new_interval_length < *MIN_INTERVAL {
 | 
			
		||||
                error.push(String::from("New interval is too short."));
 | 
			
		||||
            } else {
 | 
			
		||||
                update_field!(transaction.executor(), error, reminder.[
 | 
			
		||||
                    interval_days,
 | 
			
		||||
                    interval_months,
 | 
			
		||||
                    interval_seconds
 | 
			
		||||
                ]);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    } else {
 | 
			
		||||
        sqlx::query!(
 | 
			
		||||
            "
 | 
			
		||||
            UPDATE reminders
 | 
			
		||||
            SET interval_seconds = NULL, interval_days = NULL, interval_months = NULL
 | 
			
		||||
            WHERE uid = ?
 | 
			
		||||
            ",
 | 
			
		||||
            reminder.uid
 | 
			
		||||
        )
 | 
			
		||||
        .execute(transaction.executor())
 | 
			
		||||
        .await
 | 
			
		||||
        .map_err(|e| {
 | 
			
		||||
            warn!("Error updating reminder interval: {:?}", e);
 | 
			
		||||
            json!({ "reminder": Option::<Reminder>::None, "errors": vec!["Unknown error"] })
 | 
			
		||||
        })?;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if let Err(e) = transaction.commit().await {
 | 
			
		||||
        warn!("Couldn't commit transaction: {:?}", e);
 | 
			
		||||
        return json_err!("Couldn't commit transaction");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    match sqlx::query_as_unchecked!(
 | 
			
		||||
        Reminder,
 | 
			
		||||
        "
 | 
			
		||||
        SELECT reminders.attachment,
 | 
			
		||||
         reminders.attachment_name,
 | 
			
		||||
         reminders.content,
 | 
			
		||||
         reminders.embed_author,
 | 
			
		||||
         reminders.embed_author_url,
 | 
			
		||||
         reminders.embed_color,
 | 
			
		||||
         reminders.embed_description,
 | 
			
		||||
         reminders.embed_footer,
 | 
			
		||||
         reminders.embed_footer_url,
 | 
			
		||||
         reminders.embed_image_url,
 | 
			
		||||
         reminders.embed_thumbnail_url,
 | 
			
		||||
         reminders.embed_title,
 | 
			
		||||
         reminders.embed_fields,
 | 
			
		||||
         reminders.enabled,
 | 
			
		||||
         reminders.expires,
 | 
			
		||||
         reminders.interval_seconds,
 | 
			
		||||
         reminders.interval_days,
 | 
			
		||||
         reminders.interval_months,
 | 
			
		||||
         reminders.name,
 | 
			
		||||
         reminders.tts,
 | 
			
		||||
         reminders.uid,
 | 
			
		||||
         reminders.utc_time
 | 
			
		||||
        FROM reminders
 | 
			
		||||
        LEFT JOIN channels ON channels.id = reminders.channel_id
 | 
			
		||||
        WHERE uid = ?
 | 
			
		||||
        ",
 | 
			
		||||
        reminder.uid
 | 
			
		||||
    )
 | 
			
		||||
    .fetch_one(pool.inner())
 | 
			
		||||
    .await
 | 
			
		||||
    {
 | 
			
		||||
        Ok(reminder) => Ok(json!({"reminder": reminder, "errors": error})),
 | 
			
		||||
 | 
			
		||||
        Err(e) => {
 | 
			
		||||
            warn!("Error exiting `edit_reminder': {:?}", e);
 | 
			
		||||
 | 
			
		||||
            Err(json!({"reminder": Option::<Reminder>::None, "errors": vec!["Unknown error"]}))
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user