get started with an actual database
ci/woodpecker/push/woodpecker Pipeline failed Details

pull/11/head
Patrick Cleavelin 2023-08-04 15:16:32 -05:00
parent 88ba532d1f
commit 969a97cab7
6 changed files with 134 additions and 18 deletions

68
Cargo.lock generated
View File

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

View File

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

48
src/db.rs Normal file
View File

@ -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<Path>) -> Result<Self> {
Ok(Self {
conn: Connection::open(path)?,
})
}
pub fn get_user_guilds(&self, username: &str) -> Result<Vec<Guild>> {
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::<Result<Vec<Guild>>>();
guilds
}
}
pub struct Guild {
pub id: String,
pub name: String,
pub sound_delay: u32,
}

View File

@ -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<Mutex<Settings>>) {
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(),

View File

@ -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<User>,
) -> Result<Html<String>, 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<Item = (&'a u64, &'a GuildSettings)>,
) -> HtmxBuilder {
fn guild_list<'a>(origin: &str, guilds: impl Iterator<Item = &'a db::Guild>) -> 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 {

View File

@ -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<tokio::sync::Mutex<Database>>,
pub settings: Arc<tokio::sync::Mutex<Settings>>,
pub secrets: auth::DiscordSecret,
pub origin: String,