Compare commits
	
		
			152 Commits
		
	
	
		
			jude/confi
			...
			jude/fix-d
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					9a6b65f3a3 | ||
| 
						 | 
					b6ff149d51 | ||
| 
						 | 
					748e33566b | ||
| 
						 | 
					d7e90614c8 | ||
| 
						 | 
					b5dbfe336d | ||
| 
						 | 
					218be2f0b1 | ||
| 
						 | 
					d7515f3611 | ||
| 
						 | 
					6ae1096d79 | ||
| 
						 | 
					1f0d7adae3 | ||
| 
						 | 
					fc96ae526f | ||
| 
						 | 
					8881ef0f85 | ||
| 
						 | 
					5e82a687f9 | ||
| 
						 | 
					de4ecf8dd6 | ||
| 
						 | 
					064efd4386 | ||
| 
						 | 
					65b8ba3b47 | ||
| 9d452ed8cb | |||
| 
						 | 
					441419b92b | ||
| 
						 | 
					aecf2c15be | ||
| 
						 | 
					79da56c794 | ||
| 
						 | 
					ef10902c1e | ||
| 
						 | 
					c277f85c2a | ||
| 
						 | 
					035653c7fa | ||
| 
						 | 
					6358bc3deb | ||
| 
						 | 
					9f5066f982 | ||
| 
						 | 
					1d06999e41 | ||
| 
						 | 
					1cf707140c | ||
| 
						 | 
					e38c63f5ba | ||
| 
						 | 
					d52b8b26f2 | ||
| bb2128a7ed | |||
| 5e99a6f9de | |||
| 5406e6b8ec | |||
| 
						 | 
					4ee0bc4e37 | ||
| 
						 | 
					b99bb7dcbf | ||
| 
						 | 
					98f925dc84 | ||
| 
						 | 
					24e316b12f | ||
| 
						 | 
					4063334953 | ||
| 
						 | 
					e128b9848f | ||
| 
						 | 
					9989ab3b35 | ||
| 
						 | 
					b951db3f55 | ||
| 
						 | 
					884a47bf36 | ||
| 
						 | 
					b0f932445c | ||
| 
						 | 
					2861cdda0b | ||
| 
						 | 
					7ba8fcd6b7 | ||
| 
						 | 
					850f0fad57 | ||
| 
						 | 
					a770a17ee7 | ||
| 
						 | 
					d15a66d9d9 | ||
| 
						 | 
					30f011fcd5 | ||
| 
						 | 
					15dbed2f0f | ||
| 
						 | 
					18cac0345b | ||
| 
						 | 
					334b1bc084 | ||
| 
						 | 
					ba3c76c25f | ||
| 
						 | 
					67b6f30c62 | ||
| 
						 | 
					8ae311190f | ||
| 
						 | 
					016164affb | ||
| 
						 | 
					2c0aeef700 | ||
| 
						 | 
					ecd75d6f55 | ||
| 
						 | 
					4a80d42f86 | ||
| 
						 | 
					075fde71df | ||
| 
						 | 
					55136aecdc | ||
| 
						 | 
					63fc2cdcbc | ||
| 
						 | 
					3190738fc5 | ||
| 
						 | 
					8f4810b532 | ||
| 
						 | 
					a5e6c41fa5 | ||
| 
						 | 
					5f0aa0f834 | ||
| 
						 | 
					dbe8e8e358 | ||
| 
						 | 
					85a114e55c | ||
| 
						 | 
					329492b244 | ||
| 
						 | 
					66135ecd08 | ||
| 
						 | 
					382c2a5a1e | ||
| 
						 | 
					b91245a3f7 | ||
| 
						 | 
					6f0bdf9852 | ||
| 
						 | 
					dcee9e0d2a | ||
| 
						 | 
					8e6e1a18b7 | ||
| 
						 | 
					72af0532fa | ||
| 
						 | 
					e83b643d86 | ||
| 
						 | 
					0e0ab053f3 | ||
| 
						 | 
					8c2296b9c8 | ||
| 
						 | 
					1c6103142f | ||
| 
						 | 
					328127c55e | ||
| 
						 | 
					b0e37b56c0 | ||
| 
						 | 
					45f5b6261a | ||
| 
						 | 
					5f6326179c | ||
| 
						 | 
					6254f91841 | ||
| 
						 | 
					60b90a61d4 | ||
| 
						 | 
					90f05758d0 | ||
| 
						 | 
					74b7b5d711 | ||
| 
						 | 
					90550dc2c7 | ||
| 
						 | 
					79e6498245 | ||
| 
						 | 
					a8ef3d03f9 | ||
| 
						 | 
					53e13844f9 | ||
| 
						 | 
					dd7e681285 | ||
| 
						 | 
					6c20bf2a0f | ||
| 
						 | 
					15aa9ccffd | ||
| 
						 | 
					525471bcad | ||
| 
						 | 
					86d53b63b6 | ||
| 
						 | 
					d8f266852a | ||
| 
						 | 
					76a286076b | ||
| 
						 | 
					5e39e16060 | ||
| 
						 | 
					c1305cfb36 | ||
| 
						 | 
					4823754955 | ||
| 
						 | 
					eb92eacb90 | ||
| 
						 | 
					d0833b7bca | ||
| 
						 | 
					b81c3c80c1 | ||
| 
						 | 
					2f6d035efe | ||
| 
						 | 
					96012ce43c | ||
| 
						 | 
					fa7ec8731b | ||
| 
						 | 
					def43bfa78 | ||
| 
						 | 
					e4e9af2bb4 | ||
| 
						 | 
					cce0de7c75 | ||
| e7803b98e8 | |||
| 
						 | 
					7aae246388 | ||
| a2d442bc54 | |||
| 59982df827 | |||
| 
						 | 
					7a6372ed02 | ||
| 
						 | 
					14a54471f7 | ||
| 
						 | 
					5d3b77f1cd | ||
| 
						 | 
					1d64c8bb79 | ||
| 8ba0f02b98 | |||
| d36438c6ce | |||
| e0c60e2ce3 | |||
| 
						 | 
					e7160215b0 | ||
| 
						 | 
					6eaa6f0f28 | ||
| 
						 | 
					9db0fa2513 | ||
| 
						 | 
					ca13fd4fa7 | ||
| 
						 | 
					55acc8fd16 | ||
| 
						 | 
					145711fa5d | ||
| 
						 | 
					5524215786 | ||
| 
						 | 
					e8bd05893f | ||
| 
						 | 
					e3d3418f99 | ||
| 
						 | 
					2681280a39 | ||
| 
						 | 
					00579428a1 | ||
| 
						 | 
					b8ef999710 | ||
| 
						 | 
					e8f84e281a | ||
| 
						 | 
					8ddff698e5 | ||
| 
						 | 
					541633270c | ||
| 
						 | 
					25286da5e0 | ||
| 
						 | 
					4bad1324b9 | ||
| 
						 | 
					bd1462a00c | ||
| 
						 | 
					56ffc43616 | ||
| 
						 | 
					52cf642455 | ||
| 
						 | 
					0bf578357a | ||
| 
						 | 
					6e9eccb62e | ||
| 
						 | 
					6ea28284ce | ||
| 
						 | 
					a6525f3052 | ||
| 
						 | 
					348639270d | ||
| 
						 | 
					37177c2431 | ||
| 
						 | 
					8587bed703 | ||
| 
						 | 
					6c9af1ae8e | ||
| 
						 | 
					7695b7a476 | ||
| 651da7b28e | |||
| eb086146bf | |||
| 4ebd705e5e | 
							
								
								
									
										29
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -1,7 +1,30 @@
 | 
				
			|||||||
/target
 | 
					target
 | 
				
			||||||
.env
 | 
					.env
 | 
				
			||||||
/venv
 | 
					/venv
 | 
				
			||||||
.cargo
 | 
					.cargo
 | 
				
			||||||
/.idea
 | 
					.idea
 | 
				
			||||||
 | 
					web/static/index.html
 | 
				
			||||||
 | 
					web/static/assets
 | 
				
			||||||
 | 
					# Logs
 | 
				
			||||||
 | 
					logs
 | 
				
			||||||
 | 
					*.log
 | 
				
			||||||
 | 
					npm-debug.log*
 | 
				
			||||||
 | 
					yarn-debug.log*
 | 
				
			||||||
 | 
					yarn-error.log*
 | 
				
			||||||
 | 
					pnpm-debug.log*
 | 
				
			||||||
 | 
					lerna-debug.log*
 | 
				
			||||||
 | 
					
 | 
				
			||||||
