Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ redact = { version = "0.1.10", features = ["serde"] }
scopeguard = "1.2.0"
serde_json = "1.0.138"
serde = { version = "1.0.217", features = ["derive"] }
semver = { version = "1.0.26", features = ["serde"] }
sqlx = { version = "0.8.3", features = [
"sqlite",
"runtime-tokio-native-tls",
Expand Down
1 change: 1 addition & 0 deletions basalt-server-lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ redact.workspace = true
scopeguard.workspace = true
serde_json.workspace = true
serde.workspace = true
semver.workspace = true
sqlx.workspace = true
thiserror.workspace = true
time.workspace = true
Expand Down
113 changes: 111 additions & 2 deletions basalt-server-lib/src/services/competition.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,122 @@
use crate::server::AppState;
use axum::{
extract::State,
extract::{Query, State},
http::{header, StatusCode},
response::{AppendHeaders, IntoResponse},
Json,
};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use tokio::sync::OnceCell;
use tracing::{debug, error};
use utoipa::{IntoParams, ToSchema};
use utoipa_axum::{router::OpenApiRouter, routes};

static PDF: OnceCell<Box<[u8]>> = OnceCell::const_new();
static INFO: OnceCell<CompetitionInfo> = OnceCell::const_new();
static RAW_INFO: OnceCell<CompetitionInfo> = OnceCell::const_new();

#[derive(Serialize, ToSchema)]
pub struct CompetitionInfo {
title: String,
preamble: Option<String>,
problems: Vec<String>,
#[schema(value_type = String)]
version: semver::Version,
time_limit_secs: u64,
languages: Vec<String>,
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should add current system time


impl CompetitionInfo {
pub fn new_with_preamble(state: &AppState, preamble: Option<String>) -> Self {
Self {
title: state.config.packet.title.clone(),
preamble,
problems: state
.config
.packet
.problems
.iter()
.map(|p| p.title.clone())
.collect(),
version: semver::Version::parse(env!("CARGO_PKG_VERSION")).unwrap(),
time_limit_secs: match &state.config.game {
bedrock::Game::Points(points_settings) => points_settings.time_limit.as_secs(),
bedrock::Game::Race(race_settings) => {
race_settings.time_limit.map(|x| x.as_secs()).unwrap_or(0)
}
},
languages: state
.config
.languages
.iter()
.map(|l| l.name().to_string())
.collect(),
}
}

pub fn new_raw(state: &AppState) -> Self {
Self::new_with_preamble(
state,
state
.config
.packet
.preamble
.as_ref()
.map(|x| x.raw().to_string()),
)
}
pub fn new(state: &AppState) -> Result<Self, StatusCode> {
Ok(Self::new_with_preamble(
state,
state
.config
.packet
.preamble
.as_ref()
.map(|x| x.html())
.transpose()
.map_err(|err| {
error!("Error compiling preamble: {:?}", err);
StatusCode::INTERNAL_SERVER_ERROR
})?,
))
}
}

#[derive(Deserialize, IntoParams)]
pub struct InfoQuery {
#[serde(default)]
#[param(default = false)]
raw_markdown: bool,
}

#[axum::debug_handler]
#[utoipa::path(get, tag = "competition", path = "/", params(InfoQuery), responses((status = OK, body = CompetitionInfo, content_type = "application/json")))]
pub async fn get_info(
State(state): State<Arc<AppState>>,
Query(query): Query<InfoQuery>,
) -> Result<Json<&'static CompetitionInfo>, StatusCode> {
if query.raw_markdown {
let info = RAW_INFO
.get_or_init(|| async { CompetitionInfo::new_raw(&state) })
.await;

return Ok(Json(info));
}

// NOTE: we can't use get_or_init because we need this to give an error
let info = match INFO.get() {
Some(info) => info,
None => {
let info = CompetitionInfo::new(&state)?;
// if this fails, another thread set the cell, so it's fine
let _ = INFO.set(info);
INFO.get().unwrap()
}
};
Ok(Json(info))
}

#[axum::debug_handler]
#[utoipa::path(get, tag = "competition", path = "/packet", responses((status = OK, body = Vec<u8>, content_type = "application/pdf")))]
Expand Down Expand Up @@ -40,7 +147,9 @@ pub async fn download_packet(
}

pub fn router() -> OpenApiRouter<Arc<AppState>> {
OpenApiRouter::new().routes(routes!(download_packet))
OpenApiRouter::new()
.routes(routes!(download_packet))
.routes(routes!(get_info))
}

pub fn service() -> axum::Router<Arc<AppState>> {
Expand Down