Skip to content

Commit

Permalink
feat(wrapped): support multiple years and add 2024
Browse files Browse the repository at this point in the history
  • Loading branch information
ashhhleyyy committed Dec 21, 2024
1 parent f05fe4e commit 893cd07
Show file tree
Hide file tree
Showing 3 changed files with 107 additions and 38 deletions.
13 changes: 9 additions & 4 deletions src/statistics/database.rs
Original file line number Diff line number Diff line change
Expand Up @@ -415,8 +415,8 @@ impl StatisticDatabaseController {
Ok(data)
}

async fn wrapped_data(&self, player_id: &Uuid) -> StatisticsDatabaseResult<PlayerWrappedData> {
let result = self.wrapped.build_wrapped(player_id).await?;
async fn wrapped_data(&self, player_id: Uuid, year: u16) -> StatisticsDatabaseResult<PlayerWrappedData> {
let result = self.wrapped.build_wrapped(player_id, year).await?;
Ok(result)
}
}
Expand Down Expand Up @@ -549,13 +549,16 @@ impl Handler<DataQuery> for StatisticDatabaseController {
}
}

pub struct WrappedData(pub Uuid);
pub struct WrappedData {
pub player_id: Uuid,
pub year: u16,
}

impl Handler<WrappedData> for StatisticDatabaseController {
type Return = StatisticsDatabaseResult<PlayerWrappedData>;

async fn handle(&mut self, message: WrappedData, _ctx: &mut Context<Self>) -> Self::Return {
self.wrapped_data(&message.0).await
self.wrapped_data(message.player_id, message.year).await
}
}

