upload attachments
This commit is contained in:
		
							
								
								
									
										1
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							@@ -2142,6 +2142,7 @@ dependencies = [
 | 
			
		||||
name = "reminder_web"
 | 
			
		||||
version = "0.1.0"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "base64",
 | 
			
		||||
 "chrono",
 | 
			
		||||
 "chrono-tz 0.5.3",
 | 
			
		||||
 "lazy_static",
 | 
			
		||||
 
 | 
			
		||||
@@ -17,3 +17,4 @@ chrono = "0.4"
 | 
			
		||||
chrono-tz = "0.5"
 | 
			
		||||
lazy_static = "1.4.0"
 | 
			
		||||
rand = "0.7"
 | 
			
		||||
base64 = "0.13"
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,4 @@
 | 
			
		||||
use base64;
 | 
			
		||||
use chrono::Utc;
 | 
			
		||||
use rocket::{
 | 
			
		||||
    http::CookieJar,
 | 
			
		||||
@@ -7,7 +8,10 @@ use rocket::{
 | 
			
		||||
use serde::Serialize;
 | 
			
		||||
use serenity::{
 | 
			
		||||
    client::Context,
 | 
			
		||||
    model::id::{ChannelId, GuildId},
 | 
			
		||||
    model::{
 | 
			
		||||
        channel::GuildChannel,
 | 
			
		||||
        id::{ChannelId, GuildId},
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
use sqlx::{MySql, Pool};
 | 
			
		||||
 | 
			
		||||
@@ -19,8 +23,8 @@ use crate::{
 | 
			
		||||
        MAX_URL_LENGTH, MAX_USERNAME_LENGTH, MIN_INTERVAL,
 | 
			
		||||
    },
 | 
			
		||||
    routes::dashboard::{
 | 
			
		||||
        create_database_channel, generate_uid, name_default, DeleteReminder, PatchReminder,
 | 
			
		||||
        Reminder,
 | 
			
		||||
        create_database_channel, generate_uid, name_default, DeleteReminder, JsonReminder,
 | 
			
		||||
        PatchReminder, Reminder,
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@@ -45,12 +49,16 @@ pub async fn get_guild_channels(
 | 
			
		||||
        Some(guild) => {
 | 
			
		||||
            let mut channel_info = vec![];
 | 
			
		||||
 | 
			
		||||
            for (channel_id, channel) in guild
 | 
			
		||||
            let mut channels = guild
 | 
			
		||||
                .channels
 | 
			
		||||
                .iter()
 | 
			
		||||
                .filter_map(|(id, channel)| channel.to_owned().guild().map(|c| (id, c)))
 | 
			
		||||
                .filter_map(|(id, channel)| channel.to_owned().guild().map(|c| (id.to_owned(), c)))
 | 
			
		||||
                .filter(|(_, channel)| channel.is_text_based())
 | 
			
		||||
            {
 | 
			
		||||
                .collect::<Vec<(ChannelId, GuildChannel)>>();
 | 
			
		||||
 | 
			
		||||
            channels.sort_by(|(_, c1), (_, c2)| c1.position.cmp(&c2.position));
 | 
			
		||||
 | 
			
		||||
            for (channel_id, channel) in channels {
 | 
			
		||||
                let mut ch = ChannelInfo {
 | 
			
		||||
                    name: channel.name.to_string(),
 | 
			
		||||
                    id: channel_id.to_string(),
 | 
			
		||||
@@ -125,7 +133,7 @@ pub async fn get_guild_roles(id: u64, cookies: &CookieJar<'_>, ctx: &State<Conte
 | 
			
		||||
#[post("/api/guild/<id>/reminders", data = "<reminder>")]
 | 
			
		||||
pub async fn create_reminder(
 | 
			
		||||
    id: u64,
 | 
			
		||||
    reminder: Json<Reminder>,
 | 
			
		||||
    reminder: Json<JsonReminder>,
 | 
			
		||||
    cookies: &CookieJar<'_>,
 | 
			
		||||
    serenity_context: &State<Context>,
 | 
			
		||||
    pool: &State<Pool<MySql>>,
 | 
			
		||||
@@ -213,12 +221,16 @@ pub async fn create_reminder(
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // base64 decode error dropped here
 | 
			
		||||
    let attachment_data = reminder.attachment.as_ref().map(|s| base64::decode(s).ok()).flatten();
 | 
			
		||||
    let name = if reminder.name.is_empty() { name_default() } else { reminder.name.clone() };
 | 
			
		||||
 | 
			
		||||
    // write to db
 | 
			
		||||
    match sqlx::query!(
 | 
			
		||||
        "INSERT INTO reminders (
 | 
			
		||||
         uid,
 | 
			
		||||
         attachment,
 | 
			
		||||
         attachment_name,
 | 
			
		||||
         channel_id,
 | 
			
		||||
         avatar,
 | 
			
		||||
         content,
 | 
			
		||||
@@ -241,8 +253,10 @@ pub async fn create_reminder(
 | 
			
		||||
         tts,
 | 
			
		||||
         username,
 | 
			
		||||
         `utc_time`
 | 
			
		||||
        ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
 | 
			
		||||
        ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
 | 
			
		||||
        generate_uid(),
 | 
			
		||||
        attachment_data,
 | 
			
		||||
        reminder.attachment_name,
 | 
			
		||||
        channel,
 | 
			
		||||
        reminder.avatar,
 | 
			
		||||
        reminder.content,
 | 
			
		||||
 
 | 
			
		||||
@@ -26,6 +26,45 @@ fn channel_default() -> u64 {
 | 
			
		||||
    0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Serialize, Deserialize)]
 | 
			
		||||
pub struct EmbedField {
 | 
			
		||||
    title: String,
 | 
			
		||||
    value: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Serialize, Deserialize)]
 | 
			
		||||
pub struct JsonReminder {
 | 
			
		||||
    attachment: Option<String>,
 | 
			
		||||
    attachment_name: Option<String>,
 | 
			
		||||
    avatar: Option<String>,
 | 
			
		||||
    #[serde(with = "string")]
 | 
			
		||||
    channel: u64,
 | 
			
		||||
    content: String,
 | 
			
		||||
    embed_author: String,
 | 
			
		||||
    embed_author_url: Option<String>,
 | 
			
		||||
    embed_color: u32,
 | 
			
		||||
    embed_description: String,
 | 
			
		||||
    embed_footer: String,
 | 
			
		||||
    embed_footer_url: Option<String>,
 | 
			
		||||
    embed_image_url: Option<String>,
 | 
			
		||||
    embed_thumbnail_url: Option<String>,
 | 
			
		||||
    embed_title: String,
 | 
			
		||||
    embed_fields: Vec<EmbedField>,
 | 
			
		||||
    enabled: bool,
 | 
			
		||||
    expires: Option<NaiveDateTime>,
 | 
			
		||||
    interval_seconds: Option<u32>,
 | 
			
		||||
    interval_months: Option<u32>,
 | 
			
		||||
    #[serde(default = "name_default")]
 | 
			
		||||
    name: String,
 | 
			
		||||
    pin: bool,
 | 
			
		||||
    restartable: bool,
 | 
			
		||||
    tts: bool,
 | 
			
		||||
    #[serde(default)]
 | 
			
		||||
    uid: String,
 | 
			
		||||
    username: Option<String>,
 | 
			
		||||
    utc_time: NaiveDateTime,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Serialize, Deserialize)]
 | 
			
		||||
pub struct Reminder {
 | 
			
		||||
    attachment: Option<Vec<u8>>,
 | 
			
		||||
@@ -62,7 +101,7 @@ pub struct Reminder {
 | 
			
		||||
pub struct PatchReminder {
 | 
			
		||||
    uid: String,
 | 
			
		||||
    #[serde(default)]
 | 
			
		||||
    attachment: Unset<Option<Vec<u8>>>,
 | 
			
		||||
    attachment: Unset<Option<String>>,
 | 
			
		||||
    #[serde(default)]
 | 
			
		||||
    attachment_name: Unset<Option<String>>,
 | 
			
		||||
    #[serde(default)]
 | 
			
		||||
@@ -91,6 +130,8 @@ pub struct PatchReminder {
 | 
			
		||||
    #[serde(default)]
 | 
			
		||||
    embed_title: Unset<String>,
 | 
			
		||||
    #[serde(default)]
 | 
			
		||||
    embed_fields: Unset<EmbedField>,
 | 
			
		||||
    #[serde(default)]
 | 
			
		||||
    enabled: Unset<bool>,
 | 
			
		||||
    #[serde(default)]
 | 
			
		||||
    expires: Unset<Option<NaiveDateTime>>,
 | 
			
		||||
 
 | 
			
		||||
@@ -262,6 +262,13 @@ document.addEventListener("remindersLoaded", (event) => {
 | 
			
		||||
                node.querySelector('input[name="time"]').value
 | 
			
		||||
            ).setZone("UTC");
 | 
			
		||||
 | 
			
		||||
            let fields = node.querySelectorAll(".embed-field-box", (el) => {
 | 
			
		||||
                return {
 | 
			
		||||
                    title: el.querySelector('input[name="embed_field_title[]"]').value,
 | 
			
		||||
                    value: el.querySelector('input[name="embed_field_value[]"]').value,
 | 
			
		||||
                };
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            let reminder = {
 | 
			
		||||
                uid: node.closest(".reminderContent").dataset["uid"],
 | 
			
		||||
                avatar: has_source(node.querySelector("img.discord-avatar").src),
 | 
			
		||||
@@ -286,6 +293,7 @@ document.addEventListener("remindersLoaded", (event) => {
 | 
			
		||||
                    node.querySelector("img.embed_thumbnail_url").src
 | 
			
		||||
                ),
 | 
			
		||||
                embed_title: node.querySelector('textarea[name="embed_title"]').value,
 | 
			
		||||
                embed_fields: fields,
 | 
			
		||||
                expires: null,
 | 
			
		||||
                interval_seconds: seconds,
 | 
			
		||||
                interval_months: months,
 | 
			
		||||
@@ -493,14 +501,18 @@ function has_source(string) {
 | 
			
		||||
 | 
			
		||||
let $createReminder = document.querySelector("#reminderCreator");
 | 
			
		||||
 | 
			
		||||
$createReminder.querySelector("button#createReminder").addEventListener("click", () => {
 | 
			
		||||
$createReminder
 | 
			
		||||
    .querySelector("button#createReminder")
 | 
			
		||||
    .addEventListener("click", async () => {
 | 
			
		||||
        // create reminder object
 | 
			
		||||
        let seconds =
 | 
			
		||||
        parseInt($createReminder.querySelector('input[name="interval_seconds"]').value) ||
 | 
			
		||||
        null;
 | 
			
		||||
            parseInt(
 | 
			
		||||
                $createReminder.querySelector('input[name="interval_seconds"]').value
 | 
			
		||||
            ) || null;
 | 
			
		||||
        let months =
 | 
			
		||||
        parseInt($createReminder.querySelector('input[name="interval_months"]').value) ||
 | 
			
		||||
        null;
 | 
			
		||||
            parseInt(
 | 
			
		||||
                $createReminder.querySelector('input[name="interval_months"]').value
 | 
			
		||||
            ) || null;
 | 
			
		||||
 | 
			
		||||
        let rgb_color = window.getComputedStyle(
 | 
			
		||||
            $createReminder.querySelector("div.discord-embed")
 | 
			
		||||
@@ -512,7 +524,24 @@ $createReminder.querySelector("button#createReminder").addEventListener("click",
 | 
			
		||||
            $createReminder.querySelector('input[name="time"]').value
 | 
			
		||||
        ).setZone("UTC");
 | 
			
		||||
 | 
			
		||||
        let attachment = null;
 | 
			
		||||
        let attachment_name = null;
 | 
			
		||||
 | 
			
		||||
        if ($createReminder.querySelector('input[name="attachment"]').files.length > 0) {
 | 
			
		||||
            let file = $createReminder.querySelector('input[name="attachment"]').files[0];
 | 
			
		||||
 | 
			
		||||
            attachment = await new Promise((resolve) => {
 | 
			
		||||
                let fileReader = new FileReader();
 | 
			
		||||
                fileReader.onload = (e) => resolve(fileReader.result);
 | 
			
		||||
                fileReader.readAsDataURL(file);
 | 
			
		||||
            });
 | 
			
		||||
            attachment = attachment.split(",")[1];
 | 
			
		||||
            attachment_name = file.name;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let reminder = {
 | 
			
		||||
            attachment: attachment,
 | 
			
		||||
            attachment_name: attachment_name,
 | 
			
		||||
            avatar: has_source($createReminder.querySelector("img.discord-avatar").src),
 | 
			
		||||
            channel: $createReminder.querySelector("select.channel-selector").value,
 | 
			
		||||
            content: $createReminder.querySelector("textarea#messageContent").value,
 | 
			
		||||
@@ -534,6 +563,7 @@ $createReminder.querySelector("button#createReminder").addEventListener("click",
 | 
			
		||||
                $createReminder.querySelector("img.embed_thumbnail_url").src
 | 
			
		||||
            ),
 | 
			
		||||
            embed_title: $createReminder.querySelector("textarea#embedTitle").value,
 | 
			
		||||
            embed_fields: [],
 | 
			
		||||
            enabled: true,
 | 
			
		||||
            expires: null,
 | 
			
		||||
            interval_seconds: seconds,
 | 
			
		||||
 
 | 
			
		||||
@@ -178,7 +178,7 @@
 | 
			
		||||
                <div class="column has-text-centered">
 | 
			
		||||
                    <div class="file is-boxed">
 | 
			
		||||
                        <label class="file-label">
 | 
			
		||||
                            <input class="file-input" type="file">
 | 
			
		||||
                            <input class="file-input" type="file" name="attachment">
 | 
			
		||||
                            <span class="file-cta">
 | 
			
		||||
                                <span class="file-label">
 | 
			
		||||
                                    Add Attachment
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user