mirror of
https://github.com/natsukagami/youmubot.git
synced 2025-04-19 00:38:54 +00:00
248 lines
6.8 KiB
Rust
248 lines
6.8 KiB
Rust
use super::*;
|
|
use sqlx::{query, query_as, Executor, Transaction};
|
|
use std::collections::HashMap as Map;
|
|
|
|
/// An osu user, as represented in the SQL.
|
|
#[derive(Debug, Clone)]
|
|
pub struct OsuUser {
|
|
pub user_id: i64,
|
|
pub username: Option<String>, // should always be there
|
|
pub id: i64,
|
|
pub modes: Map<u8, OsuUserMode>,
|
|
/// Number of consecutive update failures
|
|
pub failures: u8,
|
|
}
|
|
|
|
/// Stats for a single user and mode.
|
|
#[derive(Debug, Clone)]
|
|
pub struct OsuUserMode {
|
|
pub pp: f64,
|
|
pub map_length: f64,
|
|
pub map_age: i64,
|
|
pub last_update: DateTime,
|
|
}
|
|
|
|
impl OsuUserMode {
|
|
async fn from_user<'a, E>(id: i64, conn: E) -> Result<Map<u8, Self>>
|
|
where
|
|
E: Executor<'a, Database = Database>,
|
|
{
|
|
Ok(query!(
|
|
r#"SELECT
|
|
mode as "mode: u8",
|
|
pp,
|
|
map_length,
|
|
map_age,
|
|
last_update as "last_update: DateTime"
|
|
FROM osu_user_mode_stats
|
|
WHERE user_id = ?
|
|
ORDER BY mode ASC"#,
|
|
id
|
|
)
|
|
.fetch_all(conn)
|
|
.await?
|
|
.into_iter()
|
|
.map(|row| {
|
|
(
|
|
row.mode,
|
|
Self {
|
|
pp: row.pp,
|
|
map_length: row.map_length,
|
|
map_age: row.map_age,
|
|
last_update: row.last_update,
|
|
},
|
|
)
|
|
})
|
|
.collect())
|
|
}
|
|
|
|
async fn fetch_all<'a, E>(conn: E) -> Result<Map<i64, Map<u8, Self>>>
|
|
where
|
|
E: Executor<'a, Database = Database>,
|
|
{
|
|
let mut res: Map<i64, Map<u8, Self>> = Map::new();
|
|
query!(
|
|
r#"SELECT
|
|
user_id as "user_id: i64",
|
|
mode as "mode: u8",
|
|
pp,
|
|
map_length,
|
|
map_age,
|
|
last_update as "last_update: DateTime"
|
|
FROM osu_user_mode_stats
|
|
ORDER BY user_id ASC, mode ASC"#,
|
|
)
|
|
.fetch_all(conn)
|
|
.await?
|
|
.into_iter()
|
|
.for_each(|v| {
|
|
let modes = res.entry(v.user_id).or_default();
|
|
modes.insert(
|
|
v.mode,
|
|
Self {
|
|
pp: v.pp,
|
|
map_length: v.map_length,
|
|
map_age: v.map_age,
|
|
last_update: v.last_update,
|
|
},
|
|
);
|
|
});
|
|
Ok(res)
|
|
}
|
|
}
|
|
|
|
mod raw {
|
|
#[derive(Debug)]
|
|
pub struct OsuUser {
|
|
pub user_id: i64,
|
|
pub username: Option<String>, // should always be there
|
|
pub id: i64,
|
|
pub failures: u8,
|
|
}
|
|
}
|
|
|
|
impl OsuUser {
|
|
fn from_raw(r: raw::OsuUser, modes: Map<u8, OsuUserMode>) -> Self {
|
|
Self {
|
|
user_id: r.user_id,
|
|
username: r.username,
|
|
id: r.id,
|
|
modes,
|
|
failures: r.failures,
|
|
}
|
|
}
|
|
/// Query an user by their user id.
|
|
pub async fn by_user_id(user_id: i64, conn: &Pool) -> Result<Option<Self>> {
|
|
let u = match query_as!(
|
|
raw::OsuUser,
|
|
r#"SELECT
|
|
user_id as "user_id: i64",
|
|
username,
|
|
id as "id: i64",
|
|
failures as "failures: u8"
|
|
FROM osu_users WHERE user_id = ?"#,
|
|
user_id
|
|
)
|
|
.fetch_optional(conn)
|
|
.await?
|
|
{
|
|
Some(v) => v,
|
|
None => return Ok(None),
|
|
};
|
|
let modes = OsuUserMode::from_user(u.user_id, conn).await?;
|
|
Ok(Some(Self::from_raw(u, modes)))
|
|
}
|
|
|
|
/// Query an user by their osu id.
|
|
pub async fn by_osu_id(osu_id: i64, conn: &Pool) -> Result<Option<Self>> {
|
|
let u = match query_as!(
|
|
raw::OsuUser,
|
|
r#"SELECT
|
|
user_id as "user_id: i64",
|
|
username,
|
|
id as "id: i64",
|
|
failures as "failures: u8"
|
|
FROM osu_users WHERE id = ?"#,
|
|
osu_id
|
|
)
|
|
.fetch_optional(conn)
|
|
.await?
|
|
{
|
|
Some(v) => v,
|
|
None => return Ok(None),
|
|
};
|
|
let modes = OsuUserMode::from_user(u.user_id, conn).await?;
|
|
Ok(Some(Self::from_raw(u, modes)))
|
|
}
|
|
|
|
/// Query all users.
|
|
pub async fn all(conn: &Pool) -> Result<Vec<Self>> {
|
|
// last_update as "last_update: DateTime",
|
|
let us = query_as!(
|
|
raw::OsuUser,
|
|
r#"SELECT
|
|
user_id as "user_id: i64",
|
|
username,
|
|
id as "id: i64",
|
|
failures as "failures: u8"
|
|
FROM osu_users"#,
|
|
)
|
|
.fetch_all(conn)
|
|
.await?;
|
|
let mut modes = OsuUserMode::fetch_all(conn).await?;
|
|
Ok(us
|
|
.into_iter()
|
|
.map(|u| {
|
|
let m = modes.remove(&u.user_id).unwrap_or_default();
|
|
Self::from_raw(u, m)
|
|
})
|
|
.collect())
|
|
}
|
|
}
|
|
|
|
impl OsuUser {
|
|
/// Stores the user.
|
|
pub async fn store<'a>(&self, conn: &mut Transaction<'a, Database>) -> Result<bool> {
|
|
let old_user_id = {
|
|
query!(
|
|
r#"SELECT id as "id: i64" FROM osu_users WHERE user_id = ?"#,
|
|
self.user_id
|
|
)
|
|
.fetch_optional(&mut **conn)
|
|
.await?
|
|
.map(|v| v.id)
|
|
};
|
|
|
|
if old_user_id.is_some_and(|v| v != self.id) {
|
|
// There's another update that changed the user_id
|
|
return Ok(false);
|
|
}
|
|
|
|
query!(
|
|
r#"INSERT
|
|
INTO osu_users(user_id, username, id, failures)
|
|
VALUES(?, ?, ?, ?)
|
|
ON CONFLICT (user_id) WHERE id = ? DO UPDATE
|
|
SET
|
|
username = excluded.username,
|
|
failures = excluded.failures
|
|
"#,
|
|
self.user_id,
|
|
self.username,
|
|
self.id,
|
|
self.failures,
|
|
self.user_id,
|
|
)
|
|
.execute(&mut **conn)
|
|
.await?;
|
|
// Store the modes
|
|
query!(
|
|
"DELETE FROM osu_user_mode_stats WHERE user_id = ?",
|
|
self.user_id
|
|
)
|
|
.execute(&mut **conn)
|
|
.await?;
|
|
for (mode, stats) in &self.modes {
|
|
let ts = stats.last_update.timestamp();
|
|
query!(
|
|
"INSERT INTO osu_user_mode_stats (user_id, mode, pp, map_length, map_age, last_update) VALUES (?, ?, ?, ?, ?, ?)",
|
|
self.user_id,
|
|
*mode,
|
|
stats.pp,
|
|
stats.map_length,
|
|
stats.map_age,
|
|
ts,
|
|
)
|
|
.execute(&mut **conn)
|
|
.await?;
|
|
}
|
|
Ok(true)
|
|
}
|
|
|
|
pub async fn delete(user_id: i64, conn: impl Executor<'_, Database = Database>) -> Result<()> {
|
|
query!("DELETE FROM osu_users WHERE user_id = ?", user_id)
|
|
.execute(conn)
|
|
.await?;
|
|
Ok(())
|
|
}
|
|
}
|