diff --git a/Cargo.lock b/Cargo.lock index 23788c2..ae7a8f3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -62,6 +62,17 @@ dependencies = [ "subtle", ] +[[package]] +name = "ahash" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", +] + [[package]] name = "aho-corasick" version = "1.0.2" @@ -71,6 +82,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" + [[package]] name = "android-tzdata" version = "0.1.1" @@ -514,6 +531,18 @@ dependencies = [ "libc", ] +[[package]] +name = "fallible-iterator" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" + +[[package]] +name = "fallible-streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" + [[package]] name = "fastrand" version = "2.0.0" @@ -744,6 +773,19 @@ name = "hashbrown" version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" +dependencies = [ + "ahash", + "allocator-api2", +] + +[[package]] +name = "hashlink" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "312f66718a2d7789ffef4f4b7b213138ed9f1eb3aa1d0d82fc99f88fb3ffd26f" +dependencies = [ + "hashbrown 0.14.0", +] [[package]] name = "headers" @@ -952,6 +994,17 @@ version = "0.2.147" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" +[[package]] +name = "libsqlite3-sys" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afc22eff61b133b115c6e8c74e818c628d6d5e7a502afea6f64dee076dd94326" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + [[package]] name = "linux-raw-sys" version = "0.4.3" @@ -1021,6 +1074,7 @@ dependencies = [ "dotenv", "futures", "reqwest", + "rusqlite", "serde", "serde_json", "serenity", @@ -1516,6 +1570,20 @@ dependencies = [ "winapi", ] +[[package]] +name = "rusqlite" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "549b9d036d571d42e6e85d1c1425e2ac83491075078ca9a15be021c56b1641f2" +dependencies = [ + "bitflags 2.3.3", + "fallible-iterator", + "fallible-streaming-iterator", + "hashlink", + "libsqlite3-sys", + "smallvec", +] + [[package]] name = "rustc-demangle" version = "0.1.23" diff --git a/Cargo.toml b/Cargo.toml index 51d9552..76f034c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ chrono = "0.4.23" dotenv = "0.15.0" futures = "0.3.26" reqwest = "0.11.14" +rusqlite = { version = "0.29.0", features = ["bundled"] } serde = "1.0.152" serde_json = "1.0.93" thiserror = "1.0.38" diff --git a/src/db.rs b/src/db.rs new file mode 100644 index 0000000..2c14665 --- /dev/null +++ b/src/db.rs @@ -0,0 +1,48 @@ +use std::path::Path; + +use rusqlite::{Connection, Result}; + +pub struct Database { + conn: Connection, +} + +impl Database { + pub fn new(path: impl AsRef) -> Result { + Ok(Self { + conn: Connection::open(path)?, + }) + } + + pub fn get_user_guilds(&self, username: &str) -> Result> { + let mut query = self.conn.prepare( + " + SELECT + id, name, sound_delay + FROM Guild + LEFT JOIN UserGuild ON UserGuild.guild_id = Guild.id + WHERE UserGuild.username = :username + ", + )?; + + // NOTE(pcleavelin): for some reason this needs to be a let-binding or else + // the compiler complains about it being dropped too early (maybe I should update the compiler version) + let guilds = query + .query_map(&[(":username", username)], |row| { + Ok(Guild { + id: row.get(0)?, + name: row.get(1)?, + sound_delay: row.get(2)?, + }) + })? + .into_iter() + .collect::>>(); + + guilds + } +} + +pub struct Guild { + pub id: String, + pub name: String, + pub sound_delay: u32, +} diff --git a/src/main.rs b/src/main.rs index cb038ca..bc3c601 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,7 @@ #![feature(async_closure)] mod auth; +mod db; mod htmx; mod media; mod page; @@ -128,6 +129,9 @@ fn spawn_api(settings: Arc>) { let origin = env::var("APP_ORIGIN").expect("expected APP_ORIGIN"); let state = ApiState { + db: Arc::new(tokio::sync::Mutex::new( + db::Database::new("db.sqlite").expect("couldn't open sqlite db"), + )), settings, secrets, origin: origin.clone(), diff --git a/src/page.rs b/src/page.rs index c8b1c86..e0e4eec 100644 --- a/src/page.rs +++ b/src/page.rs @@ -1,5 +1,6 @@ use crate::{ auth::{self, User}, + db, htmx::{Build, HtmxBuilder, Tag}, settings::{ApiState, GuildSettings, Intro, IntroFriendlyName}, }; @@ -27,19 +28,20 @@ pub(crate) async fn home( user: Option, ) -> Result, Redirect> { if let Some(user) = user { - let settings = state.settings.lock().await; + let db = state.db.lock().await; - let guild = settings - .guilds - .iter() - .filter(|(_, guild_settings)| guild_settings.users.contains_key(&user.name)); + let user_guilds = db.get_user_guilds(&user.name).map_err(|err| { + error!(?err, "failed to get user guilds"); + // TODO: change this to returning a error to the client + Redirect::to("/login") + })?; Ok(Html( page_header("MemeJoin - Home") .builder(Tag::Div, |b| { b.attribute("class", "container") .builder_text(Tag::Header2, "Choose a Guild") - .push_builder(guild_list(&state.origin, guild)) + .push_builder(guild_list(&state.origin, user_guilds.iter())) }) .build(), )) @@ -48,22 +50,14 @@ pub(crate) async fn home( } } -fn guild_list<'a>( - origin: &str, - guilds: impl Iterator, -) -> HtmxBuilder { +fn guild_list<'a>(origin: &str, guilds: impl Iterator) -> HtmxBuilder { HtmxBuilder::new(Tag::Empty).ul(|b| { let mut b = b; let mut in_any_guilds = false; - for (guild_id, guild_settings) in guilds { + for guild in guilds { in_any_guilds = true; - b = b.li(|b| { - b.link( - &guild_settings.name, - &format!("{}/guild/{}", origin, guild_id), - ) - }); + b = b.li(|b| b.link(&guild.name, &format!("{}/guild/{}", origin, guild.id))); } if !in_any_guilds { diff --git a/src/settings.rs b/src/settings.rs index 9ef1a19..58ecdea 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -1,6 +1,6 @@ use std::{collections::HashMap, sync::Arc}; -use crate::auth; +use crate::{auth, db::Database}; use axum::{async_trait, extract::FromRequestParts, http::request::Parts, response::Redirect}; use axum_extra::extract::CookieJar; use serde::{Deserialize, Serialize}; @@ -13,6 +13,7 @@ type UserToken = String; // TODO: make this is wrapped type so cloning isn't happening #[derive(Clone)] pub(crate) struct ApiState { + pub db: Arc>, pub settings: Arc>, pub secrets: auth::DiscordSecret, pub origin: String,