Expand All @@ -567,6 +570,8 @@ pub enum StatisticsDatabaseError {
Postgres(#[from] tokio_postgres::Error),
#[error("a database pool error occurred: {0}")]
Pool(#[from] deadpool_postgres::PoolError),
#[error("nucleoid wrapped is not available for this year")]
UnWrappedYear,
#[error("unknown error")]
Unknown,
}
Expand Down
117 changes: 86 additions & 31 deletions src/statistics/wrapped.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,28 @@
use std::convert::TryFrom;

use serde::{Deserialize, Serialize};
use uuid::Uuid;

use super::database::{StatisticsDatabaseError, StatisticsDatabaseResult};

#[derive(Clone, Copy)]
pub enum WrappedYear {
Y2023,
Y2024,
}

impl TryFrom<u16> for WrappedYear {
type Error = StatisticsDatabaseError;

fn try_from(value: u16) -> Result<Self, Self::Error> {
match value {
2023 => Ok(WrappedYear::Y2023),
2024 => Ok(WrappedYear::Y2024),
_ => Err(StatisticsDatabaseError::UnWrappedYear),
}
}
}

pub struct NucleoidWrapped {
clickhouse_pool: clickhouse_rs::Pool,
}
Expand All @@ -10,19 +32,42 @@ impl NucleoidWrapped {
Self { clickhouse_pool }
}

async fn played_count(&self, player: &Uuid) -> Result<u64, clickhouse_rs::errors::Error> {
fn start_date(year: WrappedYear) -> &'static str {
match year {
WrappedYear::Y2023 => "2022-12-31 00:00:00",
WrappedYear::Y2024 => "2023-12-01 00:00:00",
}
}

fn end_date(year: WrappedYear) -> &'static str {
match year {
WrappedYear::Y2023 => "2023-12-01 00:00:00",
WrappedYear::Y2024 => "2024-12-21 00:00:00",
}
}

fn date_range(year: WrappedYear) -> String {
format!(
"(games.date_played < '{end_date}') AND (games.date_played > '{start_date}')",
start_date = Self::start_date(year),
end_date = Self::end_date(year),
)
}

async fn played_count(&self, player: Uuid, year: WrappedYear) -> Result<u64, clickhouse_rs::errors::Error> {
let mut ch_handle = self.clickhouse_pool.get_handle().await?;
let results = ch_handle.query(format!(
r#"
SELECT
COUNT(DISTINCT game_id) AS total
FROM player_statistics
INNER JOIN games ON player_statistics.game_id = games.game_id
WHERE (player_id = '{player_id}') AND (games.date_played < '2023-12-01 00:00:00') AND (games.date_played > '2022-12-31 00:00:00')
WHERE (player_id = '{player_id}') AND {date_range}
ORDER BY total DESC
"#,
// safety: player is a uuid, which has a fixed format which is safe to insert directly into the sql
player_id = player
player_id = player,
date_range = Self::date_range(year),
)).fetch_all().await?;
if let Some(row) = results.rows().next() {
Ok(row.get("total")?)
Expand All @@ -33,7 +78,8 @@ impl NucleoidWrapped {

async fn top_games(
&self,
player: &Uuid,
player: Uuid,
year: WrappedYear,
) -> Result<Vec<PerGameStat>, clickhouse_rs::errors::Error> {
let mut ch_handle = self.clickhouse_pool.get_handle().await?;
let results = ch_handle.query(format!(
Expand All @@ -43,12 +89,13 @@ impl NucleoidWrapped {
COUNT(DISTINCT game_id) AS total
FROM player_statistics
INNER JOIN games ON player_statistics.game_id = games.game_id
WHERE (player_id = '{player_id}') AND (games.date_played < '2023-12-01 00:00:00') AND (games.date_played > '2022-12-31 00:00:00')
WHERE (player_id = '{player_id}') AND {date_range}
GROUP BY games.namespace
ORDER BY total DESC
"#,
// safety: player is a uuid, which has a fixed format which is safe to insert directly into the sql
player_id = player
player_id = player,
date_range = Self::date_range(year),
)).fetch_all().await?;

let mut top_games = Vec::with_capacity(results.row_count());
Expand All @@ -62,19 +109,20 @@ impl NucleoidWrapped {
Ok(top_games)
}

async fn days_played(&self, player: &Uuid) -> Result<u64, clickhouse_rs::errors::Error> {
async fn days_played(&self, player: Uuid, year: WrappedYear,) -> Result<u64, clickhouse_rs::errors::Error> {
let mut ch_handle = self.clickhouse_pool.get_handle().await?;
let results = ch_handle.query(format!(
r#"
SELECT
COUNT(DISTINCT toDayOfYear(date_played)) AS total
FROM player_statistics
INNER JOIN games ON player_statistics.game_id = games.game_id
WHERE (player_id = '{player_id}') AND (games.date_played < '2023-12-01 00:00:00') AND (games.date_played > '2022-12-31 00:00:00')
WHERE (player_id = '{player_id}') AND {date_range}
ORDER BY total DESC
"#,
// safety: player is a uuid, which has a fixed format which is safe to insert directly into the sql
player_id = player
player_id = player,
date_range = Self::date_range(year),
)).fetch_all().await?;
if let Some(row) = results.rows().next() {
Ok(row.get("total")?)
Expand All @@ -85,7 +133,8 @@ impl NucleoidWrapped {

async fn days_played_games(
&self,
player: &Uuid,
player: Uuid,
year: WrappedYear,
) -> Result<Vec<PerGameStat>, clickhouse_rs::errors::Error> {
let mut ch_handle = self.clickhouse_pool.get_handle().await?;
let results = ch_handle.query(format!(
Expand All @@ -95,12 +144,13 @@ impl NucleoidWrapped {
COUNT(DISTINCT toDayOfYear(date_played)) AS total
FROM player_statistics
INNER JOIN games ON player_statistics.game_id = games.game_id
WHERE (player_id = '{player_id}') AND (games.date_played < '2023-12-01 00:00:00') AND (games.date_played > '2022-12-31 00:00:00')
WHERE (player_id = '{player_id}') AND {date_range}
GROUP BY games.namespace
ORDER BY total DESC
"#,
// safety: player is a uuid, which has a fixed format which is safe to insert directly into the sql
player_id = player
player_id = player,
date_range = Self::date_range(year),
)).fetch_all().await?;

let mut top_games = Vec::with_capacity(results.row_count());
Expand All @@ -114,7 +164,7 @@ impl NucleoidWrapped {
Ok(top_games)
}

async fn most_players(&self, player: &Uuid) -> Result<u64, clickhouse_rs::errors::Error> {
async fn most_players(&self, player: Uuid, year: WrappedYear,) -> Result<u64, clickhouse_rs::errors::Error> {
let mut ch_handle = self.clickhouse_pool.get_handle().await?;
let results = ch_handle
.query(format!(
Expand All @@ -126,20 +176,22 @@ impl NucleoidWrapped {
game_id
FROM player_statistics
INNER JOIN games ON player_statistics.game_id = games.game_id
WHERE (player_id = '{player_id}')
AND (games.date_played < '2023-12-01 00:00:00')
AND (games.date_played > '2022-12-31 00:00:00')
WHERE (player_id = '{player_id}') AND {date_range}
GROUP BY game_id) AS games
INNER JOIN player_statistics ON player_statistics.game_id = games.game_id
"#,
// safety: player is a uuid, which has a fixed format which is safe to insert directly into the sql
player_id = player
player_id = player,
date_range = Self::date_range(year),
))
.fetch_all()
.await?;
if let Some(row) = results.rows().next() {
let mut total: u64 = row.get("total")?;
total -= 1;
// lets maybe not crash here
if total > 0 {
total -= 1;
}
Ok(total)
} else {
Ok(0)
Expand All @@ -148,7 +200,8 @@ impl NucleoidWrapped {

async fn most_players_games(
&self,
player: &Uuid,
player: Uuid,
year: WrappedYear,
) -> Result<Vec<PerGameStat>, clickhouse_rs::errors::Error> {
let mut ch_handle = self.clickhouse_pool.get_handle().await?;
let results = ch_handle
Expand All @@ -163,16 +216,15 @@ impl NucleoidWrapped {
namespace
FROM player_statistics
INNER JOIN games ON player_statistics.game_id = games.game_id
WHERE (player_id = '{player_id}')
AND (games.date_played < '2023-12-01 00:00:00')
AND (games.date_played > '2022-12-31 00:00:00')
WHERE (player_id = '{player_id}') AND {date_range}
GROUP BY game_id, namespace) AS games
INNER JOIN player_statistics ON player_statistics.game_id = games.game_id
GROUP BY namespace
ORDER BY total DESC
"#,
// safety: player is a uuid, which has a fixed format which is safe to insert directly into the sql
player_id = player
player_id = player,
date_range = Self::date_range(year),
))
.fetch_all()
.await?;
Expand All @@ -191,14 +243,17 @@ impl NucleoidWrapped {

pub async fn build_wrapped(
&self,
player: &Uuid,
) -> Result<PlayerWrappedData, clickhouse_rs::errors::Error> {
let played_count = self.played_count(player).await?;
let top_games = self.top_games(player).await?;
let days_played = self.days_played(player).await?;
let days_played_games = self.days_played_games(player).await?;
let most_players = self.most_players(player).await?;
let most_players_games = self.most_players_games(player).await?;
player: Uuid,
year: u16,
) -> StatisticsDatabaseResult<PlayerWrappedData> {
let year = WrappedYear::try_from(year)?;

let played_count = self.played_count(player, year).await?;
let top_games = self.top_games(player, year).await?;
let days_played = self.days_played(player, year).await?;
let days_played_games = self.days_played_games(player, year).await?;
let most_players = self.most_players(player, year).await?;
let most_players_games = self.most_players_games(player, year).await?;
Ok(PlayerWrappedData {
played_count,
top_games,
Expand Down
15 changes: 12 additions & 3 deletions src/web.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,9 +121,10 @@ pub async fn run(controller: Address<Controller>, config: WebServerConfig) {
let nucleoid_wrapped = warp::path("player")
.and(warp::path::param::<Uuid>())
.and(warp::path("wrapped"))
.and(warp::query())
.and_then({
let controller = controller.clone();
move |id| nucleoid_wrapped(controller.clone(), id)
move |id, query: WrappedQuery| nucleoid_wrapped(controller.clone(), id, query.year)
})
.with(&cors);

Expand Down Expand Up @@ -263,10 +264,13 @@ async fn get_player_username(mojang_client: Address<MojangApiClient>, id: Uuid)
handle_option_result(profile)
}

async fn nucleoid_wrapped(controller: Address<Controller>, player_id: Uuid) -> ApiResult {
async fn nucleoid_wrapped(controller: Address<Controller>, player_id: Uuid, year: Option<u16>) -> ApiResult {
let statistics = get_statistics_controller(controller).await?;
let res = statistics
.send(WrappedData(player_id))
.send(WrappedData {
player_id,
year: year.unwrap_or(2023),
})
.await
.expect("controller disconnected");
handle_result(res)
Expand All @@ -283,6 +287,11 @@ struct DataQueryQuery {
query: DataQueryType,
}

#[derive(Deserialize)]
struct WrappedQuery {
year: Option<u16>,
}

async fn get_statistics_controller(
controller: Address<Controller>,
) -> Result<Address<StatisticDatabaseController>, warp::Rejection> {
Expand Down

0 comments on commit 893cd07

Please sign in to comment.