Compare commits
	
		
			1 Commits
		
	
	
		
			jellywx/fi
			...
			postgres
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					2d1668a63a | 
							
								
								
									
										709
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										709
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -1,8 +1,9 @@
 | 
				
			|||||||
[package]
 | 
					[package]
 | 
				
			||||||
name = "reminder_rs"
 | 
					name = "reminder_rs"
 | 
				
			||||||
version = "1.6.3"
 | 
					version = "1.6.0"
 | 
				
			||||||
authors = ["jellywx <judesouthworth@pm.me>"]
 | 
					authors = ["jellywx <judesouthworth@pm.me>"]
 | 
				
			||||||
edition = "2018"
 | 
					edition = "2018"
 | 
				
			||||||
 | 
					workspaces = [".", "postman", "web", "entity", "migration"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[dependencies]
 | 
					[dependencies]
 | 
				
			||||||
poise = "0.2"
 | 
					poise = "0.2"
 | 
				
			||||||
@@ -22,8 +23,7 @@ serde_repr = "0.1"
 | 
				
			|||||||
rmp-serde = "0.15"
 | 
					rmp-serde = "0.15"
 | 
				
			||||||
rand = "0.7"
 | 
					rand = "0.7"
 | 
				
			||||||
levenshtein = "1.0"
 | 
					levenshtein = "1.0"
 | 
				
			||||||
sqlx = { version = "0.5", features = ["runtime-tokio-rustls", "macros", "mysql", "bigdecimal", "chrono"]}
 | 
					base64 = "0.13.0"
 | 
				
			||||||
base64 = "0.13"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
[dependencies.postman]
 | 
					[dependencies.postman]
 | 
				
			||||||
path = "postman"
 | 
					path = "postman"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,233 +0,0 @@
 | 
				
			|||||||
CREATE DATABASE IF NOT EXISTS reminders;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
SET FOREIGN_KEY_CHECKS=0;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
USE reminders;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
CREATE TABLE reminders.guilds (
 | 
					 | 
				
			||||||
    id INT UNSIGNED UNIQUE NOT NULL AUTO_INCREMENT,
 | 
					 | 
				
			||||||
    guild BIGINT UNSIGNED UNIQUE NOT NULL,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    name VARCHAR(100),
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    prefix VARCHAR(5) DEFAULT '$' NOT NULL,
 | 
					 | 
				
			||||||
    timezone VARCHAR(32) DEFAULT 'UTC' NOT NULL,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    default_channel_id INT UNSIGNED,
 | 
					 | 
				
			||||||
    default_username VARCHAR(32) DEFAULT 'Reminder' NOT NULL,
 | 
					 | 
				
			||||||
    default_avatar VARCHAR(512) DEFAULT 'https://raw.githubusercontent.com/reminder-bot/logos/master/Remind_Me_Bot_Logo_PPic.jpg' NOT NULL,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    PRIMARY KEY (id),
 | 
					 | 
				
			||||||
    FOREIGN KEY (default_channel_id) REFERENCES reminders.channels(id) ON DELETE SET NULL
 | 
					 | 
				
			||||||
);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
CREATE TABLE reminders.channels (
 | 
					 | 
				
			||||||
    id INT UNSIGNED UNIQUE NOT NULL AUTO_INCREMENT,
 | 
					 | 
				
			||||||
    channel BIGINT UNSIGNED UNIQUE NOT NULL,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    name VARCHAR(100),
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    nudge SMALLINT NOT NULL DEFAULT 0,
 | 
					 | 
				
			||||||
    blacklisted BOOL NOT NULL DEFAULT FALSE,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    webhook_id BIGINT UNSIGNED UNIQUE,
 | 
					 | 
				
			||||||
    webhook_token TEXT,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    paused BOOL NOT NULL DEFAULT 0,
 | 
					 | 
				
			||||||
    paused_until TIMESTAMP,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    guild_id INT UNSIGNED,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    PRIMARY KEY (id),
 | 
					 | 
				
			||||||
    FOREIGN KEY (guild_id) REFERENCES reminders.guilds(id) ON DELETE CASCADE
 | 
					 | 
				
			||||||
);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
CREATE TABLE reminders.users (
 | 
					 | 
				
			||||||
    id INT UNSIGNED AUTO_INCREMENT UNIQUE NOT NULL,
 | 
					 | 
				
			||||||
    user BIGINT UNSIGNED UNIQUE NOT NULL,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    name VARCHAR(37) NOT NULL,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    dm_channel INT UNSIGNED UNIQUE NOT NULL,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    language VARCHAR(2) DEFAULT 'EN' NOT NULL,
 | 
					 | 
				
			||||||
    timezone VARCHAR(32) DEFAULT 'UTC' NOT NULL,
 | 
					 | 
				
			||||||
    meridian_time BOOLEAN DEFAULT 0 NOT NULL,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    allowed_dm BOOLEAN DEFAULT 1 NOT NULL,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    patreon BOOLEAN NOT NULL DEFAULT 0,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    PRIMARY KEY (id),
 | 
					 | 
				
			||||||
    FOREIGN KEY (dm_channel) REFERENCES reminders.channels(id) ON DELETE RESTRICT
 | 
					 | 
				
			||||||
);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
CREATE TABLE reminders.roles (
 | 
					 | 
				
			||||||
    id INT UNSIGNED UNIQUE NOT NULL AUTO_INCREMENT,
 | 
					 | 
				
			||||||
    role BIGINT UNSIGNED UNIQUE NOT NULL,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    name VARCHAR(100),
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    guild_id INT UNSIGNED NOT NULL,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    PRIMARY KEY (id),
 | 
					 | 
				
			||||||
    FOREIGN KEY (guild_id) REFERENCES reminders.guilds(id) ON DELETE CASCADE
 | 
					 | 
				
			||||||
);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
CREATE TABLE reminders.embeds (
 | 
					 | 
				
			||||||
    id INT UNSIGNED AUTO_INCREMENT UNIQUE NOT NULL,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    title VARCHAR(256) NOT NULL DEFAULT '',
 | 
					 | 
				
			||||||
    description VARCHAR(2048) NOT NULL DEFAULT '',
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    image_url VARCHAR(512),
 | 
					 | 
				
			||||||
    thumbnail_url VARCHAR(512),
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    footer VARCHAR(2048) NOT NULL DEFAULT '',
 | 
					 | 
				
			||||||
    footer_icon VARCHAR(512),
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    color MEDIUMINT UNSIGNED NOT NULL DEFAULT 0x0,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    PRIMARY KEY (id)
 | 
					 | 
				
			||||||
);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
CREATE TABLE reminders.embed_fields (
 | 
					 | 
				
			||||||
    id INT UNSIGNED AUTO_INCREMENT UNIQUE NOT NULL,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    title VARCHAR(256) NOT NULL DEFAULT '',
 | 
					 | 
				
			||||||
    value VARCHAR(1024) NOT NULL DEFAULT '',
 | 
					 | 
				
			||||||
    inline BOOL NOT NULL DEFAULT 0,
 | 
					 | 
				
			||||||
    embed_id INT UNSIGNED NOT NULL,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    PRIMARY KEY (id),
 | 
					 | 
				
			||||||
    FOREIGN KEY (embed_id) REFERENCES reminders.embeds(id) ON DELETE CASCADE
 | 
					 | 
				
			||||||
);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
CREATE TABLE reminders.messages (
 | 
					 | 
				
			||||||
    id INT UNSIGNED AUTO_INCREMENT UNIQUE NOT NULL,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    content VARCHAR(2048) NOT NULL DEFAULT '',
 | 
					 | 
				
			||||||
    tts BOOL NOT NULL DEFAULT 0,
 | 
					 | 
				
			||||||
    embed_id INT UNSIGNED,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    attachment MEDIUMBLOB,
 | 
					 | 
				
			||||||
    attachment_name VARCHAR(260),
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    PRIMARY KEY (id),
 | 
					 | 
				
			||||||
    FOREIGN KEY (embed_id) REFERENCES reminders.embeds(id) ON DELETE SET NULL
 | 
					 | 
				
			||||||
);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
CREATE TABLE reminders.reminders (
 | 
					 | 
				
			||||||
    id INT UNSIGNED AUTO_INCREMENT UNIQUE NOT NULL,
 | 
					 | 
				
			||||||
    uid VARCHAR(64) UNIQUE NOT NULL,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    name VARCHAR(24) NOT NULL DEFAULT 'Reminder',
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    message_id INT UNSIGNED NOT NULL,
 | 
					 | 
				
			||||||
    channel_id INT UNSIGNED NOT NULL,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    `time` INT UNSIGNED DEFAULT 0 NOT NULL,
 | 
					 | 
				
			||||||
    `interval` INT UNSIGNED DEFAULT NULL,
 | 
					 | 
				
			||||||
    expires TIMESTAMP DEFAULT NULL,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    enabled BOOLEAN DEFAULT 1 NOT NULL,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    avatar VARCHAR(512),
 | 
					 | 
				
			||||||
    username VARCHAR(32),
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    method ENUM('remind', 'natural', 'dashboard', 'todo', 'countdown'),
 | 
					 | 
				
			||||||
    set_at TIMESTAMP DEFAULT NOW(),
 | 
					 | 
				
			||||||
    set_by INT UNSIGNED,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    PRIMARY KEY (id),
 | 
					 | 
				
			||||||
    FOREIGN KEY (message_id) REFERENCES reminders.messages(id) ON DELETE RESTRICT,
 | 
					 | 
				
			||||||
    FOREIGN KEY (channel_id) REFERENCES reminders.channels(id) ON DELETE CASCADE,
 | 
					 | 
				
			||||||
    FOREIGN KEY (set_by) REFERENCES reminders.users(id) ON DELETE SET NULL
 | 
					 | 
				
			||||||
);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
CREATE TRIGGER message_cleanup AFTER DELETE ON reminders.reminders
 | 
					 | 
				
			||||||
FOR EACH ROW
 | 
					 | 
				
			||||||
    DELETE FROM reminders.messages WHERE id = OLD.message_id;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
CREATE TRIGGER embed_cleanup AFTER DELETE ON reminders.messages
 | 
					 | 
				
			||||||
FOR EACH ROW
 | 
					 | 
				
			||||||
    DELETE FROM reminders.embeds WHERE id = OLD.embed_id;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
CREATE TABLE reminders.todos (
 | 
					 | 
				
			||||||
    id INT UNSIGNED AUTO_INCREMENT UNIQUE NOT NULL,
 | 
					 | 
				
			||||||
    user_id INT UNSIGNED,
 | 
					 | 
				
			||||||
    guild_id INT UNSIGNED,
 | 
					 | 
				
			||||||
    channel_id INT UNSIGNED,
 | 
					 | 
				
			||||||
    value VARCHAR(2000) NOT NULL,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    PRIMARY KEY (id),
 | 
					 | 
				
			||||||
    FOREIGN KEY (user_id) REFERENCES reminders.users(id) ON DELETE SET NULL,
 | 
					 | 
				
			||||||
    FOREIGN KEY (guild_id) REFERENCES reminders.guilds(id) ON DELETE CASCADE,
 | 
					 | 
				
			||||||
    FOREIGN KEY (channel_id) REFERENCES reminders.channels(id) ON DELETE SET NULL
 | 
					 | 
				
			||||||
);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
CREATE TABLE reminders.command_restrictions (
 | 
					 | 
				
			||||||
    id INT UNSIGNED AUTO_INCREMENT UNIQUE NOT NULL,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    role_id INT UNSIGNED NOT NULL,
 | 
					 | 
				
			||||||
    command ENUM('todos', 'natural', 'remind', 'interval', 'timer', 'del', 'look', 'alias', 'countdown') NOT NULL,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    PRIMARY KEY (id),
 | 
					 | 
				
			||||||
    FOREIGN KEY (role_id) REFERENCES reminders.roles(id) ON DELETE CASCADE,
 | 
					 | 
				
			||||||
    UNIQUE KEY (`role_id`, `command`)
 | 
					 | 
				
			||||||
);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
CREATE TABLE reminders.timers (
 | 
					 | 
				
			||||||
    id INT UNSIGNED AUTO_INCREMENT UNIQUE NOT NULL,
 | 
					 | 
				
			||||||
    start_time TIMESTAMP NOT NULL DEFAULT NOW(),
 | 
					 | 
				
			||||||
    name VARCHAR(32) NOT NULL,
 | 
					 | 
				
			||||||
    owner BIGINT UNSIGNED NOT NULL,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    PRIMARY KEY (id)
 | 
					 | 
				
			||||||
);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
CREATE TABLE reminders.events (
 | 
					 | 
				
			||||||
    id INT UNSIGNED AUTO_INCREMENT UNIQUE NOT NULL,
 | 
					 | 
				
			||||||
    `time` TIMESTAMP NOT NULL DEFAULT NOW(),
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    event_name ENUM('edit', 'enable', 'disable', 'delete') NOT NULL,
 | 
					 | 
				
			||||||
    bulk_count INT UNSIGNED,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    guild_id INT UNSIGNED NOT NULL,
 | 
					 | 
				
			||||||
    user_id INT UNSIGNED,
 | 
					 | 
				
			||||||
    reminder_id INT UNSIGNED,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    PRIMARY KEY (id),
 | 
					 | 
				
			||||||
    FOREIGN KEY (guild_id) REFERENCES reminders.guilds(id) ON DELETE CASCADE,
 | 
					 | 
				
			||||||
    FOREIGN KEY (user_id) REFERENCES reminders.users(id) ON DELETE SET NULL,
 | 
					 | 
				
			||||||
    FOREIGN KEY (reminder_id) REFERENCES reminders.reminders(id) ON DELETE SET NULL
 | 
					 | 
				
			||||||
);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
CREATE TABLE reminders.command_aliases (
 | 
					 | 
				
			||||||
    id INT UNSIGNED AUTO_INCREMENT UNIQUE NOT NULL,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    guild_id INT UNSIGNED NOT NULL,
 | 
					 | 
				
			||||||
    name VARCHAR(12) NOT NULL,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    command VARCHAR(2048) NOT NULL,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    PRIMARY KEY (id),
 | 
					 | 
				
			||||||
    FOREIGN KEY (guild_id) REFERENCES reminders.guilds(id) ON DELETE CASCADE,
 | 
					 | 
				
			||||||
    UNIQUE KEY (`guild_id`, `name`)
 | 
					 | 
				
			||||||
);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
CREATE TABLE reminders.guild_users (
 | 
					 | 
				
			||||||
    guild INT UNSIGNED NOT NULL,
 | 
					 | 
				
			||||||
    user INT UNSIGNED NOT NULL,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    can_access BOOL NOT NULL DEFAULT 0,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    FOREIGN KEY (guild) REFERENCES reminders.guilds(id) ON DELETE CASCADE,
 | 
					 | 
				
			||||||
    FOREIGN KEY (user) REFERENCES reminders.users(id) ON DELETE CASCADE,
 | 
					 | 
				
			||||||
    UNIQUE KEY (guild, user)
 | 
					 | 
				
			||||||
);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
CREATE EVENT reminders.event_cleanup
 | 
					 | 
				
			||||||
ON SCHEDULE AT CURRENT_TIMESTAMP + INTERVAL 1 DAY
 | 
					 | 
				
			||||||
ON COMPLETION PRESERVE
 | 
					 | 
				
			||||||
DO DELETE FROM reminders.events WHERE `time` < DATE_SUB(NOW(), INTERVAL 5 DAY);
 | 
					 | 
				
			||||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							@@ -1,13 +0,0 @@
 | 
				
			|||||||
USE reminders;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
CREATE TABLE macro (
 | 
					 | 
				
			||||||
    id INT UNSIGNED AUTO_INCREMENT,
 | 
					 | 
				
			||||||
    guild_id INT UNSIGNED NOT NULL,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    name VARCHAR(100) NOT NULL,
 | 
					 | 
				
			||||||
    description VARCHAR(100),
 | 
					 | 
				
			||||||
    commands TEXT NOT NULL,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    FOREIGN KEY (guild_id) REFERENCES guilds(id) ON DELETE CASCADE,
 | 
					 | 
				
			||||||
    PRIMARY KEY (id)
 | 
					 | 
				
			||||||
);
 | 
					 | 
				
			||||||
@@ -1,4 +0,0 @@
 | 
				
			|||||||
USE reminders;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
ALTER TABLE reminders.reminders RENAME COLUMN `interval` TO `interval_seconds`;
 | 
					 | 
				
			||||||
ALTER TABLE reminders.reminders ADD COLUMN `interval_months` INT UNSIGNED DEFAULT NULL;
 | 
					 | 
				
			||||||
@@ -1,51 +0,0 @@
 | 
				
			|||||||
USE reminders;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
CREATE TABLE reminder_template (
 | 
					 | 
				
			||||||
    `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    `name` VARCHAR(24) NOT NULL DEFAULT 'Reminder',
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    `guild_id` INT UNSIGNED NOT NULL,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    `username` VARCHAR(32) DEFAULT NULL,
 | 
					 | 
				
			||||||
    `avatar` VARCHAR(512) DEFAULT NULL,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    `content` VARCHAR(2048) NOT NULL DEFAULT '',
 | 
					 | 
				
			||||||
    `tts` BOOL NOT NULL DEFAULT 0,
 | 
					 | 
				
			||||||
    `attachment` MEDIUMBLOB,
 | 
					 | 
				
			||||||
    `attachment_name` VARCHAR(260),
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    `embed_title` VARCHAR(256) NOT NULL DEFAULT '',
 | 
					 | 
				
			||||||
    `embed_description` VARCHAR(2048) NOT NULL DEFAULT '',
 | 
					 | 
				
			||||||
    `embed_image_url` VARCHAR(512),
 | 
					 | 
				
			||||||
    `embed_thumbnail_url` VARCHAR(512),
 | 
					 | 
				
			||||||
    `embed_footer` VARCHAR(2048) NOT NULL DEFAULT '',
 | 
					 | 
				
			||||||
    `embed_footer_url` VARCHAR(512),
 | 
					 | 
				
			||||||
    `embed_author` VARCHAR(256) NOT NULL DEFAULT '',
 | 
					 | 
				
			||||||
    `embed_author_url` VARCHAR(512),
 | 
					 | 
				
			||||||
    `embed_color` INT UNSIGNED NOT NULL DEFAULT 0x0,
 | 
					 | 
				
			||||||
    `embed_fields` JSON,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    PRIMARY KEY (id),
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    FOREIGN KEY (`guild_id`) REFERENCES guilds (`id`) ON DELETE CASCADE
 | 
					 | 
				
			||||||
);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
ALTER TABLE reminders ADD COLUMN embed_fields JSON;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
update reminders
 | 
					 | 
				
			||||||
    inner join embed_fields as E
 | 
					 | 
				
			||||||
    on E.reminder_id = reminders.id
 | 
					 | 
				
			||||||
set embed_fields = (
 | 
					 | 
				
			||||||
    select JSON_ARRAYAGG(
 | 
					 | 
				
			||||||
        JSON_OBJECT(
 | 
					 | 
				
			||||||
            'title', E.title,
 | 
					 | 
				
			||||||
            'value', E.value,
 | 
					 | 
				
			||||||
            'inline',
 | 
					 | 
				
			||||||
            if(inline = 1, cast(TRUE as json), cast(FALSE as json))
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
    from embed_fields
 | 
					 | 
				
			||||||
    group by reminder_id
 | 
					 | 
				
			||||||
    having reminder_id = reminders.id
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
							
								
								
									
										7
									
								
								models/Cargo.lock
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								models/Cargo.lock
									
									
									
										generated
									
									
									
										Normal file
									
								
							@@ -0,0 +1,7 @@
 | 
				
			|||||||
 | 
					# This file is automatically @generated by Cargo.
 | 
				
			||||||
 | 
					# It is not intended for manual editing.
 | 
				
			||||||
 | 
					version = 3
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "models"
 | 
				
			||||||
 | 
					version = "0.1.0"
 | 
				
			||||||
							
								
								
									
										8
									
								
								models/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								models/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,8 @@
 | 
				
			|||||||
 | 
					[package]
 | 
				
			||||||
 | 
					name = "models"
 | 
				
			||||||
 | 
					version = "0.1.0"
 | 
				
			||||||
 | 
					edition = "2021"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[dependencies]
 | 
				
			||||||
							
								
								
									
										8
									
								
								models/entity/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								models/entity/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,8 @@
 | 
				
			|||||||
 | 
					[package]
 | 
				
			||||||
 | 
					name = "entity"
 | 
				
			||||||
 | 
					version = "0.1.0"
 | 
				
			||||||
 | 
					edition = "2021"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[dependencies]
 | 
				
			||||||
 | 
					chrono-tz = "^0.6"
 | 
				
			||||||
 | 
					sea-orm = { version = "^0.8", features = ["sqlx-postgres", "runtime-tokio-native-tls", "macros"] }
 | 
				
			||||||
							
								
								
									
										60
									
								
								models/entity/src/channel.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								models/entity/src/channel.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,60 @@
 | 
				
			|||||||
 | 
					//! SeaORM Entity. Generated by sea-orm-codegen 0.8.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use sea_orm::entity::prelude::*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
 | 
				
			||||||
 | 
					#[sea_orm(table_name = "channel")]
 | 
				
			||||||
 | 
					pub struct Model {
 | 
				
			||||||
 | 
					    #[sea_orm(primary_key, auto_increment = false)]
 | 
				
			||||||
 | 
					    pub id: i64,
 | 
				
			||||||
 | 
					    pub guild_id: Option<i64>,
 | 
				
			||||||
 | 
					    pub nudge: i32,
 | 
				
			||||||
 | 
					    pub webhook_id: Option<i64>,
 | 
				
			||||||
 | 
					    pub webhook_token: Option<String>,
 | 
				
			||||||
 | 
					    pub paused: bool,
 | 
				
			||||||
 | 
					    pub paused_until: Option<DateTimeUtc>,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
 | 
				
			||||||
 | 
					pub enum Relation {
 | 
				
			||||||
 | 
					    #[sea_orm(
 | 
				
			||||||
 | 
					        belongs_to = "super::guild::Entity",
 | 
				
			||||||
 | 
					        from = "Column::GuildId",
 | 
				
			||||||
 | 
					        to = "super::guild::Column::Id",
 | 
				
			||||||
 | 
					        on_update = "NoAction",
 | 
				
			||||||
 | 
					        on_delete = "Cascade"
 | 
				
			||||||
 | 
					    )]
 | 
				
			||||||
 | 
					    Guild,
 | 
				
			||||||
 | 
					    #[sea_orm(has_many = "super::user::Entity")]
 | 
				
			||||||
 | 
					    User,
 | 
				
			||||||
 | 
					    #[sea_orm(has_many = "super::reminder::Entity")]
 | 
				
			||||||
 | 
					    Reminder,
 | 
				
			||||||
 | 
					    #[sea_orm(has_many = "super::todo::Entity")]
 | 
				
			||||||
 | 
					    Todo,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Related<super::guild::Entity> for Entity {
 | 
				
			||||||
 | 
					    fn to() -> RelationDef {
 | 
				
			||||||
 | 
					        Relation::Guild.def()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Related<super::user::Entity> for Entity {
 | 
				
			||||||
 | 
					    fn to() -> RelationDef {
 | 
				
			||||||
 | 
					        Relation::User.def()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Related<super::reminder::Entity> for Entity {
 | 
				
			||||||
 | 
					    fn to() -> RelationDef {
 | 
				
			||||||
 | 
					        Relation::Reminder.def()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Related<super::todo::Entity> for Entity {
 | 
				
			||||||
 | 
					    fn to() -> RelationDef {
 | 
				
			||||||
 | 
					        Relation::Todo.def()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl ActiveModelBehavior for ActiveModel {}
 | 
				
			||||||
							
								
								
									
										34
									
								
								models/entity/src/command_macro.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								models/entity/src/command_macro.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,34 @@
 | 
				
			|||||||
 | 
					//! SeaORM Entity. Generated by sea-orm-codegen 0.8.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use sea_orm::entity::prelude::*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
 | 
				
			||||||
 | 
					#[sea_orm(table_name = "command_macro")]
 | 
				
			||||||
 | 
					pub struct Model {
 | 
				
			||||||
 | 
					    #[sea_orm(primary_key)]
 | 
				
			||||||
 | 
					    pub id: i32,
 | 
				
			||||||
 | 
					    pub guild_id: i64,
 | 
				
			||||||
 | 
					    pub name: String,
 | 
				
			||||||
 | 
					    pub description: Option<String>,
 | 
				
			||||||
 | 
					    pub commands: Option<Json>,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
 | 
				
			||||||
 | 
					pub enum Relation {
 | 
				
			||||||
 | 
					    #[sea_orm(
 | 
				
			||||||
 | 
					        belongs_to = "super::guild::Entity",
 | 
				
			||||||
 | 
					        from = "Column::GuildId",
 | 
				
			||||||
 | 
					        to = "super::guild::Column::Id",
 | 
				
			||||||
 | 
					        on_update = "NoAction",
 | 
				
			||||||
 | 
					        on_delete = "Cascade"
 | 
				
			||||||
 | 
					    )]
 | 
				
			||||||
 | 
					    Guild,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Related<super::guild::Entity> for Entity {
 | 
				
			||||||
 | 
					    fn to() -> RelationDef {
 | 
				
			||||||
 | 
					        Relation::Guild.def()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl ActiveModelBehavior for ActiveModel {}
 | 
				
			||||||
							
								
								
									
										48
									
								
								models/entity/src/guild.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								models/entity/src/guild.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,48 @@
 | 
				
			|||||||
 | 
					//! SeaORM Entity. Generated by sea-orm-codegen 0.8.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use sea_orm::entity::prelude::*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
 | 
				
			||||||
 | 
					#[sea_orm(table_name = "guild")]
 | 
				
			||||||
 | 
					pub struct Model {
 | 
				
			||||||
 | 
					    #[sea_orm(primary_key, auto_increment = false)]
 | 
				
			||||||
 | 
					    pub id: i64,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
 | 
				
			||||||
 | 
					pub enum Relation {
 | 
				
			||||||
 | 
					    #[sea_orm(has_many = "super::reminder_template::Entity")]
 | 
				
			||||||
 | 
					    ReminderTemplate,
 | 
				
			||||||
 | 
					    #[sea_orm(has_many = "super::channel::Entity")]
 | 
				
			||||||
 | 
					    Channel,
 | 
				
			||||||
 | 
					    #[sea_orm(has_many = "super::todo::Entity")]
 | 
				
			||||||
 | 
					    Todo,
 | 
				
			||||||
 | 
					    #[sea_orm(has_many = "super::command_macro::Entity")]
 | 
				
			||||||
 | 
					    CommandMacro,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Related<super::reminder_template::Entity> for Entity {
 | 
				
			||||||
 | 
					    fn to() -> RelationDef {
 | 
				
			||||||
 | 
					        Relation::ReminderTemplate.def()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Related<super::channel::Entity> for Entity {
 | 
				
			||||||
 | 
					    fn to() -> RelationDef {
 | 
				
			||||||
 | 
					        Relation::Channel.def()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Related<super::todo::Entity> for Entity {
 | 
				
			||||||
 | 
					    fn to() -> RelationDef {
 | 
				
			||||||
 | 
					        Relation::Todo.def()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Related<super::command_macro::Entity> for Entity {
 | 
				
			||||||
 | 
					    fn to() -> RelationDef {
 | 
				
			||||||
 | 
					        Relation::CommandMacro.def()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl ActiveModelBehavior for ActiveModel {}
 | 
				
			||||||
							
								
								
									
										1
									
								
								models/entity/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								models/entity/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										14
									
								
								models/entity/src/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								models/entity/src/mod.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,14 @@
 | 
				
			|||||||
 | 
					//! SeaORM Entity. Generated by sea-orm-codegen 0.8.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub mod prelude;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub mod channel;
 | 
				
			||||||
 | 
					pub mod command_macro;
 | 
				
			||||||
 | 
					pub mod guild;
 | 
				
			||||||
 | 
					pub mod reminder;
 | 
				
			||||||
 | 
					pub mod reminder_template;
 | 
				
			||||||
 | 
					pub mod sea_orm_active_enums;
 | 
				
			||||||
 | 
					pub mod seaql_migrations;
 | 
				
			||||||
 | 
					pub mod timer;
 | 
				
			||||||
 | 
					pub mod todo;
 | 
				
			||||||
 | 
					pub mod user;
 | 
				
			||||||
							
								
								
									
										8
									
								
								models/entity/src/prelude.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								models/entity/src/prelude.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,8 @@
 | 
				
			|||||||
 | 
					//! SeaORM Entity. Generated by sea-orm-codegen 0.8.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub use super::{
 | 
				
			||||||
 | 
					    channel::Entity as Channel, command_macro::Entity as CommandMacro, guild::Entity as Guild,
 | 
				
			||||||
 | 
					    reminder::Entity as Reminder, reminder_template::Entity as ReminderTemplate,
 | 
				
			||||||
 | 
					    seaql_migrations::Entity as SeaqlMigrations, timer::Entity as Timer, todo::Entity as Todo,
 | 
				
			||||||
 | 
					    user::Entity as User,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										73
									
								
								models/entity/src/reminder.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								models/entity/src/reminder.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,73 @@
 | 
				
			|||||||
 | 
					//! SeaORM Entity. Generated by sea-orm-codegen 0.8.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use sea_orm::entity::prelude::*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use super::sea_orm_active_enums::Timezone;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
 | 
				
			||||||
 | 
					#[sea_orm(table_name = "reminder")]
 | 
				
			||||||
 | 
					pub struct Model {
 | 
				
			||||||
 | 
					    #[sea_orm(primary_key)]
 | 
				
			||||||
 | 
					    pub id: i32,
 | 
				
			||||||
 | 
					    pub uid: String,
 | 
				
			||||||
 | 
					    pub name: String,
 | 
				
			||||||
 | 
					    pub channel_id: i64,
 | 
				
			||||||
 | 
					    pub utc_time: DateTimeUtc,
 | 
				
			||||||
 | 
					    pub timezone: Timezone,
 | 
				
			||||||
 | 
					    pub interval_seconds: Option<i32>,
 | 
				
			||||||
 | 
					    pub interval_months: Option<i32>,
 | 
				
			||||||
 | 
					    pub enabled: bool,
 | 
				
			||||||
 | 
					    pub expires: Option<DateTimeUtc>,
 | 
				
			||||||
 | 
					    pub username: Option<String>,
 | 
				
			||||||
 | 
					    pub avatar: Option<String>,
 | 
				
			||||||
 | 
					    pub content: Option<String>,
 | 
				
			||||||
 | 
					    pub tts: bool,
 | 
				
			||||||
 | 
					    pub attachment: Option<Vec<u8>>,
 | 
				
			||||||
 | 
					    pub attachment_name: Option<String>,
 | 
				
			||||||
 | 
					    pub embed_title: Option<String>,
 | 
				
			||||||
 | 
					    pub embed_description: Option<String>,
 | 
				
			||||||
 | 
					    pub embed_image_url: Option<String>,
 | 
				
			||||||
 | 
					    pub embed_thumbnail_url: Option<String>,
 | 
				
			||||||
 | 
					    pub embed_footer: Option<String>,
 | 
				
			||||||
 | 
					    pub embed_footer_url: Option<String>,
 | 
				
			||||||
 | 
					    pub embed_author: Option<String>,
 | 
				
			||||||
 | 
					    pub embed_author_url: Option<String>,
 | 
				
			||||||
 | 
					    pub embed_color: Option<i32>,
 | 
				
			||||||
 | 
					    pub embed_fields: Option<Json>,
 | 
				
			||||||
 | 
					    pub set_at: DateTimeUtc,
 | 
				
			||||||
 | 
					    pub set_by: i64,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
 | 
				
			||||||
 | 
					pub enum Relation {
 | 
				
			||||||
 | 
					    #[sea_orm(
 | 
				
			||||||
 | 
					        belongs_to = "super::channel::Entity",
 | 
				
			||||||
 | 
					        from = "Column::ChannelId",
 | 
				
			||||||
 | 
					        to = "super::channel::Column::Id",
 | 
				
			||||||
 | 
					        on_update = "NoAction",
 | 
				
			||||||
 | 
					        on_delete = "Cascade"
 | 
				
			||||||
 | 
					    )]
 | 
				
			||||||
 | 
					    Channel,
 | 
				
			||||||
 | 
					    #[sea_orm(
 | 
				
			||||||
 | 
					        belongs_to = "super::user::Entity",
 | 
				
			||||||
 | 
					        from = "Column::SetBy",
 | 
				
			||||||
 | 
					        to = "super::user::Column::Id",
 | 
				
			||||||
 | 
					        on_update = "NoAction",
 | 
				
			||||||
 | 
					        on_delete = "Cascade"
 | 
				
			||||||
 | 
					    )]
 | 
				
			||||||
 | 
					    User,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Related<super::channel::Entity> for Entity {
 | 
				
			||||||
 | 
					    fn to() -> RelationDef {
 | 
				
			||||||
 | 
					        Relation::Channel.def()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Related<super::user::Entity> for Entity {
 | 
				
			||||||
 | 
					    fn to() -> RelationDef {
 | 
				
			||||||
 | 
					        Relation::User.def()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl ActiveModelBehavior for ActiveModel {}
 | 
				
			||||||
							
								
								
									
										48
									
								
								models/entity/src/reminder_template.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								models/entity/src/reminder_template.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,48 @@
 | 
				
			|||||||
 | 
					//! SeaORM Entity. Generated by sea-orm-codegen 0.8.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use sea_orm::entity::prelude::*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
 | 
				
			||||||
 | 
					#[sea_orm(table_name = "reminder_template")]
 | 
				
			||||||
 | 
					pub struct Model {
 | 
				
			||||||
 | 
					    #[sea_orm(primary_key)]
 | 
				
			||||||
 | 
					    pub id: i32,
 | 
				
			||||||
 | 
					    pub guild_id: i64,
 | 
				
			||||||
 | 
					    pub name: String,
 | 
				
			||||||
 | 
					    pub username: Option<String>,
 | 
				
			||||||
 | 
					    pub avatar: Option<String>,
 | 
				
			||||||
 | 
					    pub content: Option<String>,
 | 
				
			||||||
 | 
					    pub tts: bool,
 | 
				
			||||||
 | 
					    pub attachment: Option<Vec<u8>>,
 | 
				
			||||||
 | 
					    pub attachment_name: Option<String>,
 | 
				
			||||||
 | 
					    pub embed_title: Option<String>,
 | 
				
			||||||
 | 
					    pub embed_description: Option<String>,
 | 
				
			||||||
 | 
					    pub embed_image_url: Option<String>,
 | 
				
			||||||
 | 
					    pub embed_thumbnail_url: Option<String>,
 | 
				
			||||||
 | 
					    pub embed_footer: Option<String>,
 | 
				
			||||||
 | 
					    pub embed_footer_url: Option<String>,
 | 
				
			||||||
 | 
					    pub embed_author: Option<String>,
 | 
				
			||||||
 | 
					    pub embed_author_url: Option<String>,
 | 
				
			||||||
 | 
					    pub embed_color: Option<i32>,
 | 
				
			||||||
 | 
					    pub embed_fields: Option<Json>,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
 | 
				
			||||||
 | 
					pub enum Relation {
 | 
				
			||||||
 | 
					    #[sea_orm(
 | 
				
			||||||
 | 
					        belongs_to = "super::guild::Entity",
 | 
				
			||||||
 | 
					        from = "Column::GuildId",
 | 
				
			||||||
 | 
					        to = "super::guild::Column::Id",
 | 
				
			||||||
 | 
					        on_update = "NoAction",
 | 
				
			||||||
 | 
					        on_delete = "Cascade"
 | 
				
			||||||
 | 
					    )]
 | 
				
			||||||
 | 
					    Guild,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Related<super::guild::Entity> for Entity {
 | 
				
			||||||
 | 
					    fn to() -> RelationDef {
 | 
				
			||||||
 | 
					        Relation::Guild.def()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl ActiveModelBehavior for ActiveModel {}
 | 
				
			||||||
							
								
								
									
										1196
									
								
								models/entity/src/sea_orm_active_enums.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1196
									
								
								models/entity/src/sea_orm_active_enums.rs
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										22
									
								
								models/entity/src/seaql_migrations.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								models/entity/src/seaql_migrations.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,22 @@
 | 
				
			|||||||
 | 
					//! SeaORM Entity. Generated by sea-orm-codegen 0.8.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use sea_orm::entity::prelude::*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
 | 
				
			||||||
 | 
					#[sea_orm(table_name = "seaql_migrations")]
 | 
				
			||||||
 | 
					pub struct Model {
 | 
				
			||||||
 | 
					    #[sea_orm(primary_key, auto_increment = false)]
 | 
				
			||||||
 | 
					    pub version: String,
 | 
				
			||||||
 | 
					    pub applied_at: i64,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Copy, Clone, Debug, EnumIter)]
 | 
				
			||||||
 | 
					pub enum Relation {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl RelationTrait for Relation {
 | 
				
			||||||
 | 
					    fn def(&self) -> RelationDef {
 | 
				
			||||||
 | 
					        panic!("No RelationDef")
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl ActiveModelBehavior for ActiveModel {}
 | 
				
			||||||
							
								
								
									
										36
									
								
								models/entity/src/timer.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								models/entity/src/timer.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,36 @@
 | 
				
			|||||||
 | 
					//! SeaORM Entity. Generated by sea-orm-codegen 0.8.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use sea_orm::entity::prelude::*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
 | 
				
			||||||
 | 
					#[sea_orm(table_name = "timer")]
 | 
				
			||||||
 | 
					pub struct Model {
 | 
				
			||||||
 | 
					    #[sea_orm(primary_key)]
 | 
				
			||||||
 | 
					    pub id: i32,
 | 
				
			||||||
 | 
					    pub start_time: DateTimeUtc,
 | 
				
			||||||
 | 
					    pub name: String,
 | 
				
			||||||
 | 
					    pub user_id: Option<i64>,
 | 
				
			||||||
 | 
					    pub guild_id: Option<i64>,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
 | 
				
			||||||
 | 
					pub enum Relation {
 | 
				
			||||||
 | 
					    #[sea_orm(
 | 
				
			||||||
 | 
					        belongs_to = "super::guild::Entity",
 | 
				
			||||||
 | 
					        from = "Column::GuildId",
 | 
				
			||||||
 | 
					        to = "super::guild::Column::Id",
 | 
				
			||||||
 | 
					        on_update = "NoAction",
 | 
				
			||||||
 | 
					        on_delete = "Cascade"
 | 
				
			||||||
 | 
					    )]
 | 
				
			||||||
 | 
					    Guild2,
 | 
				
			||||||
 | 
					    #[sea_orm(
 | 
				
			||||||
 | 
					        belongs_to = "super::guild::Entity",
 | 
				
			||||||
 | 
					        from = "Column::UserId",
 | 
				
			||||||
 | 
					        to = "super::guild::Column::Id",
 | 
				
			||||||
 | 
					        on_update = "NoAction",
 | 
				
			||||||
 | 
					        on_delete = "Cascade"
 | 
				
			||||||
 | 
					    )]
 | 
				
			||||||
 | 
					    Guild1,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl ActiveModelBehavior for ActiveModel {}
 | 
				
			||||||
							
								
								
									
										62
									
								
								models/entity/src/todo.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								models/entity/src/todo.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,62 @@
 | 
				
			|||||||
 | 
					//! SeaORM Entity. Generated by sea-orm-codegen 0.8.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use sea_orm::entity::prelude::*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
 | 
				
			||||||
 | 
					#[sea_orm(table_name = "todo")]
 | 
				
			||||||
 | 
					pub struct Model {
 | 
				
			||||||
 | 
					    #[sea_orm(primary_key)]
 | 
				
			||||||
 | 
					    pub id: i32,
 | 
				
			||||||
 | 
					    pub user_id: Option<i64>,
 | 
				
			||||||
 | 
					    pub guild_id: Option<i64>,
 | 
				
			||||||
 | 
					    pub channel_id: Option<i64>,
 | 
				
			||||||
 | 
					    pub value: String,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
 | 
				
			||||||
 | 
					pub enum Relation {
 | 
				
			||||||
 | 
					    #[sea_orm(
 | 
				
			||||||
 | 
					        belongs_to = "super::channel::Entity",
 | 
				
			||||||
 | 
					        from = "Column::ChannelId",
 | 
				
			||||||
 | 
					        to = "super::channel::Column::Id",
 | 
				
			||||||
 | 
					        on_update = "NoAction",
 | 
				
			||||||
 | 
					        on_delete = "Cascade"
 | 
				
			||||||
 | 
					    )]
 | 
				
			||||||
 | 
					    Channel,
 | 
				
			||||||
 | 
					    #[sea_orm(
 | 
				
			||||||
 | 
					        belongs_to = "super::guild::Entity",
 | 
				
			||||||
 | 
					        from = "Column::GuildId",
 | 
				
			||||||
 | 
					        to = "super::guild::Column::Id",
 | 
				
			||||||
 | 
					        on_update = "NoAction",
 | 
				
			||||||
 | 
					        on_delete = "Cascade"
 | 
				
			||||||
 | 
					    )]
 | 
				
			||||||
 | 
					    Guild,
 | 
				
			||||||
 | 
					    #[sea_orm(
 | 
				
			||||||
 | 
					        belongs_to = "super::user::Entity",
 | 
				
			||||||
 | 
					        from = "Column::UserId",
 | 
				
			||||||
 | 
					        to = "super::user::Column::Id",
 | 
				
			||||||
 | 
					        on_update = "NoAction",
 | 
				
			||||||
 | 
					        on_delete = "Cascade"
 | 
				
			||||||
 | 
					    )]
 | 
				
			||||||
 | 
					    User,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Related<super::channel::Entity> for Entity {
 | 
				
			||||||
 | 
					    fn to() -> RelationDef {
 | 
				
			||||||
 | 
					        Relation::Channel.def()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Related<super::guild::Entity> for Entity {
 | 
				
			||||||
 | 
					    fn to() -> RelationDef {
 | 
				
			||||||
 | 
					        Relation::Guild.def()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Related<super::user::Entity> for Entity {
 | 
				
			||||||
 | 
					    fn to() -> RelationDef {
 | 
				
			||||||
 | 
					        Relation::User.def()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl ActiveModelBehavior for ActiveModel {}
 | 
				
			||||||
							
								
								
									
										50
									
								
								models/entity/src/user.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								models/entity/src/user.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,50 @@
 | 
				
			|||||||
 | 
					//! SeaORM Entity. Generated by sea-orm-codegen 0.8.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use sea_orm::entity::prelude::*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use super::sea_orm_active_enums::Timezone;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
 | 
				
			||||||
 | 
					#[sea_orm(table_name = "user")]
 | 
				
			||||||
 | 
					pub struct Model {
 | 
				
			||||||
 | 
					    #[sea_orm(primary_key, auto_increment = false)]
 | 
				
			||||||
 | 
					    pub id: i64,
 | 
				
			||||||
 | 
					    pub dm_channel: i64,
 | 
				
			||||||
 | 
					    pub timezone: Timezone,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
 | 
				
			||||||
 | 
					pub enum Relation {
 | 
				
			||||||
 | 
					    #[sea_orm(
 | 
				
			||||||
 | 
					        belongs_to = "super::channel::Entity",
 | 
				
			||||||
 | 
					        from = "Column::DmChannel",
 | 
				
			||||||
 | 
					        to = "super::channel::Column::Id",
 | 
				
			||||||
 | 
					        on_update = "NoAction",
 | 
				
			||||||
 | 
					        on_delete = "Cascade"
 | 
				
			||||||
 | 
					    )]
 | 
				
			||||||
 | 
					    Channel,
 | 
				
			||||||
 | 
					    #[sea_orm(has_many = "super::reminder::Entity")]
 | 
				
			||||||
 | 
					    Reminder,
 | 
				
			||||||
 | 
					    #[sea_orm(has_many = "super::todo::Entity")]
 | 
				
			||||||
 | 
					    Todo,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Related<super::channel::Entity> for Entity {
 | 
				
			||||||
 | 
					    fn to() -> RelationDef {
 | 
				
			||||||
 | 
					        Relation::Channel.def()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Related<super::reminder::Entity> for Entity {
 | 
				
			||||||
 | 
					    fn to() -> RelationDef {
 | 
				
			||||||
 | 
					        Relation::Reminder.def()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Related<super::todo::Entity> for Entity {
 | 
				
			||||||
 | 
					    fn to() -> RelationDef {
 | 
				
			||||||
 | 
					        Relation::Todo.def()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl ActiveModelBehavior for ActiveModel {}
 | 
				
			||||||
							
								
								
									
										2400
									
								
								models/migration/Cargo.lock
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										2400
									
								
								models/migration/Cargo.lock
									
									
									
										generated
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										16
									
								
								models/migration/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								models/migration/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,16 @@
 | 
				
			|||||||
 | 
					[package]
 | 
				
			||||||
 | 
					name = "migration"
 | 
				
			||||||
 | 
					version = "0.1.0"
 | 
				
			||||||
 | 
					edition = "2021"
 | 
				
			||||||
 | 
					publish = false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[lib]
 | 
				
			||||||
 | 
					name = "migration"
 | 
				
			||||||
 | 
					path = "src/lib.rs"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[dependencies]
 | 
				
			||||||
 | 
					entity = { path = "../entity" }
 | 
				
			||||||
 | 
					chrono-tz = "^0.6"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[dependencies.sea-orm-migration]
 | 
				
			||||||
 | 
					version = "^0.8.0"
 | 
				
			||||||
							
								
								
									
										37
									
								
								models/migration/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								models/migration/README.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,37 @@
 | 
				
			|||||||
 | 
					# Running Migrator CLI
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Apply all pending migrations
 | 
				
			||||||
 | 
					    ```sh
 | 
				
			||||||
 | 
					    cargo run
 | 
				
			||||||
 | 
					    ```
 | 
				
			||||||
 | 
					    ```sh
 | 
				
			||||||
 | 
					    cargo run -- up
 | 
				
			||||||
 | 
					    ```
 | 
				
			||||||
 | 
					- Apply first 10 pending migrations
 | 
				
			||||||
 | 
					    ```sh
 | 
				
			||||||
 | 
					    cargo run -- up -n 10
 | 
				
			||||||
 | 
					    ```
 | 
				
			||||||
 | 
					- Rollback last applied migrations
 | 
				
			||||||
 | 
					    ```sh
 | 
				
			||||||
 | 
					    cargo run -- down
 | 
				
			||||||
 | 
					    ```
 | 
				
			||||||
 | 
					- Rollback last 10 applied migrations
 | 
				
			||||||
 | 
					    ```sh
 | 
				
			||||||
 | 
					    cargo run -- down -n 10
 | 
				
			||||||
 | 
					    ```
 | 
				
			||||||
 | 
					- Drop all tables from the database, then reapply all migrations
 | 
				
			||||||
 | 
					    ```sh
 | 
				
			||||||
 | 
					    cargo run -- fresh
 | 
				
			||||||
 | 
					    ```
 | 
				
			||||||
 | 
					- Rollback all applied migrations, then reapply all migrations
 | 
				
			||||||
 | 
					    ```sh
 | 
				
			||||||
 | 
					    cargo run -- refresh
 | 
				
			||||||
 | 
					    ```
 | 
				
			||||||
 | 
					- Rollback all applied migrations
 | 
				
			||||||
 | 
					    ```sh
 | 
				
			||||||
 | 
					    cargo run -- reset
 | 
				
			||||||
 | 
					    ```
 | 
				
			||||||
 | 
					- Check the status of all migrations
 | 
				
			||||||
 | 
					    ```sh
 | 
				
			||||||
 | 
					    cargo run -- status
 | 
				
			||||||
 | 
					    ```
 | 
				
			||||||
							
								
								
									
										12
									
								
								models/migration/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								models/migration/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,12 @@
 | 
				
			|||||||
 | 
					pub use sea_orm_migration::prelude::*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					mod m20220101_000001_create_table;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub struct Migrator;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[async_trait::async_trait]
 | 
				
			||||||
 | 
					impl MigratorTrait for Migrator {
 | 
				
			||||||
 | 
					    fn migrations() -> Vec<Box<dyn MigrationTrait>> {
 | 
				
			||||||
 | 
					        vec![Box::new(m20220101_000001_create_table::Migration)]
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										553
									
								
								models/migration/src/m20220101_000001_create_table.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										553
									
								
								models/migration/src/m20220101_000001_create_table.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,553 @@
 | 
				
			|||||||
 | 
					use chrono_tz::{Tz, TZ_VARIANTS};
 | 
				
			||||||
 | 
					use sea_orm_migration::prelude::*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use crate::extension::postgres::Type;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub struct Migration;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl MigrationName for Migration {
 | 
				
			||||||
 | 
					    fn name(&self) -> &str {
 | 
				
			||||||
 | 
					        "m20220101_000001_create_table"
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Iden)]
 | 
				
			||||||
 | 
					pub enum Guild {
 | 
				
			||||||
 | 
					    Table,
 | 
				
			||||||
 | 
					    Id,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Iden)]
 | 
				
			||||||
 | 
					pub enum Channel {
 | 
				
			||||||
 | 
					    Table,
 | 
				
			||||||
 | 
					    Id,
 | 
				
			||||||
 | 
					    GuildId,
 | 
				
			||||||
 | 
					    Nudge,
 | 
				
			||||||
 | 
					    WebhookId,
 | 
				
			||||||
 | 
					    WebhookToken,
 | 
				
			||||||
 | 
					    Paused,
 | 
				
			||||||
 | 
					    PausedUntil,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Iden)]
 | 
				
			||||||
 | 
					pub enum User {
 | 
				
			||||||
 | 
					    Table,
 | 
				
			||||||
 | 
					    Id,
 | 
				
			||||||
 | 
					    DmChannel,
 | 
				
			||||||
 | 
					    Timezone,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Iden)]
 | 
				
			||||||
 | 
					pub enum Reminder {
 | 
				
			||||||
 | 
					    Table,
 | 
				
			||||||
 | 
					    Id,
 | 
				
			||||||
 | 
					    Uid,
 | 
				
			||||||
 | 
					    Name,
 | 
				
			||||||
 | 
					    ChannelId,
 | 
				
			||||||
 | 
					    UtcTime,
 | 
				
			||||||
 | 
					    Timezone,
 | 
				
			||||||
 | 
					    IntervalSeconds,
 | 
				
			||||||
 | 
					    IntervalMonths,
 | 
				
			||||||
 | 
					    Enabled,
 | 
				
			||||||
 | 
					    Expires,
 | 
				
			||||||
 | 
					    Username,
 | 
				
			||||||
 | 
					    Avatar,
 | 
				
			||||||
 | 
					    Content,
 | 
				
			||||||
 | 
					    Tts,
 | 
				
			||||||
 | 
					    Attachment,
 | 
				
			||||||
 | 
					    AttachmentName,
 | 
				
			||||||
 | 
					    EmbedTitle,
 | 
				
			||||||
 | 
					    EmbedDescription,
 | 
				
			||||||
 | 
					    EmbedImageUrl,
 | 
				
			||||||
 | 
					    EmbedThumbnailUrl,
 | 
				
			||||||
 | 
					    EmbedFooter,
 | 
				
			||||||
 | 
					    EmbedFooterUrl,
 | 
				
			||||||
 | 
					    EmbedAuthor,
 | 
				
			||||||
 | 
					    EmbedAuthorUrl,
 | 
				
			||||||
 | 
					    EmbedColor,
 | 
				
			||||||
 | 
					    EmbedFields,
 | 
				
			||||||
 | 
					    SetAt,
 | 
				
			||||||
 | 
					    SetBy,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Iden)]
 | 
				
			||||||
 | 
					pub enum ReminderTemplate {
 | 
				
			||||||
 | 
					    Table,
 | 
				
			||||||
 | 
					    Id,
 | 
				
			||||||
 | 
					    GuildId,
 | 
				
			||||||
 | 
					    Name,
 | 
				
			||||||
 | 
					    Username,
 | 
				
			||||||
 | 
					    Avatar,
 | 
				
			||||||
 | 
					    Content,
 | 
				
			||||||
 | 
					    Tts,
 | 
				
			||||||
 | 
					    Attachment,
 | 
				
			||||||
 | 
					    AttachmentName,
 | 
				
			||||||
 | 
					    EmbedTitle,
 | 
				
			||||||
 | 
					    EmbedDescription,
 | 
				
			||||||
 | 
					    EmbedImageUrl,
 | 
				
			||||||
 | 
					    EmbedThumbnailUrl,
 | 
				
			||||||
 | 
					    EmbedFooter,
 | 
				
			||||||
 | 
					    EmbedFooterUrl,
 | 
				
			||||||
 | 
					    EmbedAuthor,
 | 
				
			||||||
 | 
					    EmbedAuthorUrl,
 | 
				
			||||||
 | 
					    EmbedColor,
 | 
				
			||||||
 | 
					    EmbedFields,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Iden)]
 | 
				
			||||||
 | 
					pub enum Timer {
 | 
				
			||||||
 | 
					    Table,
 | 
				
			||||||
 | 
					    Id,
 | 
				
			||||||
 | 
					    StartTime,
 | 
				
			||||||
 | 
					    Name,
 | 
				
			||||||
 | 
					    UserId,
 | 
				
			||||||
 | 
					    GuildId,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Iden)]
 | 
				
			||||||
 | 
					pub enum Todo {
 | 
				
			||||||
 | 
					    Table,
 | 
				
			||||||
 | 
					    Id,
 | 
				
			||||||
 | 
					    UserId,
 | 
				
			||||||
 | 
					    GuildId,
 | 
				
			||||||
 | 
					    ChannelId,
 | 
				
			||||||
 | 
					    Value,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Iden)]
 | 
				
			||||||
 | 
					pub enum CommandMacro {
 | 
				
			||||||
 | 
					    Table,
 | 
				
			||||||
 | 
					    Id,
 | 
				
			||||||
 | 
					    GuildId,
 | 
				
			||||||
 | 
					    Name,
 | 
				
			||||||
 | 
					    Description,
 | 
				
			||||||
 | 
					    Commands,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub enum Timezone {
 | 
				
			||||||
 | 
					    Type,
 | 
				
			||||||
 | 
					    Tz(Tz),
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Iden for Timezone {
 | 
				
			||||||
 | 
					    fn unquoted(&self, s: &mut dyn Write) {
 | 
				
			||||||
 | 
					        write!(
 | 
				
			||||||
 | 
					            s,
 | 
				
			||||||
 | 
					            "{}",
 | 
				
			||||||
 | 
					            match self {
 | 
				
			||||||
 | 
					                Self::Type => "timezone".to_string(),
 | 
				
			||||||
 | 
					                Self::Tz(tz) => tz.to_string(),
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        .unwrap();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[async_trait::async_trait]
 | 
				
			||||||
 | 
					impl MigrationTrait for Migration {
 | 
				
			||||||
 | 
					    async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
 | 
				
			||||||
 | 
					        manager
 | 
				
			||||||
 | 
					            .create_type(
 | 
				
			||||||
 | 
					                Type::create()
 | 
				
			||||||
 | 
					                    .as_enum(Timezone::Type)
 | 
				
			||||||
 | 
					                    .values(TZ_VARIANTS.iter().map(|tz| Timezone::Tz(tz.to_owned())))
 | 
				
			||||||
 | 
					                    .to_owned(),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            .await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        manager
 | 
				
			||||||
 | 
					            .create_table(
 | 
				
			||||||
 | 
					                Table::create()
 | 
				
			||||||
 | 
					                    .table(Guild::Table)
 | 
				
			||||||
 | 
					                    .if_not_exists()
 | 
				
			||||||
 | 
					                    .col(ColumnDef::new(Guild::Id).big_integer().not_null().primary_key())
 | 
				
			||||||
 | 
					                    .to_owned(),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            .await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        manager
 | 
				
			||||||
 | 
					            .create_table(
 | 
				
			||||||
 | 
					                Table::create()
 | 
				
			||||||
 | 
					                    .table(Channel::Table)
 | 
				
			||||||
 | 
					                    .if_not_exists()
 | 
				
			||||||
 | 
					                    .col(ColumnDef::new(Channel::Id).big_integer().not_null().primary_key())
 | 
				
			||||||
 | 
					                    .col(ColumnDef::new(Channel::GuildId).big_integer())
 | 
				
			||||||
 | 
					                    .col(ColumnDef::new(Channel::Nudge).integer().not_null().default(0))
 | 
				
			||||||
 | 
					                    .col(ColumnDef::new(Channel::WebhookId).big_integer())
 | 
				
			||||||
 | 
					                    .col(ColumnDef::new(Channel::WebhookToken).string())
 | 
				
			||||||
 | 
					                    .col(ColumnDef::new(Channel::Paused).boolean().not_null().default(false))
 | 
				
			||||||
 | 
					                    .col(ColumnDef::new(Channel::PausedUntil).date_time())
 | 
				
			||||||
 | 
					                    .to_owned(),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            .await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        manager
 | 
				
			||||||
 | 
					            .create_foreign_key(
 | 
				
			||||||
 | 
					                ForeignKey::create()
 | 
				
			||||||
 | 
					                    .name("fk_channel_guild")
 | 
				
			||||||
 | 
					                    .from(Channel::Table, Channel::GuildId)
 | 
				
			||||||
 | 
					                    .to(Guild::Table, Guild::Id)
 | 
				
			||||||
 | 
					                    .on_delete(ForeignKeyAction::Cascade)
 | 
				
			||||||
 | 
					                    .to_owned(),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            .await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        manager
 | 
				
			||||||
 | 
					            .create_table(
 | 
				
			||||||
 | 
					                Table::create()
 | 
				
			||||||
 | 
					                    .table(User::Table)
 | 
				
			||||||
 | 
					                    .if_not_exists()
 | 
				
			||||||
 | 
					                    .col(ColumnDef::new(User::Id).big_integer().not_null().primary_key())
 | 
				
			||||||
 | 
					                    .col(ColumnDef::new(User::DmChannel).big_integer().not_null())
 | 
				
			||||||
 | 
					                    .col(
 | 
				
			||||||
 | 
					                        ColumnDef::new(User::Timezone)
 | 
				
			||||||
 | 
					                            .custom(Timezone::Type)
 | 
				
			||||||
 | 
					                            .not_null()
 | 
				
			||||||
 | 
					                            .default("UTC"),
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                    .to_owned(),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            .await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        manager
 | 
				
			||||||
 | 
					            .create_foreign_key(
 | 
				
			||||||
 | 
					                ForeignKey::create()
 | 
				
			||||||
 | 
					                    .name("fk_user_channel")
 | 
				
			||||||
 | 
					                    .from(User::Table, User::DmChannel)
 | 
				
			||||||
 | 
					                    .to(Channel::Table, Channel::Id)
 | 
				
			||||||
 | 
					                    .on_delete(ForeignKeyAction::Cascade)
 | 
				
			||||||
 | 
					                    .to_owned(),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            .await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        manager
 | 
				
			||||||
 | 
					            .create_table(
 | 
				
			||||||
 | 
					                Table::create()
 | 
				
			||||||
 | 
					                    .table(Reminder::Table)
 | 
				
			||||||
 | 
					                    .if_not_exists()
 | 
				
			||||||
 | 
					                    .col(
 | 
				
			||||||
 | 
					                        ColumnDef::new(Reminder::Id)
 | 
				
			||||||
 | 
					                            .integer()
 | 
				
			||||||
 | 
					                            .not_null()
 | 
				
			||||||
 | 
					                            .auto_increment()
 | 
				
			||||||
 | 
					                            .primary_key(),
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                    .col(ColumnDef::new(Reminder::Uid).string().char_len(64).not_null())
 | 
				
			||||||
 | 
					                    .col(
 | 
				
			||||||
 | 
					                        ColumnDef::new(Reminder::Name)
 | 
				
			||||||
 | 
					                            .string()
 | 
				
			||||||
 | 
					                            .char_len(24)
 | 
				
			||||||
 | 
					                            .default("Reminder")
 | 
				
			||||||
 | 
					                            .not_null(),
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                    .col(ColumnDef::new(Reminder::ChannelId).big_integer().not_null())
 | 
				
			||||||
 | 
					                    .col(ColumnDef::new(Reminder::UtcTime).date_time().not_null())
 | 
				
			||||||
 | 
					                    .col(
 | 
				
			||||||
 | 
					                        ColumnDef::new(Reminder::Timezone)
 | 
				
			||||||
 | 
					                            .custom(Timezone::Type)
 | 
				
			||||||
 | 
					                            .not_null()
 | 
				
			||||||
 | 
					                            .default("UTC"),
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                    .col(ColumnDef::new(Reminder::IntervalSeconds).integer())
 | 
				
			||||||
 | 
					                    .col(ColumnDef::new(Reminder::IntervalMonths).integer())
 | 
				
			||||||
 | 
					                    .col(ColumnDef::new(Reminder::Enabled).boolean().not_null().default(false))
 | 
				
			||||||
 | 
					                    .col(ColumnDef::new(Reminder::Expires).date_time())
 | 
				
			||||||
 | 
					                    .col(ColumnDef::new(Reminder::Username).string_len(32))
 | 
				
			||||||
 | 
					                    .col(ColumnDef::new(Reminder::Avatar).string_len(512))
 | 
				
			||||||
 | 
					                    .col(ColumnDef::new(Reminder::Content).string_len(2000))
 | 
				
			||||||
 | 
					                    .col(ColumnDef::new(Reminder::Tts).boolean().not_null().default(false))
 | 
				
			||||||
 | 
					                    .col(ColumnDef::new(Reminder::Attachment).binary_len(8 * 1024 * 1024))
 | 
				
			||||||
 | 
					                    .col(ColumnDef::new(Reminder::AttachmentName).string_len(260))
 | 
				
			||||||
 | 
					                    .col(ColumnDef::new(Reminder::EmbedTitle).string_len(256))
 | 
				
			||||||
 | 
					                    .col(ColumnDef::new(Reminder::EmbedDescription).string_len(4096))
 | 
				
			||||||
 | 
					                    .col(ColumnDef::new(Reminder::EmbedImageUrl).string_len(500))
 | 
				
			||||||
 | 
					                    .col(ColumnDef::new(Reminder::EmbedThumbnailUrl).string_len(500))
 | 
				
			||||||
 | 
					                    .col(ColumnDef::new(Reminder::EmbedFooter).string_len(2048))
 | 
				
			||||||
 | 
					                    .col(ColumnDef::new(Reminder::EmbedFooterUrl).string_len(500))
 | 
				
			||||||
 | 
					                    .col(ColumnDef::new(Reminder::EmbedAuthor).string_len(256))
 | 
				
			||||||
 | 
					                    .col(ColumnDef::new(Reminder::EmbedAuthorUrl).string_len(500))
 | 
				
			||||||
 | 
					                    .col(ColumnDef::new(Reminder::EmbedColor).integer())
 | 
				
			||||||
 | 
					                    .col(ColumnDef::new(Reminder::EmbedFields).json())
 | 
				
			||||||
 | 
					                    .col(ColumnDef::new(Reminder::SetAt).date_time().not_null().default("NOW()"))
 | 
				
			||||||
 | 
					                    .col(ColumnDef::new(Reminder::SetBy).big_integer().not_null())
 | 
				
			||||||
 | 
					                    .to_owned(),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            .await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        manager
 | 
				
			||||||
 | 
					            .create_foreign_key(
 | 
				
			||||||
 | 
					                ForeignKey::create()
 | 
				
			||||||
 | 
					                    .name("fk_reminder_channel")
 | 
				
			||||||
 | 
					                    .from(Reminder::Table, Reminder::ChannelId)
 | 
				
			||||||
 | 
					                    .to(Channel::Table, Channel::Id)
 | 
				
			||||||
 | 
					                    .on_delete(ForeignKeyAction::Cascade)
 | 
				
			||||||
 | 
					                    .to_owned(),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            .await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        manager
 | 
				
			||||||
 | 
					            .create_foreign_key(
 | 
				
			||||||
 | 
					                ForeignKey::create()
 | 
				
			||||||
 | 
					                    .name("fk_reminder_user")
 | 
				
			||||||
 | 
					                    .from(Reminder::Table, Reminder::SetBy)
 | 
				
			||||||
 | 
					                    .to(User::Table, User::Id)
 | 
				
			||||||
 | 
					                    .on_delete(ForeignKeyAction::Cascade)
 | 
				
			||||||
 | 
					                    .to_owned(),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            .await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        manager
 | 
				
			||||||
 | 
					            .create_table(
 | 
				
			||||||
 | 
					                Table::create()
 | 
				
			||||||
 | 
					                    .table(ReminderTemplate::Table)
 | 
				
			||||||
 | 
					                    .if_not_exists()
 | 
				
			||||||
 | 
					                    .col(
 | 
				
			||||||
 | 
					                        ColumnDef::new(ReminderTemplate::Id)
 | 
				
			||||||
 | 
					                            .integer()
 | 
				
			||||||
 | 
					                            .not_null()
 | 
				
			||||||
 | 
					                            .auto_increment()
 | 
				
			||||||
 | 
					                            .primary_key(),
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                    .col(ColumnDef::new(ReminderTemplate::GuildId).big_integer().not_null())
 | 
				
			||||||
 | 
					                    .col(
 | 
				
			||||||
 | 
					                        ColumnDef::new(ReminderTemplate::Name)
 | 
				
			||||||
 | 
					                            .string()
 | 
				
			||||||
 | 
					                            .char_len(24)
 | 
				
			||||||
 | 
					                            .default("Reminder")
 | 
				
			||||||
 | 
					                            .not_null(),
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                    .col(ColumnDef::new(ReminderTemplate::Username).string_len(32))
 | 
				
			||||||
 | 
					                    .col(ColumnDef::new(ReminderTemplate::Avatar).string_len(512))
 | 
				
			||||||
 | 
					                    .col(ColumnDef::new(ReminderTemplate::Content).string_len(2000))
 | 
				
			||||||
 | 
					                    .col(ColumnDef::new(ReminderTemplate::Tts).boolean().not_null().default(false))
 | 
				
			||||||
 | 
					                    .col(ColumnDef::new(ReminderTemplate::Attachment).binary_len(8 * 1024 * 1024))
 | 
				
			||||||
 | 
					                    .col(ColumnDef::new(ReminderTemplate::AttachmentName).string_len(260))
 | 
				
			||||||
 | 
					                    .col(ColumnDef::new(ReminderTemplate::EmbedTitle).string_len(256))
 | 
				
			||||||
 | 
					                    .col(ColumnDef::new(ReminderTemplate::EmbedDescription).string_len(4096))
 | 
				
			||||||
 | 
					                    .col(ColumnDef::new(ReminderTemplate::EmbedImageUrl).string_len(500))
 | 
				
			||||||
 | 
					                    .col(ColumnDef::new(ReminderTemplate::EmbedThumbnailUrl).string_len(500))
 | 
				
			||||||
 | 
					                    .col(ColumnDef::new(ReminderTemplate::EmbedFooter).string_len(2048))
 | 
				
			||||||
 | 
					                    .col(ColumnDef::new(ReminderTemplate::EmbedFooterUrl).string_len(500))
 | 
				
			||||||
 | 
					                    .col(ColumnDef::new(ReminderTemplate::EmbedAuthor).string_len(256))
 | 
				
			||||||
 | 
					                    .col(ColumnDef::new(ReminderTemplate::EmbedAuthorUrl).string_len(500))
 | 
				
			||||||
 | 
					                    .col(ColumnDef::new(ReminderTemplate::EmbedColor).integer())
 | 
				
			||||||
 | 
					                    .col(ColumnDef::new(ReminderTemplate::EmbedFields).json())
 | 
				
			||||||
 | 
					                    .to_owned(),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            .await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        manager
 | 
				
			||||||
 | 
					            .create_foreign_key(
 | 
				
			||||||
 | 
					                ForeignKey::create()
 | 
				
			||||||
 | 
					                    .name("fk_reminder_template_guild")
 | 
				
			||||||
 | 
					                    .from(ReminderTemplate::Table, ReminderTemplate::GuildId)
 | 
				
			||||||
 | 
					                    .to(Guild::Table, Guild::Id)
 | 
				
			||||||
 | 
					                    .on_delete(ForeignKeyAction::Cascade)
 | 
				
			||||||
 | 
					                    .to_owned(),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            .await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        manager
 | 
				
			||||||
 | 
					            .create_table(
 | 
				
			||||||
 | 
					                Table::create()
 | 
				
			||||||
 | 
					                    .table(Timer::Table)
 | 
				
			||||||
 | 
					                    .if_not_exists()
 | 
				
			||||||
 | 
					                    .col(
 | 
				
			||||||
 | 
					                        ColumnDef::new(Timer::Id)
 | 
				
			||||||
 | 
					                            .integer()
 | 
				
			||||||
 | 
					                            .not_null()
 | 
				
			||||||
 | 
					                            .auto_increment()
 | 
				
			||||||
 | 
					                            .primary_key(),
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                    .col(ColumnDef::new(Timer::StartTime).date_time().not_null().default("NOW()"))
 | 
				
			||||||
 | 
					                    .col(ColumnDef::new(Timer::Name).string_len(32).not_null().default("Timer"))
 | 
				
			||||||
 | 
					                    .col(ColumnDef::new(Timer::UserId).big_integer())
 | 
				
			||||||
 | 
					                    .col(ColumnDef::new(Timer::GuildId).big_integer())
 | 
				
			||||||
 | 
					                    .to_owned(),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            .await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        manager
 | 
				
			||||||
 | 
					            .create_foreign_key(
 | 
				
			||||||
 | 
					                ForeignKey::create()
 | 
				
			||||||
 | 
					                    .name("fk_timer_user")
 | 
				
			||||||
 | 
					                    .from(Timer::Table, Timer::UserId)
 | 
				
			||||||
 | 
					                    .to(Guild::Table, Guild::Id)
 | 
				
			||||||
 | 
					                    .on_delete(ForeignKeyAction::Cascade)
 | 
				
			||||||
 | 
					                    .to_owned(),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            .await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        manager
 | 
				
			||||||
 | 
					            .create_foreign_key(
 | 
				
			||||||
 | 
					                ForeignKey::create()
 | 
				
			||||||
 | 
					                    .name("fk_timer_guild")
 | 
				
			||||||
 | 
					                    .from(Timer::Table, Timer::GuildId)
 | 
				
			||||||
 | 
					                    .to(Guild::Table, Guild::Id)
 | 
				
			||||||
 | 
					                    .on_delete(ForeignKeyAction::Cascade)
 | 
				
			||||||
 | 
					                    .to_owned(),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            .await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        manager
 | 
				
			||||||
 | 
					            .create_table(
 | 
				
			||||||
 | 
					                Table::create()
 | 
				
			||||||
 | 
					                    .table(Todo::Table)
 | 
				
			||||||
 | 
					                    .if_not_exists()
 | 
				
			||||||
 | 
					                    .col(
 | 
				
			||||||
 | 
					                        ColumnDef::new(Todo::Id)
 | 
				
			||||||
 | 
					                            .integer()
 | 
				
			||||||
 | 
					                            .not_null()
 | 
				
			||||||
 | 
					                            .auto_increment()
 | 
				
			||||||
 | 
					                            .primary_key(),
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                    .col(ColumnDef::new(Todo::UserId).big_integer())
 | 
				
			||||||
 | 
					                    .col(ColumnDef::new(Todo::GuildId).big_integer())
 | 
				
			||||||
 | 
					                    .col(ColumnDef::new(Todo::ChannelId).big_integer())
 | 
				
			||||||
 | 
					                    .col(ColumnDef::new(Todo::Value).string_len(2000).not_null())
 | 
				
			||||||
 | 
					                    .to_owned(),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            .await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        manager
 | 
				
			||||||
 | 
					            .create_foreign_key(
 | 
				
			||||||
 | 
					                ForeignKey::create()
 | 
				
			||||||
 | 
					                    .name("fk_todo_user")
 | 
				
			||||||
 | 
					                    .from(Todo::Table, Todo::UserId)
 | 
				
			||||||
 | 
					                    .to(User::Table, User::Id)
 | 
				
			||||||
 | 
					                    .on_delete(ForeignKeyAction::Cascade)
 | 
				
			||||||
 | 
					                    .to_owned(),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            .await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        manager
 | 
				
			||||||
 | 
					            .create_foreign_key(
 | 
				
			||||||
 | 
					                ForeignKey::create()
 | 
				
			||||||
 | 
					                    .name("fk_todo_guild")
 | 
				
			||||||
 | 
					                    .from(Todo::Table, Todo::GuildId)
 | 
				
			||||||
 | 
					                    .to(Guild::Table, Guild::Id)
 | 
				
			||||||
 | 
					                    .on_delete(ForeignKeyAction::Cascade)
 | 
				
			||||||
 | 
					                    .to_owned(),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            .await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        manager
 | 
				
			||||||
 | 
					            .create_foreign_key(
 | 
				
			||||||
 | 
					                ForeignKey::create()
 | 
				
			||||||
 | 
					                    .name("fk_todo_channel")
 | 
				
			||||||
 | 
					                    .from(Todo::Table, Todo::ChannelId)
 | 
				
			||||||
 | 
					                    .to(Channel::Table, Channel::Id)
 | 
				
			||||||
 | 
					                    .on_delete(ForeignKeyAction::Cascade)
 | 
				
			||||||
 | 
					                    .to_owned(),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            .await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        manager
 | 
				
			||||||
 | 
					            .create_table(
 | 
				
			||||||
 | 
					                Table::create()
 | 
				
			||||||
 | 
					                    .table(CommandMacro::Table)
 | 
				
			||||||
 | 
					                    .if_not_exists()
 | 
				
			||||||
 | 
					                    .col(
 | 
				
			||||||
 | 
					                        ColumnDef::new(CommandMacro::Id)
 | 
				
			||||||
 | 
					                            .integer()
 | 
				
			||||||
 | 
					                            .not_null()
 | 
				
			||||||
 | 
					                            .auto_increment()
 | 
				
			||||||
 | 
					                            .primary_key(),
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                    .col(ColumnDef::new(CommandMacro::GuildId).big_integer().not_null())
 | 
				
			||||||
 | 
					                    .col(ColumnDef::new(CommandMacro::Name).string_len(100).not_null())
 | 
				
			||||||
 | 
					                    .col(ColumnDef::new(CommandMacro::Description).string_len(100))
 | 
				
			||||||
 | 
					                    .col(ColumnDef::new(CommandMacro::Commands).json())
 | 
				
			||||||
 | 
					                    .to_owned(),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            .await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        manager
 | 
				
			||||||
 | 
					            .create_foreign_key(
 | 
				
			||||||
 | 
					                ForeignKey::create()
 | 
				
			||||||
 | 
					                    .name("fk_command_macro_guild")
 | 
				
			||||||
 | 
					                    .from(CommandMacro::Table, CommandMacro::GuildId)
 | 
				
			||||||
 | 
					                    .to(Guild::Table, Guild::Id)
 | 
				
			||||||
 | 
					                    .on_delete(ForeignKeyAction::Cascade)
 | 
				
			||||||
 | 
					                    .to_owned(),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            .await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Ok(())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
 | 
				
			||||||
 | 
					        manager
 | 
				
			||||||
 | 
					            .drop_foreign_key(
 | 
				
			||||||
 | 
					                ForeignKey::drop().table(Channel::Table).name("fk_channel_guild").to_owned(),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            .await?;
 | 
				
			||||||
 | 
					        manager
 | 
				
			||||||
 | 
					            .drop_foreign_key(
 | 
				
			||||||
 | 
					                ForeignKey::drop().table(User::Table).name("fk_user_channel").to_owned(),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            .await?;
 | 
				
			||||||
 | 
					        manager
 | 
				
			||||||
 | 
					            .drop_foreign_key(
 | 
				
			||||||
 | 
					                ForeignKey::drop().table(Reminder::Table).name("fk_reminder_channel").to_owned(),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            .await?;
 | 
				
			||||||
 | 
					        manager
 | 
				
			||||||
 | 
					            .drop_foreign_key(
 | 
				
			||||||
 | 
					                ForeignKey::drop().table(Reminder::Table).name("fk_reminder_user").to_owned(),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            .await?;
 | 
				
			||||||
 | 
					        manager
 | 
				
			||||||
 | 
					            .drop_foreign_key(
 | 
				
			||||||
 | 
					                ForeignKey::drop()
 | 
				
			||||||
 | 
					                    .table(ReminderTemplate::Table)
 | 
				
			||||||
 | 
					                    .name("fk_reminder_template_guild")
 | 
				
			||||||
 | 
					                    .to_owned(),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            .await?;
 | 
				
			||||||
 | 
					        manager
 | 
				
			||||||
 | 
					            .drop_foreign_key(
 | 
				
			||||||
 | 
					                ForeignKey::drop().table(Timer::Table).name("fk_timer_user").to_owned(),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            .await?;
 | 
				
			||||||
 | 
					        manager
 | 
				
			||||||
 | 
					            .drop_foreign_key(
 | 
				
			||||||
 | 
					                ForeignKey::drop().table(Timer::Table).name("fk_timer_guild").to_owned(),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            .await?;
 | 
				
			||||||
 | 
					        manager
 | 
				
			||||||
 | 
					            .drop_foreign_key(ForeignKey::drop().table(Todo::Table).name("fk_todo_user").to_owned())
 | 
				
			||||||
 | 
					            .await?;
 | 
				
			||||||
 | 
					        manager
 | 
				
			||||||
 | 
					            .drop_foreign_key(
 | 
				
			||||||
 | 
					                ForeignKey::drop().table(Todo::Table).name("fk_todo_guild").to_owned(),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            .await?;
 | 
				
			||||||
 | 
					        manager
 | 
				
			||||||
 | 
					            .drop_foreign_key(
 | 
				
			||||||
 | 
					                ForeignKey::drop().table(Todo::Table).name("fk_todo_channel").to_owned(),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            .await?;
 | 
				
			||||||
 | 
					        manager
 | 
				
			||||||
 | 
					            .drop_foreign_key(
 | 
				
			||||||
 | 
					                ForeignKey::drop()
 | 
				
			||||||
 | 
					                    .table(CommandMacro::Table)
 | 
				
			||||||
 | 
					                    .name("fk_command_macro_guild")
 | 
				
			||||||
 | 
					                    .to_owned(),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            .await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        manager.drop_table(Table::drop().table(Guild::Table).to_owned()).await?;
 | 
				
			||||||
 | 
					        manager.drop_table(Table::drop().table(Channel::Table).to_owned()).await?;
 | 
				
			||||||
 | 
					        manager.drop_table(Table::drop().table(User::Table).to_owned()).await?;
 | 
				
			||||||
 | 
					        manager.drop_table(Table::drop().table(Reminder::Table).to_owned()).await?;
 | 
				
			||||||
 | 
					        manager.drop_table(Table::drop().table(ReminderTemplate::Table).to_owned()).await?;
 | 
				
			||||||
 | 
					        manager.drop_table(Table::drop().table(Timer::Table).to_owned()).await?;
 | 
				
			||||||
 | 
					        manager.drop_table(Table::drop().table(Todo::Table).to_owned()).await?;
 | 
				
			||||||
 | 
					        manager.drop_table(Table::drop().table(CommandMacro::Table).to_owned()).await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        manager.drop_type(Type::drop().name(Timezone::Type).to_owned()).await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Ok(())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										6
									
								
								models/migration/src/main.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								models/migration/src/main.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
				
			|||||||
 | 
					use sea_orm_migration::prelude::*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[async_std::main]
 | 
				
			||||||
 | 
					async fn main() {
 | 
				
			||||||
 | 
					    cli::run_cli(migration::Migrator).await;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										1
									
								
								models/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								models/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
@@ -7,10 +7,12 @@ edition = "2021"
 | 
				
			|||||||
tokio = { version = "1", features = ["process", "full"] }
 | 
					tokio = { version = "1", features = ["process", "full"] }
 | 
				
			||||||
regex = "1.4"
 | 
					regex = "1.4"
 | 
				
			||||||
log = "0.4"
 | 
					log = "0.4"
 | 
				
			||||||
 | 
					env_logger = "0.8"
 | 
				
			||||||
chrono = "0.4"
 | 
					chrono = "0.4"
 | 
				
			||||||
chrono-tz = { version = "0.5", features = ["serde"] }
 | 
					chrono-tz = { version = "0.5", features = ["serde"] }
 | 
				
			||||||
lazy_static = "1.4"
 | 
					lazy_static = "1.4"
 | 
				
			||||||
num-integer = "0.1"
 | 
					num-integer = "0.1"
 | 
				
			||||||
serde = "1.0"
 | 
					serde = "1.0"
 | 
				
			||||||
 | 
					serde_json = "1.0"
 | 
				
			||||||
sqlx = { version = "0.5", features = ["runtime-tokio-rustls", "macros", "mysql", "bigdecimal", "chrono", "json"]}
 | 
					sqlx = { version = "0.5", features = ["runtime-tokio-rustls", "macros", "mysql", "bigdecimal", "chrono", "json"]}
 | 
				
			||||||
serenity = { version = "0.11.1", default-features = false, features = ["builder", "cache", "client", "gateway", "http", "model", "utils", "rustls_backend"] }
 | 
					serenity = { version = "0.11.1", default-features = false, features = ["builder", "cache", "client", "gateway", "http", "model", "utils", "rustls_backend"] }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -226,6 +226,7 @@ impl Into<CreateEmbed> for Embed {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug)]
 | 
				
			||||||
pub struct Reminder {
 | 
					pub struct Reminder {
 | 
				
			||||||
    id: u32,
 | 
					    id: u32,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -292,20 +293,8 @@ INNER JOIN
 | 
				
			|||||||
ON
 | 
					ON
 | 
				
			||||||
    reminders.channel_id = channels.id
 | 
					    reminders.channel_id = channels.id
 | 
				
			||||||
WHERE
 | 
					WHERE
 | 
				
			||||||
    reminders.`id` IN (
 | 
					    reminders.`utc_time` < NOW()
 | 
				
			||||||
        SELECT
 | 
					LIMIT 25
 | 
				
			||||||
            MIN(id)
 | 
					 | 
				
			||||||
        FROM
 | 
					 | 
				
			||||||
            reminders
 | 
					 | 
				
			||||||
        WHERE
 | 
					 | 
				
			||||||
            reminders.`utc_time` <= NOW()
 | 
					 | 
				
			||||||
            AND (
 | 
					 | 
				
			||||||
                reminders.`interval_seconds` IS NOT NULL
 | 
					 | 
				
			||||||
                OR reminders.`interval_months` IS NOT NULL
 | 
					 | 
				
			||||||
                OR reminders.enabled
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
        GROUP BY channel_id
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
            "#,
 | 
					            "#,
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        .fetch_all(pool)
 | 
					        .fetch_all(pool)
 | 
				
			||||||
@@ -577,7 +566,7 @@ UPDATE `channels` SET paused = 0, paused_until = NULL WHERE `channel` = ?
 | 
				
			|||||||
            };
 | 
					            };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if let Err(e) = result {
 | 
					            if let Err(e) = result {
 | 
				
			||||||
                error!("Error sending reminder {}: {:?}", self.id, e);
 | 
					                error!("Error sending {:?}: {:?}", self, e);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if let Error::Http(error) = e {
 | 
					                if let Error::Http(error) = e {
 | 
				
			||||||
                    if error.status_code() == Some(StatusCode::NOT_FOUND) {
 | 
					                    if error.status_code() == Some(StatusCode::NOT_FOUND) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -49,7 +49,6 @@ __Todo Commands__
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
__Setup Commands__
 | 
					__Setup Commands__
 | 
				
			||||||
`/timezone` - Set your timezone (necessary for `/remind` to work properly)
 | 
					`/timezone` - Set your timezone (necessary for `/remind` to work properly)
 | 
				
			||||||
`/dm allow/block` - Change your DM settings for reminders.
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
__Advanced Commands__
 | 
					__Advanced Commands__
 | 
				
			||||||
`/macro` - Record and replay command sequences
 | 
					`/macro` - Record and replay command sequences
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -124,85 +124,6 @@ You may want to use one of the popular timezones below, otherwise click [here](h
 | 
				
			|||||||
    Ok(())
 | 
					    Ok(())
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Configure whether other users can set reminders to your direct messages
 | 
					 | 
				
			||||||
#[poise::command(slash_command, rename = "dm", identifying_name = "allowed_dm")]
 | 
					 | 
				
			||||||
pub async fn allowed_dm(_ctx: Context<'_>) -> Result<(), Error> {
 | 
					 | 
				
			||||||
    Ok(())
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/// Allow other users to set reminders in your direct messages
 | 
					 | 
				
			||||||
#[poise::command(slash_command, rename = "allow", identifying_name = "allowed_dm")]
 | 
					 | 
				
			||||||
pub async fn set_allowed_dm(ctx: Context<'_>) -> Result<(), Error> {
 | 
					 | 
				
			||||||
    let mut user_data = ctx.author_data().await?;
 | 
					 | 
				
			||||||
    user_data.allowed_dm = true;
 | 
					 | 
				
			||||||
    user_data.commit_changes(&ctx.data().database).await;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ctx.send(|r| {
 | 
					 | 
				
			||||||
        r.ephemeral(true).embed(|e| {
 | 
					 | 
				
			||||||
            e.title("DMs permitted")
 | 
					 | 
				
			||||||
                .description("You will receive a message if a user sets a DM reminder for you.")
 | 
					 | 
				
			||||||
                .color(*THEME_COLOR)
 | 
					 | 
				
			||||||
        })
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
    .await?;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Ok(())
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/// Block other users from setting reminders in your direct messages
 | 
					 | 
				
			||||||
#[poise::command(slash_command, rename = "block", identifying_name = "allowed_dm")]
 | 
					 | 
				
			||||||
pub async fn unset_allowed_dm(ctx: Context<'_>) -> Result<(), Error> {
 | 
					 | 
				
			||||||
    let mut user_data = ctx.author_data().await?;
 | 
					 | 
				
			||||||
    user_data.allowed_dm = false;
 | 
					 | 
				
			||||||
    user_data.commit_changes(&ctx.data().database).await;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ctx.send(|r| {
 | 
					 | 
				
			||||||
        r.ephemeral(true).embed(|e| {
 | 
					 | 
				
			||||||
            e.title("DMs blocked")
 | 
					 | 
				
			||||||
                .description(
 | 
					 | 
				
			||||||
                    "You can still set DM reminders for yourself or for users with DMs enabled.",
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
                .color(*THEME_COLOR)
 | 
					 | 
				
			||||||
        })
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
    .await?;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Ok(())
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/// View the webhook being used to send reminders to this channel
 | 
					 | 
				
			||||||
#[poise::command(
 | 
					 | 
				
			||||||
    slash_command,
 | 
					 | 
				
			||||||
    identifying_name = "webhook_url",
 | 
					 | 
				
			||||||
    required_permissions = "ADMINISTRATOR"
 | 
					 | 
				
			||||||
)]
 | 
					 | 
				
			||||||
pub async fn webhook(ctx: Context<'_>) -> Result<(), Error> {
 | 
					 | 
				
			||||||
    match ctx.channel_data().await {
 | 
					 | 
				
			||||||
        Ok(data) => {
 | 
					 | 
				
			||||||
            if let (Some(id), Some(token)) = (data.webhook_id, data.webhook_token) {
 | 
					 | 
				
			||||||
                let _ = ctx
 | 
					 | 
				
			||||||
                    .send(|b| {
 | 
					 | 
				
			||||||
                        b.ephemeral(true).content(format!(
 | 
					 | 
				
			||||||
                            "**Warning!**
 | 
					 | 
				
			||||||
This link can be used by users to anonymously send messages, with or without permissions.
 | 
					 | 
				
			||||||
Do not share it!
 | 
					 | 
				
			||||||
|| https://discord.com/api/webhooks/{}/{} ||",
 | 
					 | 
				
			||||||
                            id, token,
 | 
					 | 
				
			||||||
                        ))
 | 
					 | 
				
			||||||
                    })
 | 
					 | 
				
			||||||
                    .await;
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                let _ = ctx.say("No webhook configured on this channel.").await;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        Err(_) => {
 | 
					 | 
				
			||||||
            let _ = ctx.say("No webhook configured on this channel.").await;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Ok(())
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async fn macro_name_autocomplete(ctx: Context<'_>, partial: String) -> Vec<String> {
 | 
					async fn macro_name_autocomplete(ctx: Context<'_>, partial: String) -> Vec<String> {
 | 
				
			||||||
    sqlx::query!(
 | 
					    sqlx::query!(
 | 
				
			||||||
        "
 | 
					        "
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,7 +9,7 @@ use chrono_tz::Tz;
 | 
				
			|||||||
use num_integer::Integer;
 | 
					use num_integer::Integer;
 | 
				
			||||||
use poise::{
 | 
					use poise::{
 | 
				
			||||||
    serenity::{builder::CreateEmbed, model::channel::Channel},
 | 
					    serenity::{builder::CreateEmbed, model::channel::Channel},
 | 
				
			||||||
    serenity_prelude::{component::ButtonStyle, ReactionType},
 | 
					    serenity_prelude::{ButtonStyle, ReactionType},
 | 
				
			||||||
    CreateReply,
 | 
					    CreateReply,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -694,7 +694,6 @@ pub async fn remind(
 | 
				
			|||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					 | 
				
			||||||
        None => {
 | 
					        None => {
 | 
				
			||||||
            ctx.say("Time could not be processed").await?;
 | 
					            ctx.say("Time could not be processed").await?;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,7 +6,6 @@ use crate::{
 | 
				
			|||||||
        ComponentDataModel, TodoSelector,
 | 
					        ComponentDataModel, TodoSelector,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    consts::{EMBED_DESCRIPTION_MAX_LENGTH, SELECT_MAX_ENTRIES, THEME_COLOR},
 | 
					    consts::{EMBED_DESCRIPTION_MAX_LENGTH, SELECT_MAX_ENTRIES, THEME_COLOR},
 | 
				
			||||||
    models::CtxData,
 | 
					 | 
				
			||||||
    Context, Error,
 | 
					    Context, Error,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -117,9 +116,6 @@ pub async fn todo_channel_add(
 | 
				
			|||||||
    ctx: Context<'_>,
 | 
					    ctx: Context<'_>,
 | 
				
			||||||
    #[description = "The task to add to the todo list"] task: String,
 | 
					    #[description = "The task to add to the todo list"] task: String,
 | 
				
			||||||
) -> Result<(), Error> {
 | 
					) -> Result<(), Error> {
 | 
				
			||||||
    // ensure channel is cached
 | 
					 | 
				
			||||||
    let _ = ctx.channel_data().await;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    sqlx::query!(
 | 
					    sqlx::query!(
 | 
				
			||||||
        "INSERT INTO todos (guild_id, channel_id, value)
 | 
					        "INSERT INTO todos (guild_id, channel_id, value)
 | 
				
			||||||
VALUES ((SELECT id FROM guilds WHERE guild = ?), (SELECT id FROM channels WHERE channel = ?), ?)",
 | 
					VALUES ((SELECT id FROM guilds WHERE guild = ?), (SELECT id FROM channels WHERE channel = ?), ?)",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,11 +9,11 @@ use poise::{
 | 
				
			|||||||
        builder::CreateEmbed,
 | 
					        builder::CreateEmbed,
 | 
				
			||||||
        client::Context,
 | 
					        client::Context,
 | 
				
			||||||
        model::{
 | 
					        model::{
 | 
				
			||||||
            application::interaction::{
 | 
					 | 
				
			||||||
                message_component::MessageComponentInteraction, InteractionResponseType,
 | 
					 | 
				
			||||||
                MessageFlags,
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
            channel::Channel,
 | 
					            channel::Channel,
 | 
				
			||||||
 | 
					            interactions::{
 | 
				
			||||||
 | 
					                message_component::MessageComponentInteraction, InteractionResponseType,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            prelude::InteractionApplicationCommandCallbackDataFlags,
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    serenity_prelude as serenity,
 | 
					    serenity_prelude as serenity,
 | 
				
			||||||
@@ -260,7 +260,7 @@ WHERE guilds.guild = ?",
 | 
				
			|||||||
                            r.kind(InteractionResponseType::ChannelMessageWithSource)
 | 
					                            r.kind(InteractionResponseType::ChannelMessageWithSource)
 | 
				
			||||||
                                .interaction_response_data(|d| {
 | 
					                                .interaction_response_data(|d| {
 | 
				
			||||||
                                    d.flags(
 | 
					                                    d.flags(
 | 
				
			||||||
                                        MessageFlags::EPHEMERAL,
 | 
					                                        InteractionApplicationCommandCallbackDataFlags::EPHEMERAL,
 | 
				
			||||||
                                    )
 | 
					                                    )
 | 
				
			||||||
                                    .content("Only the user who performed the command can use these components")
 | 
					                                    .content("Only the user who performed the command can use these components")
 | 
				
			||||||
                                })
 | 
					                                })
 | 
				
			||||||
@@ -314,7 +314,7 @@ WHERE guilds.guild = ?",
 | 
				
			|||||||
                            r.kind(InteractionResponseType::ChannelMessageWithSource)
 | 
					                            r.kind(InteractionResponseType::ChannelMessageWithSource)
 | 
				
			||||||
                                .interaction_response_data(|d| {
 | 
					                                .interaction_response_data(|d| {
 | 
				
			||||||
                                    d.flags(
 | 
					                                    d.flags(
 | 
				
			||||||
                                        MessageFlags::EPHEMERAL,
 | 
					                                        InteractionApplicationCommandCallbackDataFlags::EPHEMERAL,
 | 
				
			||||||
                                    )
 | 
					                                    )
 | 
				
			||||||
                                    .content("Only the user who performed the command can use these components")
 | 
					                                    .content("Only the user who performed the command can use these components")
 | 
				
			||||||
                                })
 | 
					                                })
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,8 @@
 | 
				
			|||||||
// todo split pager out into a single struct
 | 
					// todo split pager out into a single struct
 | 
				
			||||||
use chrono_tz::Tz;
 | 
					use chrono_tz::Tz;
 | 
				
			||||||
use poise::serenity::{builder::CreateComponents, model::application::component::ButtonStyle};
 | 
					use poise::serenity::{
 | 
				
			||||||
 | 
					    builder::CreateComponents, model::interactions::message_component::ButtonStyle,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
use serde::{Deserialize, Serialize};
 | 
					use serde::{Deserialize, Serialize};
 | 
				
			||||||
use serde_repr::*;
 | 
					use serde_repr::*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,7 +2,7 @@ use std::{collections::HashMap, env, sync::atomic::Ordering};
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
use log::{error, info, warn};
 | 
					use log::{error, info, warn};
 | 
				
			||||||
use poise::{
 | 
					use poise::{
 | 
				
			||||||
    serenity::{model::application::interaction::Interaction, utils::shard_id},
 | 
					    serenity::{model::interactions::Interaction, utils::shard_id},
 | 
				
			||||||
    serenity_prelude as serenity,
 | 
					    serenity_prelude as serenity,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -101,14 +101,6 @@ async fn _main(tx: Sender<()>) -> Result<(), Box<dyn StdError + Send + Sync>> {
 | 
				
			|||||||
            info_cmds::clock_context_menu(),
 | 
					            info_cmds::clock_context_menu(),
 | 
				
			||||||
            info_cmds::dashboard(),
 | 
					            info_cmds::dashboard(),
 | 
				
			||||||
            moderation_cmds::timezone(),
 | 
					            moderation_cmds::timezone(),
 | 
				
			||||||
            poise::Command {
 | 
					 | 
				
			||||||
                subcommands: vec![
 | 
					 | 
				
			||||||
                    moderation_cmds::set_allowed_dm(),
 | 
					 | 
				
			||||||
                    moderation_cmds::unset_allowed_dm(),
 | 
					 | 
				
			||||||
                ],
 | 
					 | 
				
			||||||
                ..moderation_cmds::allowed_dm()
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
            moderation_cmds::webhook(),
 | 
					 | 
				
			||||||
            poise::Command {
 | 
					            poise::Command {
 | 
				
			||||||
                subcommands: vec![
 | 
					                subcommands: vec![
 | 
				
			||||||
                    moderation_cmds::delete_macro(),
 | 
					                    moderation_cmds::delete_macro(),
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,5 @@
 | 
				
			|||||||
use poise::serenity::model::{
 | 
					use poise::serenity::model::{
 | 
				
			||||||
    application::interaction::application_command::CommandDataOption, id::GuildId,
 | 
					    id::GuildId, interactions::application_command::ApplicationCommandInteractionDataOption,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
use serde::{Deserialize, Serialize};
 | 
					use serde::{Deserialize, Serialize};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -19,7 +19,7 @@ pub struct RecordedCommand<U, E> {
 | 
				
			|||||||
    #[serde(default = "default_none::<U, E>")]
 | 
					    #[serde(default = "default_none::<U, E>")]
 | 
				
			||||||
    pub action: Option<Func<U, E>>,
 | 
					    pub action: Option<Func<U, E>>,
 | 
				
			||||||
    pub command_name: String,
 | 
					    pub command_name: String,
 | 
				
			||||||
    pub options: Vec<CommandDataOption>,
 | 
					    pub options: Vec<ApplicationCommandInteractionDataOption>,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub struct CommandMacro<U, E> {
 | 
					pub struct CommandMacro<U, E> {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -233,10 +233,6 @@ impl<'a> MultiReminderBuilder<'a> {
 | 
				
			|||||||
                            if let Some(guild_id) = self.guild_id {
 | 
					                            if let Some(guild_id) = self.guild_id {
 | 
				
			||||||
                                if guild_id.member(&self.ctx.discord(), user).await.is_err() {
 | 
					                                if guild_id.member(&self.ctx.discord(), user).await.is_err() {
 | 
				
			||||||
                                    Err(ReminderError::InvalidTag)
 | 
					                                    Err(ReminderError::InvalidTag)
 | 
				
			||||||
                                } else if self.set_by.map_or(true, |i| i != user_data.id)
 | 
					 | 
				
			||||||
                                    && !user_data.allowed_dm
 | 
					 | 
				
			||||||
                                {
 | 
					 | 
				
			||||||
                                    Err(ReminderError::UserBlockedDm)
 | 
					 | 
				
			||||||
                                } else {
 | 
					                                } else {
 | 
				
			||||||
                                    Ok(user_data.dm_channel)
 | 
					                                    Ok(user_data.dm_channel)
 | 
				
			||||||
                                }
 | 
					                                }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,7 +7,6 @@ pub enum ReminderError {
 | 
				
			|||||||
    PastTime,
 | 
					    PastTime,
 | 
				
			||||||
    ShortInterval,
 | 
					    ShortInterval,
 | 
				
			||||||
    InvalidTag,
 | 
					    InvalidTag,
 | 
				
			||||||
    UserBlockedDm,
 | 
					 | 
				
			||||||
    DiscordError(String),
 | 
					    DiscordError(String),
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -31,9 +30,6 @@ impl ToString for ReminderError {
 | 
				
			|||||||
            ReminderError::InvalidTag => {
 | 
					            ReminderError::InvalidTag => {
 | 
				
			||||||
                "Couldn't find a location by your tag. Your tag must be either a channel or a user (not a role)".to_string()
 | 
					                "Couldn't find a location by your tag. Your tag must be either a channel or a user (not a role)".to_string()
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            ReminderError::UserBlockedDm => {
 | 
					 | 
				
			||||||
                "User has DM reminders disabled".to_string()
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            ReminderError::DiscordError(s) => format!("A Discord error occurred: **{}**", s),
 | 
					            ReminderError::DiscordError(s) => format!("A Discord error occurred: **{}**", s),
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,7 +10,6 @@ pub struct UserData {
 | 
				
			|||||||
    pub user: u64,
 | 
					    pub user: u64,
 | 
				
			||||||
    pub dm_channel: u32,
 | 
					    pub dm_channel: u32,
 | 
				
			||||||
    pub timezone: String,
 | 
					    pub timezone: String,
 | 
				
			||||||
    pub allowed_dm: bool,
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl UserData {
 | 
					impl UserData {
 | 
				
			||||||
@@ -47,7 +46,7 @@ SELECT timezone FROM users WHERE user = ?
 | 
				
			|||||||
        match sqlx::query_as_unchecked!(
 | 
					        match sqlx::query_as_unchecked!(
 | 
				
			||||||
            Self,
 | 
					            Self,
 | 
				
			||||||
            "
 | 
					            "
 | 
				
			||||||
SELECT id, user, dm_channel, IF(timezone IS NULL, ?, timezone) AS timezone, allowed_dm FROM users WHERE user = ?
 | 
					SELECT id, user, dm_channel, IF(timezone IS NULL, ?, timezone) AS timezone FROM users WHERE user = ?
 | 
				
			||||||
            ",
 | 
					            ",
 | 
				
			||||||
            *LOCAL_TIMEZONE,
 | 
					            *LOCAL_TIMEZONE,
 | 
				
			||||||
            user_id.0
 | 
					            user_id.0
 | 
				
			||||||
@@ -84,7 +83,7 @@ INSERT INTO users (name, user, dm_channel, timezone) VALUES ('', ?, (SELECT id F
 | 
				
			|||||||
                Ok(sqlx::query_as_unchecked!(
 | 
					                Ok(sqlx::query_as_unchecked!(
 | 
				
			||||||
                    Self,
 | 
					                    Self,
 | 
				
			||||||
                    "
 | 
					                    "
 | 
				
			||||||
SELECT id, user, dm_channel, timezone, allowed_dm FROM users WHERE user = ?
 | 
					SELECT id, user, dm_channel, timezone FROM users WHERE user = ?
 | 
				
			||||||
                    ",
 | 
					                    ",
 | 
				
			||||||
                    user_id.0
 | 
					                    user_id.0
 | 
				
			||||||
                )
 | 
					                )
 | 
				
			||||||
@@ -103,10 +102,9 @@ SELECT id, user, dm_channel, timezone, allowed_dm FROM users WHERE user = ?
 | 
				
			|||||||
    pub async fn commit_changes(&self, pool: &MySqlPool) {
 | 
					    pub async fn commit_changes(&self, pool: &MySqlPool) {
 | 
				
			||||||
        sqlx::query!(
 | 
					        sqlx::query!(
 | 
				
			||||||
            "
 | 
					            "
 | 
				
			||||||
UPDATE users SET timezone = ?, allowed_dm = ? WHERE id = ?
 | 
					UPDATE users SET timezone = ? WHERE id = ?
 | 
				
			||||||
            ",
 | 
					            ",
 | 
				
			||||||
            self.timezone,
 | 
					            self.timezone,
 | 
				
			||||||
            self.allowed_dm,
 | 
					 | 
				
			||||||
            self.id
 | 
					            self.id
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        .execute(pool)
 | 
					        .execute(pool)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,7 +5,6 @@ use poise::{
 | 
				
			|||||||
        model::id::{GuildId, UserId},
 | 
					        model::id::{GuildId, UserId},
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    serenity_prelude as serenity,
 | 
					    serenity_prelude as serenity,
 | 
				
			||||||
    serenity_prelude::interaction::MessageFlags,
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::{
 | 
					use crate::{
 | 
				
			||||||
@@ -103,6 +102,6 @@ pub fn send_as_initial_response(
 | 
				
			|||||||
        });
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if ephemeral {
 | 
					    if ephemeral {
 | 
				
			||||||
        f.flags(MessageFlags::EPHEMERAL);
 | 
					        f.flags(serenity::InteractionApplicationCommandCallbackDataFlags::EPHEMERAL);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,10 +12,10 @@ oauth2 = "4"
 | 
				
			|||||||
log = "0.4"
 | 
					log = "0.4"
 | 
				
			||||||
reqwest = "0.11"
 | 
					reqwest = "0.11"
 | 
				
			||||||
serde = { version = "1.0", features = ["derive"] }
 | 
					serde = { version = "1.0", features = ["derive"] }
 | 
				
			||||||
 | 
					serde_json = "1.0"
 | 
				
			||||||
sqlx = { version = "0.5", features = ["runtime-tokio-rustls", "macros", "mysql", "chrono", "json"] }
 | 
					sqlx = { version = "0.5", features = ["runtime-tokio-rustls", "macros", "mysql", "chrono", "json"] }
 | 
				
			||||||
chrono = "0.4"
 | 
					chrono = "0.4"
 | 
				
			||||||
chrono-tz = "0.5"
 | 
					chrono-tz = "0.5"
 | 
				
			||||||
lazy_static = "1.4.0"
 | 
					lazy_static = "1.4.0"
 | 
				
			||||||
rand = "0.7"
 | 
					rand = "0.7"
 | 
				
			||||||
base64 = "0.13"
 | 
					base64 = "0.13"
 | 
				
			||||||
csv = "1.1"
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -26,8 +26,12 @@ use serenity::model::prelude::AttachmentType;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
lazy_static! {
 | 
					lazy_static! {
 | 
				
			||||||
    pub static ref DEFAULT_AVATAR: AttachmentType<'static> = (
 | 
					    pub static ref DEFAULT_AVATAR: AttachmentType<'static> = (
 | 
				
			||||||
        include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/../assets/webhook.jpg")) as &[u8],
 | 
					        include_bytes!(concat!(
 | 
				
			||||||
        "webhook.jpg",
 | 
					            env!("CARGO_MANIFEST_DIR"),
 | 
				
			||||||
 | 
					            "/../assets/",
 | 
				
			||||||
 | 
					            env!("WEBHOOK_AVATAR", "WEBHOOK_AVATAR not provided for compilation")
 | 
				
			||||||
 | 
					        )) as &[u8],
 | 
				
			||||||
 | 
					        env!("WEBHOOK_AVATAR"),
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
        .into();
 | 
					        .into();
 | 
				
			||||||
    pub static ref SUBSCRIPTION_ROLES: HashSet<u64> = HashSet::from_iter(
 | 
					    pub static ref SUBSCRIPTION_ROLES: HashSet<u64> = HashSet::from_iter(
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -146,15 +146,10 @@ pub async fn initialize(
 | 
				
			|||||||
                routes::dashboard::guild::get_reminder_templates,
 | 
					                routes::dashboard::guild::get_reminder_templates,
 | 
				
			||||||
                routes::dashboard::guild::create_reminder_template,
 | 
					                routes::dashboard::guild::create_reminder_template,
 | 
				
			||||||
                routes::dashboard::guild::delete_reminder_template,
 | 
					                routes::dashboard::guild::delete_reminder_template,
 | 
				
			||||||
                routes::dashboard::guild::create_guild_reminder,
 | 
					                routes::dashboard::guild::create_reminder,
 | 
				
			||||||
                routes::dashboard::guild::get_reminders,
 | 
					                routes::dashboard::guild::get_reminders,
 | 
				
			||||||
                routes::dashboard::guild::edit_reminder,
 | 
					                routes::dashboard::guild::edit_reminder,
 | 
				
			||||||
                routes::dashboard::guild::delete_reminder,
 | 
					                routes::dashboard::guild::delete_reminder,
 | 
				
			||||||
                routes::dashboard::export::export_reminders,
 | 
					 | 
				
			||||||
                routes::dashboard::export::export_reminder_templates,
 | 
					 | 
				
			||||||
                routes::dashboard::export::export_todos,
 | 
					 | 
				
			||||||
                routes::dashboard::export::import_reminders,
 | 
					 | 
				
			||||||
                routes::dashboard::export::import_todos,
 | 
					 | 
				
			||||||
            ],
 | 
					            ],
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        .launch()
 | 
					        .launch()
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,7 @@
 | 
				
			|||||||
macro_rules! check_length {
 | 
					macro_rules! check_length {
 | 
				
			||||||
    ($max:ident, $field:expr) => {
 | 
					    ($max:ident, $field:expr) => {
 | 
				
			||||||
        if $field.len() > $max {
 | 
					        if $field.len() > $max {
 | 
				
			||||||
            return Err(json!({ "error": format!("{} exceeded", stringify!($max)) }));
 | 
					            return json!({ "error": format!("{} exceeded", stringify!($max)) });
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
    ($max:ident, $field:expr, $($fields:expr),+) => {
 | 
					    ($max:ident, $field:expr, $($fields:expr),+) => {
 | 
				
			||||||
@@ -25,7 +25,7 @@ macro_rules! check_length_opt {
 | 
				
			|||||||
macro_rules! check_url {
 | 
					macro_rules! check_url {
 | 
				
			||||||
    ($field:expr) => {
 | 
					    ($field:expr) => {
 | 
				
			||||||
        if !($field.starts_with("http://") || $field.starts_with("https://")) {
 | 
					        if !($field.starts_with("http://") || $field.starts_with("https://")) {
 | 
				
			||||||
            return Err(json!({ "error": "URL invalid" }));
 | 
					            return json!({ "error": "URL invalid" });
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
    ($field:expr, $($fields:expr),+) => {
 | 
					    ($field:expr, $($fields:expr),+) => {
 | 
				
			||||||
@@ -60,7 +60,7 @@ macro_rules! check_authorization {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                        match member {
 | 
					                        match member {
 | 
				
			||||||
                            Err(_) => {
 | 
					                            Err(_) => {
 | 
				
			||||||
                                return Err(json!({"error": "User not in guild"}));
 | 
					                                return json!({"error": "User not in guild"})
 | 
				
			||||||
                            }
 | 
					                            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                            Ok(_) => {}
 | 
					                            Ok(_) => {}
 | 
				
			||||||
@@ -68,13 +68,13 @@ macro_rules! check_authorization {
 | 
				
			|||||||
                    }
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    None => {
 | 
					                    None => {
 | 
				
			||||||
                        return Err(json!({"error": "Bot not in guild"}));
 | 
					                        return json!({"error": "Bot not in guild"})
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            None => {
 | 
					            None => {
 | 
				
			||||||
                return Err(json!({"error": "User not authorized"}));
 | 
					                return json!({"error": "User not authorized"});
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -117,9 +117,3 @@ macro_rules! update_field {
 | 
				
			|||||||
        update_field!($pool, $error, $reminder.[$($fields),+]);
 | 
					        update_field!($pool, $error, $reminder.[$($fields),+]);
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
macro_rules! json_err {
 | 
					 | 
				
			||||||
    ($message:expr) => {
 | 
					 | 
				
			||||||
        Err(json!({ "error": $message }))
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,430 +0,0 @@
 | 
				
			|||||||
use csv::{QuoteStyle, WriterBuilder};
 | 
					 | 
				
			||||||
use rocket::{
 | 
					 | 
				
			||||||
    http::CookieJar,
 | 
					 | 
				
			||||||
    serde::json::{json, serde_json, Json},
 | 
					 | 
				
			||||||
    State,
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
use serenity::{
 | 
					 | 
				
			||||||
    client::Context,
 | 
					 | 
				
			||||||
    model::id::{ChannelId, GuildId},
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
use sqlx::{MySql, Pool};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
use crate::routes::dashboard::{
 | 
					 | 
				
			||||||
    create_reminder, generate_uid, ImportBody, JsonResult, Reminder, ReminderCsv,
 | 
					 | 
				
			||||||
    ReminderTemplateCsv, TodoCsv,
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[get("/api/guild/<id>/export/reminders")]
 | 
					 | 
				
			||||||
pub async fn export_reminders(
 | 
					 | 
				
			||||||
    id: u64,
 | 
					 | 
				
			||||||
    cookies: &CookieJar<'_>,
 | 
					 | 
				
			||||||
    ctx: &State<Context>,
 | 
					 | 
				
			||||||
    pool: &State<Pool<MySql>>,
 | 
					 | 
				
			||||||
) -> JsonResult {
 | 
					 | 
				
			||||||
    check_authorization!(cookies, ctx.inner(), id);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let mut csv_writer = WriterBuilder::new().quote_style(QuoteStyle::Always).from_writer(vec![]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let channels_res = GuildId(id).channels(&ctx.inner()).await;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    match channels_res {
 | 
					 | 
				
			||||||
        Ok(channels) => {
 | 
					 | 
				
			||||||
            let channels = channels
 | 
					 | 
				
			||||||
                .keys()
 | 
					 | 
				
			||||||
                .into_iter()
 | 
					 | 
				
			||||||
                .map(|k| k.as_u64().to_string())
 | 
					 | 
				
			||||||
                .collect::<Vec<String>>()
 | 
					 | 
				
			||||||
                .join(",");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            let result = sqlx::query_as_unchecked!(
 | 
					 | 
				
			||||||
                ReminderCsv,
 | 
					 | 
				
			||||||
                "SELECT
 | 
					 | 
				
			||||||
                 reminders.attachment,
 | 
					 | 
				
			||||||
                 reminders.attachment_name,
 | 
					 | 
				
			||||||
                 reminders.avatar,
 | 
					 | 
				
			||||||
                 CONCAT('#', channels.channel) AS channel,
 | 
					 | 
				
			||||||
                 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_months,
 | 
					 | 
				
			||||||
                 reminders.name,
 | 
					 | 
				
			||||||
                 reminders.restartable,
 | 
					 | 
				
			||||||
                 reminders.tts,
 | 
					 | 
				
			||||||
                 reminders.username,
 | 
					 | 
				
			||||||
                 reminders.utc_time
 | 
					 | 
				
			||||||
                FROM reminders
 | 
					 | 
				
			||||||
                LEFT JOIN channels ON channels.id = reminders.channel_id
 | 
					 | 
				
			||||||
                WHERE FIND_IN_SET(channels.channel, ?)",
 | 
					 | 
				
			||||||
                channels
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
            .fetch_all(pool.inner())
 | 
					 | 
				
			||||||
            .await;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            match result {
 | 
					 | 
				
			||||||
                Ok(reminders) => {
 | 
					 | 
				
			||||||
                    reminders.iter().for_each(|reminder| {
 | 
					 | 
				
			||||||
                        csv_writer.serialize(reminder).unwrap();
 | 
					 | 
				
			||||||
                    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    match csv_writer.into_inner() {
 | 
					 | 
				
			||||||
                        Ok(inner) => match String::from_utf8(inner) {
 | 
					 | 
				
			||||||
                            Ok(encoded) => Ok(json!({ "body": encoded })),
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                            Err(e) => {
 | 
					 | 
				
			||||||
                                warn!("Failed to write UTF-8: {:?}", e);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                                Err(json!({"error": "Failed to write UTF-8"}))
 | 
					 | 
				
			||||||
                            }
 | 
					 | 
				
			||||||
                        },
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                        Err(e) => {
 | 
					 | 
				
			||||||
                            warn!("Failed to extract CSV: {:?}", e);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                            Err(json!({"error": "Failed to extract CSV"}))
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                Err(e) => {
 | 
					 | 
				
			||||||
                    warn!("Failed to complete SQL query: {:?}", e);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    Err(json!({"error": "Failed to query reminders"}))
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        Err(e) => {
 | 
					 | 
				
			||||||
            warn!("Could not fetch channels from {}: {:?}", id, e);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            Err(json!({"error": "Failed to get guild channels"}))
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[put("/api/guild/<id>/export/reminders", data = "<body>")]
 | 
					 | 
				
			||||||
pub async fn import_reminders(
 | 
					 | 
				
			||||||
    id: u64,
 | 
					 | 
				
			||||||
    cookies: &CookieJar<'_>,
 | 
					 | 
				
			||||||
    body: Json<ImportBody>,
 | 
					 | 
				
			||||||
    ctx: &State<Context>,
 | 
					 | 
				
			||||||
    pool: &State<Pool<MySql>>,
 | 
					 | 
				
			||||||
) -> JsonResult {
 | 
					 | 
				
			||||||
    check_authorization!(cookies, ctx.inner(), id);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let user_id =
 | 
					 | 
				
			||||||
        cookies.get_private("userid").map(|c| c.value().parse::<u64>().ok()).flatten().unwrap();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    match base64::decode(&body.body) {
 | 
					 | 
				
			||||||
        Ok(body) => {
 | 
					 | 
				
			||||||
            let mut reader = csv::Reader::from_reader(body.as_slice());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            for result in reader.deserialize::<ReminderCsv>() {
 | 
					 | 
				
			||||||
                match result {
 | 
					 | 
				
			||||||
                    Ok(record) => {
 | 
					 | 
				
			||||||
                        let channel_id = record.channel.split_at(1).1;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                        match channel_id.parse::<u64>() {
 | 
					 | 
				
			||||||
                            Ok(channel_id) => {
 | 
					 | 
				
			||||||
                                let reminder = Reminder {
 | 
					 | 
				
			||||||
                                    attachment: record.attachment,
 | 
					 | 
				
			||||||
                                    attachment_name: record.attachment_name,
 | 
					 | 
				
			||||||
                                    avatar: record.avatar,
 | 
					 | 
				
			||||||
                                    channel: channel_id,
 | 
					 | 
				
			||||||
                                    content: record.content,
 | 
					 | 
				
			||||||
                                    embed_author: record.embed_author,
 | 
					 | 
				
			||||||
                                    embed_author_url: record.embed_author_url,
 | 
					 | 
				
			||||||
                                    embed_color: record.embed_color,
 | 
					 | 
				
			||||||
                                    embed_description: record.embed_description,
 | 
					 | 
				
			||||||
                                    embed_footer: record.embed_footer,
 | 
					 | 
				
			||||||
                                    embed_footer_url: record.embed_footer_url,
 | 
					 | 
				
			||||||
                                    embed_image_url: record.embed_image_url,
 | 
					 | 
				
			||||||
                                    embed_thumbnail_url: record.embed_thumbnail_url,
 | 
					 | 
				
			||||||
                                    embed_title: record.embed_title,
 | 
					 | 
				
			||||||
                                    embed_fields: record
 | 
					 | 
				
			||||||
                                        .embed_fields
 | 
					 | 
				
			||||||
                                        .map(|s| serde_json::from_str(&s).ok())
 | 
					 | 
				
			||||||
                                        .flatten(),
 | 
					 | 
				
			||||||
                                    enabled: record.enabled,
 | 
					 | 
				
			||||||
                                    expires: record.expires,
 | 
					 | 
				
			||||||
                                    interval_seconds: record.interval_seconds,
 | 
					 | 
				
			||||||
                                    interval_months: record.interval_months,
 | 
					 | 
				
			||||||
                                    name: record.name,
 | 
					 | 
				
			||||||
                                    restartable: record.restartable,
 | 
					 | 
				
			||||||
                                    tts: record.tts,
 | 
					 | 
				
			||||||
                                    uid: generate_uid(),
 | 
					 | 
				
			||||||
                                    username: record.username,
 | 
					 | 
				
			||||||
                                    utc_time: record.utc_time,
 | 
					 | 
				
			||||||
                                };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                                create_reminder(
 | 
					 | 
				
			||||||
                                    ctx.inner(),
 | 
					 | 
				
			||||||
                                    pool.inner(),
 | 
					 | 
				
			||||||
                                    GuildId(id),
 | 
					 | 
				
			||||||
                                    UserId(user_id),
 | 
					 | 
				
			||||||
                                    reminder,
 | 
					 | 
				
			||||||
                                )
 | 
					 | 
				
			||||||
                                .await?;
 | 
					 | 
				
			||||||
                            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                            Err(_) => {
 | 
					 | 
				
			||||||
                                return json_err!(format!(
 | 
					 | 
				
			||||||
                                    "Failed to parse channel {}",
 | 
					 | 
				
			||||||
                                    channel_id
 | 
					 | 
				
			||||||
                                ));
 | 
					 | 
				
			||||||
                            }
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    Err(e) => {
 | 
					 | 
				
			||||||
                        warn!("Couldn't deserialize CSV row: {:?}", e);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                        return json_err!("Deserialize error. Aborted");
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            Ok(json!({}))
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        Err(_) => {
 | 
					 | 
				
			||||||
            json_err!("Malformed base64")
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[get("/api/guild/<id>/export/todos")]
 | 
					 | 
				
			||||||
pub async fn export_todos(
 | 
					 | 
				
			||||||
    id: u64,
 | 
					 | 
				
			||||||
    cookies: &CookieJar<'_>,
 | 
					 | 
				
			||||||
    ctx: &State<Context>,
 | 
					 | 
				
			||||||
    pool: &State<Pool<MySql>>,
 | 
					 | 
				
			||||||
) -> JsonResult {
 | 
					 | 
				
			||||||
    check_authorization!(cookies, ctx.inner(), id);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let mut csv_writer = WriterBuilder::new().quote_style(QuoteStyle::Always).from_writer(vec![]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    match sqlx::query_as_unchecked!(
 | 
					 | 
				
			||||||
        TodoCsv,
 | 
					 | 
				
			||||||
        "SELECT value, CONCAT('#', channels.channel) AS channel_id FROM todos
 | 
					 | 
				
			||||||
        LEFT JOIN channels ON todos.channel_id = channels.id
 | 
					 | 
				
			||||||
        INNER JOIN guilds ON todos.guild_id = guilds.id
 | 
					 | 
				
			||||||
        WHERE guilds.guild = ?",
 | 
					 | 
				
			||||||
        id
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    .fetch_all(pool.inner())
 | 
					 | 
				
			||||||
    .await
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        Ok(todos) => {
 | 
					 | 
				
			||||||
            todos.iter().for_each(|todo| {
 | 
					 | 
				
			||||||
                csv_writer.serialize(todo).unwrap();
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            match csv_writer.into_inner() {
 | 
					 | 
				
			||||||
                Ok(inner) => match String::from_utf8(inner) {
 | 
					 | 
				
			||||||
                    Ok(encoded) => Ok(json!({ "body": encoded })),
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    Err(e) => {
 | 
					 | 
				
			||||||
                        warn!("Failed to write UTF-8: {:?}", e);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                        json_err!("Failed to write UTF-8")
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                },
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                Err(e) => {
 | 
					 | 
				
			||||||
                    warn!("Failed to extract CSV: {:?}", e);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    json_err!("Failed to extract CSV")
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        Err(e) => {
 | 
					 | 
				
			||||||
            warn!("Could not fetch templates from {}: {:?}", id, e);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            json_err!("Failed to query templates")
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[put("/api/guild/<id>/export/todos", data = "<body>")]
 | 
					 | 
				
			||||||
pub async fn import_todos(
 | 
					 | 
				
			||||||
    id: u64,
 | 
					 | 
				
			||||||
    cookies: &CookieJar<'_>,
 | 
					 | 
				
			||||||
    body: Json<ImportBody>,
 | 
					 | 
				
			||||||
    ctx: &State<Context>,
 | 
					 | 
				
			||||||
    pool: &State<Pool<MySql>>,
 | 
					 | 
				
			||||||
) -> JsonResult {
 | 
					 | 
				
			||||||
    check_authorization!(cookies, ctx.inner(), id);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let channels_res = GuildId(id).channels(&ctx.inner()).await;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    match channels_res {
 | 
					 | 
				
			||||||
        Ok(channels) => match base64::decode(&body.body) {
 | 
					 | 
				
			||||||
            Ok(body) => {
 | 
					 | 
				
			||||||
                let mut reader = csv::Reader::from_reader(body.as_slice());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                let query_placeholder = "(?, (SELECT id FROM channels WHERE channel = ?), (SELECT id FROM guilds WHERE guild = ?))";
 | 
					 | 
				
			||||||
                let mut query_params = vec![];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                for result in reader.deserialize::<TodoCsv>() {
 | 
					 | 
				
			||||||
                    match result {
 | 
					 | 
				
			||||||
                        Ok(record) => match record.channel_id {
 | 
					 | 
				
			||||||
                            Some(channel_id) => {
 | 
					 | 
				
			||||||
                                let channel_id = channel_id.split_at(1).1;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                                match channel_id.parse::<u64>() {
 | 
					 | 
				
			||||||
                                    Ok(channel_id) => {
 | 
					 | 
				
			||||||
                                        if channels.contains_key(&ChannelId(channel_id)) {
 | 
					 | 
				
			||||||
                                            query_params.push((record.value, Some(channel_id), id));
 | 
					 | 
				
			||||||
                                        } else {
 | 
					 | 
				
			||||||
                                            return json_err!(format!(
 | 
					 | 
				
			||||||
                                                "Invalid channel ID {}",
 | 
					 | 
				
			||||||
                                                channel_id
 | 
					 | 
				
			||||||
                                            ));
 | 
					 | 
				
			||||||
                                        }
 | 
					 | 
				
			||||||
                                    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                                    Err(_) => {
 | 
					 | 
				
			||||||
                                        return json_err!(format!(
 | 
					 | 
				
			||||||
                                            "Invalid channel ID {}",
 | 
					 | 
				
			||||||
                                            channel_id
 | 
					 | 
				
			||||||
                                        ));
 | 
					 | 
				
			||||||
                                    }
 | 
					 | 
				
			||||||
                                }
 | 
					 | 
				
			||||||
                            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                            None => {
 | 
					 | 
				
			||||||
                                query_params.push((record.value, None, id));
 | 
					 | 
				
			||||||
                            }
 | 
					 | 
				
			||||||
                        },
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                        Err(e) => {
 | 
					 | 
				
			||||||
                            warn!("Couldn't deserialize CSV row: {:?}", e);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                            return json_err!("Deserialize error. Aborted");
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                let _ = sqlx::query!(
 | 
					 | 
				
			||||||
                    "DELETE FROM todos WHERE guild_id = (SELECT id FROM guilds WHERE guild = ?)",
 | 
					 | 
				
			||||||
                    id
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
                .execute(pool.inner())
 | 
					 | 
				
			||||||
                .await;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                let query_str = format!(
 | 
					 | 
				
			||||||
                    "INSERT INTO todos (value, channel_id, guild_id) VALUES {}",
 | 
					 | 
				
			||||||
                    vec![query_placeholder].repeat(query_params.len()).join(",")
 | 
					 | 
				
			||||||
                );
 | 
					 | 
				
			||||||
                let mut query = sqlx::query(&query_str);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                for param in query_params {
 | 
					 | 
				
			||||||
                    query = query.bind(param.0).bind(param.1).bind(param.2);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                let res = query.execute(pool.inner()).await;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                match res {
 | 
					 | 
				
			||||||
                    Ok(_) => Ok(json!({})),
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    Err(e) => {
 | 
					 | 
				
			||||||
                        warn!("Couldn't execute todo query: {:?}", e);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                        json_err!("An unexpected error occured.")
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            Err(_) => {
 | 
					 | 
				
			||||||
                json_err!("Malformed base64")
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        Err(e) => {
 | 
					 | 
				
			||||||
            warn!("Couldn't fetch channels for guild {}: {:?}", id, e);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            json_err!("Couldn't fetch channels.")
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[get("/api/guild/<id>/export/reminder_templates")]
 | 
					 | 
				
			||||||
pub async fn export_reminder_templates(
 | 
					 | 
				
			||||||
    id: u64,
 | 
					 | 
				
			||||||
    cookies: &CookieJar<'_>,
 | 
					 | 
				
			||||||
    ctx: &State<Context>,
 | 
					 | 
				
			||||||
    pool: &State<Pool<MySql>>,
 | 
					 | 
				
			||||||
) -> JsonResult {
 | 
					 | 
				
			||||||
    check_authorization!(cookies, ctx.inner(), id);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let mut csv_writer = WriterBuilder::new().quote_style(QuoteStyle::Always).from_writer(vec![]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    match sqlx::query_as_unchecked!(
 | 
					 | 
				
			||||||
        ReminderTemplateCsv,
 | 
					 | 
				
			||||||
        "SELECT
 | 
					 | 
				
			||||||
         name,
 | 
					 | 
				
			||||||
         attachment,
 | 
					 | 
				
			||||||
         attachment_name,
 | 
					 | 
				
			||||||
         avatar,
 | 
					 | 
				
			||||||
         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,
 | 
					 | 
				
			||||||
         tts,
 | 
					 | 
				
			||||||
         username
 | 
					 | 
				
			||||||
        FROM reminder_template WHERE guild_id = (SELECT id FROM guilds WHERE guild = ?)",
 | 
					 | 
				
			||||||
        id
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    .fetch_all(pool.inner())
 | 
					 | 
				
			||||||
    .await
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        Ok(templates) => {
 | 
					 | 
				
			||||||
            templates.iter().for_each(|template| {
 | 
					 | 
				
			||||||
                csv_writer.serialize(template).unwrap();
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            match csv_writer.into_inner() {
 | 
					 | 
				
			||||||
                Ok(inner) => match String::from_utf8(inner) {
 | 
					 | 
				
			||||||
                    Ok(encoded) => Ok(json!({ "body": encoded })),
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    Err(e) => {
 | 
					 | 
				
			||||||
                        warn!("Failed to write UTF-8: {:?}", e);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                        json_err!("Failed to write UTF-8")
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                },
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                Err(e) => {
 | 
					 | 
				
			||||||
                    warn!("Failed to extract CSV: {:?}", e);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    json_err!("Failed to extract CSV")
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        Err(e) => {
 | 
					 | 
				
			||||||
            warn!("Could not fetch templates from {}: {:?}", id, e);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            json_err!("Failed to query templates")
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,8 +1,10 @@
 | 
				
			|||||||
use std::env;
 | 
					use std::env;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use base64;
 | 
				
			||||||
 | 
					use chrono::Utc;
 | 
				
			||||||
use rocket::{
 | 
					use rocket::{
 | 
				
			||||||
    http::CookieJar,
 | 
					    http::CookieJar,
 | 
				
			||||||
    serde::json::{json, Json},
 | 
					    serde::json::{json, Json, Value as JsonValue},
 | 
				
			||||||
    State,
 | 
					    State,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
use serde::Serialize;
 | 
					use serde::Serialize;
 | 
				
			||||||
@@ -16,14 +18,16 @@ use serenity::{
 | 
				
			|||||||
use sqlx::{MySql, Pool};
 | 
					use sqlx::{MySql, Pool};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::{
 | 
					use crate::{
 | 
				
			||||||
 | 
					    check_guild_subscription, check_subscription,
 | 
				
			||||||
    consts::{
 | 
					    consts::{
 | 
				
			||||||
        MAX_CONTENT_LENGTH, MAX_EMBED_AUTHOR_LENGTH, MAX_EMBED_DESCRIPTION_LENGTH,
 | 
					        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_FIELDS, MAX_EMBED_FIELD_TITLE_LENGTH, MAX_EMBED_FIELD_VALUE_LENGTH,
 | 
				
			||||||
        MAX_EMBED_FOOTER_LENGTH, MAX_EMBED_TITLE_LENGTH, MAX_URL_LENGTH, MAX_USERNAME_LENGTH,
 | 
					        MAX_EMBED_FOOTER_LENGTH, MAX_EMBED_TITLE_LENGTH, MAX_URL_LENGTH, MAX_USERNAME_LENGTH,
 | 
				
			||||||
 | 
					        MIN_INTERVAL,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    routes::dashboard::{
 | 
					    routes::dashboard::{
 | 
				
			||||||
        create_database_channel, create_reminder, template_name_default, DeleteReminder,
 | 
					        create_database_channel, generate_uid, name_default, template_name_default, DeleteReminder,
 | 
				
			||||||
        DeleteReminderTemplate, JsonResult, PatchReminder, Reminder, ReminderTemplate,
 | 
					        DeleteReminderTemplate, PatchReminder, Reminder, ReminderTemplate,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -40,7 +44,7 @@ pub async fn get_guild_patreon(
 | 
				
			|||||||
    id: u64,
 | 
					    id: u64,
 | 
				
			||||||
    cookies: &CookieJar<'_>,
 | 
					    cookies: &CookieJar<'_>,
 | 
				
			||||||
    ctx: &State<Context>,
 | 
					    ctx: &State<Context>,
 | 
				
			||||||
) -> JsonResult {
 | 
					) -> JsonValue {
 | 
				
			||||||
    check_authorization!(cookies, ctx.inner(), id);
 | 
					    check_authorization!(cookies, ctx.inner(), id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    match GuildId(id).to_guild_cached(ctx.inner()) {
 | 
					    match GuildId(id).to_guild_cached(ctx.inner()) {
 | 
				
			||||||
@@ -55,10 +59,12 @@ pub async fn get_guild_patreon(
 | 
				
			|||||||
                    .contains(&RoleId(env::var("PATREON_ROLE_ID").unwrap().parse().unwrap()))
 | 
					                    .contains(&RoleId(env::var("PATREON_ROLE_ID").unwrap().parse().unwrap()))
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            Ok(json!({ "patreon": patreon }))
 | 
					            json!({ "patreon": patreon })
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        None => json_err!("Bot not in guild"),
 | 
					        None => {
 | 
				
			||||||
 | 
					            json!({"error": "Bot not in guild"})
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -67,7 +73,7 @@ pub async fn get_guild_channels(
 | 
				
			|||||||
    id: u64,
 | 
					    id: u64,
 | 
				
			||||||
    cookies: &CookieJar<'_>,
 | 
					    cookies: &CookieJar<'_>,
 | 
				
			||||||
    ctx: &State<Context>,
 | 
					    ctx: &State<Context>,
 | 
				
			||||||
) -> JsonResult {
 | 
					) -> JsonValue {
 | 
				
			||||||
    check_authorization!(cookies, ctx.inner(), id);
 | 
					    check_authorization!(cookies, ctx.inner(), id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    match GuildId(id).to_guild_cached(ctx.inner()) {
 | 
					    match GuildId(id).to_guild_cached(ctx.inner()) {
 | 
				
			||||||
@@ -91,10 +97,12 @@ pub async fn get_guild_channels(
 | 
				
			|||||||
                })
 | 
					                })
 | 
				
			||||||
                .collect::<Vec<ChannelInfo>>();
 | 
					                .collect::<Vec<ChannelInfo>>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            Ok(json!(channel_info))
 | 
					            json!(channel_info)
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        None => json_err!("Bot not in guild"),
 | 
					        None => {
 | 
				
			||||||
 | 
					            json!({"error": "Bot not in guild"})
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -105,7 +113,7 @@ struct RoleInfo {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[get("/api/guild/<id>/roles")]
 | 
					#[get("/api/guild/<id>/roles")]
 | 
				
			||||||
pub async fn get_guild_roles(id: u64, cookies: &CookieJar<'_>, ctx: &State<Context>) -> JsonResult {
 | 
					pub async fn get_guild_roles(id: u64, cookies: &CookieJar<'_>, ctx: &State<Context>) -> JsonValue {
 | 
				
			||||||
    check_authorization!(cookies, ctx.inner(), id);
 | 
					    check_authorization!(cookies, ctx.inner(), id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let roles_res = ctx.cache.guild_roles(id);
 | 
					    let roles_res = ctx.cache.guild_roles(id);
 | 
				
			||||||
@@ -117,12 +125,12 @@ pub async fn get_guild_roles(id: u64, cookies: &CookieJar<'_>, ctx: &State<Conte
 | 
				
			|||||||
                .map(|(_, r)| RoleInfo { id: r.id.to_string(), name: r.name.to_string() })
 | 
					                .map(|(_, r)| RoleInfo { id: r.id.to_string(), name: r.name.to_string() })
 | 
				
			||||||
                .collect::<Vec<RoleInfo>>();
 | 
					                .collect::<Vec<RoleInfo>>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            Ok(json!(roles))
 | 
					            json!(roles)
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        None => {
 | 
					        None => {
 | 
				
			||||||
            warn!("Could not fetch roles from {}", id);
 | 
					            warn!("Could not fetch roles from {}", id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            json_err!("Could not get roles")
 | 
					            json!({"error": "Could not get roles"})
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -133,7 +141,7 @@ pub async fn get_reminder_templates(
 | 
				
			|||||||
    cookies: &CookieJar<'_>,
 | 
					    cookies: &CookieJar<'_>,
 | 
				
			||||||
    ctx: &State<Context>,
 | 
					    ctx: &State<Context>,
 | 
				
			||||||
    pool: &State<Pool<MySql>>,
 | 
					    pool: &State<Pool<MySql>>,
 | 
				
			||||||
) -> JsonResult {
 | 
					) -> JsonValue {
 | 
				
			||||||
    check_authorization!(cookies, ctx.inner(), id);
 | 
					    check_authorization!(cookies, ctx.inner(), id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    match sqlx::query_as_unchecked!(
 | 
					    match sqlx::query_as_unchecked!(
 | 
				
			||||||
@@ -144,11 +152,13 @@ pub async fn get_reminder_templates(
 | 
				
			|||||||
    .fetch_all(pool.inner())
 | 
					    .fetch_all(pool.inner())
 | 
				
			||||||
    .await
 | 
					    .await
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        Ok(templates) => Ok(json!(templates)),
 | 
					        Ok(templates) => {
 | 
				
			||||||
 | 
					            json!(templates)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        Err(e) => {
 | 
					        Err(e) => {
 | 
				
			||||||
            warn!("Could not fetch templates from {}: {:?}", id, e);
 | 
					            warn!("Could not fetch templates from {}: {:?}", id, e);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            json_err!("Could not get templates")
 | 
					            json!({"error": "Could not get templates"})
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -160,7 +170,7 @@ pub async fn create_reminder_template(
 | 
				
			|||||||
    cookies: &CookieJar<'_>,
 | 
					    cookies: &CookieJar<'_>,
 | 
				
			||||||
    ctx: &State<Context>,
 | 
					    ctx: &State<Context>,
 | 
				
			||||||
    pool: &State<Pool<MySql>>,
 | 
					    pool: &State<Pool<MySql>>,
 | 
				
			||||||
) -> JsonResult {
 | 
					) -> JsonValue {
 | 
				
			||||||
    check_authorization!(cookies, ctx.inner(), id);
 | 
					    check_authorization!(cookies, ctx.inner(), id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // validate lengths
 | 
					    // validate lengths
 | 
				
			||||||
@@ -244,12 +254,12 @@ pub async fn create_reminder_template(
 | 
				
			|||||||
    .await
 | 
					    .await
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        Ok(_) => {
 | 
					        Ok(_) => {
 | 
				
			||||||
            Ok(json!({}))
 | 
					            json!({})
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        Err(e) => {
 | 
					        Err(e) => {
 | 
				
			||||||
            warn!("Could not fetch templates from {}: {:?}", id, e);
 | 
					            warn!("Could not fetch templates from {}: {:?}", id, e);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            json_err!("Could not get templates")
 | 
					            json!({"error": "Could not get templates"})
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -261,7 +271,7 @@ pub async fn delete_reminder_template(
 | 
				
			|||||||
    cookies: &CookieJar<'_>,
 | 
					    cookies: &CookieJar<'_>,
 | 
				
			||||||
    ctx: &State<Context>,
 | 
					    ctx: &State<Context>,
 | 
				
			||||||
    pool: &State<Pool<MySql>>,
 | 
					    pool: &State<Pool<MySql>>,
 | 
				
			||||||
) -> JsonResult {
 | 
					) -> JsonValue {
 | 
				
			||||||
    check_authorization!(cookies, ctx.inner(), id);
 | 
					    check_authorization!(cookies, ctx.inner(), id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    match sqlx::query!(
 | 
					    match sqlx::query!(
 | 
				
			||||||
@@ -272,41 +282,230 @@ pub async fn delete_reminder_template(
 | 
				
			|||||||
    .await
 | 
					    .await
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        Ok(_) => {
 | 
					        Ok(_) => {
 | 
				
			||||||
            Ok(json!({}))
 | 
					            json!({})
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        Err(e) => {
 | 
					        Err(e) => {
 | 
				
			||||||
            warn!("Could not delete template from {}: {:?}", id, e);
 | 
					            warn!("Could not delete template from {}: {:?}", id, e);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            json_err!("Could not delete template")
 | 
					            json!({"error": "Could not delete template"})
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[post("/api/guild/<id>/reminders", data = "<reminder>")]
 | 
					#[post("/api/guild/<id>/reminders", data = "<reminder>")]
 | 
				
			||||||
pub async fn create_guild_reminder(
 | 
					pub async fn create_reminder(
 | 
				
			||||||
    id: u64,
 | 
					    id: u64,
 | 
				
			||||||
    reminder: Json<Reminder>,
 | 
					    reminder: Json<Reminder>,
 | 
				
			||||||
    cookies: &CookieJar<'_>,
 | 
					    cookies: &CookieJar<'_>,
 | 
				
			||||||
    serenity_context: &State<Context>,
 | 
					    serenity_context: &State<Context>,
 | 
				
			||||||
    pool: &State<Pool<MySql>>,
 | 
					    pool: &State<Pool<MySql>>,
 | 
				
			||||||
) -> JsonResult {
 | 
					) -> JsonValue {
 | 
				
			||||||
    check_authorization!(cookies, serenity_context.inner(), id);
 | 
					    check_authorization!(cookies, serenity_context.inner(), id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let user_id =
 | 
					    let user_id =
 | 
				
			||||||
        cookies.get_private("userid").map(|c| c.value().parse::<u64>().ok()).flatten().unwrap();
 | 
					        cookies.get_private("userid").map(|c| c.value().parse::<u64>().ok()).flatten().unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    create_reminder(
 | 
					    // validate channel
 | 
				
			||||||
 | 
					    let channel = ChannelId(reminder.channel).to_channel_cached(&serenity_context.inner());
 | 
				
			||||||
 | 
					    let channel_exists = channel.is_some();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let channel_matches_guild =
 | 
				
			||||||
 | 
					        channel.map_or(false, |c| c.guild().map_or(false, |c| c.guild_id.0 == id));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if !channel_matches_guild || !channel_exists {
 | 
				
			||||||
 | 
					        warn!(
 | 
				
			||||||
 | 
					            "Error in `create_reminder`: channel {} not found for guild {} (channel exists: {})",
 | 
				
			||||||
 | 
					            reminder.channel, id, channel_exists
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return json!({"error": "Channel not found"});
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let channel = create_database_channel(
 | 
				
			||||||
        serenity_context.inner(),
 | 
					        serenity_context.inner(),
 | 
				
			||||||
 | 
					        ChannelId(reminder.channel),
 | 
				
			||||||
        pool.inner(),
 | 
					        pool.inner(),
 | 
				
			||||||
        GuildId(id),
 | 
					 | 
				
			||||||
        UserId(user_id),
 | 
					 | 
				
			||||||
        reminder.into_inner(),
 | 
					 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					    .await;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if let Err(e) = channel {
 | 
				
			||||||
 | 
					        warn!("`create_database_channel` returned an error code: {:?}", e);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return json!({"error": "Failed to configure channel for reminders. Please check the bot permissions"});
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let channel = channel.unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // validate lengths
 | 
				
			||||||
 | 
					    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_USERNAME_LENGTH, reminder.username);
 | 
				
			||||||
 | 
					    check_length_opt!(
 | 
				
			||||||
 | 
					        MAX_URL_LENGTH,
 | 
				
			||||||
 | 
					        reminder.embed_footer_url,
 | 
				
			||||||
 | 
					        reminder.embed_thumbnail_url,
 | 
				
			||||||
 | 
					        reminder.embed_author_url,
 | 
				
			||||||
 | 
					        reminder.embed_image_url,
 | 
				
			||||||
 | 
					        reminder.avatar
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // validate urls
 | 
				
			||||||
 | 
					    check_url_opt!(
 | 
				
			||||||
 | 
					        reminder.embed_footer_url,
 | 
				
			||||||
 | 
					        reminder.embed_thumbnail_url,
 | 
				
			||||||
 | 
					        reminder.embed_author_url,
 | 
				
			||||||
 | 
					        reminder.embed_image_url,
 | 
				
			||||||
 | 
					        reminder.avatar
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // validate time and interval
 | 
				
			||||||
 | 
					    if reminder.utc_time < Utc::now().naive_utc() {
 | 
				
			||||||
 | 
					        return json!({"error": "Time must be in the future"});
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if reminder.interval_seconds.is_some() || reminder.interval_months.is_some() {
 | 
				
			||||||
 | 
					        if reminder.interval_months.unwrap_or(0) * 30 * DAY as u32
 | 
				
			||||||
 | 
					            + reminder.interval_seconds.unwrap_or(0)
 | 
				
			||||||
 | 
					            < *MIN_INTERVAL
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            return json!({"error": "Interval too short"});
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // check patreon if necessary
 | 
				
			||||||
 | 
					    if reminder.interval_seconds.is_some() || reminder.interval_months.is_some() {
 | 
				
			||||||
 | 
					        if !check_guild_subscription(serenity_context.inner(), GuildId(id)).await
 | 
				
			||||||
 | 
					            && !check_subscription(serenity_context.inner(), user_id).await
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            return json!({"error": "Patreon is required to set intervals"});
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 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() };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let new_uid = generate_uid();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // write to db
 | 
				
			||||||
 | 
					    match sqlx::query!(
 | 
				
			||||||
 | 
					        "INSERT INTO reminders (
 | 
				
			||||||
 | 
					         uid,
 | 
				
			||||||
 | 
					         attachment,
 | 
				
			||||||
 | 
					         attachment_name,
 | 
				
			||||||
 | 
					         channel_id,
 | 
				
			||||||
 | 
					         avatar,
 | 
				
			||||||
 | 
					         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_months,
 | 
				
			||||||
 | 
					         name,
 | 
				
			||||||
 | 
					         restartable,
 | 
				
			||||||
 | 
					         tts,
 | 
				
			||||||
 | 
					         username,
 | 
				
			||||||
 | 
					         `utc_time`
 | 
				
			||||||
 | 
					        ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
 | 
				
			||||||
 | 
					        new_uid,
 | 
				
			||||||
 | 
					        attachment_data,
 | 
				
			||||||
 | 
					        reminder.attachment_name,
 | 
				
			||||||
 | 
					        channel,
 | 
				
			||||||
 | 
					        reminder.avatar,
 | 
				
			||||||
 | 
					        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_months,
 | 
				
			||||||
 | 
					        name,
 | 
				
			||||||
 | 
					        reminder.restartable,
 | 
				
			||||||
 | 
					        reminder.tts,
 | 
				
			||||||
 | 
					        reminder.username,
 | 
				
			||||||
 | 
					        reminder.utc_time,
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    .execute(pool.inner())
 | 
				
			||||||
    .await
 | 
					    .await
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        Ok(_) => sqlx::query_as_unchecked!(
 | 
				
			||||||
 | 
					            Reminder,
 | 
				
			||||||
 | 
					            "SELECT
 | 
				
			||||||
 | 
					             reminders.attachment,
 | 
				
			||||||
 | 
					             reminders.attachment_name,
 | 
				
			||||||
 | 
					             reminders.avatar,
 | 
				
			||||||
 | 
					             channels.channel,
 | 
				
			||||||
 | 
					             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_months,
 | 
				
			||||||
 | 
					             reminders.name,
 | 
				
			||||||
 | 
					             reminders.restartable,
 | 
				
			||||||
 | 
					             reminders.tts,
 | 
				
			||||||
 | 
					             reminders.uid,
 | 
				
			||||||
 | 
					             reminders.username,
 | 
				
			||||||
 | 
					             reminders.utc_time
 | 
				
			||||||
 | 
					            FROM reminders
 | 
				
			||||||
 | 
					            LEFT JOIN channels ON channels.id = reminders.channel_id
 | 
				
			||||||
 | 
					            WHERE uid = ?",
 | 
				
			||||||
 | 
					            new_uid
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        .fetch_one(pool.inner())
 | 
				
			||||||
 | 
					        .await
 | 
				
			||||||
 | 
					        .map(|r| json!(r))
 | 
				
			||||||
 | 
					        .unwrap_or_else(|e| {
 | 
				
			||||||
 | 
					            warn!("Failed to complete SQL query: {:?}", e);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            json!({"error": "Could not load reminder"})
 | 
				
			||||||
 | 
					        }),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Err(e) => {
 | 
				
			||||||
 | 
					            warn!("Error in `create_reminder`: Could not execute query: {:?}", e);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            json!({"error": "Unknown error"})
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[get("/api/guild/<id>/reminders")]
 | 
					#[get("/api/guild/<id>/reminders")]
 | 
				
			||||||
pub async fn get_reminders(id: u64, ctx: &State<Context>, pool: &State<Pool<MySql>>) -> JsonResult {
 | 
					pub async fn get_reminders(id: u64, ctx: &State<Context>, pool: &State<Pool<MySql>>) -> JsonValue {
 | 
				
			||||||
    let channels_res = GuildId(id).channels(&ctx.inner()).await;
 | 
					    let channels_res = GuildId(id).channels(&ctx.inner()).await;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    match channels_res {
 | 
					    match channels_res {
 | 
				
			||||||
@@ -353,17 +552,17 @@ pub async fn get_reminders(id: u64, ctx: &State<Context>, pool: &State<Pool<MySq
 | 
				
			|||||||
            )
 | 
					            )
 | 
				
			||||||
            .fetch_all(pool.inner())
 | 
					            .fetch_all(pool.inner())
 | 
				
			||||||
            .await
 | 
					            .await
 | 
				
			||||||
            .map(|r| Ok(json!(r)))
 | 
					            .map(|r| json!(r))
 | 
				
			||||||
            .unwrap_or_else(|e| {
 | 
					            .unwrap_or_else(|e| {
 | 
				
			||||||
                warn!("Failed to complete SQL query: {:?}", e);
 | 
					                warn!("Failed to complete SQL query: {:?}", e);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                json_err!("Could not load reminders")
 | 
					                json!({"error": "Could not load reminders"})
 | 
				
			||||||
            })
 | 
					            })
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        Err(e) => {
 | 
					        Err(e) => {
 | 
				
			||||||
            warn!("Could not fetch channels from {}: {:?}", id, e);
 | 
					            warn!("Could not fetch channels from {}: {:?}", id, e);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            Ok(json!([]))
 | 
					            json!([])
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -374,7 +573,7 @@ pub async fn edit_reminder(
 | 
				
			|||||||
    reminder: Json<PatchReminder>,
 | 
					    reminder: Json<PatchReminder>,
 | 
				
			||||||
    serenity_context: &State<Context>,
 | 
					    serenity_context: &State<Context>,
 | 
				
			||||||
    pool: &State<Pool<MySql>>,
 | 
					    pool: &State<Pool<MySql>>,
 | 
				
			||||||
) -> JsonResult {
 | 
					) -> JsonValue {
 | 
				
			||||||
    let mut error = vec![];
 | 
					    let mut error = vec![];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    update_field!(pool.inner(), error, reminder.[
 | 
					    update_field!(pool.inner(), error, reminder.[
 | 
				
			||||||
@@ -415,7 +614,7 @@ pub async fn edit_reminder(
 | 
				
			|||||||
                        reminder.channel, id
 | 
					                        reminder.channel, id
 | 
				
			||||||
                    );
 | 
					                    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    return Err(json!({"error": "Channel not found"}));
 | 
					                    return json!({"error": "Channel not found"});
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                let channel = create_database_channel(
 | 
					                let channel = create_database_channel(
 | 
				
			||||||
@@ -428,9 +627,7 @@ pub async fn edit_reminder(
 | 
				
			|||||||
                if let Err(e) = channel {
 | 
					                if let Err(e) = channel {
 | 
				
			||||||
                    warn!("`create_database_channel` returned an error code: {:?}", e);
 | 
					                    warn!("`create_database_channel` returned an error code: {:?}", e);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    return Err(
 | 
					                    return json!({"error": "Failed to configure channel for reminders. Please check the bot permissions"});
 | 
				
			||||||
                        json!({"error": "Failed to configure channel for reminders. Please check the bot permissions"}),
 | 
					 | 
				
			||||||
                    );
 | 
					 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                let channel = channel.unwrap();
 | 
					                let channel = channel.unwrap();
 | 
				
			||||||
@@ -458,7 +655,7 @@ pub async fn edit_reminder(
 | 
				
			|||||||
                    reminder.channel, id
 | 
					                    reminder.channel, id
 | 
				
			||||||
                );
 | 
					                );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                return Err(json!({"error": "Channel not found"}));
 | 
					                return json!({"error": "Channel not found"});
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -498,12 +695,12 @@ pub async fn edit_reminder(
 | 
				
			|||||||
    .fetch_one(pool.inner())
 | 
					    .fetch_one(pool.inner())
 | 
				
			||||||
    .await
 | 
					    .await
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        Ok(reminder) => Ok(json!({"reminder": reminder, "errors": error})),
 | 
					        Ok(reminder) => json!({"reminder": reminder, "errors": error}),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Err(e) => {
 | 
					        Err(e) => {
 | 
				
			||||||
            warn!("Error exiting `edit_reminder': {:?}", e);
 | 
					            warn!("Error exiting `edit_reminder': {:?}", e);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            Err(json!({"reminder": Option::<Reminder>::None, "errors": vec!["Unknown error"]}))
 | 
					            json!({"reminder": Option::<Reminder>::None, "errors": vec!["Unknown error"]})
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -512,17 +709,19 @@ pub async fn edit_reminder(
 | 
				
			|||||||
pub async fn delete_reminder(
 | 
					pub async fn delete_reminder(
 | 
				
			||||||
    reminder: Json<DeleteReminder>,
 | 
					    reminder: Json<DeleteReminder>,
 | 
				
			||||||
    pool: &State<Pool<MySql>>,
 | 
					    pool: &State<Pool<MySql>>,
 | 
				
			||||||
) -> JsonResult {
 | 
					) -> JsonValue {
 | 
				
			||||||
    match sqlx::query!("DELETE FROM reminders WHERE uid = ?", reminder.uid)
 | 
					    match sqlx::query!("DELETE FROM reminders WHERE uid = ?", reminder.uid)
 | 
				
			||||||
        .execute(pool.inner())
 | 
					        .execute(pool.inner())
 | 
				
			||||||
        .await
 | 
					        .await
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        Ok(_) => Ok(json!({})),
 | 
					        Ok(_) => {
 | 
				
			||||||
 | 
					            json!({})
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Err(e) => {
 | 
					        Err(e) => {
 | 
				
			||||||
            warn!("Error in `delete_reminder`: {:?}", e);
 | 
					            warn!("Error in `delete_reminder`: {:?}", e);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            Err(json!({"error": "Could not delete reminder"}))
 | 
					            json!({"error": "Could not delete reminder"})
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,37 +1,21 @@
 | 
				
			|||||||
use std::collections::HashMap;
 | 
					use std::collections::HashMap;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use chrono::{naive::NaiveDateTime, Utc};
 | 
					use chrono::naive::NaiveDateTime;
 | 
				
			||||||
use rand::{rngs::OsRng, seq::IteratorRandom};
 | 
					use rand::{rngs::OsRng, seq::IteratorRandom};
 | 
				
			||||||
use rocket::{
 | 
					use rocket::{http::CookieJar, response::Redirect};
 | 
				
			||||||
    http::CookieJar,
 | 
					 | 
				
			||||||
    response::Redirect,
 | 
					 | 
				
			||||||
    serde::json::{json, Value as JsonValue},
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
use rocket_dyn_templates::Template;
 | 
					use rocket_dyn_templates::Template;
 | 
				
			||||||
use serde::{Deserialize, Serialize};
 | 
					use serde::{Deserialize, Serialize};
 | 
				
			||||||
use serenity::{
 | 
					use serenity::{http::Http, model::id::ChannelId};
 | 
				
			||||||
    client::Context,
 | 
					use sqlx::{types::Json, Executor};
 | 
				
			||||||
    http::Http,
 | 
					 | 
				
			||||||
    model::id::{ChannelId, GuildId, UserId},
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
use sqlx::{types::Json, Executor, MySql, Pool};
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::{
 | 
					use crate::{
 | 
				
			||||||
    check_guild_subscription, check_subscription,
 | 
					    consts::{CHARACTERS, DEFAULT_AVATAR},
 | 
				
			||||||
    consts::{
 | 
					 | 
				
			||||||
        CHARACTERS, DAY, DEFAULT_AVATAR, 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_URL_LENGTH, MAX_USERNAME_LENGTH, MIN_INTERVAL,
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    Database, Error,
 | 
					    Database, Error,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub mod export;
 | 
					 | 
				
			||||||
pub mod guild;
 | 
					pub mod guild;
 | 
				
			||||||
pub mod user;
 | 
					pub mod user;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub type JsonResult = Result<JsonValue, JsonValue>;
 | 
					 | 
				
			||||||
type Unset<T> = Option<T>;
 | 
					type Unset<T> = Option<T>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn name_default() -> String {
 | 
					fn name_default() -> String {
 | 
				
			||||||
@@ -76,28 +60,6 @@ pub struct ReminderTemplate {
 | 
				
			|||||||
    username: Option<String>,
 | 
					    username: Option<String>,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Serialize, Deserialize)]
 | 
					 | 
				
			||||||
pub struct ReminderTemplateCsv {
 | 
					 | 
				
			||||||
    #[serde(default = "template_name_default")]
 | 
					 | 
				
			||||||
    name: String,
 | 
					 | 
				
			||||||
    attachment: Option<Vec<u8>>,
 | 
					 | 
				
			||||||
    attachment_name: Option<String>,
 | 
					 | 
				
			||||||
    avatar: Option<String>,
 | 
					 | 
				
			||||||
    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: Option<String>,
 | 
					 | 
				
			||||||
    tts: bool,
 | 
					 | 
				
			||||||
    username: Option<String>,
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[derive(Deserialize)]
 | 
					#[derive(Deserialize)]
 | 
				
			||||||
pub struct DeleteReminderTemplate {
 | 
					pub struct DeleteReminderTemplate {
 | 
				
			||||||
    id: u32,
 | 
					    id: u32,
 | 
				
			||||||
@@ -143,36 +105,6 @@ pub struct Reminder {
 | 
				
			|||||||
    utc_time: NaiveDateTime,
 | 
					    utc_time: NaiveDateTime,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Serialize, Deserialize)]
 | 
					 | 
				
			||||||
pub struct ReminderCsv {
 | 
					 | 
				
			||||||
    #[serde(with = "base64s")]
 | 
					 | 
				
			||||||
    attachment: Option<Vec<u8>>,
 | 
					 | 
				
			||||||
    attachment_name: Option<String>,
 | 
					 | 
				
			||||||
    avatar: Option<String>,
 | 
					 | 
				
			||||||
    channel: String,
 | 
					 | 
				
			||||||
    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: Option<String>,
 | 
					 | 
				
			||||||
    enabled: bool,
 | 
					 | 
				
			||||||
    expires: Option<NaiveDateTime>,
 | 
					 | 
				
			||||||
    interval_seconds: Option<u32>,
 | 
					 | 
				
			||||||
    interval_months: Option<u32>,
 | 
					 | 
				
			||||||
    #[serde(default = "name_default")]
 | 
					 | 
				
			||||||
    name: String,
 | 
					 | 
				
			||||||
    restartable: bool,
 | 
					 | 
				
			||||||
    tts: bool,
 | 
					 | 
				
			||||||
    username: Option<String>,
 | 
					 | 
				
			||||||
    utc_time: NaiveDateTime,
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[derive(Deserialize)]
 | 
					#[derive(Deserialize)]
 | 
				
			||||||
pub struct PatchReminder {
 | 
					pub struct PatchReminder {
 | 
				
			||||||
    uid: String,
 | 
					    uid: String,
 | 
				
			||||||
@@ -288,225 +220,13 @@ pub struct DeleteReminder {
 | 
				
			|||||||
    uid: String,
 | 
					    uid: String,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Deserialize)]
 | 
					 | 
				
			||||||
pub struct ImportBody {
 | 
					 | 
				
			||||||
    body: String,
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[derive(Serialize, Deserialize)]
 | 
					 | 
				
			||||||
pub struct TodoCsv {
 | 
					 | 
				
			||||||
    value: String,
 | 
					 | 
				
			||||||
    channel_id: Option<String>,
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub async fn create_reminder(
 | 
					 | 
				
			||||||
    ctx: &Context,
 | 
					 | 
				
			||||||
    pool: &Pool<MySql>,
 | 
					 | 
				
			||||||
    guild_id: GuildId,
 | 
					 | 
				
			||||||
    user_id: UserId,
 | 
					 | 
				
			||||||
    reminder: Reminder,
 | 
					 | 
				
			||||||
) -> JsonResult {
 | 
					 | 
				
			||||||
    // validate channel
 | 
					 | 
				
			||||||
    let channel = ChannelId(reminder.channel).to_channel_cached(&ctx);
 | 
					 | 
				
			||||||
    let channel_exists = channel.is_some();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let channel_matches_guild =
 | 
					 | 
				
			||||||
        channel.map_or(false, |c| c.guild().map_or(false, |c| c.guild_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"}));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let channel = create_database_channel(&ctx, ChannelId(reminder.channel), pool).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. Please check the bot permissions"}),
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let channel = channel.unwrap();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // validate lengths
 | 
					 | 
				
			||||||
    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_USERNAME_LENGTH, reminder.username);
 | 
					 | 
				
			||||||
    check_length_opt!(
 | 
					 | 
				
			||||||
        MAX_URL_LENGTH,
 | 
					 | 
				
			||||||
        reminder.embed_footer_url,
 | 
					 | 
				
			||||||
        reminder.embed_thumbnail_url,
 | 
					 | 
				
			||||||
        reminder.embed_author_url,
 | 
					 | 
				
			||||||
        reminder.embed_image_url,
 | 
					 | 
				
			||||||
        reminder.avatar
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // validate urls
 | 
					 | 
				
			||||||
    check_url_opt!(
 | 
					 | 
				
			||||||
        reminder.embed_footer_url,
 | 
					 | 
				
			||||||
        reminder.embed_thumbnail_url,
 | 
					 | 
				
			||||||
        reminder.embed_author_url,
 | 
					 | 
				
			||||||
        reminder.embed_image_url,
 | 
					 | 
				
			||||||
        reminder.avatar
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // 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_months.is_some() {
 | 
					 | 
				
			||||||
        if reminder.interval_months.unwrap_or(0) * 30 * 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_months.is_some() {
 | 
					 | 
				
			||||||
        if !check_guild_subscription(&ctx, guild_id).await
 | 
					 | 
				
			||||||
            && !check_subscription(&ctx, user_id).await
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return Err(json!({"error": "Patreon is required to set intervals"}));
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // 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() };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let new_uid = generate_uid();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // write to db
 | 
					 | 
				
			||||||
    match sqlx::query!(
 | 
					 | 
				
			||||||
        "INSERT INTO reminders (
 | 
					 | 
				
			||||||
         uid,
 | 
					 | 
				
			||||||
         attachment,
 | 
					 | 
				
			||||||
         attachment_name,
 | 
					 | 
				
			||||||
         channel_id,
 | 
					 | 
				
			||||||
         avatar,
 | 
					 | 
				
			||||||
         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_months,
 | 
					 | 
				
			||||||
         name,
 | 
					 | 
				
			||||||
         restartable,
 | 
					 | 
				
			||||||
         tts,
 | 
					 | 
				
			||||||
         username,
 | 
					 | 
				
			||||||
         `utc_time`
 | 
					 | 
				
			||||||
        ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
 | 
					 | 
				
			||||||
        new_uid,
 | 
					 | 
				
			||||||
        attachment_data,
 | 
					 | 
				
			||||||
        reminder.attachment_name,
 | 
					 | 
				
			||||||
        channel,
 | 
					 | 
				
			||||||
        reminder.avatar,
 | 
					 | 
				
			||||||
        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_months,
 | 
					 | 
				
			||||||
        name,
 | 
					 | 
				
			||||||
        reminder.restartable,
 | 
					 | 
				
			||||||
        reminder.tts,
 | 
					 | 
				
			||||||
        reminder.username,
 | 
					 | 
				
			||||||
        reminder.utc_time,
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    .execute(pool)
 | 
					 | 
				
			||||||
    .await
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        Ok(_) => sqlx::query_as_unchecked!(
 | 
					 | 
				
			||||||
            Reminder,
 | 
					 | 
				
			||||||
            "SELECT
 | 
					 | 
				
			||||||
             reminders.attachment,
 | 
					 | 
				
			||||||
             reminders.attachment_name,
 | 
					 | 
				
			||||||
             reminders.avatar,
 | 
					 | 
				
			||||||
             channels.channel,
 | 
					 | 
				
			||||||
             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_months,
 | 
					 | 
				
			||||||
             reminders.name,
 | 
					 | 
				
			||||||
             reminders.restartable,
 | 
					 | 
				
			||||||
             reminders.tts,
 | 
					 | 
				
			||||||
             reminders.uid,
 | 
					 | 
				
			||||||
             reminders.username,
 | 
					 | 
				
			||||||
             reminders.utc_time
 | 
					 | 
				
			||||||
            FROM reminders
 | 
					 | 
				
			||||||
            LEFT JOIN channels ON channels.id = reminders.channel_id
 | 
					 | 
				
			||||||
            WHERE uid = ?",
 | 
					 | 
				
			||||||
            new_uid
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        .fetch_one(pool)
 | 
					 | 
				
			||||||
        .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"}))
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async fn create_database_channel(
 | 
					async fn create_database_channel(
 | 
				
			||||||
    ctx: impl AsRef<Http>,
 | 
					    ctx: impl AsRef<Http>,
 | 
				
			||||||
    channel: ChannelId,
 | 
					    channel: ChannelId,
 | 
				
			||||||
    pool: impl Executor<'_, Database = Database> + Copy,
 | 
					    pool: impl Executor<'_, Database = Database> + Copy,
 | 
				
			||||||
) -> Result<u32, crate::Error> {
 | 
					) -> Result<u32, crate::Error> {
 | 
				
			||||||
 | 
					    println!("{:?}", channel);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let row =
 | 
					    let row =
 | 
				
			||||||
        sqlx::query!("SELECT webhook_token, webhook_id FROM channels WHERE channel = ?", channel.0)
 | 
					        sqlx::query!("SELECT webhook_token, webhook_id FROM channels WHERE channel = ?", channel.0)
 | 
				
			||||||
            .fetch_one(pool)
 | 
					            .fetch_one(pool)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -25,6 +25,7 @@ pub async fn discord_login(
 | 
				
			|||||||
        // Set the desired scopes.
 | 
					        // Set the desired scopes.
 | 
				
			||||||
        .add_scope(Scope::new("identify".to_string()))
 | 
					        .add_scope(Scope::new("identify".to_string()))
 | 
				
			||||||
        .add_scope(Scope::new("guilds".to_string()))
 | 
					        .add_scope(Scope::new("guilds".to_string()))
 | 
				
			||||||
 | 
					        .add_scope(Scope::new("email".to_string()))
 | 
				
			||||||
        // Set the PKCE code challenge.
 | 
					        // Set the PKCE code challenge.
 | 
				
			||||||
        .set_pkce_challenge(pkce_challenge)
 | 
					        .set_pkce_challenge(pkce_challenge)
 | 
				
			||||||
        .url();
 | 
					        .url();
 | 
				
			||||||
 
 | 
				
			|||||||
										
											Binary file not shown.
										
									
								
							| 
		 Before Width: | Height: | Size: 44 KiB  | 
										
											Binary file not shown.
										
									
								
							| 
		 Before Width: | Height: | Size: 40 KiB  | 
										
											Binary file not shown.
										
									
								
							| 
		 Before Width: | Height: | Size: 44 KiB  | 
										
											Binary file not shown.
										
									
								
							| 
		 Before Width: | Height: | Size: 18 KiB  | 
										
											Binary file not shown.
										
									
								
							| 
		 Before Width: | Height: | Size: 20 KiB  | 
@@ -12,10 +12,6 @@ const $createTemplateBtn = $createReminder.querySelector("button#createTemplate"
 | 
				
			|||||||
const $loadTemplateBtn = document.querySelector("button#load-template");
 | 
					const $loadTemplateBtn = document.querySelector("button#load-template");
 | 
				
			||||||
const $deleteTemplateBtn = document.querySelector("button#delete-template");
 | 
					const $deleteTemplateBtn = document.querySelector("button#delete-template");
 | 
				
			||||||
const $templateSelect = document.querySelector("select#templateSelect");
 | 
					const $templateSelect = document.querySelector("select#templateSelect");
 | 
				
			||||||
const $exportBtn = document.querySelector("button#export-data");
 | 
					 | 
				
			||||||
const $importBtn = document.querySelector("button#import-data");
 | 
					 | 
				
			||||||
const $downloader = document.querySelector("a#downloader");
 | 
					 | 
				
			||||||
const $uploader = document.querySelector("input#uploader");
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
let channels = [];
 | 
					let channels = [];
 | 
				
			||||||
let guildNames = {};
 | 
					let guildNames = {};
 | 
				
			||||||
@@ -674,39 +670,6 @@ function has_source(string) {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
$uploader.addEventListener("change", (ev) => {
 | 
					 | 
				
			||||||
    const urlTail = document.querySelector('input[name="exportSelect"]:checked').value;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    new Promise((resolve) => {
 | 
					 | 
				
			||||||
        let fileReader = new FileReader();
 | 
					 | 
				
			||||||
        fileReader.onload = (e) => resolve(fileReader.result);
 | 
					 | 
				
			||||||
        fileReader.readAsDataURL($uploader.files[0]);
 | 
					 | 
				
			||||||
    }).then((dataUrl) => {
 | 
					 | 
				
			||||||
        fetch(`/dashboard/api/guild/${guildId()}/export/${urlTail}`, {
 | 
					 | 
				
			||||||
            method: "PUT",
 | 
					 | 
				
			||||||
            body: JSON.stringify({ body: dataUrl.split(",")[1] }),
 | 
					 | 
				
			||||||
        }).then(() => {
 | 
					 | 
				
			||||||
            delete $uploader.files[0];
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
$importBtn.addEventListener("click", () => {
 | 
					 | 
				
			||||||
    $uploader.click();
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
$exportBtn.addEventListener("click", () => {
 | 
					 | 
				
			||||||
    const urlTail = document.querySelector('input[name="exportSelect"]:checked').value;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    fetch(`/dashboard/api/guild/${guildId()}/export/${urlTail}`)
 | 
					 | 
				
			||||||
        .then((response) => response.json())
 | 
					 | 
				
			||||||
        .then((data) => {
 | 
					 | 
				
			||||||
            $downloader.href =
 | 
					 | 
				
			||||||
                "data:text/plain;charset=utf-8," + encodeURIComponent(data.body);
 | 
					 | 
				
			||||||
            $downloader.click();
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
$createReminderBtn.addEventListener("click", async () => {
 | 
					$createReminderBtn.addEventListener("click", async () => {
 | 
				
			||||||
    $createReminderBtn.querySelector("span.icon > i").classList = [
 | 
					    $createReminderBtn.querySelector("span.icon > i").classList = [
 | 
				
			||||||
        "fas fa-spinner fa-spin",
 | 
					        "fas fa-spinner fa-spin",
 | 
				
			||||||
@@ -871,7 +834,7 @@ document.addEventListener("remindersLoaded", () => {
 | 
				
			|||||||
        });
 | 
					        });
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const fileInput = document.querySelectorAll("input.file-input[type=file]");
 | 
					    const fileInput = document.querySelectorAll("input[type=file]");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fileInput.forEach((element) => {
 | 
					    fileInput.forEach((element) => {
 | 
				
			||||||
        element.addEventListener("change", () => {
 | 
					        element.addEventListener("change", () => {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -177,41 +177,38 @@
 | 
				
			|||||||
        <section class="modal-card-body">
 | 
					        <section class="modal-card-body">
 | 
				
			||||||
            <div class="control">
 | 
					            <div class="control">
 | 
				
			||||||
                <div class="field">
 | 
					                <div class="field">
 | 
				
			||||||
                    <label>
 | 
					                    <input type="checkbox" class="default-width">
 | 
				
			||||||
                        <input type="radio" class="default-width" name="exportSelect" value="reminders" checked>
 | 
					                    <label>Reminders</label>
 | 
				
			||||||
                        Reminders
 | 
					 | 
				
			||||||
                    </label>
 | 
					 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
            <div class="control">
 | 
					            <div class="control">
 | 
				
			||||||
                <div class="field">
 | 
					                <div class="field">
 | 
				
			||||||
                    <label>
 | 
					                    <input type="checkbox" class="default-width">
 | 
				
			||||||
                        <input type="radio" class="default-width" name="exportSelect" value="todos">
 | 
					                    <label>Todo Lists</label>
 | 
				
			||||||
                        Todo Lists
 | 
					 | 
				
			||||||
                    </label>
 | 
					 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
            <div class="control">
 | 
					            <div class="control">
 | 
				
			||||||
                <div class="field">
 | 
					                <div class="field">
 | 
				
			||||||
                    <label>
 | 
					                    <input type="checkbox" class="default-width">
 | 
				
			||||||
                        <input type="radio" class="default-width" name="exportSelect" value="reminder_templates">
 | 
					                    <label>Timers</label>
 | 
				
			||||||
                        Reminder templates
 | 
					                </div>
 | 
				
			||||||
                    </label>
 | 
					            </div>
 | 
				
			||||||
 | 
					            <div class="control">
 | 
				
			||||||
 | 
					                <div class="field">
 | 
				
			||||||
 | 
					                    <input type="checkbox" class="default-width">
 | 
				
			||||||
 | 
					                    <label>Reminder templates</label>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					            <div class="control">
 | 
				
			||||||
 | 
					                <div class="field">
 | 
				
			||||||
 | 
					                    <input type="checkbox" class="default-width">
 | 
				
			||||||
 | 
					                    <label>Macros</label>
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
            <br>
 | 
					 | 
				
			||||||
            <div class="has-text-centered">
 | 
					            <div class="has-text-centered">
 | 
				
			||||||
                <div style="color: red; font-weight: bold;">
 | 
					 | 
				
			||||||
                    By selecting "Import", you understand that this will overwrite existing data.
 | 
					 | 
				
			||||||
                </div>
 | 
					 | 
				
			||||||
                <div style="color: red">
 | 
					 | 
				
			||||||
                    Please first read the <a href="/help/iemanager">support page</a>
 | 
					 | 
				
			||||||
                </div>
 | 
					 | 
				
			||||||
                <button class="button is-success is-outlined" id="import-data">Import Data</button>
 | 
					                <button class="button is-success is-outlined" id="import-data">Import Data</button>
 | 
				
			||||||
                <button class="button is-success" id="export-data">Export Data</button>
 | 
					                <button class="button is-success" id="export-data">Export Data</button>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
            <a id="downloader" download="export.csv" class="is-hidden"></a>
 | 
					 | 
				
			||||||
            <input id="uploader" type="file" hidden></input>
 | 
					 | 
				
			||||||
        </section>
 | 
					        </section>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
    <button class="modal-close is-large close-modal" aria-label="close"></button>
 | 
					    <button class="modal-close is-large close-modal" aria-label="close"></button>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,5 +5,5 @@
 | 
				
			|||||||
    {% set show_contact = True %}
 | 
					    {% set show_contact = True %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    {% set page_title = "An Error Has Occurred" %}
 | 
					    {% set page_title = "An Error Has Occurred" %}
 | 
				
			||||||
    {% set page_subtitle = "A server error has occurred. Please retry, or ask in our Discord." %}
 | 
					    {% set page_subtitle = "A server error has occurred. Please contact me and I will try and resolve this" %}
 | 
				
			||||||
{% endblock %}
 | 
					{% endblock %}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -49,7 +49,7 @@
 | 
				
			|||||||
        <div class="container">
 | 
					        <div class="container">
 | 
				
			||||||
            <h2 class="title">Who your data is shared with</h2>
 | 
					            <h2 class="title">Who your data is shared with</h2>
 | 
				
			||||||
            <p class="is-size-5 pl-6">
 | 
					            <p class="is-size-5 pl-6">
 | 
				
			||||||
                Your data is also guarded by the privacy policies of <strong>MEGA</strong>, our backup provider, and
 | 
					                Your data may also be guarded by the privacy policies of <strong>MEGA</strong>, our backup provider, and
 | 
				
			||||||
                <strong>Hetzner</strong>, our hosting provider.
 | 
					                <strong>Hetzner</strong>, our hosting provider.
 | 
				
			||||||
            </p>
 | 
					            </p>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
@@ -68,7 +68,7 @@
 | 
				
			|||||||
                <br>
 | 
					                <br>
 | 
				
			||||||
                <br>
 | 
					                <br>
 | 
				
			||||||
                Reminders deleted with <strong>/del</strong> or via the dashboard are removed from the live database
 | 
					                Reminders deleted with <strong>/del</strong> or via the dashboard are removed from the live database
 | 
				
			||||||
                instantly, but may persist in backups for up to a year.
 | 
					                instantly, but may persist in backups.
 | 
				
			||||||
            </p>
 | 
					            </p>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
    </section>
 | 
					    </section>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,75 +14,13 @@
 | 
				
			|||||||
            <div class="container">
 | 
					            <div class="container">
 | 
				
			||||||
                <p class="title">Export your data</p>
 | 
					                <p class="title">Export your data</p>
 | 
				
			||||||
                <p class="content">
 | 
					                <p class="content">
 | 
				
			||||||
                    You can export data associated with your server from the dashboard. The data will export as a CSV
 | 
					                    You can create reminders with the <code>/remind</code> command.
 | 
				
			||||||
                    file. The CSV file can then be edited and imported to bulk edit server data.
 | 
					                    <br>
 | 
				
			||||||
 | 
					                    Fill out the "time" and "content" fields. If you wish, press on "Optional" to view other options
 | 
				
			||||||
 | 
					                    for the reminder.
 | 
				
			||||||
                </p>
 | 
					                </p>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
    </section>
 | 
					    </section>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <section class="hero is-small">
 | 
					 | 
				
			||||||
        <div class="hero-body">
 | 
					 | 
				
			||||||
            <div class="container">
 | 
					 | 
				
			||||||
                <p class="title">Import data</p>
 | 
					 | 
				
			||||||
                <p class="content">
 | 
					 | 
				
			||||||
                    You can import previous exports or modified exports. When importing a file, <strong>existing data
 | 
					 | 
				
			||||||
                    will be overwritten</strong>.
 | 
					 | 
				
			||||||
                </p>
 | 
					 | 
				
			||||||
            </div>
 | 
					 | 
				
			||||||
        </div>
 | 
					 | 
				
			||||||
    </section>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    <section class="hero is-small">
 | 
					 | 
				
			||||||
        <div class="hero-body">
 | 
					 | 
				
			||||||
            <div class="container content">
 | 
					 | 
				
			||||||
                <p class="title">Edit your data</p>
 | 
					 | 
				
			||||||
                <p>
 | 
					 | 
				
			||||||
                    The CSV can be edited either as a text file or in a spreadsheet editor such as LibreOffice Calc. To
 | 
					 | 
				
			||||||
                    set up LibreOffice Calc for editing, do the following:
 | 
					 | 
				
			||||||
                </p>
 | 
					 | 
				
			||||||
                <ol>
 | 
					 | 
				
			||||||
                    <li>
 | 
					 | 
				
			||||||
                        Export data from dashboard.
 | 
					 | 
				
			||||||
                        <figure>
 | 
					 | 
				
			||||||
                            <img src="/static/img/support/iemanager/select_export.png" alt="Selecting export button">
 | 
					 | 
				
			||||||
                        </figure>
 | 
					 | 
				
			||||||
                    </li>
 | 
					 | 
				
			||||||
                    <li>
 | 
					 | 
				
			||||||
                        Open the file in LibreOffice. <strong>During the import dialogue, select "Format quoted field as text".</strong>
 | 
					 | 
				
			||||||
                        <figure>
 | 
					 | 
				
			||||||
                            <img src="/static/img/support/iemanager/format_text.png" alt="Selecting format button">
 | 
					 | 
				
			||||||
                        </figure>
 | 
					 | 
				
			||||||
                    </li>
 | 
					 | 
				
			||||||
                    <li>
 | 
					 | 
				
			||||||
                        Make edits to the spreadsheet. You can add, edit, and remove rows for reminders. Don't remove the title row.
 | 
					 | 
				
			||||||
                        <figure>
 | 
					 | 
				
			||||||
                            <img src="/static/img/support/iemanager/edit_spreadsheet.png" alt="Editing spreadsheet">
 | 
					 | 
				
			||||||
                        </figure>
 | 
					 | 
				
			||||||
                    </li>
 | 
					 | 
				
			||||||
                    <li>
 | 
					 | 
				
			||||||
                        Save the edited CSV file and import it on the dashboard.
 | 
					 | 
				
			||||||
                        <figure>
 | 
					 | 
				
			||||||
                            <img src="/static/img/support/iemanager/import.png" alt="Import new reminders">
 | 
					 | 
				
			||||||
                        </figure>
 | 
					 | 
				
			||||||
                    </li>
 | 
					 | 
				
			||||||
                </ol>
 | 
					 | 
				
			||||||
                Other spreadsheet tools can also be used to edit exports, as long as they are properly configured:
 | 
					 | 
				
			||||||
                <ul>
 | 
					 | 
				
			||||||
                    <li>
 | 
					 | 
				
			||||||
                        <strong>Google Sheets</strong>: Create a new blank spreadsheet. Select <strong>File >> Import >> Upload >> export.csv</strong>.
 | 
					 | 
				
			||||||
                        Use the following import settings:
 | 
					 | 
				
			||||||
                        <figure>
 | 
					 | 
				
			||||||
                            <img src="/static/img/support/iemanager/sheets_settings.png" alt="Google sheets import settings">
 | 
					 | 
				
			||||||
                        </figure>
 | 
					 | 
				
			||||||
                    </li>
 | 
					 | 
				
			||||||
                    <li>
 | 
					 | 
				
			||||||
                        <strong>Excel (including Excel Online)</strong>: Avoid using Excel. Excel will not correctly import channels, or give
 | 
					 | 
				
			||||||
                        clear options to correct imports.
 | 
					 | 
				
			||||||
                    </li>
 | 
					 | 
				
			||||||
                </ul>
 | 
					 | 
				
			||||||
            </div>
 | 
					 | 
				
			||||||
        </div>
 | 
					 | 
				
			||||||
    </section>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
{% endblock %}
 | 
					{% endblock %}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -20,12 +20,11 @@
 | 
				
			|||||||
                <br>
 | 
					                <br>
 | 
				
			||||||
                Violating the Terms of Service may result in receiving a permanent ban from the Discord server,
 | 
					                Violating the Terms of Service may result in receiving a permanent ban from the Discord server,
 | 
				
			||||||
                permanent restriction on your usage of Reminder Bot, or removal of some or all of your content on
 | 
					                permanent restriction on your usage of Reminder Bot, or removal of some or all of your content on
 | 
				
			||||||
                Reminder Bot or the Discord server. None of these will necessarily be preceded or succeeded by a warning
 | 
					                Reminder Bot or the Discord server.
 | 
				
			||||||
                or notice.
 | 
					 | 
				
			||||||
                <br>
 | 
					                <br>
 | 
				
			||||||
                <br>
 | 
					                <br>
 | 
				
			||||||
                The Terms of Service may be updated. Notice will be provided via the Discord server. You
 | 
					                The Terms of Service may be updated at any time. Notice will be provided via the Discord server. You
 | 
				
			||||||
                should consider the Terms of Service to be a strong for appropriate behaviour.
 | 
					                should consider the Terms of Service to be a guideline for appropriate behaviour.
 | 
				
			||||||
            </p>
 | 
					            </p>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
    </section>
 | 
					    </section>
 | 
				
			||||||
@@ -38,12 +37,6 @@
 | 
				
			|||||||
                <li>Do not use the bot to harass other Discord users</li>
 | 
					                <li>Do not use the bot to harass other Discord users</li>
 | 
				
			||||||
                <li>Do not use the bot to transmit malware or other illegal content</li>
 | 
					                <li>Do not use the bot to transmit malware or other illegal content</li>
 | 
				
			||||||
                <li>Do not use the bot to send more than 15 messages during a 60 second period</li>
 | 
					                <li>Do not use the bot to send more than 15 messages during a 60 second period</li>
 | 
				
			||||||
                <li>
 | 
					 | 
				
			||||||
                    Do not attempt to circumvent restrictions imposed by the bot or website, including trying to access
 | 
					 | 
				
			||||||
                    data of other users, circumvent Patreon restrictions, or uploading files and creating reminders that
 | 
					 | 
				
			||||||
                    are too large for the bot to send or process. Some or all of these actions may be illegal in your
 | 
					 | 
				
			||||||
                    country
 | 
					 | 
				
			||||||
                </li>
 | 
					 | 
				
			||||||
            </ul>
 | 
					            </ul>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
    </section>
 | 
					    </section>
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user