Skip to content

Commit

Permalink
stuff
Browse files Browse the repository at this point in the history
  • Loading branch information
mbund committed Dec 16, 2024
1 parent cb7ff65 commit 97b1118
Show file tree
Hide file tree
Showing 8 changed files with 78 additions and 139 deletions.
6 changes: 0 additions & 6 deletions rhombus/migrations/libsql/0001_setup.up.sql
Original file line number Diff line number Diff line change
Expand Up @@ -194,12 +194,6 @@ FROM rhombus_solve
JOIN rhombus_team ON rhombus_solve.team_id = rhombus_team.id
GROUP BY rhombus_solve.challenge_id, rhombus_team.division_id;

-- CREATE VIEW IF NOT EXISTS rhombus_team_points AS
-- SELECT rhombus_solve.team_id, SUM(COALESCE(rhombus_solve.points, rhombus_challenge.points)) AS points, MAX(rhombus_solve.solved_at) AS last_solved_at
-- FROM rhombus_solve
-- JOIN rhombus_challenge ON rhombus_solve.challenge_id = rhombus_challenge.id
-- GROUP BY rhombus_solve.team_id;

CREATE TABLE IF NOT EXISTS rhombus_track (
id INTEGER PRIMARY KEY NOT NULL,
ip BLOB NOT NULL,
Expand Down
21 changes: 8 additions & 13 deletions rhombus/src/internal/database/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -443,8 +443,8 @@ impl Database for DbCache {
get_scoreboard(&self.inner, division_id).await
}

async fn get_leaderboard(&self, division_id: &str, page: Option<u64>) -> Result<Leaderboard> {
get_leaderboard(&self.inner, division_id, page).await
async fn get_leaderboard(&self, division_id: &str) -> Result<Leaderboard> {
get_leaderboard(&self.inner, division_id).await
}

async fn get_top10_discord_ids(&self) -> Result<BTreeSet<NonZeroU64>> {
Expand Down Expand Up @@ -699,23 +699,18 @@ pub async fn get_scoreboard(db: &Connection, division_id: &str) -> Result<Scoreb
scoreboard
}

pub static LEADERBOARD_CACHE: LazyLock<DashMap<(String, Option<u64>), Leaderboard>> =
LazyLock::new(DashMap::new);
pub static LEADERBOARD_CACHE: LazyLock<DashMap<String, Leaderboard>> = LazyLock::new(DashMap::new);

pub async fn get_leaderboard(
db: &Connection,
division_id: &str,
page: Option<u64>,
) -> Result<Leaderboard> {
if let Some(leaderboard) = LEADERBOARD_CACHE.get(&(division_id.to_owned(), page)) {
pub async fn get_leaderboard(db: &Connection, division_id: &str) -> Result<Leaderboard> {
if let Some(leaderboard) = LEADERBOARD_CACHE.get(division_id) {
return Ok(leaderboard.clone());
}
tracing::trace!(division_id, page, "cache miss: get_leaderboard");
tracing::trace!(division_id, "cache miss: get_leaderboard");

let leaderboard = db.get_leaderboard(division_id, page).await;
let leaderboard = db.get_leaderboard(division_id).await;

if let Ok(leaderboard) = &leaderboard {
LEADERBOARD_CACHE.insert((division_id.to_owned(), page), leaderboard.clone());
LEADERBOARD_CACHE.insert(division_id.to_owned(), leaderboard.clone());
}
leaderboard
}
Expand Down
130 changes: 33 additions & 97 deletions rhombus/src/internal/database/libsql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@ use crate::{
provider::{
Author, Category, Challenge, ChallengeAttachment, ChallengeData, ChallengeDivision,
ChallengeSolve, Challenges, Database, DiscordUpsertError, Email, Leaderboard,
LeaderboardEntry, Scoreboard, ScoreboardSeriesPoint, ScoreboardTeam,
SetAccountNameError, SetTeamNameError, SiteStatistics, StatisticsCategory, Team,
TeamInner, TeamMeta, TeamMetaInner, TeamStanding, TeamUser, Ticket,
ToBeClosedTicket, UserTrack, Writeup,
LeaderboardEntry, Scoreboard, ScoreboardInner, ScoreboardSeriesPoint,
ScoreboardTeam, SetAccountNameError, SetTeamNameError, SiteStatistics,
StatisticsCategory, Team, TeamInner, TeamMeta, TeamMetaInner, TeamStanding,
TeamUser, Ticket, ToBeClosedTicket, UserTrack, Writeup,
},
},
division::Division,
Expand Down Expand Up @@ -1902,111 +1902,47 @@ impl<T: ?Sized + LibSQLConnection + Send + Sync> Database for T {

tx.commit().await?;

Ok(Scoreboard { teams })
Ok(Arc::new(ScoreboardInner::new(teams)))
}

async fn get_leaderboard(&self, division_id: &str, page: Option<u64>) -> Result<Leaderboard> {
async fn get_leaderboard(&self, division_id: &str) -> Result<Leaderboard> {
#[derive(Debug, Deserialize)]
struct DbLeaderboard {
team_id: i64,
name: String,
points: f64,
}

if let Some(page) = page {
let tx = self.transaction().await?;
let mut rank = 0;

let num_teams = tx
.query(
"
SELECT COUNT(*)
FROM rhombus_team
WHERE rhombus_team.division_id = ?1
",
[division_id],
)
.await?
.next()
.await?
.unwrap()
.get::<u64>(0)
.unwrap();

const PAGE_SIZE: u64 = 25;

let num_pages = (num_teams + (PAGE_SIZE - 1)) / PAGE_SIZE;

let page = page.min(num_pages);

let mut rank = page * PAGE_SIZE;

let leaderboard_entries = tx
.query(
"
SELECT id AS team_id, name, points
FROM rhombus_team
WHERE division_id = ?1
ORDER BY points DESC, last_solved_at ASC
LIMIT ?3 OFFSET ?2
",
params!(division_id, page * PAGE_SIZE, PAGE_SIZE),
)
.await?
.into_stream()
.map(|row| {
let db_leaderboard = de::from_row::<DbLeaderboard>(&row.unwrap()).unwrap();
rank += 1;
LeaderboardEntry {
rank,
team_id: db_leaderboard.team_id,
team_name: db_leaderboard.name,
score: db_leaderboard.points.round() as i64,
}
})
.collect::<Vec<_>>()
.await;

tx.commit().await?;

Ok(Leaderboard {
entries: leaderboard_entries,
num_pages,
let leaderboard_entries = self
.connect()
.await?
.query(
"
SELECT id AS team_id, name, points
FROM rhombus_team
WHERE division_id = ?1
ORDER BY points DESC, last_solved_at ASC
",
params!(division_id),
)
.await?
.into_stream()
.map(|row| {
let db_leaderboard = de::from_row::<DbLeaderboard>(&row.unwrap()).unwrap();
rank += 1;
LeaderboardEntry {
rank,
team_id: db_leaderboard.team_id,
team_name: db_leaderboard.name,
score: db_leaderboard.points.round() as i64,
}
})
} else {
let mut rank = 0;

let leaderboard_entries = self
.connect()
.await?
.query(
"
SELECT id AS team_id, name, points
FROM rhombus_team
WHERE division_id = ?1
ORDER BY points DESC, last_solved_at ASC
",
params!(division_id),
)
.await?
.into_stream()
.map(|row| {
let db_leaderboard = de::from_row::<DbLeaderboard>(&row.unwrap()).unwrap();
rank += 1;
LeaderboardEntry {
rank,
team_id: db_leaderboard.team_id,
team_name: db_leaderboard.name,
score: db_leaderboard.points.round() as i64,
}
})
.collect::<Vec<_>>()
.await;
.collect::<Vec<_>>()
.await;

Ok(Leaderboard {
entries: leaderboard_entries,
num_pages: 1,
})
}
Ok(Arc::new(leaderboard_entries))
}

async fn get_top10_discord_ids(&self) -> Result<BTreeSet<NonZeroU64>> {
Expand Down
2 changes: 1 addition & 1 deletion rhombus/src/internal/database/postgres.rs
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,7 @@ impl Database for Postgres {
todo!()
}

async fn get_leaderboard(&self, _division_id: &str, _page: Option<u64>) -> Result<Leaderboard> {
async fn get_leaderboard(&self, _division_id: &str) -> Result<Leaderboard> {
todo!()
}

Expand Down
20 changes: 13 additions & 7 deletions rhombus/src/internal/database/provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,10 +132,20 @@ pub struct ScoreboardTeam {
}

#[derive(Debug, Serialize, Clone)]
pub struct Scoreboard {
pub struct ScoreboardInner {
pub teams: BTreeMap<i64, ScoreboardTeam>,
pub cached_json: String,
}

impl ScoreboardInner {
pub fn new(teams: BTreeMap<i64, ScoreboardTeam>) -> Self {
let cached_json = serde_json::to_string(&teams).unwrap();
Self { teams, cached_json }
}
}

pub type Scoreboard = Arc<ScoreboardInner>;

#[derive(Debug, Serialize, Clone)]
pub struct LeaderboardEntry {
pub team_id: i64,
Expand All @@ -144,11 +154,7 @@ pub struct LeaderboardEntry {
pub rank: u64,
}

#[derive(Debug, Serialize, Clone)]
pub struct Leaderboard {
pub num_pages: u64,
pub entries: Vec<LeaderboardEntry>,
}
pub type Leaderboard = Arc<Vec<LeaderboardEntry>>;

#[derive(Debug, Serialize, Clone)]
pub struct Email {
Expand Down Expand Up @@ -343,7 +349,7 @@ pub trait Database {
async fn save_settings(&self, settings: &Settings) -> Result<()>;
async fn load_settings(&self, settings: &mut Settings) -> Result<()>;
async fn get_scoreboard(&self, division_id: &str) -> Result<Scoreboard>;
async fn get_leaderboard(&self, division_id: &str, page: Option<u64>) -> Result<Leaderboard>;
async fn get_leaderboard(&self, division_id: &str) -> Result<Leaderboard>;
async fn get_top10_discord_ids(&self) -> Result<BTreeSet<NonZeroU64>>;
async fn get_emails_for_user_id(&self, user_id: i64) -> Result<Vec<Email>>;
async fn get_team_tracks(&self, team_id: i64) -> Result<BTreeMap<i64, UserTrack>>;
Expand Down
8 changes: 2 additions & 6 deletions rhombus/src/internal/open_graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -190,12 +190,8 @@ pub async fn route_default_og_image(state: State<RouterState>) -> impl IntoRespo
let mut division_meta = Vec::with_capacity(state.divisions.len());
for division in state.divisions.iter() {
let mut places = Vec::with_capacity(3);
let leaderboard = state
.db
.get_leaderboard(&division.id, Some(0))
.await
.unwrap();
leaderboard.entries.iter().take(3).for_each(|entry| {
let leaderboard = state.db.get_leaderboard(&division.id).await.unwrap();
leaderboard.iter().take(3).for_each(|entry| {
places.push(TeamMeta {
name: entry.team_name.clone(),
score: entry.score as u64,
Expand Down
22 changes: 17 additions & 5 deletions rhombus/src/internal/routes/scoreboard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,20 +60,32 @@ pub async fn route_scoreboard_division(
params: Query<PageParams>,
uri: Uri,
) -> impl IntoResponse {
let page_num = params.page.unwrap_or(1).saturating_sub(1);
let page_num = params.page.unwrap_or(1).saturating_sub(1) as usize;

let division_id = division_id.strip_suffix(".json").unwrap_or(&division_id);

let scoreboard = state.db.get_scoreboard(division_id);
let challenge_data = state.db.get_challenges();
let leaderboard = state.db.get_leaderboard(division_id, Some(page_num));
let leaderboard = state.db.get_leaderboard(division_id);
let (scoreboard, challenge_data, leaderboard) =
futures::future::try_join3(scoreboard, challenge_data, leaderboard)
.await
.unwrap();

const PAGE_SIZE: usize = 25;
let num_pages = (leaderboard.len() + (PAGE_SIZE - 1)) / PAGE_SIZE;
let page_num = page_num.min(num_pages);

let start = std::cmp::min(leaderboard.len(), page_num * PAGE_SIZE);
let end = std::cmp::min(leaderboard.len(), start + PAGE_SIZE);
let leaderboard = &leaderboard[start..end];

if uri.path().ends_with(".json") {
return Json(scoreboard.teams).into_response();
return (
[("Content-Type", "application/json")],
scoreboard.cached_json.clone(),
)
.into_response();
}

Html(
Expand All @@ -92,6 +104,7 @@ pub async fn route_scoreboard_division(
leaderboard,
selected_division_id => division_id,
page_num,
num_pages,
})
.unwrap(),
)
Expand All @@ -104,7 +117,7 @@ pub async fn route_scoreboard_division_ctftime(
Path(division_id): Path<String>,
) -> impl IntoResponse {
let challenge_data = state.db.get_challenges().await.unwrap();
let leaderboard = state.db.get_leaderboard(&division_id, None).await.unwrap();
let leaderboard = state.db.get_leaderboard(&division_id).await.unwrap();

let tasks = challenge_data
.challenges
Expand All @@ -113,7 +126,6 @@ pub async fn route_scoreboard_division_ctftime(
.collect::<Vec<_>>();

let standings = leaderboard
.entries
.iter()
.map(|team| {
json!({
Expand Down
8 changes: 4 additions & 4 deletions rhombus/templates/scoreboard.html
Original file line number Diff line number Diff line change
Expand Up @@ -93,10 +93,10 @@
{% endcall %}
{% endcall %}
{% call card.content() %}
{% if leaderboard.entries | length > 0 %}
{% if leaderboard | length > 0 %}
<div class="flex w-full">
<table class="grow table-fixed">
{% for entry in leaderboard.entries %}
{% for entry in leaderboard %}
<tr class="even:bg-secondary *:p-2">
<td>{{ entry.rank }}</td>
<td class="w-2/3">
Expand All @@ -114,10 +114,10 @@
{% endfor %}
</table>
</div>
{% if leaderboard.num_pages > 1 %}
{% if num_pages > 1 %}
<div class="flex justify-center">
<div>
{% for i in range(leaderboard.num_pages) %}
{% for i in range(num_pages) %}
{% if i == 0 and page_num != 0 %}
<a
hx-boost="true"
Expand Down

0 comments on commit 97b1118

Please sign in to comment.