node_modules/
 | 
					node_modules
 | 
				
			||||||
 | 
					dist
 | 
				
			||||||
 | 
					dist-ssr
 | 
				
			||||||
 | 
					*.local
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Editor directories and files
 | 
				
			||||||
 | 
					.vscode/*
 | 
				
			||||||
 | 
					!.vscode/extensions.json
 | 
				
			||||||
 | 
					.DS_Store
 | 
				
			||||||
 | 
					*.suo
 | 
				
			||||||
 | 
					*.ntvs*
 | 
				
			||||||
 | 
					*.njsproj
 | 
				
			||||||
 | 
					*.sln
 | 
				
			||||||
 | 
					*.sw?
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										2819
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
							
								
								
									
										49
									
								
								Cargo.toml
									
									
									
									
									
								
							
							
						
						@@ -1,22 +1,21 @@
 | 
				
			|||||||
[package]
 | 
					[package]
 | 
				
			||||||
name = "reminder-rs"
 | 
					name = "reminder-rs"
 | 
				
			||||||
version = "1.6.36"
 | 
					version = "1.7.27"
 | 
				
			||||||
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"
 | 
				
			||||||
description = "Reminder Bot for Discord, now in Rust"
 | 
					description = "Reminder Bot for Discord, now in Rust"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[dependencies]
 | 
					[dependencies]
 | 
				
			||||||
poise = "0.5.5"
 | 
					poise = "0.6.1"
 | 
				
			||||||
dotenv = "0.15"
 | 
					dotenv = "0.15"
 | 
				
			||||||
tokio = { version = "1", features = ["process", "full"] }
 | 
					tokio = { version = "1", features = ["process", "full"] }
 | 
				
			||||||
reqwest = "0.11"
 | 
					reqwest = { version = "0.12", features = ["json"] }
 | 
				
			||||||
lazy-regex = "2.3.0"
 | 
					regex = "1.10"
 | 
				
			||||||
regex = "1.6"
 | 
					 | 
				
			||||||
log = "0.4"
 | 
					log = "0.4"
 | 
				
			||||||
env_logger = "0.10"
 | 
					env_logger = "0.11"
 | 
				
			||||||
chrono = "0.4"
 | 
					chrono = "0.4"
 | 
				
			||||||
chrono-tz = { version = "0.8", features = ["serde"] }
 | 
					chrono-tz = { version = "0.9", features = ["serde"] }
 | 
				
			||||||
lazy_static = "1.4"
 | 
					lazy_static = "1.4"
 | 
				
			||||||
num-integer = "0.1"
 | 
					num-integer = "0.1"
 | 
				
			||||||
serde = "1.0"
 | 
					serde = "1.0"
 | 
				
			||||||
@@ -25,14 +24,23 @@ serde_repr = "0.1"
 | 
				
			|||||||
rmp-serde = "1.1"
 | 
					rmp-serde = "1.1"
 | 
				
			||||||
rand = "0.8"
 | 
					rand = "0.8"
 | 
				
			||||||
levenshtein = "1.0"
 | 
					levenshtein = "1.0"
 | 
				
			||||||
sqlx = { version = "0.6", features = ["runtime-tokio-rustls", "macros", "mysql", "bigdecimal", "chrono", "migrate"]}
 | 
					sqlx = { version = "0.7", features = ["runtime-tokio-rustls", "macros", "mysql", "bigdecimal", "chrono", "migrate"] }
 | 
				
			||||||
base64 = "0.21.0"
 | 
					base64 = "0.22"
 | 
				
			||||||
 | 
					secrecy = "0.8.0"
 | 
				
			||||||
 | 
					futures = "0.3.30"
 | 
				
			||||||
 | 
					prometheus = "0.13.3"
 | 
				
			||||||
 | 
					rocket = { version = "0.5.0", features = ["tls", "secrets", "json"] }
 | 
				
			||||||
 | 
					rocket_dyn_templates = { version = "0.1.0", features = ["tera"] }
 | 
				
			||||||
 | 
					serenity = { version = "0.12", default-features = false, features = ["builder", "cache", "client", "gateway", "http", "model", "utils", "rustls_backend"] }
 | 
				
			||||||
 | 
					oauth2 = "4"
 | 
				
			||||||
 | 
					csv = "1.2"
 | 
				
			||||||
 | 
					sd-notify = "0.4.1"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[dependencies.postman]
 | 
					[dependencies.extract_derive]
 | 
				
			||||||
path = "postman"
 | 
					path = "extract_derive"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[dependencies.reminder_web]
 | 
					[dependencies.recordable_derive]
 | 
				
			||||||
path = "web"
 | 
					path = "recordable_derive"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[package.metadata.deb]
 | 
					[package.metadata.deb]
 | 
				
			||||||
depends = "$auto, python3-dateparser (>= 1.0.0)"
 | 
					depends = "$auto, python3-dateparser (>= 1.0.0)"
 | 
				
			||||||
@@ -40,13 +48,18 @@ suggests = "mysql-server-8.0, nginx"
 | 
				
			|||||||
maintainer-scripts = "debian"
 | 
					maintainer-scripts = "debian"
 | 
				
			||||||
assets = [
 | 
					assets = [
 | 
				
			||||||
    ["target/release/reminder-rs", "usr/bin/reminder-rs", "755"],
 | 
					    ["target/release/reminder-rs", "usr/bin/reminder-rs", "755"],
 | 
				
			||||||
 | 
					    ["static/css/*", "lib/reminder-rs/static/css", "644"],
 | 
				
			||||||
 | 
					    ["static/favicon/*", "lib/reminder-rs/static/favicon", "644"],
 | 
				
			||||||
 | 
					    ["static/img/*", "lib/reminder-rs/static/img", "644"],
 | 
				
			||||||
 | 
					    ["static/js/*", "lib/reminder-rs/static/js", "644"],
 | 
				
			||||||
 | 
					    ["static/webfonts/*", "lib/reminder-rs/static/webfonts", "644"],
 | 
				
			||||||
 | 
					    ["static/site.webmanifest", "lib/reminder-rs/static/site.webmanifest", "644"],
 | 
				
			||||||
 | 
					    ["templates/**/*", "lib/reminder-rs/templates", "644"],
 | 
				
			||||||
 | 
					    ["reminder-dashboard/dist/static/assets/*", "lib/reminder-rs/static/assets", "644"],
 | 
				
			||||||
 | 
					    ["reminder-dashboard/dist/index.html", "lib/reminder-rs/static/index.html", "644"],
 | 
				
			||||||
    ["conf/default.env", "etc/reminder-rs/config.env", "600"],
 | 
					    ["conf/default.env", "etc/reminder-rs/config.env", "600"],
 | 
				
			||||||
    ["conf/Rocket.toml", "etc/reminder-rs/Rocket.toml", "600"],
 | 
					    ["conf/Rocket.toml", "etc/reminder-rs/Rocket.toml", "600"],
 | 
				
			||||||
    ["$OUT_DIR/web/static/**/*", "lib/reminder-rs/static", "644"],
 | 
					    # ["nginx/reminder-rs", "etc/nginx/sites-available/reminder-rs", "755"]
 | 
				
			||||||
    ["web/templates/**/*", "lib/reminder-rs/templates", "644"],
 | 
					 | 
				
			||||||
    ["healthcheck", "lib/reminder-rs/healthcheck", "755"],
 | 
					 | 
				
			||||||
    ["cron.d/reminder_health", "etc/cron.d/reminder_health", "644"],
 | 
					 | 
				
			||||||
#    ["nginx/reminder-rs", "etc/nginx/sites-available/reminder-rs", "755"]
 | 
					 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
