Compare commits
10 Commits
d2d9b8d6d0
...
109cf16dbb
Author | SHA1 | Date | |
---|---|---|---|
|
109cf16dbb | ||
|
6726ca0c2d | ||
38133be15d | |||
|
8587bed703 | ||
|
6c9af1ae8e | ||
|
7695b7a476 | ||
651da7b28e | |||
eb086146bf | |||
4ebd705e5e | |||
5a85f1d83a |
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -2217,7 +2217,7 @@ checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "reminder-rs"
|
name = "reminder-rs"
|
||||||
version = "1.6.36"
|
version = "1.6.38"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.21.2",
|
"base64 0.21.2",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "reminder-rs"
|
name = "reminder-rs"
|
||||||
version = "1.6.36"
|
version = "1.6.38"
|
||||||
authors = ["Jude Southworth <judesouthworth@pm.me>"]
|
authors = ["Jude Southworth <judesouthworth@pm.me>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "AGPL-3.0 only"
|
license = "AGPL-3.0 only"
|
||||||
|
19
migrations/20230812111348_orphan_reminders.sql
Normal file
19
migrations/20230812111348_orphan_reminders.sql
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
-- Drop existing constraint
|
||||||
|
ALTER TABLE `reminders` DROP CONSTRAINT `reminders_ibfk_1`;
|
||||||
|
|
||||||
|
ALTER TABLE `reminders` MODIFY COLUMN `channel_id` INT UNSIGNED;
|
||||||
|
ALTER TABLE `reminders` ADD COLUMN `guild_id` INT UNSIGNED;
|
||||||
|
|
||||||
|
ALTER TABLE `reminders`
|
||||||
|
ADD CONSTRAINT `guild_id_fk`
|
||||||
|
FOREIGN KEY (`guild_id`)
|
||||||
|
REFERENCES `guilds`(`id`)
|
||||||
|
ON DELETE CASCADE;
|
||||||
|
|
||||||
|
ALTER TABLE `reminders`
|
||||||
|
ADD CONSTRAINT `channel_id_fk`
|
||||||
|
FOREIGN KEY (`channel_id`)
|
||||||
|
REFERENCES `channels`(`id`)
|
||||||
|
ON DELETE SET NULL;
|
||||||
|
|
||||||
|
UPDATE `reminders` SET `guild_id` = (SELECT guilds.`id` FROM `channels` INNER JOIN `guilds` ON channels.guild_id = guilds.id WHERE reminders.channel_id = channels.id);
|
@ -169,7 +169,7 @@ impl ComponentDataModel {
|
|||||||
let selected_id = component.data.values.join(",");
|
let selected_id = component.data.values.join(",");
|
||||||
|
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
"UPDATE reminders SET `status` = 'pending' WHERE FIND_IN_SET(id, ?)",
|
"UPDATE reminders SET `status` = 'deleted' WHERE FIND_IN_SET(id, ?)",
|
||||||
selected_id
|
selected_id
|
||||||
)
|
)
|
||||||
.execute(&data.database)
|
.execute(&data.database)
|
||||||
|
@ -10,6 +10,7 @@ pub struct ChannelData {
|
|||||||
pub webhook_id: Option<u64>,
|
pub webhook_id: Option<u64>,
|
||||||
pub webhook_token: Option<String>,
|
pub webhook_token: Option<String>,
|
||||||
pub paused: bool,
|
pub paused: bool,
|
||||||
|
pub db_guild_id: Option<u32>,
|
||||||
pub paused_until: Option<NaiveDateTime>,
|
pub paused_until: Option<NaiveDateTime>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -22,7 +23,7 @@ impl ChannelData {
|
|||||||
|
|
||||||
if let Ok(c) = sqlx::query_as_unchecked!(
|
if let Ok(c) = sqlx::query_as_unchecked!(
|
||||||
Self,
|
Self,
|
||||||
"SELECT id, name, nudge, blacklisted, webhook_id, webhook_token, paused, paused_until FROM channels WHERE channel = ?",
|
"SELECT id, name, nudge, blacklisted, webhook_id, webhook_token, paused, paused_until, guild_id AS db_guild_id FROM channels WHERE channel = ?",
|
||||||
channel_id
|
channel_id
|
||||||
)
|
)
|
||||||
.fetch_one(pool)
|
.fetch_one(pool)
|
||||||
@ -46,7 +47,7 @@ impl ChannelData {
|
|||||||
Ok(sqlx::query_as_unchecked!(
|
Ok(sqlx::query_as_unchecked!(
|
||||||
Self,
|
Self,
|
||||||
"
|
"
|
||||||
SELECT id, name, nudge, blacklisted, webhook_id, webhook_token, paused, paused_until FROM channels WHERE channel = ?
|
SELECT id, name, nudge, blacklisted, webhook_id, webhook_token, paused, paused_until, guild_id AS db_guild_id FROM channels WHERE channel = ?
|
||||||
",
|
",
|
||||||
channel_id
|
channel_id
|
||||||
)
|
)
|
||||||
|
@ -51,6 +51,7 @@ pub struct ReminderBuilder {
|
|||||||
pool: MySqlPool,
|
pool: MySqlPool,
|
||||||
uid: String,
|
uid: String,
|
||||||
channel: u32,
|
channel: u32,
|
||||||
|
guild: Option<u32>,
|
||||||
thread_id: Option<u64>,
|
thread_id: Option<u64>,
|
||||||
utc_time: NaiveDateTime,
|
utc_time: NaiveDateTime,
|
||||||
timezone: String,
|
timezone: String,
|
||||||
@ -86,6 +87,7 @@ impl ReminderBuilder {
|
|||||||
INSERT INTO reminders (
|
INSERT INTO reminders (
|
||||||
`uid`,
|
`uid`,
|
||||||
`channel_id`,
|
`channel_id`,
|
||||||
|
`guild_id`,
|
||||||
`utc_time`,
|
`utc_time`,
|
||||||
`timezone`,
|
`timezone`,
|
||||||
`interval_seconds`,
|
`interval_seconds`,
|
||||||
@ -110,11 +112,13 @@ INSERT INTO reminders (
|
|||||||
?,
|
?,
|
||||||
?,
|
?,
|
||||||
?,
|
?,
|
||||||
|
?,
|
||||||
?
|
?
|
||||||
)
|
)
|
||||||
",
|
",
|
||||||
self.uid,
|
self.uid,
|
||||||
self.channel,
|
self.channel,
|
||||||
|
self.guild,
|
||||||
utc_time,
|
utc_time,
|
||||||
self.timezone,
|
self.timezone,
|
||||||
self.interval_seconds,
|
self.interval_seconds,
|
||||||
@ -247,10 +251,10 @@ impl<'a> MultiReminderBuilder<'a> {
|
|||||||
{
|
{
|
||||||
Err(ReminderError::UserBlockedDm)
|
Err(ReminderError::UserBlockedDm)
|
||||||
} else {
|
} else {
|
||||||
Ok(user_data.dm_channel)
|
Ok((user_data.dm_channel, None))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Ok(user_data.dm_channel)
|
Ok((user_data.dm_channel, None))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Err(ReminderError::InvalidTag)
|
Err(ReminderError::InvalidTag)
|
||||||
@ -297,13 +301,13 @@ impl<'a> MultiReminderBuilder<'a> {
|
|||||||
.commit_changes(&self.ctx.data().database)
|
.commit_changes(&self.ctx.data().database)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
Ok(channel_data.id)
|
Ok((channel_data.id, channel_data.db_guild_id))
|
||||||
}
|
}
|
||||||
|
|
||||||
Err(e) => Err(ReminderError::DiscordError(e.to_string())),
|
Err(e) => Err(ReminderError::DiscordError(e.to_string())),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Ok(channel_data.id)
|
Ok((channel_data.id, channel_data.db_guild_id))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -317,7 +321,8 @@ impl<'a> MultiReminderBuilder<'a> {
|
|||||||
let builder = ReminderBuilder {
|
let builder = ReminderBuilder {
|
||||||
pool: self.ctx.data().database.clone(),
|
pool: self.ctx.data().database.clone(),
|
||||||
uid: generate_uid(),
|
uid: generate_uid(),
|
||||||
channel: c,
|
channel: c.0,
|
||||||
|
guild: c.1,
|
||||||
thread_id,
|
thread_id,
|
||||||
utc_time: self.utc_time,
|
utc_time: self.utc_time,
|
||||||
timezone: self.timezone.to_string(),
|
timezone: self.timezone.to_string(),
|
||||||
|
@ -72,10 +72,14 @@ pub async fn initialize(
|
|||||||
db_pool: Pool<Database>,
|
db_pool: Pool<Database>,
|
||||||
) -> Result<(), Box<dyn std::error::Error>> {
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
info!("Checking environment variables...");
|
info!("Checking environment variables...");
|
||||||
env::var("OAUTH2_CLIENT_ID").expect("`OAUTH2_CLIENT_ID' not supplied");
|
|
||||||
env::var("OAUTH2_CLIENT_SECRET").expect("`OAUTH2_CLIENT_SECRET' not supplied");
|
if env::var("OFFLINE").map_or(true, |v| v != "1") {
|
||||||
env::var("OAUTH2_DISCORD_CALLBACK").expect("`OAUTH2_DISCORD_CALLBACK' not supplied");
|
env::var("OAUTH2_CLIENT_ID").expect("`OAUTH2_CLIENT_ID' not supplied");
|
||||||
env::var("PATREON_GUILD_ID").expect("`PATREON_GUILD_ID' not supplied");
|
env::var("OAUTH2_CLIENT_SECRET").expect("`OAUTH2_CLIENT_SECRET' not supplied");
|
||||||
|
env::var("OAUTH2_DISCORD_CALLBACK").expect("`OAUTH2_DISCORD_CALLBACK' not supplied");
|
||||||
|
env::var("PATREON_GUILD_ID").expect("`PATREON_GUILD_ID' not supplied");
|
||||||
|
}
|
||||||
|
|
||||||
info!("Done!");
|
info!("Done!");
|
||||||
|
|
||||||
let oauth2_client = BasicClient::new(
|
let oauth2_client = BasicClient::new(
|
||||||
@ -161,6 +165,7 @@ pub async fn initialize(
|
|||||||
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::guild::get_reminder_errors,
|
||||||
routes::dashboard::export::export_reminders,
|
routes::dashboard::export::export_reminders,
|
||||||
routes::dashboard::export::export_reminder_templates,
|
routes::dashboard::export::export_reminder_templates,
|
||||||
routes::dashboard::export::export_todos,
|
routes::dashboard::export::export_todos,
|
||||||
@ -185,6 +190,8 @@ pub async fn initialize(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn check_subscription(cache_http: impl CacheHttp, user_id: impl Into<UserId>) -> bool {
|
pub async fn check_subscription(cache_http: impl CacheHttp, user_id: impl Into<UserId>) -> bool {
|
||||||
|
offline!(true);
|
||||||
|
|
||||||
if let Some(subscription_guild) = *CNC_GUILD {
|
if let Some(subscription_guild) = *CNC_GUILD {
|
||||||
let guild_member = GuildId(subscription_guild).member(cache_http, user_id).await;
|
let guild_member = GuildId(subscription_guild).member(cache_http, user_id).await;
|
||||||
|
|
||||||
@ -206,6 +213,8 @@ pub async fn check_guild_subscription(
|
|||||||
cache_http: impl CacheHttp,
|
cache_http: impl CacheHttp,
|
||||||
guild_id: impl Into<GuildId>,
|
guild_id: impl Into<GuildId>,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
|
offline!(true);
|
||||||
|
|
||||||
if let Some(guild) = cache_http.cache().unwrap().guild(guild_id) {
|
if let Some(guild) = cache_http.cache().unwrap().guild(guild_id) {
|
||||||
let owner = guild.owner_id;
|
let owner = guild.owner_id;
|
||||||
|
|
||||||
|
@ -1,3 +1,11 @@
|
|||||||
|
macro_rules! offline {
|
||||||
|
($field:expr) => {
|
||||||
|
if std::env::var("OFFLINE").map_or(false, |v| v == "1") {
|
||||||
|
return $field;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
macro_rules! check_length {
|
macro_rules! check_length {
|
||||||
($max:ident, $field:expr) => {
|
($max:ident, $field:expr) => {
|
||||||
if $field.len() > $max {
|
if $field.len() > $max {
|
||||||
@ -52,43 +60,45 @@ macro_rules! check_authorization {
|
|||||||
|
|
||||||
let user_id = $cookies.get_private("userid").map(|c| c.value().parse::<u64>().ok()).flatten();
|
let user_id = $cookies.get_private("userid").map(|c| c.value().parse::<u64>().ok()).flatten();
|
||||||
|
|
||||||
match user_id {
|
if std::env::var("OFFLINE").map_or(true, |v| v != "1") {
|
||||||
Some(user_id) => {
|
match user_id {
|
||||||
match GuildId($guild).to_guild_cached($ctx) {
|
Some(user_id) => {
|
||||||
Some(guild) => {
|
match GuildId($guild).to_guild_cached($ctx) {
|
||||||
let member_res = guild.member($ctx, UserId(user_id)).await;
|
Some(guild) => {
|
||||||
|
let member_res = guild.member($ctx, UserId(user_id)).await;
|
||||||
|
|
||||||
match member_res {
|
match member_res {
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
return Err(json!({"error": "User not in guild"}));
|
return Err(json!({"error": "User not in guild"}));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(member) => {
|
Ok(member) => {
|
||||||
let permissions_res = member.permissions($ctx);
|
let permissions_res = member.permissions($ctx);
|
||||||
|
|
||||||
match permissions_res {
|
match permissions_res {
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
return Err(json!({"error": "Couldn't fetch permissions"}));
|
return Err(json!({"error": "Couldn't fetch permissions"}));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(permissions) => {
|
Ok(permissions) => {
|
||||||
if !(permissions.manage_messages() || permissions.manage_guild() || permissions.administrator()) {
|
if !(permissions.manage_messages() || permissions.manage_guild() || permissions.administrator()) {
|
||||||
return Err(json!({"error": "Incorrect permissions"}));
|
return Err(json!({"error": "Incorrect permissions"}));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
None => {
|
None => {
|
||||||
return Err(json!({"error": "Bot not in guild"}));
|
return Err(json!({"error": "Bot not in guild"}));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
None => {
|
None => {
|
||||||
return Err(json!({"error": "User not authorized"}));
|
return Err(json!({"error": "User not authorized"}));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,7 @@ use crate::{
|
|||||||
routes::{
|
routes::{
|
||||||
dashboard::{
|
dashboard::{
|
||||||
create_database_channel, create_reminder, template_name_default, DeleteReminder,
|
create_database_channel, create_reminder, template_name_default, DeleteReminder,
|
||||||
DeleteReminderTemplate, PatchReminder, Reminder, ReminderTemplate,
|
DeleteReminderTemplate, PatchReminder, Reminder, ReminderError, ReminderTemplate,
|
||||||
},
|
},
|
||||||
JsonResult,
|
JsonResult,
|
||||||
},
|
},
|
||||||
@ -46,6 +46,7 @@ pub async fn get_guild_patreon(
|
|||||||
cookies: &CookieJar<'_>,
|
cookies: &CookieJar<'_>,
|
||||||
ctx: &State<Context>,
|
ctx: &State<Context>,
|
||||||
) -> JsonResult {
|
) -> JsonResult {
|
||||||
|
offline!(Ok(json!({ "patreon": true })));
|
||||||
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()) {
|
||||||
@ -73,6 +74,12 @@ pub async fn get_guild_channels(
|
|||||||
cookies: &CookieJar<'_>,
|
cookies: &CookieJar<'_>,
|
||||||
ctx: &State<Context>,
|
ctx: &State<Context>,
|
||||||
) -> JsonResult {
|
) -> JsonResult {
|
||||||
|
offline!(Ok(json!(vec![ChannelInfo {
|
||||||
|
name: "general".to_string(),
|
||||||
|
id: "1".to_string(),
|
||||||
|
webhook_avatar: None,
|
||||||
|
webhook_name: None,
|
||||||
|
}])));
|
||||||
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()) {
|
||||||
@ -111,6 +118,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>) -> JsonResult {
|
||||||
|
offline!(Ok(json!(vec![RoleInfo { name: "@everyone".to_string(), id: "1".to_string() }])));
|
||||||
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);
|
||||||
@ -314,72 +322,53 @@ pub async fn create_guild_reminder(
|
|||||||
pub async fn get_reminders(
|
pub async fn get_reminders(
|
||||||
id: u64,
|
id: u64,
|
||||||
cookies: &CookieJar<'_>,
|
cookies: &CookieJar<'_>,
|
||||||
ctx: &State<Context>,
|
|
||||||
serenity_context: &State<Context>,
|
serenity_context: &State<Context>,
|
||||||
pool: &State<Pool<MySql>>,
|
pool: &State<Pool<MySql>>,
|
||||||
) -> JsonResult {
|
) -> JsonResult {
|
||||||
check_authorization!(cookies, serenity_context.inner(), id);
|
check_authorization!(cookies, serenity_context.inner(), id);
|
||||||
|
|
||||||
let channels_res = GuildId(id).channels(&ctx.inner()).await;
|
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,
|
||||||
|
IFNULL(reminders.embed_fields, '[]') AS embed_fields,
|
||||||
|
reminders.enabled,
|
||||||
|
reminders.expires,
|
||||||
|
reminders.interval_seconds,
|
||||||
|
reminders.interval_days,
|
||||||
|
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 `status` = 'pending' AND reminders.guild_id = (SELECT id FROM guilds WHERE guild = ?)",
|
||||||
|
id
|
||||||
|
)
|
||||||
|
.fetch_all(pool.inner())
|
||||||
|
.await
|
||||||
|
.map(|r| Ok(json!(r)))
|
||||||
|
.unwrap_or_else(|e| {
|
||||||
|
warn!("Failed to complete SQL query: {:?}", e);
|
||||||
|
|
||||||
match channels_res {
|
json_err!("Could not load reminders")
|
||||||
Ok(channels) => {
|
})
|
||||||
let channels = channels
|
|
||||||
.keys()
|
|
||||||
.into_iter()
|
|
||||||
.map(|k| k.as_u64().to_string())
|
|
||||||
.collect::<Vec<String>>()
|
|
||||||
.join(",");
|
|
||||||
|
|
||||||
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,
|
|
||||||
IFNULL(reminders.embed_fields, '[]') AS embed_fields,
|
|
||||||
reminders.enabled,
|
|
||||||
reminders.expires,
|
|
||||||
reminders.interval_seconds,
|
|
||||||
reminders.interval_days,
|
|
||||||
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 `status` = 'pending' AND FIND_IN_SET(channels.channel, ?)",
|
|
||||||
channels
|
|
||||||
)
|
|
||||||
.fetch_all(pool.inner())
|
|
||||||
.await
|
|
||||||
.map(|r| Ok(json!(r)))
|
|
||||||
.unwrap_or_else(|e| {
|
|
||||||
warn!("Failed to complete SQL query: {:?}", e);
|
|
||||||
|
|
||||||
json_err!("Could not load reminders")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
warn!("Could not fetch channels from {}: {:?}", id, e);
|
|
||||||
|
|
||||||
Ok(json!([]))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[patch("/api/guild/<id>/reminders", data = "<reminder>")]
|
#[patch("/api/guild/<id>/reminders", data = "<reminder>")]
|
||||||
@ -615,3 +604,35 @@ pub async fn delete_reminder(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[get("/api/guild/<id>/errors")]
|
||||||
|
pub async fn get_reminder_errors(
|
||||||
|
id: u64,
|
||||||
|
cookies: &CookieJar<'_>,
|
||||||
|
serenity_context: &State<Context>,
|
||||||
|
pool: &State<Pool<MySql>>,
|
||||||
|
) -> JsonResult {
|
||||||
|
check_authorization!(cookies, serenity_context.inner(), id);
|
||||||
|
|
||||||
|
sqlx::query_as_unchecked!(
|
||||||
|
ReminderError,
|
||||||
|
"SELECT
|
||||||
|
reminders.status,
|
||||||
|
reminders.utc_time,
|
||||||
|
reminders.name,
|
||||||
|
reminders.uid,
|
||||||
|
reminders.channel_id AS channel
|
||||||
|
FROM reminders
|
||||||
|
LEFT JOIN channels ON channels.id = reminders.channel_id
|
||||||
|
WHERE (`status` != 'pending' OR reminders.channel_id IS NULL) AND reminders.guild_id = (SELECT id FROM guilds WHERE guild = ?)",
|
||||||
|
id
|
||||||
|
)
|
||||||
|
.fetch_all(pool.inner())
|
||||||
|
.await
|
||||||
|
.map(|r| Ok(json!(r)))
|
||||||
|
.unwrap_or_else(|e| {
|
||||||
|
warn!("Failed to complete SQL query: {:?}", e);
|
||||||
|
|
||||||
|
json_err!("Could not load reminders")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -152,6 +152,18 @@ pub struct Reminder {
|
|||||||
utc_time: NaiveDateTime,
|
utc_time: NaiveDateTime,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct ReminderError {
|
||||||
|
#[serde(with = "string")]
|
||||||
|
channel: u64,
|
||||||
|
status: String,
|
||||||
|
#[serde(default = "name_default")]
|
||||||
|
name: String,
|
||||||
|
#[serde(default)]
|
||||||
|
uid: String,
|
||||||
|
utc_time: NaiveDateTime,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct ReminderCsv {
|
pub struct ReminderCsv {
|
||||||
#[serde(with = "base64s")]
|
#[serde(with = "base64s")]
|
||||||
@ -479,6 +491,7 @@ pub async fn create_reminder(
|
|||||||
attachment,
|
attachment,
|
||||||
attachment_name,
|
attachment_name,
|
||||||
channel_id,
|
channel_id,
|
||||||
|
guild_id,
|
||||||
avatar,
|
avatar,
|
||||||
content,
|
content,
|
||||||
embed_author,
|
embed_author,
|
||||||
@ -501,11 +514,12 @@ pub async fn create_reminder(
|
|||||||
tts,
|
tts,
|
||||||
username,
|
username,
|
||||||
`utc_time`
|
`utc_time`
|
||||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||||
new_uid,
|
new_uid,
|
||||||
attachment_data,
|
attachment_data,
|
||||||
reminder.attachment_name,
|
reminder.attachment_name,
|
||||||
channel,
|
channel,
|
||||||
|
guild_id.0,
|
||||||
reminder.avatar,
|
reminder.avatar,
|
||||||
reminder.content,
|
reminder.content,
|
||||||
reminder.embed_author,
|
reminder.embed_author,
|
||||||
|
@ -54,6 +54,8 @@ pub async fn get_user_info(
|
|||||||
ctx: &State<Context>,
|
ctx: &State<Context>,
|
||||||
pool: &State<Pool<MySql>>,
|
pool: &State<Pool<MySql>>,
|
||||||
) -> JsonValue {
|
) -> JsonValue {
|
||||||
|
offline!(json!(UserInfo { name: "Discord".to_string(), patreon: true, timezone: None }));
|
||||||
|
|
||||||
if let Some(user_id) =
|
if let Some(user_id) =
|
||||||
cookies.get_private("userid").map(|u| u.value().parse::<u64>().ok()).flatten()
|
cookies.get_private("userid").map(|u| u.value().parse::<u64>().ok()).flatten()
|
||||||
{
|
{
|
||||||
@ -116,6 +118,8 @@ pub async fn update_user_info(
|
|||||||
|
|
||||||
#[get("/api/user/guilds")]
|
#[get("/api/user/guilds")]
|
||||||
pub async fn get_user_guilds(cookies: &CookieJar<'_>, reqwest_client: &State<Client>) -> JsonValue {
|
pub async fn get_user_guilds(cookies: &CookieJar<'_>, reqwest_client: &State<Client>) -> JsonValue {
|
||||||
|
offline!(json!(vec![GuildInfo { id: "1".to_string(), name: "Guild".to_string() }]));
|
||||||
|
|
||||||
if let Some(access_token) = cookies.get_private("access_token") {
|
if let Some(access_token) = cookies.get_private("access_token") {
|
||||||
let request_res = reqwest_client
|
let request_res = reqwest_client
|
||||||
.get(format!("{}/users/@me/guilds", DISCORD_API))
|
.get(format!("{}/users/@me/guilds", DISCORD_API))
|
||||||
|
@ -249,11 +249,11 @@ div#pageNavbar a {
|
|||||||
|
|
||||||
.navbar-item.pageTitle {
|
.navbar-item.pageTitle {
|
||||||
flex-shrink: 1;
|
flex-shrink: 1;
|
||||||
text-wrap: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar-burger, .navbar-burger:active, .navbar-burger.is-active {
|
.dashboard-burger, .dashboard-burger:active, .dashboard-burger.is-active {
|
||||||
background-color: #adc99c !important;
|
background-color: #adc99c !important;
|
||||||
border-radius: 14px;
|
border-radius: 14px;
|
||||||
padding: 6px;
|
padding: 6px;
|
||||||
@ -644,9 +644,11 @@ li.highlight {
|
|||||||
}
|
}
|
||||||
|
|
||||||
p.title.pageTitle {
|
p.title.pageTitle {
|
||||||
visibility: hidden;
|
display: none;
|
||||||
text-wrap: nowrap;
|
}
|
||||||
overflow: hidden;
|
|
||||||
|
.dashboard-frame {
|
||||||
|
margin-top: 4rem !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -665,6 +667,7 @@ li.highlight {
|
|||||||
/* loader */
|
/* loader */
|
||||||
#loader {
|
#loader {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
background-color: rgba(255, 255, 255, 0.8);
|
background-color: rgba(255, 255, 255, 0.8);
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
z-index: 999;
|
z-index: 999;
|
||||||
@ -719,9 +722,20 @@ a.switch-pane {
|
|||||||
|
|
||||||
.is-locked {
|
.is-locked {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.is-locked > :not(.patreon-invert) {
|
||||||
opacity: 0.4;
|
opacity: 0.4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.is-locked .patreon-invert {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.patreon-invert {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
.is-locked .foreground {
|
.is-locked .foreground {
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
}
|
}
|
||||||
@ -751,5 +765,5 @@ a.switch-pane {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.figure-num {
|
.figure-num {
|
||||||
font-size: 2em;
|
font-size: 2rem;
|
||||||
}
|
}
|
||||||
|
19
web/static/js/reminder_errors.js
Normal file
19
web/static/js/reminder_errors.js
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
let _reminderErrors = [];
|
||||||
|
|
||||||
|
const reminderErrors = () => {
|
||||||
|
return _reminderErrors;
|
||||||
|
}
|
||||||
|
|
||||||
|
const guildId = () => {
|
||||||
|
let selected = document.querySelector(".guildList a.is-active");
|
||||||
|
return selected.dataset["guild"];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function loadErrors() {
|
||||||
|
fetch(`/dashboard/api/guild/${guildId()}/errors`).then(response => response.json())
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
|
||||||
|
})
|
19
web/static/js/reminder_errors.ts
Normal file
19
web/static/js/reminder_errors.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
let _reminderErrors = [];
|
||||||
|
|
||||||
|
const reminderErrors = () => {
|
||||||
|
return _reminderErrors;
|
||||||
|
}
|
||||||
|
|
||||||
|
const guildId = () => {
|
||||||
|
let selected: HTMLElement = document.querySelector(".guildList a.is-active");
|
||||||
|
return selected.dataset["guild"];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function loadErrors() {
|
||||||
|
fetch(`/dashboard/api/guild/${guildId()}/errors`).then(response => response.json())
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
|
||||||
|
})
|
@ -47,7 +47,7 @@
|
|||||||
<p class="navbar-item pageTitle">
|
<p class="navbar-item pageTitle">
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<a role="button" class="navbar-burger is-right" aria-label="menu" aria-expanded="false"
|
<a role="button" class="dashboard-burger navbar-burger is-right" aria-label="menu" aria-expanded="false"
|
||||||
data-target="mobileSidebar">
|
data-target="mobileSidebar">
|
||||||
<span aria-hidden="true"></span>
|
<span aria-hidden="true"></span>
|
||||||
<span aria-hidden="true"></span>
|
<span aria-hidden="true"></span>
|
||||||
@ -335,34 +335,14 @@
|
|||||||
<section id="guild" class="is-hidden">
|
<section id="guild" class="is-hidden">
|
||||||
{% include "reminder_dashboard/reminder_dashboard" %}
|
{% include "reminder_dashboard/reminder_dashboard" %}
|
||||||
</section>
|
</section>
|
||||||
<section id="guild-error" class="is-hidden hero is-fullheight">
|
<section id="reminder-errors" class="is-hidden">
|
||||||
<div class="hero-body">
|
{% include "reminder_dashboard/reminder_errors" %}
|
||||||
<div class="container has-text-centered">
|
|
||||||
<p class="title">
|
|
||||||
We couldn't get this server's data
|
|
||||||
</p>
|
|
||||||
<p class="subtitle">
|
|
||||||
Please check Reminder Bot is in the server, and has correct permissions.
|
|
||||||
</p>
|
|
||||||
<a class="button is-size-4 is-rounded is-success" href="https://invite.reminder-bot.com">
|
|
||||||
<p class="is-size-4">
|
|
||||||
<span>Add to Server</span> <span class="icon"><i class="fas fa-chevron-right"></i></span>
|
|
||||||
</p>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
</section>
|
||||||
<section id="user-error" class="is-hidden hero is-fullheight">
|
<section id="guild-error" class="is-hidden">
|
||||||
<div class="hero-body">
|
{% include "reminder_dashboard/guild_error" %}
|
||||||
<div class="container has-text-centered">
|
</section>
|
||||||
<p class="title">
|
<section id="user-error" class="is-hidden">
|
||||||
You do not have permissions for this server
|
{% include "reminder_dashboard/user_error" %}
|
||||||
</p>
|
|
||||||
<p class="subtitle">
|
|
||||||
Ask an admin to grant you the "Manage Messages" permission.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
<!-- /main content -->
|
<!-- /main content -->
|
||||||
|
17
web/templates/reminder_dashboard/guild_error.html.tera
Normal file
17
web/templates/reminder_dashboard/guild_error.html.tera
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<div class="hero is-fullheight">
|
||||||
|
<div class="hero-body">
|
||||||
|
<div class="container has-text-centered">
|
||||||
|
<p class="title">
|
||||||
|
We couldn't get this server's data
|
||||||
|
</p>
|
||||||
|
<p class="subtitle">
|
||||||
|
Please check Reminder Bot is in the server, and has correct permissions.
|
||||||
|
</p>
|
||||||
|
<a class="button is-size-4 is-rounded is-success" href="https://invite.reminder-bot.com">
|
||||||
|
<p class="is-size-4">
|
||||||
|
<span>Add to Server</span> <span class="icon"><i class="fas fa-chevron-right"></i></span>
|
||||||
|
</p>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -165,6 +165,9 @@
|
|||||||
<div class="collapses split-controls">
|
<div class="collapses split-controls">
|
||||||
<div>
|
<div>
|
||||||
<div class="patreon-only">
|
<div class="patreon-only">
|
||||||
|
<div class="patreon-invert foreground">
|
||||||
|
Intervals available on <a href="https://patreon.com/jellywx">Patreon</a> or <a href="https://gitea.jellypro.xyz/jude/reminder-bot">self-hosting</a>
|
||||||
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label">Interval <a class="foreground" href="/help/intervals"><i class="fas fa-question-circle"></i></a></label>
|
<label class="label">Interval <a class="foreground" href="/help/intervals"><i class="fas fa-question-circle"></i></a></label>
|
||||||
<div class="control intervalSelector">
|
<div class="control intervalSelector">
|
||||||
|
@ -0,0 +1,5 @@
|
|||||||
|
<div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="/static/js/reminder_errors.js"></script>
|
12
web/templates/reminder_dashboard/user_error.html.tera
Normal file
12
web/templates/reminder_dashboard/user_error.html.tera
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<div class="hero is-fullheight">
|
||||||
|
<div class="hero-body">
|
||||||
|
<div class="container has-text-centered">
|
||||||
|
<p class="title">
|
||||||
|
You do not have permissions for this server
|
||||||
|
</p>
|
||||||
|
<p class="subtitle">
|
||||||
|
Ask an admin to grant you the "Manage Messages" permission.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
Loading…
Reference in New Issue
Block a user