Add dark mode support

This commit is contained in:
jude
2026-05-16 18:20:00 +01:00
parent 9c6c324b02
commit 050277ac8b
20 changed files with 431 additions and 125 deletions
@@ -0,0 +1,17 @@
SET foreign_key_checks = 0;
-- Drop all old tables
DROP TABLE IF EXISTS users_old;
DROP TABLE IF EXISTS messages;
DROP TABLE IF EXISTS embeds;
DROP TABLE IF EXISTS embed_fields;
DROP TABLE IF EXISTS command_aliases;
DROP TABLE IF EXISTS macro;
DROP TABLE IF EXISTS roles;
DROP TABLE IF EXISTS command_restrictions;
-- Drop columns from channels that are no longer used
ALTER TABLE channels DROP COLUMN `name`;
ALTER TABLE channels DROP COLUMN `blacklisted`;
SET foreign_key_checks = 1;
+2 -1
View File
@@ -5,7 +5,8 @@
"scripts": { "scripts": {
"dev": "vite build --watch --mode development", "dev": "vite build --watch --mode development",
"build": "vite build", "build": "vite build",
"preview": "vite preview" "preview": "vite preview",
"prettier": "prettier -w src/"
}, },
"dependencies": { "dependencies": {
"axios": "^1.5.1", "axios": "^1.5.1",
@@ -0,0 +1,43 @@
import { createContext } from "preact";
import { useContext } from "preact/compat";
import { useEffect, useState } from "preact/hooks";
import { fetchUserInfo } from "../../api";
import { useQuery } from "react-query";
type TColorScheme = "light" | "dark";
type TColorSchemeContext = {
colorScheme: TColorScheme;
};
const ColorSchemeContext = createContext({ colorScheme: "light" } as TColorSchemeContext);
export const ColorSchemeProvider = ({ children }) => {
const { data } = useQuery({ ...fetchUserInfo() });
const [activeScheme, setActiveScheme] = useState<TColorScheme>("light");
useEffect(() => {
const preference = data?.preferences?.dashboard_color_scheme || "system";
if (preference === "system") {
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
const handleChange = () => {
setActiveScheme(mediaQuery.matches ? "dark" : "light");
};
handleChange();
mediaQuery.addEventListener("change", handleChange);
return () => mediaQuery.removeEventListener("change", handleChange);
} else {
setActiveScheme(preference as TColorScheme);
}
}, [data]);
return (
<ColorSchemeContext.Provider value={{ colorScheme: activeScheme }}>
{children}
</ColorSchemeContext.Provider>
);
};
export const useColorScheme = () => useContext(ColorSchemeContext);
+63 -50
View File
@@ -1,59 +1,72 @@
import { Sidebar } from "../Sidebar"; import {Sidebar} from "../Sidebar";
import { QueryClient, QueryClientProvider } from "react-query"; import {QueryClient, QueryClientProvider} from "react-query";
import { Route, Router, Switch } from "wouter"; import {Route, Router, Switch} from "wouter";
import { Welcome } from "../Welcome"; import {Welcome} from "../Welcome";
import { Guild } from "../Guild"; import {Guild} from "../Guild";
import { FlashProvider } from "./FlashProvider"; import {FlashProvider} from "./FlashProvider";
import { TimezoneProvider } from "./TimezoneProvider"; import {TimezoneProvider} from "./TimezoneProvider";
import { User } from "../User"; import {User} from "../User";
import { GuildReminders } from "../Guild/GuildReminders"; import {GuildReminders} from "../Guild/GuildReminders";
import { GuildTodos } from "../Guild/GuildTodos"; import {GuildTodos} from "../Guild/GuildTodos";
import {ColorSchemeProvider, useColorScheme} from "./ColorSchemeProvider";
import {useEffect} from "preact/hooks";
const InnerApp = () => {
const {colorScheme} = useColorScheme();
useEffect(() => {
const body = document.querySelector("body");
body.className = body.className.replace(/scheme-\w+/g, "");
body.classList.add(`scheme-${colorScheme}`);
}, [colorScheme]);
return (
<Router base={"/dashboard"}>
<div class={`columns is-gapless dashboard-frame scheme-${colorScheme}`}>
<Sidebar/>
<div class="column is-main-content">
<div style={{margin: "0 12px 12px 12px"}}>
<Switch>
<Route path={"/@me/reminders"} component={User}></Route>
<Route
path={"/:guild/reminders"}
component={() => (
<Guild>
<GuildReminders/>
</Guild>
)}
></Route>
<Route
path={"/:guild/todos"}
component={() => (
<Guild>
<GuildTodos/>
</Guild>
)}
></Route>
<Route>
<Welcome/>
</Route>
</Switch>
</div>
</div>
</div>
</Router>
);
};
export function App() { export function App() {
const queryClient = new QueryClient(); const queryClient = new QueryClient();
let scheme = "light";
// if (window.matchMedia("(prefers-color-scheme: dark)").matches) {
// scheme = "dark";
// }
return ( return (
<QueryClientProvider client={queryClient}> <QueryClientProvider client={queryClient}>
<TimezoneProvider> <ColorSchemeProvider>
<FlashProvider> <TimezoneProvider>
<Router base={"/dashboard"}> <FlashProvider>
<div class={`columns is-gapless dashboard-frame scheme-${scheme}`}> <InnerApp/>
<Sidebar /> </FlashProvider>
<div class="column is-main-content"> </TimezoneProvider>
<div style={{ margin: "0 12px 12px 12px" }}> </ColorSchemeProvider>
<Switch>
<Route path={"/@me/reminders"} component={User}></Route>
<Route
path={"/:guild/reminders"}
component={() => (
<Guild>
<GuildReminders />
</Guild>
)}
></Route>
<Route
path={"/:guild/todos"}
component={() => (
<Guild>
<GuildTodos />
</Guild>
)}
></Route>
<Route>
<Welcome />
</Route>
</Switch>
</div>
</div>
</div>
</Router>
</FlashProvider>
</TimezoneProvider>
</QueryClientProvider> </QueryClientProvider>
); );
} }
@@ -1,5 +1,5 @@
import {JSX} from "preact"; import { JSX } from "preact";
import {createPortal} from "preact/compat"; import { createPortal } from "preact/compat";
type Props = { type Props = {
setModalOpen: (open: boolean) => never; setModalOpen: (open: boolean) => never;
@@ -9,7 +9,7 @@ type Props = {
children: string | JSX.Element | JSX.Element[] | (() => JSX.Element); children: string | JSX.Element | JSX.Element[] | (() => JSX.Element);
}; };
export const Modal = ({setModalOpen, title, onSubmit, onSubmitText, children}: Props) => { export const Modal = ({ setModalOpen, title, onSubmit, onSubmitText, children }: Props) => {
const body = document.querySelector("body"); const body = document.querySelector("body");
return createPortal( return createPortal(
@@ -11,7 +11,7 @@
border-radius: 8px; border-radius: 8px;
margin: 4px; margin: 4px;
padding: 4px; padding: 4px;
box-shadow: 0 0 5px 0 rgba(0,0,0,0.75); box-shadow: 0 0 5px 0 rgba(0, 0, 0, 0.75);
.highlight { .highlight {
background-color: #35373c; background-color: #35373c;
@@ -66,7 +66,7 @@ aside.menu {
padding-left: 4px; padding-left: 4px;
} }
@media screen and (max-width: 1023px),print { @media screen and (max-width: 1023px), print {
.columns:not(.is-desktop) { .columns:not(.is-desktop) {
flex-direction: column; flex-direction: column;
} }
@@ -5,6 +5,12 @@ import { useRef, useState } from "preact/hooks";
import { ICON_FLASH_TIME } from "../../consts"; import { ICON_FLASH_TIME } from "../../consts";
import { useFlash } from "../App/FlashContext"; import { useFlash } from "../App/FlashContext";
enum ColorScheme {
System = "system",
Dark = "dark",
Light = "light",
}
export const UserPreferences = () => { export const UserPreferences = () => {
const [modalOpen, setModalOpen] = useState(false); const [modalOpen, setModalOpen] = useState(false);
@@ -96,6 +102,40 @@ const PreferencesModal = ({ setModalOpen }) => {
</label> </label>
</div> </div>
<br></br> <br></br>
<div style={{ display: "flex", flexDirection: "row", alignContent: "center" }}>
<label>
<div class={"is-inline-block"} style={{ marginRight: "6px" }}>
Dashboard Color Scheme:
</div>
<div class={"control"}>
<div class={"is-inline-block select"}>
{isLoading && <i class={"fa fa-spinner"} />}
{isSuccess && (
<select
class={"channel-selector"}
value={
updatedSettings.dashboard_color_scheme === undefined
? data.preferences.dashboard_color_scheme
: updatedSettings.dashboard_color_scheme
}
onChange={(e) =>
setUpdatedSettings((s) => ({
...s,
dashboard_color_scheme: (e.target as HTMLSelectElement)
.value as ColorScheme,
}))
}
>
<option value={ColorScheme.System}>System default</option>
<option value={ColorScheme.Light}>Light</option>
<option value={ColorScheme.Dark}>Dark</option>
</select>
)}
</div>
</div>
</label>
</div>
<br></br>
<div class="has-text-centered"> <div class="has-text-centered">
<button <button
class="button is-success is-outlined" class="button is-success is-outlined"
+240 -18
View File
@@ -2,58 +2,280 @@
/* override styles for when the div is collapsed */ /* override styles for when the div is collapsed */
div.reminderContent.is-collapsed .column.discord-frame { div.reminderContent.is-collapsed .column.discord-frame {
display: none; display: none;
} }
div.reminderContent.is-collapsed .column.settings { div.reminderContent.is-collapsed .column.settings {
display: none; display: none;
} }
div.reminderContent.is-collapsed .reminder-settings { div.reminderContent.is-collapsed .reminder-settings {
margin-bottom: 0; margin-bottom: 0;
} }
div.reminderContent.is-collapsed .button-row { div.reminderContent.is-collapsed .button-row {
display: none; display: none;
} }
div.reminderContent.is-collapsed .button-row-edit { div.reminderContent.is-collapsed .button-row-edit {
display: none; display: none;
} }
div.reminderContent.is-collapsed .reminder-topbar { div.reminderContent.is-collapsed .reminder-topbar {
padding-bottom: 0; padding-bottom: 0;
} }
div.reminderContent.is-collapsed .invert-collapses { div.reminderContent.is-collapsed .invert-collapses {
display: inline-flex; display: inline-flex;
} }
div.reminderContent .invert-collapses { div.reminderContent .invert-collapses {
display: none; display: none;
} }
div.reminderContent.is-collapsed input[name="name"] { div.reminderContent.is-collapsed input[name="name"] {
display: inline-flex; display: inline-flex;
flex-grow: 1; flex-grow: 1;
border: none; border: none;
background: none; background: none;
box-shadow: none; box-shadow: none;
opacity: 1; opacity: 1;
} }
div.reminderContent.is-collapsed .hide-box { div.reminderContent.is-collapsed .hide-box {
display: inline-flex; display: inline-flex;
} }
div.reminderContent.is-collapsed .hide-box i { div.reminderContent.is-collapsed .hide-box i {
transform: rotate(90deg); transform: rotate(90deg);
} }
.button.is-success:not(.is-outlined) { .button.is-success:not(.is-outlined) {
color: white; color: white;
} }
.button.is-outlined.is-success:not(.is-focused, :hover, :focus) { .button.is-outlined.is-success:not(.is-focused, :hover, :focus) {
background-color: white; background-color: white;
}
@import "vars";
.scheme-light {
background-color: $primary-background-light;
color: $primary-text-light;
.column.is-main-content {
background-color: $primary-background-light;
}
.reminderContent {
background-color: $secondary-background-light;
}
}
.scheme-dark {
background-color: $secondary-background-dark;
color: $primary-text-dark;
.column.is-main-content {
background-color: $secondary-background-dark;
}
.reminderContent {
background-color: $primary-background-dark;
color: $primary-text-dark;
}
.title,
.subtitle,
.label,
.help,
strong {
color: $primary-text-dark;
}
input,
textarea,
select,
.input,
.textarea {
background-color: $secondary-background-dark;
color: $primary-text-dark;
border-color: $contrast-background-dark;
&::placeholder {
color: #aaa;
}
}
// Buttons
.button.is-outlined.is-success:not(.is-focused, :hover, :focus) {
background-color: $primary-background-dark;
color: #48c78e;
border-color: #48c78e;
}
.button.is-outlined.is-danger:not(.is-focused, :hover, :focus) {
background-color: $primary-background-dark;
}
.button.is-outlined.is-warning:not(.is-focused, :hover, :focus) {
background-color: $primary-background-dark;
}
.button:not(.is-success, .is-danger, .is-warning, .is-light) {
background-color: $secondary-background-dark;
color: $primary-text-dark;
border-color: $contrast-background-dark;
}
.button.is-light {
background-color: $secondary-background-dark;
color: $primary-text-dark;
}
// Modal
.modal-card-head,
.modal-card-foot {
background-color: $contrast-background-dark;
border-color: $contrast-background-dark;
}
.modal-card-head .modal-card-title {
color: $primary-text-dark;
}
.modal-card-body {
background-color: $primary-background-dark;
color: $primary-text-dark;
}
// Navbar
.navbar {
background-color: $contrast-background-dark;
}
.navbar-item,
.navbar-burger {
color: $primary-text-dark;
}
// Select dropdown arrow
.select:not(.is-multiple):not(.is-loading)::after {
border-color: $primary-text-dark;
}
.select select {
background-color: $secondary-background-dark;
color: $primary-text-dark;
border-color: $contrast-background-dark;
}
// Interval selector inputs
.interval-group input {
background-color: $secondary-background-dark !important;
color: $primary-text-dark;
}
// Notification
.notification {
background-color: $secondary-background-dark;
color: $primary-text-dark;
}
// Links
a:not(.menu a):not(.button) {
color: #7eaef1;
}
// Content area
.content {
color: $primary-text-dark;
}
// Checkbox
.checkbox {
color: $primary-text-dark;
}
// Message input
.message-input {
background-color: $secondary-background-dark;
color: $primary-text-dark;
}
// Box
.box {
background-color: $secondary-background-dark;
color: $primary-text-dark;
}
// Panel
.panel {
background-color: $secondary-background-dark;
.panel-heading {
background-color: $contrast-background-dark;
color: $primary-text-dark;
}
.panel-block {
color: $primary-text-dark;
border-color: $contrast-background-dark;
}
}
// Horizontal rule
hr {
background-color: $contrast-background-dark;
}
// Tag
.tag:not(.is-success, .is-danger, .is-warning, .is-info) {
background-color: $contrast-background-dark;
color: $primary-text-dark;
}
// Todo
.todo {
color: $primary-text-dark;
}
// Page links
.page-links .button {
background-color: $secondary-background-dark;
color: $primary-text-dark;
border-color: $contrast-background-dark;
}
// Loading overlay
.load-screen {
background-color: rgba(36, 36, 36, 0.8);
color: $primary-text-dark;
}
// File/attachment input
.file-cta {
background-color: $secondary-background-dark;
color: $primary-text-dark;
border-color: $contrast-background-dark;
}
.file-name {
background-color: $secondary-background-dark;
color: $primary-text-dark;
border-color: $contrast-background-dark;
}
// Date/time inputs color scheme
input[type="date"],
input[type="time"],
input[type="datetime-local"] {
color-scheme: dark;
}
// Modal background overlay
.modal-background {
background-color: rgba(10, 10, 10, 0.86);
}
} }
+2 -3
View File
@@ -56,7 +56,7 @@ impl Recordable for Options {
}), }),
}; };
let channel_id = if let Some(channel) = ctx.channel_id().to_channel_cached(&ctx.cache()) { let channel_id = if let Some(channel) = ctx.cache().channel(ctx.channel_id()) {
if Some(channel.guild_id) == ctx.guild_id() { if Some(channel.guild_id) == ctx.guild_id() {
flags.channel_id.unwrap_or_else(|| ctx.channel_id()) flags.channel_id.unwrap_or_else(|| ctx.channel_id())
} else { } else {
@@ -66,8 +66,7 @@ impl Recordable for Options {
ctx.channel_id() ctx.channel_id()
}; };
let channel_name = let channel_name = ctx.cache().channel(channel_id).map(|channel| channel.name.clone());
channel_id.to_channel_cached(&ctx.cache()).map(|channel| channel.name.clone());
let reminders = Reminder::from_channel(&ctx.data().database, channel_id, &flags).await; let reminders = Reminder::from_channel(&ctx.data().database, channel_id, &flags).await;
+2 -2
View File
@@ -63,7 +63,7 @@ impl ComponentDataModel {
let flags = pager.flags; let flags = pager.flags;
let channel_id = { let channel_id = {
let channel_opt = component.channel_id.to_channel_cached(&ctx.cache); let channel_opt = ctx.cache.channel(component.channel_id);
if let Some(channel) = channel_opt { if let Some(channel) = channel_opt {
if Some(channel.guild_id) == component.guild_id { if Some(channel.guild_id) == component.guild_id {
@@ -85,7 +85,7 @@ impl ComponentDataModel {
.div_ceil(EMBED_DESCRIPTION_MAX_LENGTH); .div_ceil(EMBED_DESCRIPTION_MAX_LENGTH);
let channel_name = let channel_name =
channel_id.to_channel_cached(&ctx.cache).map(|channel| channel.name.clone()); ctx.cache.channel(channel_id).map(|channel| channel.name.clone());
let next_page = pager.next_page(pages); let next_page = pager.next_page(pages);
+3 -10
View File
@@ -8,9 +8,7 @@ use crate::{consts::DEFAULT_AVATAR, Error};
pub struct ChannelData { pub struct ChannelData {
pub id: u32, pub id: u32,
pub channel: u64, pub channel: u64,
pub name: Option<String>,
pub nudge: i16, pub nudge: i16,
pub blacklisted: bool,
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,
@@ -27,7 +25,7 @@ impl ChannelData {
if let Ok(c) = sqlx::query_as_unchecked!( if let Ok(c) = sqlx::query_as_unchecked!(
Self, Self,
" "
SELECT id, channel, name, nudge, blacklisted, webhook_id, webhook_token, paused, SELECT id, channel, nudge, webhook_id, webhook_token, paused,
paused_until paused_until
FROM channels FROM channels
WHERE channel = ? WHERE channel = ?
@@ -45,9 +43,8 @@ impl ChannelData {
if let Some((a, b)) = props { (Some(a), Some(b)) } else { (None, None) }; if let Some((a, b)) = props { (Some(a), Some(b)) } else { (None, None) };
sqlx::query!( sqlx::query!(
"INSERT IGNORE INTO channels (channel, name, guild_id) VALUES (?, ?, (SELECT id FROM guilds WHERE guild = ?))", "INSERT IGNORE INTO channels (channel, guild_id) VALUES (?, (SELECT id FROM guilds WHERE guild = ?))",
channel_id, channel_id,
channel_name,
guild_id guild_id
) )
.execute(&pool.clone()) .execute(&pool.clone())
@@ -56,7 +53,7 @@ impl ChannelData {
Ok(sqlx::query_as_unchecked!( Ok(sqlx::query_as_unchecked!(
Self, Self,
" "
SELECT id, channel, name, nudge, blacklisted, webhook_id, webhook_token, paused, paused_until SELECT id, channel, nudge, webhook_id, webhook_token, paused, paused_until
FROM channels FROM channels
WHERE channel = ? WHERE channel = ?
", ",
@@ -72,18 +69,14 @@ impl ChannelData {
" "
UPDATE channels UPDATE channels
SET SET
name = ?,
nudge = ?, nudge = ?,
blacklisted = ?,
webhook_id = ?, webhook_id = ?,
webhook_token = ?, webhook_token = ?,
paused = ?, paused = ?,
paused_until = ? paused_until = ?
WHERE id = ? WHERE id = ?
", ",
self.name,
self.nudge, self.nudge,
self.blacklisted,
self.webhook_id, self.webhook_id,
self.webhook_token, self.webhook_token,
self.paused, self.paused,
+1 -1
View File
@@ -42,7 +42,7 @@ impl IpBlocking {
fn contains<I: Into<u32>>(&self, ip: I) -> bool { fn contains<I: Into<u32>>(&self, ip: I) -> bool {
let ip: u32 = ip.into(); let ip: u32 = ip.into();
let mut prev_index = self.upper_ips.len() - 1; let _prev_index = self.upper_ips.len() - 1;
let mut index = self.upper_ips.len() / 2; let mut index = self.upper_ips.len() / 2;
loop { loop {
if self.upper_ips[index] <= ip && self.lower_ips[index] >= ip { if self.upper_ips[index] <= ip && self.lower_ips[index] >= ip {
+3 -7
View File
@@ -63,8 +63,7 @@ use log::{error, info, warn};
use oauth2::{basic::BasicClient, AuthUrl, ClientId, ClientSecret, RedirectUrl, TokenUrl}; use oauth2::{basic::BasicClient, AuthUrl, ClientId, ClientSecret, RedirectUrl, TokenUrl};
use poise::serenity_prelude::{ use poise::serenity_prelude::{
client::Context, client::Context,
http::CacheHttp, model::id::{ChannelId, GuildId, UserId},
model::id::{GuildId, UserId},
}; };
use rocket::{ use rocket::{
catchers, catchers,
@@ -76,13 +75,10 @@ use rocket::{
}; };
use rocket_dyn_templates::Template; use rocket_dyn_templates::Template;
use sqlx::{MySql, Pool}; use sqlx::{MySql, Pool};
use std::net::Ipv4Addr;
use std::{env, path::Path}; use std::{env, path::Path};
use crate::web::{ use crate::web::consts::{DISCORD_OAUTH_AUTHORIZE, DISCORD_OAUTH_TOKEN};
consts::{CNC_GUILD, DISCORD_OAUTH_AUTHORIZE, DISCORD_OAUTH_TOKEN, SUBSCRIPTION_ROLES}, use crate::web::fairings::metrics::MetricProducer;
fairings::metrics::MetricProducer,
};
type Database = MySql; type Database = MySql;
+1 -6
View File
@@ -5,8 +5,6 @@ mod roles;
mod templates; mod templates;
pub mod todos; pub mod todos;
use std::env;
use crate::utils::check_subscription; use crate::utils::check_subscription;
use crate::web::guards::transaction::Transaction; use crate::web::guards::transaction::Transaction;
use crate::web::{check_authorization, routes::JsonResult}; use crate::web::{check_authorization, routes::JsonResult};
@@ -16,10 +14,7 @@ pub use reminders::*;
use rocket::{get, http::CookieJar, serde::json::json, State}; use rocket::{get, http::CookieJar, serde::json::json, State};
pub use roles::get_guild_roles; pub use roles::get_guild_roles;
use serenity::all::UserId; use serenity::all::UserId;
use serenity::{ use serenity::{client::Context, model::id::GuildId};
client::Context,
model::id::{GuildId, RoleId},
};
pub use templates::*; pub use templates::*;
#[get("/api/guild/<id>")] #[get("/api/guild/<id>")]
@@ -267,9 +267,8 @@ pub async fn edit_reminder(
} }
if reminder.channel > 0 { if reminder.channel > 0 {
let channel_guild = ChannelId::new(reminder.channel) let channel_guild =
.to_channel_cached(&ctx.cache) ctx.cache.channel(ChannelId::new(reminder.channel)).map(|channel| channel.guild_id);
.map(|channel| channel.guild_id);
match channel_guild { match channel_guild {
Some(channel_guild) => { Some(channel_guild) => {
let channel_matches_guild = channel_guild.get() == id; let channel_matches_guild = channel_guild.get() == id;
@@ -3,13 +3,10 @@ use crate::web::{
check_authorization, check_authorization,
guards::transaction::Transaction, guards::transaction::Transaction,
routes::{ routes::{
dashboard::{ dashboard::{ImportBody, ReminderTemplateCsv},
create_reminder, CreateReminder, ImportBody, ReminderCsv, ReminderTemplateCsv, TodoCsv,
},
JsonResult, JsonResult,
}, },
}; };
use crate::Database;
use base64::{prelude::BASE64_STANDARD, Engine}; use base64::{prelude::BASE64_STANDARD, Engine};
use csv::{QuoteStyle, WriterBuilder}; use csv::{QuoteStyle, WriterBuilder};
use log::warn; use log::warn;
@@ -20,10 +17,7 @@ use rocket::{
serde::json::{json, Json}, serde::json::{json, Json},
State, State,
}; };
use serenity::{ use serenity::{client::Context, model::id::GuildId};
client::Context,
model::id::{ChannelId, GuildId, UserId},
};
use sqlx::{MySql, Pool}; use sqlx::{MySql, Pool};
#[get("/api/guild/<id>/export/reminder_templates")] #[get("/api/guild/<id>/export/reminder_templates")]
+2 -4
View File
@@ -2,9 +2,7 @@ use crate::web::{
check_authorization, check_authorization,
guards::transaction::Transaction, guards::transaction::Transaction,
routes::{ routes::{
dashboard::{ dashboard::{create_reminder, CreateReminder, ImportBody, ReminderCsv},
create_reminder, CreateReminder, ImportBody, ReminderCsv, ReminderTemplateCsv, TodoCsv,
},
JsonResult, JsonResult,
}, },
}; };
@@ -21,7 +19,7 @@ use rocket::{
}; };
use serenity::{ use serenity::{
client::Context, client::Context,
model::id::{ChannelId, GuildId, UserId}, model::id::{GuildId, UserId},
}; };
use sqlx::{MySql, Pool}; use sqlx::{MySql, Pool};
+2 -6
View File
@@ -1,14 +1,10 @@
use crate::web::{ use crate::web::{
check_authorization, check_authorization,
guards::transaction::Transaction,
routes::{ routes::{
dashboard::{ dashboard::{ImportBody, TodoCsv},
create_reminder, CreateReminder, ImportBody, ReminderCsv, ReminderTemplateCsv, TodoCsv,
},
JsonResult, JsonResult,
}, },
}; };
use crate::Database;
use base64::{prelude::BASE64_STANDARD, Engine}; use base64::{prelude::BASE64_STANDARD, Engine};
use csv::{QuoteStyle, WriterBuilder}; use csv::{QuoteStyle, WriterBuilder};
use log::warn; use log::warn;
@@ -21,7 +17,7 @@ use rocket::{
}; };
use serenity::{ use serenity::{
client::Context, client::Context,
model::id::{ChannelId, GuildId, UserId}, model::id::{ChannelId, GuildId},
}; };
use sqlx::{MySql, Pool}; use sqlx::{MySql, Pool};