Compare commits

..

10 Commits

Author SHA1 Message Date
jude
109cf16dbb Store guild when creating reminders 2023-08-19 21:24:02 +01:00
jude
6726ca0c2d Correct migration script.
Stub code for routes. Update existing routes to set the reminder's guild
ID.
2023-08-19 21:24:02 +01:00
38133be15d In progress 2023-08-19 21:24:02 +01:00
jude
8587bed703 Bump ver 2023-08-19 17:10:45 +01:00
jude
6c9af1ae8e Correct styles on mobile burger 2023-08-19 17:09:46 +01:00
jude
7695b7a476 Fix delete command 2023-08-19 14:35:07 +01:00
651da7b28e Improve some styles. Add an offline mode 2023-08-19 14:20:48 +01:00
eb086146bf Bump version 2023-08-16 17:05:18 +01:00
4ebd705e5e Add clearer indication of interval patreon requirements 2023-08-16 17:03:38 +01:00
5a85f1d83a Extract error sections to templates 2023-08-13 18:29:30 +01:00
14 changed files with 145 additions and 65 deletions

2
Cargo.lock generated
View File

@ -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",

View File

@ -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"

View File

@ -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)

View File

@ -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(
@ -186,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;
@ -207,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;

View File

@ -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"}));
}
} }
} }
} }

View File

@ -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);

View File

@ -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))

View File

@ -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;
} }

View 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', () => {
})

View File

@ -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 -->

View 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>

View File

@ -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">

View File

@ -0,0 +1,5 @@
<div>
</div>
<script src="/static/js/reminder_errors.js"></script>

View 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>