conf-files = [
 | 
					conf-files = [
 | 
				
			||||||
    "/etc/reminder-rs/config.env",
 | 
					    "/etc/reminder-rs/config.env",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,6 +4,6 @@ ENV RUSTUP_HOME=/usr/local/rustup \
 | 
				
			|||||||
    CARGO_HOME=/usr/local/cargo \
 | 
					    CARGO_HOME=/usr/local/cargo \
 | 
				
			||||||
    PATH=/usr/local/cargo/bin:$PATH
 | 
					    PATH=/usr/local/cargo/bin:$PATH
 | 
				
			||||||
 | 
					
 | 
				
			||||||
RUN apt update && DEBIAN_FRONTEND=noninteractive TZ=Etc/UTC apt install -y gcc gcc-multilib cmake pkg-config libssl-dev curl mysql-client-8.0
 | 
					RUN apt update && DEBIAN_FRONTEND=noninteractive TZ=Etc/UTC apt install -y gcc gcc-multilib cmake pkg-config libssl-dev curl mysql-client-8.0 npm
 | 
				
			||||||
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --no-modify-path --profile minimal --default-toolchain nightly
 | 
					RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --no-modify-path --profile minimal --default-toolchain nightly
 | 
				
			||||||
RUN cargo install cargo-deb
 | 
					RUN cargo install cargo-deb
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										22
									
								
								Rocket.toml
									
									
									
									
									
								
							
							
						
						@@ -1,28 +1,28 @@
 | 
				
			|||||||
[default]
 | 
					[default]
 | 
				
			||||||
address = "0.0.0.0"
 | 
					address = "0.0.0.0"
 | 
				
			||||||
port = 18920
 | 
					port = 18920
 | 
				
			||||||
template_dir = "web/templates"
 | 
					template_dir = "templates"
 | 
				
			||||||
limits = { json = "10MiB" }
 | 
					limits = { json = "10MiB" }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[debug]
 | 
					[debug]
 | 
				
			||||||
secret_key = "tR8krio5FXTnnyIZNiJDXPondz0kI1v6X6BXZcBGIRY="
 | 
					secret_key = "tR8krio5FXTnnyIZNiJDXPondz0kI1v6X6BXZcBGIRY="
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[debug.tls]
 | 
					[debug.tls]
 | 
				
			||||||
certs = "web/private/rsa_sha256_cert.pem"
 | 
					certs = "private/rsa_sha256_cert.pem"
 | 
				
			||||||
key = "web/private/rsa_sha256_key.pem"
 | 
					key = "private/rsa_sha256_key.pem"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[debug.rsa_sha256.tls]
 | 
					[debug.rsa_sha256.tls]
 | 
				
			||||||
certs = "web/private/rsa_sha256_cert.pem"
 | 
					certs = "private/rsa_sha256_cert.pem"
 | 
				
			||||||
key = "web/private/rsa_sha256_key.pem"
 | 
					key = "private/rsa_sha256_key.pem"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[debug.ecdsa_nistp256_sha256.tls]
 | 
					[debug.ecdsa_nistp256_sha256.tls]
 | 
				
			||||||
certs = "web/private/ecdsa_nistp256_sha256_cert.pem"
 | 
					certs = "private/ecdsa_nistp256_sha256_cert.pem"
 | 
				
			||||||
key = "web/private/ecdsa_nistp256_sha256_key_pkcs8.pem"
 | 
					key = "private/ecdsa_nistp256_sha256_key_pkcs8.pem"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[debug.ecdsa_nistp384_sha384.tls]
 | 
					[debug.ecdsa_nistp384_sha384.tls]
 | 
				
			||||||
certs = "web/private/ecdsa_nistp384_sha384_cert.pem"
 | 
					certs = "private/ecdsa_nistp384_sha384_cert.pem"
 | 
				
			||||||
key = "web/private/ecdsa_nistp384_sha384_key_pkcs8.pem"
 | 
					key = "private/ecdsa_nistp384_sha384_key_pkcs8.pem"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[debug.ed25519.tls]
 | 
					[debug.ed25519.tls]
 | 
				
			||||||
certs = "web/private/ed25519_cert.pem"
 | 
					certs = "private/ed25519_cert.pem"
 | 
				
			||||||
key = "eb/private/ed25519_key.pem"
 | 
					key = "private/ed25519_key.pem"
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										102
									
								
								build.rs
									
									
									
									
									
								
							
							
						
						@@ -1,99 +1,13 @@
 | 
				
			|||||||
#[cfg(not(debug_assertions))]
 | 
					use std::{path::Path, process::Command};
 | 
				
			||||||
use std::{
 | 
					 | 
				
			||||||
    env, fs,
 | 
					 | 
				
			||||||
    fs::{create_dir_all, DirEntry, File},
 | 
					 | 
				
			||||||
    io,
 | 
					 | 
				
			||||||
    io::Write,
 | 
					 | 
				
			||||||
    path::Path,
 | 
					 | 
				
			||||||
    process::Command,
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[cfg(not(debug_assertions))]
 | 
					 | 
				
			||||||
fn visit_dirs(dir: &Path, cb: &dyn Fn(&DirEntry)) -> io::Result<()> {
 | 
					 | 
				
			||||||
    if dir.is_dir() {
 | 
					 | 
				
			||||||
        for entry in fs::read_dir(dir)? {
 | 
					 | 
				
			||||||
            let entry = entry?;
 | 
					 | 
				
			||||||
            let path = entry.path();
 | 
					 | 
				
			||||||
            if path.is_dir() {
 | 
					 | 
				
			||||||
                visit_dirs(&path, cb)?;
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                cb(&entry);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    Ok(())
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[cfg(not(debug_assertions))]
 | 
					 | 
				
			||||||
fn process_static(file: &DirEntry) {
 | 
					 | 
				
			||||||
    let out_dir = env::var("OUT_DIR").unwrap();
 | 
					 | 
				
			||||||
    let path = file.path();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let in_path = path.to_str().unwrap();
 | 
					 | 
				
			||||||
    let art_path = format!("{}/{}", out_dir, in_path);
 | 
					 | 
				
			||||||
    let art_dir = format!("{}/{}", out_dir, path.parent().unwrap().to_str().unwrap());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    match path.extension().map(|o| o.to_str()).flatten() {
 | 
					 | 
				
			||||||
        Some("ts") => {}
 | 
					 | 
				
			||||||
        Some("js") => {
 | 
					 | 
				
			||||||
            create_dir_all(art_dir).unwrap();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if art_path.ends_with(".min.js") {
 | 
					 | 
				
			||||||
                Command::new("cp").arg(in_path).arg(art_path).spawn().expect("Could not copy");
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                let minified = Command::new("npx")
 | 
					 | 
				
			||||||
                    .arg("minify")
 | 
					 | 
				
			||||||
                    .arg(in_path)
 | 
					 | 
				
			||||||
                    .output()
 | 
					 | 
				
			||||||
                    .expect("Could not minify");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                let mut fh = File::create(art_path).expect("Couldn't create file");
 | 
					 | 
				
			||||||
                fh.write(&minified.stdout).unwrap();
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        Some("css") => {
 | 
					 | 
				
			||||||
            create_dir_all(art_dir).unwrap();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if art_path.ends_with(".min.css") {
 | 
					 | 
				
			||||||
                Command::new("cp").arg(in_path).arg(art_path).spawn().expect("Could not copy");
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                let minified = Command::new("npx")
 | 
					 | 
				
			||||||
                    .arg("minify")
 | 
					 | 
				
			||||||
                    .arg(in_path)
 | 
					 | 
				
			||||||
                    .output()
 | 
					 | 
				
			||||||
                    .expect("Could not minify");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                let mut fh = File::create(art_path).expect("Couldn't create file");
 | 
					 | 
				
			||||||
                fh.write(&minified.stdout).unwrap();
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        _ => {
 | 
					 | 
				
			||||||
            create_dir_all(art_dir).unwrap();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            Command::new("cp").arg(in_path).arg(art_path).spawn().expect("Could not copy");
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// fn compile_tsc(file: &DirEntry) {
 | 
					 | 
				
			||||||
//     if path.extension() == Some("ts") {
 | 
					 | 
				
			||||||
//         let out_dir = env::var("OUT_DIR").unwrap();
 | 
					 | 
				
			||||||
//         let path = file.path();
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
//         Command::new("npx")
 | 
					 | 
				
			||||||
//             .arg("tsc")
 | 
					 | 
				
			||||||
//             .arg(in_path)
 | 
					 | 
				
			||||||
//             .arg(art_path)
 | 
					 | 
				
			||||||
//             .spawn()
 | 
					 | 
				
			||||||
//             .expect("Could not compile");
 | 
					 | 
				
			||||||
//     }
 | 
					 | 
				
			||||||
// }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn main() {
 | 
					fn main() {
 | 
				
			||||||
    println!("cargo:rerun-if-changed=migrations");
 | 
					    println!("cargo:rerun-if-changed=migrations");
 | 
				
			||||||
 | 
					    println!("cargo:rerun-if-changed=reminder-dashboard");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    #[cfg(not(debug_assertions))]
 | 
					    Command::new("npm")
 | 
				
			||||||
    visit_dirs("web/static".as_ref(), &process_static).unwrap();
 | 
					        .arg("run")
 | 
				
			||||||
 | 
					        .arg("build")
 | 
				
			||||||
    // visit_dirs("web/static".as_ref(), &compile_tsc).unwrap();
 | 
					        .current_dir(Path::new("reminder-dashboard"))
 | 
				
			||||||
 | 
					        .spawn()
 | 
				
			||||||
 | 
					        .expect("Failed to build NPM");
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										46
									
								
								extract_derive/Cargo.lock
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,46 @@
 | 
				
			|||||||
 | 
					# This file is automatically @generated by Cargo.
 | 
				
			||||||
 | 
					# It is not intended for manual editing.
 | 
				
			||||||
 | 
					version = 3
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "extract_macro"
 | 
				
			||||||
 | 
					version = "0.1.0"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "quote",
 | 
				
			||||||
 | 
					 "syn",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "proc-macro2"
 | 
				
			||||||
 | 
					version = "1.0.78"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "unicode-ident",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "quote"
 | 
				
			||||||
 | 
					version = "1.0.35"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "proc-macro2",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "syn"
 | 
				
			||||||
 | 
					version = "2.0.49"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "915aea9e586f80826ee59f8453c1101f9d1c4b3964cd2460185ee8e299ada496"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "proc-macro2",
 | 
				
			||||||
 | 
					 "quote",
 | 
				
			||||||
 | 
					 "unicode-ident",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "unicode-ident"
 | 
				
			||||||
 | 
					version = "1.0.12"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
 | 
				
			||||||
							
								
								
									
										11
									
								
								extract_derive/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,11 @@
 | 
				
			|||||||
 | 
					[package]
 | 
				
			||||||
 | 
					name = "extract_derive"
 | 
				
			||||||
 | 
					version = "0.1.0"
 | 
				
			||||||
 | 
					edition = "2021"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[lib]
 | 
				
			||||||
 | 
					proc-macro = true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[dependencies]
 | 
				
			||||||
 | 
					quote = "1.0.35"
 | 
				
			||||||
 | 
					syn = { version = "2.0.49", features = ["full"] }
 | 
				
			||||||
							
								
								
									
										53
									
								
								extract_derive/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,53 @@
 | 
				
			|||||||
 | 
					use proc_macro::TokenStream;
 | 
				
			||||||
 | 
					use syn::{spanned::Spanned, Data, Fields};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[proc_macro_derive(Extract)]
 | 
				
			||||||
 | 
					pub fn extract(input: TokenStream) -> TokenStream {
 | 
				
			||||||
 | 
					    let ast = syn::parse_macro_input!(input);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    impl_extract(&ast)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn impl_extract(ast: &syn::DeriveInput) -> TokenStream {
 | 
				
			||||||
 | 
					    let name = &ast.ident;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    match &ast.data {
 | 
				
			||||||
 | 
					        // Dispatch over struct: extract args directly from context
 | 
				
			||||||
 | 
					        Data::Struct(st) => match &st.fields {
 | 
				
			||||||
 | 
					            Fields::Named(fields) => {
 | 
				
			||||||
 | 
					                let extracted = fields.named.iter().map(|field| {
 | 
				
			||||||
 | 
					                    let ident = &field.ident;
 | 
				
			||||||
 | 
					                    let ty = &field.ty;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    quote::quote_spanned! {field.span()=>
 | 
				
			||||||
 | 
					                        #ident : crate::utils::extract_arg!(ctx, #ident, #ty)
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                TokenStream::from(quote::quote! {
 | 
				
			||||||
 | 
					                    impl Extract for #name {
 | 
				
			||||||
 | 
					                        fn extract(ctx: crate::ApplicationContext) -> Self {
 | 
				
			||||||
 | 
					                            Self {
 | 
				
			||||||
 | 
					                                #(#extracted,)*
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                })
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            Fields::Unit => TokenStream::from(quote::quote! {
 | 
				
			||||||
 | 
					                impl Extract for #name {
 | 
				
			||||||
 | 
					                    fn extract(ctx: crate::ApplicationContext) -> Self {
 | 
				
			||||||
 | 
					                        Self {}
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }),
 | 
				
			||||||
 | 
					            _ => {
 | 
				
			||||||
 | 
					                panic!("Only named/unit structs can derive Extract");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        _ => {
 | 
				
			||||||
 | 
					            panic!("Only structs can derive Extract");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										13
									
								
								healthcheck
									
									
									
									
									
								
							
							
						
						@@ -1,13 +0,0 @@
 | 
				
			|||||||
#!/bin/bash
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export $(grep -v '^#' /etc/reminder-rs/config.env | xargs -d '\n')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
REGEX='mysql://([A-Za-z]+)@(.+)/(.+)'
 | 
					 | 
				
			||||||
[[ $DATABASE_URL =~ $REGEX ]]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
VAR=$(mysql -u "${BASH_REMATCH[1]}" -h "${BASH_REMATCH[2]}" -N -D "${BASH_REMATCH[3]}" -e "SELECT COUNT(1) FROM reminders WHERE utc_time < NOW() - INTERVAL 10 MINUTE AND enabled = 1 AND status = 'pending'")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
if [ "$VAR" -gt 0 ]
 | 
					 | 
				
			||||||
then
 | 
					 | 
				
			||||||
  echo "This is to inform that there is a reminder backlog which must be resolved." | mail -s "Backlog: $VAR" "$REPORT_EMAIL"
 | 
					 | 
				
			||||||
fi
 | 
					 | 
				
			||||||
@@ -0,0 +1,3 @@
 | 
				
			|||||||
 | 
					ALTER TABLE `reminder_template` ADD COLUMN `interval_seconds` INT UNSIGNED;
 | 
				
			||||||
 | 
					ALTER TABLE `reminder_template` ADD COLUMN `interval_days` INT UNSIGNED;
 | 
				
			||||||
 | 
					ALTER TABLE `reminder_template` ADD COLUMN `interval_months` INT UNSIGNED;
 | 
				
			||||||
							
								
								
									
										50
									
								
								migrations/20240210133900_macro_restructure.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,50 @@
 | 
				
			|||||||
 | 
					CREATE TABLE command_macro (
 | 
				
			||||||
 | 
					    id INT UNSIGNED AUTO_INCREMENT,
 | 
				
			||||||
 | 
					    guild_id INT UNSIGNED NOT NULL,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    name VARCHAR(100) NOT NULL,
 | 
				
			||||||
 | 
					    description VARCHAR(100),
 | 
				
			||||||
 | 
					    commands JSON NOT NULL,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    FOREIGN KEY (guild_id) REFERENCES guilds(id) ON DELETE CASCADE,
 | 
				
			||||||
 | 
					    PRIMARY KEY (id)
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# New JSON structure is {command_name: "Remind", "<option name>": "<option value>", ...}
 | 
				
			||||||
 | 
					INSERT INTO command_macro (guild_id, description, name, commands)
 | 
				
			||||||
 | 
					SELECT
 | 
				
			||||||
 | 
					    guild_id,
 | 
				
			||||||
 | 
					    description,
 | 
				
			||||||
 | 
					    name,
 | 
				
			||||||
 | 
					    (
 | 
				
			||||||
 | 
					        SELECT JSON_ARRAYAGG(
 | 
				
			||||||
 | 
					            (
 | 
				
			||||||
 | 
					                SELECT JSON_OBJECTAGG(t2.name, t2.value)
 | 
				
			||||||
 | 
					                FROM JSON_TABLE(
 | 
				
			||||||
 | 
					                    JSON_ARRAY_APPEND(t1.options, '$', JSON_OBJECT('name', 'command_name', 'value', t1.command_name)),
 | 
				
			||||||
 | 
					                    '$[*]' COLUMNS (
 | 
				
			||||||
 | 
					                        name VARCHAR(64) PATH '$.name' ERROR ON ERROR,
 | 
				
			||||||
 | 
					                        value TEXT PATH '$.value' ERROR ON ERROR
 | 
				
			||||||
 | 
					                    )) AS t2
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        FROM macro m2
 | 
				
			||||||
 | 
					        JOIN JSON_TABLE(
 | 
				
			||||||
 | 
					            commands,
 | 
				
			||||||
 | 
					            '$[*]' COLUMNS (
 | 
				
			||||||
 | 
					                command_name VARCHAR(64) PATH '$.command_name' ERROR ON ERROR,
 | 
				
			||||||
 | 
					                options JSON PATH '$.options' ERROR ON ERROR
 | 
				
			||||||
 | 
					            )) AS t1
 | 
				
			||||||
 | 
					        WHERE m1.id = m2.id
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					FROM macro m1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# # Check which commands are used in macros
 | 
				
			||||||
 | 
					# SELECT DISTINCT command_name
 | 
				
			||||||
 | 
					# FROM macro m2
 | 
				
			||||||
 | 
					# JOIN JSON_TABLE(
 | 
				
			||||||
 | 
					#     commands,
 | 
				
			||||||
 | 
					#     '$[*]' COLUMNS (
 | 
				
			||||||
 | 
					#         command_name VARCHAR(64) PATH '$.command_name' ERROR ON ERROR,
 | 
				
			||||||
 | 
					#         options JSON PATH '$.options' ERROR ON ERROR
 | 
				
			||||||
 | 
					#     )) AS t1
 | 
				
			||||||
							
								
								
									
										5
									
								
								migrations/20240303125837_add_indexes.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,5 @@
 | 
				
			|||||||
 | 
					-- Add migration script here
 | 
				
			||||||
 | 
					ALTER TABLE reminders
 | 
				
			||||||
 | 
					    ADD INDEX `utc_time_index` (`utc_time`);
 | 
				
			||||||
 | 
					ALTER TABLE reminders
 | 
				
			||||||
 | 
					    ADD INDEX `status_index` (`status`);
 | 
				
			||||||
							
								
								
									
										53
									
								
								nginx/reminder-bot
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,53 @@
 | 
				
			|||||||
 | 
					server {
 | 
				
			||||||
 | 
					    server_name www.reminder-bot.com;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return 301 https://reminder-bot.com$request_uri;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					server {
 | 
				
			||||||
 | 
					    listen 80;
 | 
				
			||||||
 | 
					    server_name beta.reminder-bot.com;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return 301 https://reminder-bot.com$request_uri;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					server {
 | 
				
			||||||
 | 
					    listen 443 ssl;
 | 
				
			||||||
 | 
					    server_name beta.reminder-bot.com;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ssl_certificate /etc/letsencrypt/live/beta.reminder-bot.com/fullchain.pem;
 | 
				
			||||||
 | 
					    ssl_certificate_key /etc/letsencrypt/live/beta.reminder-bot.com/privkey.pem;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return 301 https://reminder-bot.com$request_uri;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					server {
 | 
				
			||||||
 | 
					    listen 443 ssl;
 | 
				
			||||||
 | 
					    server_name reminder-bot.com;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ssl_certificate /etc/letsencrypt/live/reminder-bot.com/fullchain.pem;
 | 
				
			||||||
 | 
					    ssl_certificate_key /etc/letsencrypt/live/reminder-bot.com/privkey.pem;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    access_log /var/log/nginx/access.log;
 | 
				
			||||||
 | 
					    error_log /var/log/nginx/error.log;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    proxy_buffer_size 128k;
 | 
				
			||||||
 | 
					    proxy_buffers 4 256k;
 | 
				
			||||||
 | 
					    proxy_busy_buffers_size 256k;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    client_max_body_size 10M;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    location / {
 | 
				
			||||||
 | 
					        proxy_pass http://localhost:18920;
 | 
				
			||||||
 | 
					        proxy_redirect off;
 | 
				
			||||||
 | 
					        proxy_set_header Host $host;
 | 
				
			||||||
 | 
					        proxy_set_header X-Real-IP $remote_addr;
 | 
				
			||||||
 | 
					        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
 | 
				
			||||||
 | 
					        proxy_set_header X-Forwarded-Proto $scheme;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    location /static {
 | 
				
			||||||
 | 
					        alias /var/www/reminder-rs/static;
 | 
				
			||||||
 | 
					        expires 30d;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,41 +0,0 @@
 | 
				
			|||||||
server {
 | 
					 | 
				
			||||||
        server_name www.reminder-bot.com;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return 301 $scheme://reminder-bot.com$request_uri;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
server {
 | 
					 | 
				
			||||||
        listen 80;
 | 
					 | 
				
			||||||
        server_name reminder-bot.com;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	    return 301 https://reminder-bot.com$request_uri;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
server {
 | 
					 | 
				
			||||||
        listen 443 ssl;
 | 
					 | 
				
			||||||
        server_name reminder-bot.com;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        ssl_certificate /etc/letsencrypt/live/reminder-bot.com/fullchain.pem;
 | 
					 | 
				
			||||||
        ssl_certificate_key /etc/letsencrypt/live/reminder-bot.com/privkey.pem;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        access_log /var/log/nginx/access.log;
 | 
					 | 
				
			||||||
        error_log /var/log/nginx/error.log;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        proxy_buffer_size 128k;
 | 
					 | 
				
			||||||
        proxy_buffers 4 256k;
 | 
					 | 
				
			||||||
        proxy_busy_buffers_size 256k;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        location / {
 | 
					 | 
				
			||||||
                proxy_pass http://localhost:18920;
 | 
					 | 
				
			||||||
                proxy_redirect off;
 | 
					 | 
				
			||||||
                proxy_set_header Host $host;
 | 
					 | 
				
			||||||
                proxy_set_header X-Real-IP $remote_addr;
 | 
					 | 
				
			||||||
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
 | 
					 | 
				
			||||||
		        proxy_set_header X-Forwarded-Proto $scheme;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        location /static {
 | 
					 | 
				
			||||||
                alias /var/www/reminder-rs/static;
 | 
					 | 
				
			||||||
                expires 30d;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										485
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						@@ -1,485 +0,0 @@
 | 
				
			|||||||
{
 | 
					 | 
				
			||||||
  "name": "reminder-rs",
 | 
					 | 
				
			||||||
  "lockfileVersion": 3,
 | 
					 | 
				
			||||||
  "requires": true,
 | 
					 | 
				
			||||||
  "packages": {
 | 
					 | 
				
			||||||
    "": {
 | 
					 | 
				
			||||||
      "devDependencies": {
 | 
					 | 
				
			||||||
        "minify": "^10.3.0",
 | 
					 | 
				
			||||||
        "prettier": "^3.0.1",
 | 
					 | 
				
			||||||
        "tsc": "^2.0.4"
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/@jridgewell/gen-mapping": {
 | 
					 | 
				
			||||||
      "version": "0.3.3",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==",
 | 
					 | 
				
			||||||
      "dev": true,
 | 
					 | 
				
			||||||
      "dependencies": {
 | 
					 | 
				
			||||||
        "@jridgewell/set-array": "^1.0.1",
 | 
					 | 
				
			||||||
        "@jridgewell/sourcemap-codec": "^1.4.10",
 | 
					 | 
				
			||||||
        "@jridgewell/trace-mapping": "^0.3.9"
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      "engines": {
 | 
					 | 
				
			||||||
        "node": ">=6.0.0"
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/@jridgewell/resolve-uri": {
 | 
					 | 
				
			||||||
      "version": "3.1.1",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==",
 | 
					 | 
				
			||||||
      "dev": true,
 | 
					 | 
				
			||||||
      "engines": {
 | 
					 | 
				
			||||||
        "node": ">=6.0.0"
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/@jridgewell/set-array": {
 | 
					 | 
				
			||||||
      "version": "1.1.2",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==",
 | 
					 | 
				
			||||||
      "dev": true,
 | 
					 | 
				
			||||||
      "engines": {
 | 
					 | 
				
			||||||
        "node": ">=6.0.0"
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/@jridgewell/source-map": {
 | 
					 | 
				
			||||||
      "version": "0.3.5",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==",
 | 
					 | 
				
			||||||
      "dev": true,
 | 
					 | 
				
			||||||
      "dependencies": {
 | 
					 | 
				
			||||||
        "@jridgewell/gen-mapping": "^0.3.0",
 | 
					 | 
				
			||||||
        "@jridgewell/trace-mapping": "^0.3.9"
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/@jridgewell/sourcemap-codec": {
 | 
					 | 
				
			||||||
      "version": "1.4.15",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==",
 | 
					 | 
				
			||||||
      "dev": true
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/@jridgewell/trace-mapping": {
 | 
					 | 
				
			||||||
      "version": "0.3.19",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==",
 | 
					 | 
				
			||||||
      "dev": true,
 | 
					 | 
				
			||||||
      "dependencies": {
 | 
					 | 
				
			||||||
        "@jridgewell/resolve-uri": "^3.1.0",
 | 
					 | 
				
			||||||
        "@jridgewell/sourcemap-codec": "^1.4.14"
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/@putout/minify": {
 | 
					 | 
				
			||||||
      "version": "1.49.0",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/@putout/minify/-/minify-1.49.0.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-T/eS9rJC0tgq/s8uLpB0cpbsUaY7KSML3UbvPri2qjVCcEK/qwi8+lNWdp8VSyOWiC25Ntrt/DewOu6dXRX1ng==",
 | 
					 | 
				
			||||||
      "dev": true,
 | 
					 | 
				
			||||||
      "engines": {
 | 
					 | 
				
			||||||
        "node": ">=16"
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/acorn": {
 | 
					 | 
				
			||||||
      "version": "8.10.0",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==",
 | 
					 | 
				
			||||||
      "dev": true,
 | 
					 | 
				
			||||||
      "bin": {
 | 
					 | 
				
			||||||
        "acorn": "bin/acorn"
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      "engines": {
 | 
					 | 
				
			||||||
        "node": ">=0.4.0"
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/buffer-from": {
 | 
					 | 
				
			||||||
      "version": "1.1.2",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
 | 
					 | 
				
			||||||
      "dev": true
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/camel-case": {
 | 
					 | 
				
			||||||
      "version": "4.1.2",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==",
 | 
					 | 
				
			||||||
      "dev": true,
 | 
					 | 
				
			||||||
      "dependencies": {
 | 
					 | 
				
			||||||
        "pascal-case": "^3.1.2",
 | 
					 | 
				
			||||||
        "tslib": "^2.0.3"
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/clean-css": {
 | 
					 | 
				
			||||||
      "version": "5.3.2",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.2.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-JVJbM+f3d3Q704rF4bqQ5UUyTtuJ0JRKNbTKVEeujCCBoMdkEi+V+e8oktO9qGQNSvHrFTM6JZRXrUvGR1czww==",
 | 
					 | 
				
			||||||
      "dev": true,
 | 
					 | 
				
			||||||
      "dependencies": {
 | 
					 | 
				
			||||||
        "source-map": "~0.6.0"
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      "engines": {
 | 
					 | 
				
			||||||
        "node": ">= 10.0"
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/commander": {
 | 
					 | 
				
			||||||
      "version": "10.0.1",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==",
 | 
					 | 
				
			||||||
      "dev": true,
 | 
					 | 
				
			||||||
      "engines": {
 | 
					 | 
				
			||||||
        "node": ">=14"
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/css-b64-images": {
 | 
					 | 
				
			||||||
      "version": "0.2.5",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/css-b64-images/-/css-b64-images-0.2.5.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-TgQBEdP07adhrDfXvI5o6bHGukKBNMzp2Ngckc/6d09zpjD2gc1Hl3Ca1CKgb8FXjHi88+Phv2Uegs2kTL4zjg==",
 | 
					 | 
				
			||||||
      "dev": true,
 | 
					 | 
				
			||||||
      "bin": {
 | 
					 | 
				
			||||||
        "css-b64-images": "bin/css-b64-images"
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      "engines": {
 | 
					 | 
				
			||||||
        "node": "*"
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/debug": {
 | 
					 | 
				
			||||||
      "version": "4.3.4",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
 | 
					 | 
				
			||||||
      "dev": true,
 | 
					 | 
				
			||||||
      "dependencies": {
 | 
					 | 
				
			||||||
        "ms": "2.1.2"
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      "engines": {
 | 
					 | 
				
			||||||
        "node": ">=6.0"
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      "peerDependenciesMeta": {
 | 
					 | 
				
			||||||
        "supports-color": {
 | 
					 | 
				
			||||||
          "optional": true
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/dot-case": {
 | 
					 | 
				
			||||||
      "version": "3.0.4",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==",
 | 
					 | 
				
			||||||
      "dev": true,
 | 
					 | 
				
			||||||
      "dependencies": {
 | 
					 | 
				
			||||||
        "no-case": "^3.0.4",
 | 
					 | 
				
			||||||
        "tslib": "^2.0.3"
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/entities": {
 | 
					 | 
				
			||||||
      "version": "4.5.0",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
 | 
					 | 
				
			||||||
      "dev": true,
 | 
					 | 
				
			||||||
      "engines": {
 | 
					 | 
				
			||||||
        "node": ">=0.12"
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      "funding": {
 | 
					 | 
				
			||||||
        "url": "https://github.com/fb55/entities?sponsor=1"
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/find-up": {
 | 
					 | 
				
			||||||
      "version": "6.3.0",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==",
 | 
					 | 
				
			||||||
      "dev": true,
 | 
					 | 
				
			||||||
      "dependencies": {
 | 
					 | 
				
			||||||
        "locate-path": "^7.1.0",
 | 
					 | 
				
			||||||
        "path-exists": "^5.0.0"
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      "engines": {
 | 
					 | 
				
			||||||
        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      "funding": {
 | 
					 | 
				
			||||||
        "url": "https://github.com/sponsors/sindresorhus"
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/html-minifier-terser": {
 | 
					 | 
				
			||||||
      "version": "7.2.0",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-7.2.0.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-tXgn3QfqPIpGl9o+K5tpcj3/MN4SfLtsx2GWwBC3SSd0tXQGyF3gsSqad8loJgKZGM3ZxbYDd5yhiBIdWpmvLA==",
 | 
					 | 
				
			||||||
      "dev": true,
 | 
					 | 
				
			||||||
      "dependencies": {
 | 
					 | 
				
			||||||
        "camel-case": "^4.1.2",
 | 
					 | 
				
			||||||
        "clean-css": "~5.3.2",
 | 
					 | 
				
			||||||
        "commander": "^10.0.0",
 | 
					 | 
				
			||||||
        "entities": "^4.4.0",
 | 
					 | 
				
			||||||
        "param-case": "^3.0.4",
 | 
					 | 
				
			||||||
        "relateurl": "^0.2.7",
 | 
					 | 
				
			||||||
        "terser": "^5.15.1"
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      "bin": {
 | 
					 | 
				
			||||||
        "html-minifier-terser": "cli.js"
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      "engines": {
 | 
					 | 
				
			||||||
        "node": "^14.13.1 || >=16.0.0"
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/jju": {
 | 
					 | 
				
			||||||
      "version": "1.4.0",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/jju/-/jju-1.4.0.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==",
 | 
					 | 
				
			||||||
      "dev": true
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/locate-path": {
 | 
					 | 
				
			||||||
      "version": "7.2.0",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==",
 | 
					 | 
				
			||||||
      "dev": true,
 | 
					 | 
				
			||||||
      "dependencies": {
 | 
					 | 
				
			||||||
        "p-locate": "^6.0.0"
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      "engines": {
 | 
					 | 
				
			||||||
        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      "funding": {
 | 
					 | 
				
			||||||
        "url": "https://github.com/sponsors/sindresorhus"
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/lower-case": {
 | 
					 | 
				
			||||||
      "version": "2.0.2",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==",
 | 
					 | 
				
			||||||
      "dev": true,
 | 
					 | 
				
			||||||
      "dependencies": {
 | 
					 | 
				
			||||||
        "tslib": "^2.0.3"
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/minify": {
 | 
					 | 
				
			||||||
      "version": "10.3.0",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/minify/-/minify-10.3.0.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-eRkx2J1ykkGBVi1gI2sksmovWFzts+GYi2u3Jd/S5eNIkzj0pidciICsWRWdTKTLZVFUP7b6IvoAzasvQkMicg==",
 | 
					 | 
				
			||||||
      "dev": true,
 | 
					 | 
				
			||||||
      "dependencies": {
 | 
					 | 
				
			||||||
        "@putout/minify": "^1.0.4",
 | 
					 | 
				
			||||||
        "clean-css": "^5.0.1",
 | 
					 | 
				
			||||||
        "css-b64-images": "~0.2.5",
 | 
					 | 
				
			||||||
        "debug": "^4.1.0",
 | 
					 | 
				
			||||||
        "find-up": "^6.1.0",
 | 
					 | 
				
			||||||
        "html-minifier-terser": "^7.1.0",
 | 
					 | 
				
			||||||
        "readjson": "^2.2.2",
 | 
					 | 
				
			||||||
        "simport": "^1.2.0",
 | 
					 | 
				
			||||||
        "try-catch": "^3.0.0",
 | 
					 | 
				
			||||||
        "try-to-catch": "^3.0.0"
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      "bin": {
 | 
					 | 
				
			||||||
        "minify": "bin/minify.js"
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      "engines": {
 | 
					 | 
				
			||||||
        "node": ">=16"
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/ms": {
 | 
					 | 
				
			||||||
      "version": "2.1.2",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
 | 
					 | 
				
			||||||
      "dev": true
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/no-case": {
 | 
					 | 
				
			||||||
      "version": "3.0.4",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==",
 | 
					 | 
				
			||||||
      "dev": true,
 | 
					 | 
				
			||||||
      "dependencies": {
 | 
					 | 
				
			||||||
        "lower-case": "^2.0.2",
 | 
					 | 
				
			||||||
        "tslib": "^2.0.3"
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/p-limit": {
 | 
					 | 
				
			||||||
      "version": "4.0.0",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==",
 | 
					 | 
				
			||||||
      "dev": true,
 | 
					 | 
				
			||||||
      "dependencies": {
 | 
					 | 
				
			||||||
        "yocto-queue": "^1.0.0"
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      "engines": {
 | 
					 | 
				
			||||||
        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      "funding": {
 | 
					 | 
				
			||||||
        "url": "https://github.com/sponsors/sindresorhus"
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/p-locate": {
 | 
					 | 
				
			||||||
      "version": "6.0.0",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==",
 | 
					 | 
				
			||||||
      "dev": true,
 | 
					 | 
				
			||||||
      "dependencies": {
 | 
					 | 
				
			||||||
        "p-limit": "^4.0.0"
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      "engines": {
 | 
					 | 
				
			||||||
        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      "funding": {
 | 
					 | 
				
			||||||
        "url": "https://github.com/sponsors/sindresorhus"
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/param-case": {
 | 
					 | 
				
			||||||
      "version": "3.0.4",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==",
 | 
					 | 
				
			||||||
      "dev": true,
 | 
					 | 
				
			||||||
      "dependencies": {
 | 
					 | 
				
			||||||
        "dot-case": "^3.0.4",
 | 
					 | 
				
			||||||
        "tslib": "^2.0.3"
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/pascal-case": {
 | 
					 | 
				
			||||||
      "version": "3.1.2",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==",
 | 
					 | 
				
			||||||
      "dev": true,
 | 
					 | 
				
			||||||
      "dependencies": {
 | 
					 | 
				
			||||||
        "no-case": "^3.0.4",
 | 
					 | 
				
			||||||
        "tslib": "^2.0.3"
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/path-exists": {
 | 
					 | 
				
			||||||
      "version": "5.0.0",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==",
 | 
					 | 
				
			||||||
      "dev": true,
 | 
					 | 
				
			||||||
      "engines": {
 | 
					 | 
				
			||||||
        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/prettier": {
 | 
					 | 
				
			||||||
      "version": "3.0.1",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.1.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-fcOWSnnpCrovBsmFZIGIy9UqK2FaI7Hqax+DIO0A9UxeVoY4iweyaFjS5TavZN97Hfehph0nhsZnjlVKzEQSrQ==",
 | 
					 | 
				
			||||||
      "dev": true,
 | 
					 | 
				
			||||||
      "bin": {
 | 
					 | 
				
			||||||
        "prettier": "bin/prettier.cjs"
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      "engines": {
 | 
					 | 
				
			||||||
        "node": ">=14"
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      "funding": {
 | 
					 | 
				
			||||||
        "url": "https://github.com/prettier/prettier?sponsor=1"
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/readjson": {
 | 
					 | 
				
			||||||
      "version": "2.2.2",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/readjson/-/readjson-2.2.2.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-PdeC9tsmLWBiL8vMhJvocq+OezQ3HhsH2HrN7YkhfYcTjQSa/iraB15A7Qvt7Xpr0Yd2rDNt6GbFwVQDg3HcAw==",
 | 
					 | 
				
			||||||
      "dev": true,
 | 
					 | 
				
			||||||
      "dependencies": {
 | 
					 | 
				
			||||||
        "jju": "^1.4.0",
 | 
					 | 
				
			||||||
        "try-catch": "^3.0.0"
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      "engines": {
 | 
					 | 
				
			||||||
        "node": ">=10"
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/relateurl": {
 | 
					 | 
				
			||||||
      "version": "0.2.7",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==",
 | 
					 | 
				
			||||||
      "dev": true,
 | 
					 | 
				
			||||||
      "engines": {
 | 
					 | 
				
			||||||
        "node": ">= 0.10"
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/simport": {
 | 
					 | 
				
			||||||
      "version": "1.2.0",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/simport/-/simport-1.2.0.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-85Bm7pKsqiiQ8rmYCaPDdlXZjJvuW6/k/FY8MTtLFMgU7f8S00CgTHfRtWB6KwSb6ek4p9YyG2enG1+yJbl+CA==",
 | 
					 | 
				
			||||||
      "dev": true,
 | 
					 | 
				
			||||||
      "dependencies": {
 | 
					 | 
				
			||||||
        "readjson": "^2.2.0",
 | 
					 | 
				
			||||||
        "try-to-catch": "^3.0.0"
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      "engines": {
 | 
					 | 
				
			||||||
        "node": ">=12.2"
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/source-map": {
 | 
					 | 
				
			||||||
      "version": "0.6.1",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
 | 
					 | 
				
			||||||
      "dev": true,
 | 
					 | 
				
			||||||
      "engines": {
 | 
					 | 
				
			||||||
        "node": ">=0.10.0"
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/source-map-support": {
 | 
					 | 
				
			||||||
      "version": "0.5.21",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
 | 
					 | 
				
			||||||
      "dev": true,
 | 
					 | 
				
			||||||
      "dependencies": {
 | 
					 | 
				
			||||||
        "buffer-from": "^1.0.0",
 | 
					 | 
				
			||||||
        "source-map": "^0.6.0"
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/terser": {
 | 
					 | 
				
			||||||
      "version": "5.19.2",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/terser/-/terser-5.19.2.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-qC5+dmecKJA4cpYxRa5aVkKehYsQKc+AHeKl0Oe62aYjBL8ZA33tTljktDHJSaxxMnbI5ZYw+o/S2DxxLu8OfA==",
 | 
					 | 
				
			||||||
      "dev": true,
 | 
					 | 
				
			||||||
      "dependencies": {
 | 
					 | 
				
			||||||
        "@jridgewell/source-map": "^0.3.3",
 | 
					 | 
				
			||||||
        "acorn": "^8.8.2",
 | 
					 | 
				
			||||||
        "commander": "^2.20.0",
 | 
					 | 
				
			||||||
        "source-map-support": "~0.5.20"
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      "bin": {
 | 
					 | 
				
			||||||
        "terser": "bin/terser"
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      "engines": {
 | 
					 | 
				
			||||||
        "node": ">=10"
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/terser/node_modules/commander": {
 | 
					 | 
				
			||||||
      "version": "2.20.3",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
 | 
					 | 
				
			||||||
      "dev": true
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/try-catch": {
 | 
					 | 
				
			||||||
      "version": "3.0.1",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/try-catch/-/try-catch-3.0.1.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-91yfXw1rr/P6oLpHSyHDOHm0vloVvUoo9FVdw8YwY05QjJQG9OT0LUxe2VRAzmHG+0CUOmI3nhxDUMLxDN/NEQ==",
 | 
					 | 
				
			||||||
      "dev": true,
 | 
					 | 
				
			||||||
      "engines": {
 | 
					 | 
				
			||||||
        "node": ">=6"
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/try-to-catch": {
 | 
					 | 
				
			||||||
      "version": "3.0.1",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/try-to-catch/-/try-to-catch-3.0.1.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-hOY83V84Hx/1sCzDSaJA+Xz2IIQOHRvjxzt+F0OjbQGPZ6yLPLArMA0gw/484MlfUkQbCpKYMLX3VDCAjWKfzQ==",
 | 
					 | 
				
			||||||
      "dev": true,
 | 
					 | 
				
			||||||
      "engines": {
 | 
					 | 
				
			||||||
        "node": ">=6"
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/tsc": {
 | 
					 | 
				
			||||||
      "version": "2.0.4",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/tsc/-/tsc-2.0.4.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-fzoSieZI5KKJVBYGvwbVZs/J5za84f2lSTLPYf6AGiIf43tZ3GNrI1QzTLcjtyDDP4aLxd46RTZq1nQxe7+k5Q==",
 | 
					 | 
				
			||||||
      "dev": true,
 | 
					 | 
				
			||||||
      "bin": {
 | 
					 | 
				
			||||||
        "tsc": "bin/tsc"
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/tslib": {
 | 
					 | 
				
			||||||
      "version": "2.6.1",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.1.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig==",
 | 
					 | 
				
			||||||
      "dev": true
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/yocto-queue": {
 | 
					 | 
				
			||||||
      "version": "1.0.0",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==",
 | 
					 | 
				
			||||||
      "dev": true,
 | 
					 | 
				
			||||||
      "engines": {
 | 
					 | 
				
			||||||
        "node": ">=12.20"
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      "funding": {
 | 
					 | 
				
			||||||
        "url": "https://github.com/sponsors/sindresorhus"
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,7 +0,0 @@
 | 
				
			|||||||
{
 | 
					 | 
				
			||||||
  "devDependencies": {
 | 
					 | 
				
			||||||
    "minify": "^10.3.0",
 | 
					 | 
				
			||||||
    "prettier": "^3.0.1",
 | 
					 | 
				
			||||||
    "tsc": "^2.0.4"
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,16 +0,0 @@
 | 
				
			|||||||
[package]
 | 
					 | 
				
			||||||
name = "postman"
 | 
					 | 
				
			||||||
version = "0.1.0"
 | 
					 | 
				
			||||||
edition = "2021"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[dependencies]
 | 
					 | 
				
			||||||
tokio = { version = "1", features = ["process", "full"] }
 | 
					 | 
				
			||||||
regex = "1.4"
 | 
					 | 
				
			||||||
log = "0.4"
 | 
					 | 
				
			||||||
chrono = "0.4"
 | 
					 | 
				
			||||||
chrono-tz = { version = "0.5", features = ["serde"] }
 | 
					 | 
				
			||||||
lazy_static = "1.4"
 | 
					 | 
				
			||||||
num-integer = "0.1"
 | 
					 | 
				
			||||||
serde = "1.0"
 | 
					 | 
				
			||||||
sqlx = { version = "0.6", 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"] }
 | 
					 | 
				
			||||||
							
								
								
									
										11
									
								
								recordable_derive/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,11 @@
 | 
				
			|||||||
 | 
					[package]
 | 
				
			||||||
 | 
					name = "recordable_derive"
 | 
				
			||||||
 | 
					version = "0.1.0"
 | 
				
			||||||
 | 
					edition = "2021"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[lib]
 | 
				
			||||||
 | 
					proc-macro = true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[dependencies]
 | 
				
			||||||
 | 
					quote = "1.0.35"
 | 
				
			||||||
 | 
					syn = { version = "2.0.49", features = ["full"] }
 | 
				
			||||||
							
								
								
									
										42
									
								
								recordable_derive/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,42 @@
 | 
				
			|||||||
 | 
					use proc_macro::TokenStream;
 | 
				
			||||||
 | 
					use syn::{spanned::Spanned, Data};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Macro to allow Recordable to be implemented on an enum by dispatching over each variant.
 | 
				
			||||||
 | 
					#[proc_macro_derive(Recordable)]
 | 
				
			||||||
 | 
					pub fn extract(input: TokenStream) -> TokenStream {
 | 
				
			||||||
 | 
					    let ast = syn::parse_macro_input!(input);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    impl_recordable(&ast)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn impl_recordable(ast: &syn::DeriveInput) -> TokenStream {
 | 
				
			||||||
 | 
					    let name = &ast.ident;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    match &ast.data {
 | 
				
			||||||
 | 
					        Data::Enum(en) => {
 | 
				
			||||||
 | 
					            let extracted = en.variants.iter().map(|var| {
 | 
				
			||||||
 | 
					                let ident = &var.ident;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                quote::quote_spanned! {var.span()=>
 | 
				
			||||||
 | 
					                    Self::#ident (opt) => opt.run(ctx).await?
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            TokenStream::from(quote::quote! {
 | 
				
			||||||
 | 
					                impl Recordable for #name {
 | 
				
			||||||
 | 
					                    async fn run(self, ctx: crate::Context<'_>) -> Result<(), crate::Error> {
 | 
				
			||||||
 | 
					                        match self {
 | 
				
			||||||
 | 
					                            #(#extracted,)*
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        Ok(())
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        _ => {
 | 
				
			||||||
 | 
					            panic!("Only enums can derive Recordable");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										2
									
								
								reminder-dashboard/.prettierrc.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,2 @@
 | 
				
			|||||||
 | 
					printWidth = 100
 | 
				
			||||||
 | 
					tabWidth = 4
 | 
				
			||||||
							
								
								
									
										19
									
								
								reminder-dashboard/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,19 @@
 | 
				
			|||||||
 | 
					# reminder-dashboard
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The re-re-rewrite of the dashboard.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Why
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The existing beta variant of the dashboard is written using vanilla JavaScript. This is fine,
 | 
				
			||||||
 | 
					but annoying to update. This would've been okay if I was more dedicated to updating the vanilla
 | 
				
			||||||
 | 
					JavaScript too, but I want to experiment with "new" things.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					This also allows me to expand my frontend skills, which is relevant to part of my job.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Developing
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					1. Run both `npm run dev` and `cargo run`
 | 
				
			||||||
 | 
					2. Symlink assets: assuming cloned
 | 
				
			||||||
 | 
					   into `$HOME`, `ln -s $HOME/reminder-bot/reminder-dashboard/dist/index.html $HOME/reminder-bot/web/static/index.html`
 | 
				
			||||||
 | 
					   and
 | 
				
			||||||
 | 
					   `ln -s $HOME/reminder-bot/reminder-dashboard/dist/static/assets $HOME/reminder-bot/web/static/assets`
 | 
				
			||||||
							
								
								
									
										34
									
								
								reminder-dashboard/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,34 @@
 | 
				
			|||||||
 | 
					<!DOCTYPE html>
 | 
				
			||||||
 | 
					<html lang="EN">
 | 
				
			||||||
 | 
					<head>
 | 
				
			||||||
 | 
						<meta name="description" content="The most powerful Discord Reminders Bot">
 | 
				
			||||||
 | 
						<meta name="viewport" content="width=device-width, initial-scale=1.0">
 | 
				
			||||||
 | 
						<meta charset="UTF-8">
 | 
				
			||||||
 | 
						<meta name="yandex-verification" content="bb77b8681eb64a90"/>
 | 
				
			||||||
 | 
						<meta name="google-site-verification" content="7h7UVTeEe0AOzHiH3cFtsqMULYGN-zCZdMT_YCkW1Ho"/>
 | 
				
			||||||
 | 
						<!-- <meta http-equiv="Content-Security-Policy" content="default-src 'self'; img-src *; font-src fonts.gstatic.com 'self'"> -->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						<!-- favicon -->
 | 
				
			||||||
 | 
						<link rel="apple-touch-icon" sizes="180x180"
 | 
				
			||||||
 | 
							  href="/static/favicon/apple-touch-icon.png">
 | 
				
			||||||
 | 
						<link rel="icon" type="image/png" sizes="32x32"
 | 
				
			||||||
 | 
							  href="/static/favicon/favicon-32x32.png">
 | 
				
			||||||
 | 
						<link rel="icon" type="image/png" sizes="16x16"
 | 
				
			||||||
 | 
							  href="/static/favicon/favicon-16x16.png">
 | 
				
			||||||
 | 
						<link rel="manifest" href="/static/site.webmanifest">
 | 
				
			||||||
 | 
						<meta name="msapplication-TileColor" content="#da532c">
 | 
				
			||||||
 | 
						<meta name="theme-color" content="#ffffff">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						<title>Reminder Bot | Dashboard</title>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						<!-- styles -->
 | 
				
			||||||
 | 
						<link rel="stylesheet" href="/static/css/bulma.min.css">
 | 
				
			||||||
 | 
						<link rel="stylesheet" href="/static/css/fa.css">
 | 
				
			||||||
 | 
						<link rel="stylesheet" href="/static/css/font.css">
 | 
				
			||||||
 | 
						<link rel="stylesheet" href="/static/css/style.css">
 | 
				
			||||||
 | 
					</head>
 | 
				
			||||||
 | 
					<body>
 | 
				
			||||||
 | 
						<div id="app"></div>
 | 
				
			||||||
 | 
						<script type="module" src="/src/index.tsx"></script>
 | 
				
			||||||
 | 
					</body>
 | 
				
			||||||
 | 
					</html>
 | 
				
			||||||
							
								
								
									
										5626
									
								
								reminder-dashboard/package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										32
									
								
								reminder-dashboard/package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,32 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
						"name": "example",
 | 
				
			||||||
 | 
						"private": true,
 | 
				
			||||||
 | 
						"type": "module",
 | 
				
			||||||
 | 
						"scripts": {
 | 
				
			||||||
 | 
							"dev": "vite build --watch --mode development",
 | 
				
			||||||
 | 
							"build": "vite build",
 | 
				
			||||||
 | 
							"preview": "vite preview"
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						"dependencies": {
 | 
				
			||||||
 | 
							"axios": "^1.5.1",
 | 
				
			||||||
 | 
							"bulma": "^0.9.4",
 | 
				
			||||||
 | 
							"luxon": "^3.4.3",
 | 
				
			||||||
 | 
							"preact": "^10.13.1",
 | 
				
			||||||
 | 
							"react-colorful": "^5.6.1",
 | 
				
			||||||
 | 
							"react-query": "^3.39.3",
 | 
				
			||||||
 | 
							"tributejs": "^5.1.3",
 | 
				
			||||||
 | 
							"use-debounce": "^10.0.0",
 | 
				
			||||||
 | 
							"wouter": "^3.0"
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						"devDependencies": {
 | 
				
			||||||
 | 
							"@preact/preset-vite": "^2.5.0",
 | 
				
			||||||
 | 
							"@types/luxon": "^3.3.2",
 | 
				
			||||||
 | 
							"eslint": "^8.50.0",
 | 
				
			||||||
 | 
							"eslint-config-preact": "^1.3.0",
 | 
				
			||||||
 | 
							"prettier": "^3.0.3",
 | 
				
			||||||
 | 
							"react-datepicker": "^4.21.0",
 | 
				
			||||||
 | 
							"sass": "^1.71.1",
 | 
				
			||||||
 | 
							"typescript": "^5.2.2",
 | 
				
			||||||
 | 
							"vite": "^5.1"
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -15,6 +15,22 @@ div.reminderContent.is-collapsed .column.settings {
 | 
				
			|||||||
    display: none;
 | 
					    display: none;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.reminderContent.is-collapsed .reminder-settings {
 | 
				
			||||||
 | 
					    margin-bottom: 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.reminderContent.is-collapsed .button-row {
 | 
				
			||||||
 | 
					    display: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.reminderContent.is-collapsed .button-row-edit {
 | 
				
			||||||
 | 
					    display: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.reminderContent.is-collapsed .reminder-topbar {
 | 
				
			||||||
 | 
					    padding-bottom: 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
div.reminderContent.is-collapsed .invert-collapses {
 | 
					div.reminderContent.is-collapsed .invert-collapses {
 | 
				
			||||||
    display: inline-flex;
 | 
					    display: inline-flex;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -129,6 +145,12 @@ div.split-controls {
 | 
				
			|||||||
    margin-top: 0 !important;
 | 
					    margin-top: 0 !important;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.reminder-settings > .column {
 | 
				
			||||||
 | 
					    flex-grow: 0;
 | 
				
			||||||
 | 
					    flex-shrink: 0;
 | 
				
			||||||
 | 
					    flex-basis: 50%;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
div.reminderContent {
 | 
					div.reminderContent {
 | 
				
			||||||
    margin-top: 10px;
 | 
					    margin-top: 10px;
 | 
				
			||||||
    margin-bottom: 10px;
 | 
					    margin-bottom: 10px;
 | 
				
			||||||
@@ -249,11 +271,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;
 | 
				
			||||||
@@ -286,17 +308,24 @@ div.dashboard-sidebar {
 | 
				
			|||||||
    padding-right: 0;
 | 
					    padding-right: 0;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
div.dashboard-sidebar:not(.mobile-sidebar) {
 | 
					ul.guildList {
 | 
				
			||||||
    display: flex;
 | 
					    flex-grow: 1;
 | 
				
			||||||
    flex-direction: column;
 | 
					    flex-shrink: 1;
 | 
				
			||||||
 | 
					    overflow: auto;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
div.dashboard-sidebar:not(.mobile-sidebar) .aside-footer {
 | 
					div.dashboard-sidebar:not(.mobile-sidebar) .aside-footer {
 | 
				
			||||||
 | 
					    flex-shrink: 0;
 | 
				
			||||||
 | 
					    flex-grow: 0;
 | 
				
			||||||
    position: fixed;
 | 
					    position: fixed;
 | 
				
			||||||
    bottom: 0;
 | 
					    bottom: 0;
 | 
				
			||||||
    width: 226px;
 | 
					    width: 226px;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.dashboard-sidebar svg {
 | 
				
			||||||
 | 
					    flex-shrink: 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
div.mobile-sidebar {
 | 
					div.mobile-sidebar {
 | 
				
			||||||
    z-index: 100;
 | 
					    z-index: 100;
 | 
				
			||||||
    min-height: 100vh;
 | 
					    min-height: 100vh;
 | 
				
			||||||
@@ -444,8 +473,7 @@ input.default-width {
 | 
				
			|||||||
.customizable.is-400x300 img {
 | 
					.customizable.is-400x300 img {
 | 
				
			||||||
    margin-top: 10px;
 | 
					    margin-top: 10px;
 | 
				
			||||||
    width: 100%;
 | 
					    width: 100%;
 | 
				
			||||||
    min-height: 100px;
 | 
					    height: 100px;
 | 
				
			||||||
    max-height: 400px;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.customizable.is-32x32 img {
 | 
					.customizable.is-32x32 img {
 | 
				
			||||||
@@ -589,6 +617,14 @@ input.default-width {
 | 
				
			|||||||
    border-bottom: 1px solid #fff;
 | 
					    border-bottom: 1px solid #fff;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.channel-selector {
 | 
				
			||||||
 | 
					    width: 100%;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.select {
 | 
				
			||||||
 | 
					    width: 100%;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
li.highlight {
 | 
					li.highlight {
 | 
				
			||||||
    margin-bottom: 0 !important;
 | 
					    margin-bottom: 0 !important;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -612,7 +648,22 @@ li.highlight {
 | 
				
			|||||||
    padding: 2px;
 | 
					    padding: 2px;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@media only screen and (max-width: 1408px) {
 | 
					@media only screen and (max-width: 1023px) {
 | 
				
			||||||
 | 
					    p.title.pageTitle {
 | 
				
			||||||
 | 
					        display: none;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .dashboard-frame {
 | 
				
			||||||
 | 
					        margin-top: 4rem !important;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .customizable.thumbnail img {
 | 
				
			||||||
 | 
					        width: 60px;
 | 
				
			||||||
 | 
					        height: 60px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@media only screen and (max-width: 768px) {
 | 
				
			||||||
    .button-row {
 | 
					    .button-row {
 | 
				
			||||||
        display: flex;
 | 
					        display: flex;
 | 
				
			||||||
        flex-direction: column;
 | 
					        flex-direction: column;
 | 
				
			||||||
@@ -630,41 +681,20 @@ li.highlight {
 | 
				
			|||||||
    .button-row button {
 | 
					    .button-row button {
 | 
				
			||||||
        width: 100%;
 | 
					        width: 100%;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
@media only screen and (max-width: 768px) {
 | 
					    .reminder-settings {
 | 
				
			||||||
    .button-row-edit {
 | 
					        margin-bottom: 0 !important;
 | 
				
			||||||
        display: flex;
 | 
					 | 
				
			||||||
        flex-direction: column;
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    .button-row-edit > button {
 | 
					    .tts-row {
 | 
				
			||||||
        width: 100%;
 | 
					        padding-bottom: 0;
 | 
				
			||||||
        margin: 4px;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    p.title.pageTitle {
 | 
					 | 
				
			||||||
        visibility: hidden;
 | 
					 | 
				
			||||||
        text-wrap: nowrap;
 | 
					 | 
				
			||||||
        overflow: hidden;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@media only screen and (max-width: 768px) {
 | 
					 | 
				
			||||||
    .customizable.thumbnail img {
 | 
					 | 
				
			||||||
        width: 60px;
 | 
					 | 
				
			||||||
        height: 60px;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    .customizable.is-24x24 img {
 | 
					 | 
				
			||||||
        width: 16px;
 | 
					 | 
				
			||||||
        height: 16px;
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* 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;
 | 
				
			||||||
@@ -676,6 +706,86 @@ li.highlight {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
/* END */
 | 
					/* END */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.reminderError {
 | 
				
			||||||
 | 
					    margin: 10px;
 | 
				
			||||||
 | 
					    padding: 14px;
 | 
				
			||||||
 | 
					    background-color: #f5f5f5;
 | 
				
			||||||
 | 
					    border-radius: 8px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.reminderError .errorHead {
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    flex-direction: row;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.reminderError .errorIcon {
 | 
				
			||||||
 | 
					    padding: 8px;
 | 
				
			||||||
 | 
					    border-radius: 4px;
 | 
				
			||||||
 | 
					    margin-right: 12px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.reminderError .errorIcon .fas {
 | 
				
			||||||
 | 
					    display: none
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.reminderError[data-case="deleted"] .errorIcon {
 | 
				
			||||||
 | 
					    background-color: #e7e5e4;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.reminderError[data-case="failed"] .errorIcon {
 | 
				
			||||||
 | 
					    background-color: #fecaca;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.reminderError[data-case="sent"] .errorIcon {
 | 
				
			||||||
 | 
					    background-color: #d9f99d;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.reminderError[data-case="deleted"] .errorIcon .fas.fa-trash {
 | 
				
			||||||
 | 
					    display: block;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.reminderError[data-case="failed"] .errorIcon .fas.fa-exclamation-triangle {
 | 
				
			||||||
 | 
					    display: block;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.reminderError[data-case="sent"] .errorIcon .fas.fa-check {
 | 
				
			||||||
 | 
					    display: block;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.reminderError .errorHead .reminderName {
 | 
				
			||||||
 | 
					    font-size: 1rem;
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    flex-direction: column;
 | 
				
			||||||
 | 
					    justify-content: center;
 | 
				
			||||||
 | 
					    color: rgb(54, 54, 54);
 | 
				
			||||||
 | 
					    flex-grow: 1;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.reminderError .errorHead .reminderTime {
 | 
				
			||||||
 | 
					    font-size: 1rem;
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    flex-direction: column;
 | 
				
			||||||
 | 
					    flex-shrink: 1;
 | 
				
			||||||
 | 
					    justify-content: center;
 | 
				
			||||||
 | 
					    color: rgb(54, 54, 54);
 | 
				
			||||||
 | 
					    background-color: #ffffff;
 | 
				
			||||||
 | 
					    padding: 8px;
 | 
				
			||||||
 | 
					    border-radius: 4px;
 | 
				
			||||||
 | 
					    border-color: #e5e5e5;
 | 
				
			||||||
 | 
					    border-width: 1px;
 | 
				
			||||||
 | 
					    border-style: solid;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.reminderError .reminderMessage {
 | 
				
			||||||
 | 
					    font-size: 1rem;
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    flex-direction: column;
 | 
				
			||||||
 | 
					    justify-content: center;
 | 
				
			||||||
 | 
					    color: rgb(54, 54, 54);
 | 
				
			||||||
 | 
					    flex-grow: 1;
 | 
				
			||||||
 | 
					    font-style: italic;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* other stuff */
 | 
					/* other stuff */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.half-rem {
 | 
					.half-rem {
 | 
				
			||||||
@@ -713,15 +823,38 @@ a.switch-pane {
 | 
				
			|||||||
    text-overflow: ellipsis;
 | 
					    text-overflow: ellipsis;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.guild-submenu {
 | 
				
			||||||
 | 
					    display: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.guild-submenu li {
 | 
				
			||||||
 | 
					    font-size: 0.8rem;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					a.switch-pane.is-active ~ .guild-submenu {
 | 
				
			||||||
 | 
					    display: block;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.feedback {
 | 
					.feedback {
 | 
				
			||||||
    background-color: #5865F2;
 | 
					    background-color: #5865F2;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.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 +884,5 @@ a.switch-pane {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.figure-num {
 | 
					.figure-num {
 | 
				
			||||||
    font-size: 2em;
 | 
					    font-size: 2rem;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
		 Before Width: | Height: | Size: 7.2 KiB After Width: | Height: | Size: 7.2 KiB  | 
| 
		 Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB  | 
| 
		 Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 6.8 KiB  | 
| 
		 Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB  | 
| 
		 Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB  | 
| 
		 Before Width: | Height: | Size: 7.2 KiB After Width: | Height: | Size: 7.2 KiB  | 
| 
		 Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 4.9 KiB  | 
| 
		 Before Width: | Height: | Size: 762 B After Width: | Height: | Size: 762 B  | 
| 
		 Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB  | 
| 
		 Before Width: | Height: | Size: 323 KiB After Width: | Height: | Size: 323 KiB  | 
| 
		 Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 61 KiB  | 
| 
		 Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 81 KiB  | 
| 
		 Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB  | 
| 
		 Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 35 KiB  | 
| 
		 Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 55 KiB  | 
| 
		 Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.6 KiB  | 
| 
		 Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB  | 
| 
		 Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB  | 
| 
		 Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB  | 
| 
		 Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB  | 
| 
		 Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB  | 
| 
		 Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB  | 
| 
		 Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB  | 
| 
		 Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB  | 
| 
		 Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB  | 
| 
		 Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 65 KiB  | 
@@ -223,11 +223,10 @@ async function fetch_reminders(guild_id) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async function serialize_reminder(node, mode) {
 | 
					async function serialize_reminder(node, mode) {
 | 
				
			||||||
    let interval, utc_time, expiration_time;
 | 
					    let utc_time, expiration_time;
 | 
				
			||||||
 | 
					    let interval = get_interval(node);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (mode !== "template") {
 | 
					    if (mode !== "template") {
 | 
				
			||||||
        interval = get_interval(node);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        utc_time = luxon.DateTime.fromISO(
 | 
					        utc_time = luxon.DateTime.fromISO(
 | 
				
			||||||
            node.querySelector('input[name="time"]').value
 | 
					            node.querySelector('input[name="time"]').value
 | 
				
			||||||
        ).setZone("UTC");
 | 
					        ).setZone("UTC");
 | 
				
			||||||
@@ -356,9 +355,9 @@ async function serialize_reminder(node, mode) {
 | 
				
			|||||||
        embed_title: embed_title,
 | 
					        embed_title: embed_title,
 | 
				
			||||||
        embed_fields: fields,
 | 
					        embed_fields: fields,
 | 
				
			||||||
        expires: expiration_time,
 | 
					        expires: expiration_time,
 | 
				
			||||||
        interval_seconds: mode !== "template" ? interval.seconds : null,
 | 
					        interval_seconds: interval.seconds,
 | 
				
			||||||
        interval_days: mode !== "template" ? interval.days : null,
 | 
					        interval_days: interval.days,
 | 
				
			||||||
        interval_months: mode !== "template" ? interval.months : null,
 | 
					        interval_months: interval.months,
 | 
				
			||||||
        name: node.querySelector('input[name="name"]').value,
 | 
					        name: node.querySelector('input[name="name"]').value,
 | 
				
			||||||
        tts: node.querySelector('input[name="tts"]').checked,
 | 
					        tts: node.querySelector('input[name="tts"]').checked,
 | 
				
			||||||
        username: node.querySelector('input[name="username"]').value,
 | 
					        username: node.querySelector('input[name="username"]').value,
 | 
				
			||||||
@@ -420,9 +419,9 @@ function deserialize_reminder(reminder, frame, mode) {
 | 
				
			|||||||
            .insertBefore(embed_field, lastChild);
 | 
					            .insertBefore(embed_field, lastChild);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (mode !== "template") {
 | 
					 | 
				
			||||||
    if (reminder["interval_seconds"]) update_interval(frame);
 | 
					    if (reminder["interval_seconds"]) update_interval(frame);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (mode !== "template") {
 | 
				
			||||||
        let $enableBtn = frame.querySelector(".disable-enable");
 | 
					        let $enableBtn = frame.querySelector(".disable-enable");
 | 
				
			||||||
        $enableBtn.dataset["action"] = reminder["enabled"] ? "disable" : "enable";
 | 
					        $enableBtn.dataset["action"] = reminder["enabled"] ? "disable" : "enable";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -604,6 +603,16 @@ function show_error(error) {
 | 
				
			|||||||
    }, 5000);
 | 
					    }, 5000);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function show_success(error) {
 | 
				
			||||||
 | 
					    document.getElementById("success").querySelector("span.success-message").textContent =
 | 
				
			||||||
 | 
					        error;
 | 
				
			||||||
 | 
					    document.getElementById("success").classList.add("is-active");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    window.setTimeout(() => {
 | 
				
			||||||
 | 
					        document.getElementById("success").classList.remove("is-active");
 | 
				
			||||||
 | 
					    }, 5000);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
$colorPickerInput.value = colorPicker.color.hexString;
 | 
					$colorPickerInput.value = colorPicker.color.hexString;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
$colorPickerInput.addEventListener("input", () => {
 | 
					$colorPickerInput.addEventListener("input", () => {
 | 
				
			||||||
@@ -754,11 +763,25 @@ $uploader.addEventListener("change", (ev) => {
 | 
				
			|||||||
        fileReader.onload = (e) => resolve(fileReader.result);
 | 
					        fileReader.onload = (e) => resolve(fileReader.result);
 | 
				
			||||||
        fileReader.readAsDataURL($uploader.files[0]);
 | 
					        fileReader.readAsDataURL($uploader.files[0]);
 | 
				
			||||||
    }).then((dataUrl) => {
 | 
					    }).then((dataUrl) => {
 | 
				
			||||||
 | 
					        $importBtn.setAttribute("disabled", true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        fetch(`/dashboard/api/guild/${guildId()}/export/${urlTail}`, {
 | 
					        fetch(`/dashboard/api/guild/${guildId()}/export/${urlTail}`, {
 | 
				
			||||||
            method: "PUT",
 | 
					            method: "PUT",
 | 
				
			||||||
            body: JSON.stringify({ body: dataUrl.split(",")[1] }),
 | 
					            body: JSON.stringify({ body: dataUrl.split(",")[1] }),
 | 
				
			||||||
        }).then(() => {
 | 
					        })
 | 
				
			||||||
 | 
					            .then((response) => response.json())
 | 
				
			||||||
 | 
					            .then((data) => {
 | 
				
			||||||
 | 
					                $importBtn.removeAttribute("disabled");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (data.error) {
 | 
				
			||||||
 | 
					                    show_error(data.error);
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    show_success(data.message);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					            .then(() => {
 | 
				
			||||||
                delete $uploader.files[0];
 | 
					                delete $uploader.files[0];
 | 
				
			||||||
 | 
					                fetch_reminders(guild);
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
		 Before Width: | Height: | Size: 712 KiB After Width: | Height: | Size: 712 KiB  | 
| 
		 Before Width: | Height: | Size: 2.5 MiB After Width: | Height: | Size: 2.5 MiB  | 
| 
		 Before Width: | Height: | Size: 2.3 MiB After Width: | Height: | Size: 2.3 MiB  | 
| 
		 Before Width: | Height: | Size: 2.1 MiB After Width: | Height: | Size: 2.1 MiB  |