cargo fmt

This commit is contained in:
Jonas Rabenstein 2026-08-13 13:39:36 +02:00
commit e37a2d9263
6 changed files with 55 additions and 127 deletions

View file

@ -54,9 +54,7 @@ impl IntervalsRateLimiters {
.expect("Intervals IP limiter mutex poisoned");
let next = match *last {
Some(previous) => {
std::cmp::max(now, previous + IP_REQUEST_INTERVAL)
}
Some(previous) => std::cmp::max(now, previous + IP_REQUEST_INTERVAL),
None => now,
};
@ -183,15 +181,13 @@ impl Config {
cookie_secure,
intervals_rate_limiters:
IntervalsRateLimiters::default(),
intervals_rate_limiters: IntervalsRateLimiters::default(),
})
}
}
fn required(name: &str) -> Result<String> {
let value = env::var(name)
.with_context(|| format!("{name} is not configured"))?;
let value = env::var(name).with_context(|| format!("{name} is not configured"))?;
if value.trim().is_empty() {
anyhow::bail!("{name} is empty");
@ -201,9 +197,7 @@ fn required(name: &str) -> Result<String> {
}
fn optional(name: &str) -> Option<String> {
env::var(name)
.ok()
.filter(|value| !value.trim().is_empty())
env::var(name).ok().filter(|value| !value.trim().is_empty())
}
#[cfg(test)]
@ -221,9 +215,7 @@ mod tests {
limiters.wait_for_ip_slot().await;
}
assert!(
Instant::now().duration_since(start) >= Duration::from_millis(190)
);
assert!(Instant::now().duration_since(start) >= Duration::from_millis(190));
}
#[tokio::test]
@ -234,14 +226,10 @@ mod tests {
let start = Instant::now();
limiters.wait_for_caller("caller-a").await;
assert!(
Instant::now().duration_since(start) >= Duration::from_millis(70)
);
assert!(Instant::now().duration_since(start) >= Duration::from_millis(70));
let start = Instant::now();
limiters.wait_for_caller("caller-b").await;
assert!(
Instant::now().duration_since(start) < Duration::from_millis(40)
);
assert!(Instant::now().duration_since(start) < Duration::from_millis(40));
}
}

View file

@ -232,7 +232,6 @@ pub async fn upsert_oauth_athlete(
Ok(row.try_get("id")?)
}
/// Ensure that an athlete accessible through the current session exists
/// locally so activities can reference it. The session credential itself is
/// intentionally not stored here; callers continue to use their authenticated

View file

@ -20,10 +20,7 @@ pub struct IntervalsClient {
impl IntervalsClient {
pub fn new(config: Config) -> Self {
let authentication = config
.intervals_api_key
.clone()
.map(Authentication::ApiKey);
let authentication = config.intervals_api_key.clone().map(Authentication::ApiKey);
Self {
client: Client::new(),
@ -43,10 +40,7 @@ impl IntervalsClient {
/// Create a client using an OAuth access token.
/// Requests are authenticated with HTTP Bearer auth.
pub fn with_access_token(
config: Config,
access_token: impl Into<String>,
) -> Self {
pub fn with_access_token(config: Config, access_token: impl Into<String>) -> Self {
Self {
client: Client::new(),
config,
@ -70,9 +64,7 @@ impl IntervalsClient {
.intervals_client_id
.clone()
.unwrap_or_else(|| token.clone())),
None => Err(anyhow!(
"Intervals.icu authentication is not configured"
)),
None => Err(anyhow!("Intervals.icu authentication is not configured")),
}
}
@ -84,29 +76,17 @@ impl IntervalsClient {
)
}
fn authenticated(
&self,
request: reqwest::RequestBuilder,
) -> Result<reqwest::RequestBuilder> {
fn authenticated(&self, request: reqwest::RequestBuilder) -> Result<reqwest::RequestBuilder> {
match self.authentication.as_ref() {
Some(Authentication::ApiKey(token)) => {
Ok(request.basic_auth("API_KEY", Some(token)))
}
Some(Authentication::Bearer(token)) => {
Ok(request.bearer_auth(token))
}
None => Err(anyhow!(
"Intervals.icu authentication is not configured"
)),
Some(Authentication::ApiKey(token)) => Ok(request.basic_auth("API_KEY", Some(token))),
Some(Authentication::Bearer(token)) => Ok(request.bearer_auth(token)),
None => Err(anyhow!("Intervals.icu authentication is not configured")),
}
}
async fn get_json(&self, url: String) -> Result<Value> {
let caller = self.rate_limit_caller()?;
let limiters = self
.config
.intervals_rate_limiters
.clone();
let limiters = self.config.intervals_rate_limiters.clone();
// Intervals.icu enforces an additional limit of ten calls/sec per
// source IP. Pace every request start globally for this process.
@ -126,10 +106,8 @@ impl IntervalsClient {
.context("Intervals.icu request failed")?;
let status = response.status();
let retry_after =
parse_retry_after(response.headers().get("Retry-After"));
let rate_limit =
parse_rate_limit_headers(response.headers());
let retry_after = parse_retry_after(response.headers().get("Retry-After"));
let rate_limit = parse_rate_limit_headers(response.headers());
if status == reqwest::StatusCode::TOO_MANY_REQUESTS {
let body = response
@ -173,11 +151,7 @@ impl IntervalsClient {
.context("cannot read Intervals.icu response")?;
if !status.is_success() {
return Err(anyhow!(
"Intervals.icu returned HTTP {}: {}",
status,
body
));
return Err(anyhow!("Intervals.icu returned HTTP {}: {}", status, body));
}
tracing::debug!(
@ -190,12 +164,7 @@ impl IntervalsClient {
);
return serde_json::from_str(&body)
.with_context(|| {
format!(
"invalid JSON returned by Intervals.icu: {}",
body
)
});
.with_context(|| format!("invalid JSON returned by Intervals.icu: {}", body));
}
unreachable!("rate-limit retry loop must return")
@ -519,10 +488,7 @@ impl IntervalsClient {
}
}
fn parse_retry_after(
value: Option<&reqwest::header::HeaderValue>,
) -> Option<std::time::Duration> {
fn parse_retry_after(value: Option<&reqwest::header::HeaderValue>) -> Option<std::time::Duration> {
let value = value?.to_str().ok()?.trim();
let seconds = value.parse::<u64>().ok()?;
Some(std::time::Duration::from_secs(seconds))
@ -537,9 +503,7 @@ fn parse_retry_after(
fn parse_rate_limit_headers(
headers: &reqwest::header::HeaderMap,
) -> (Option<u64>, Option<u64>, Option<u64>, Option<u64>) {
fn pair(
value: Option<&reqwest::header::HeaderValue>,
) -> (Option<u64>, Option<u64>) {
fn pair(value: Option<&reqwest::header::HeaderValue>) -> (Option<u64>, Option<u64>) {
let Some(value) = value else {
return (None, None);
};
@ -556,17 +520,10 @@ fn parse_rate_limit_headers(
(first, second)
}
let (limit_15m, limit_daily) =
pair(headers.get("X-RateLimit-Limit"));
let (remaining_15m, remaining_daily) =
pair(headers.get("X-RateLimit-Remaining"));
let (limit_15m, limit_daily) = pair(headers.get("X-RateLimit-Limit"));
let (remaining_15m, remaining_daily) = pair(headers.get("X-RateLimit-Remaining"));
(
limit_15m,
limit_daily,
remaining_15m,
remaining_daily,
)
(limit_15m, limit_daily, remaining_15m, remaining_daily)
}
fn parse_datetime(value: &str) -> Result<DateTime<Utc>> {

View file

@ -36,7 +36,10 @@ pub async fn api(
build(&state.db, query.group_id, offset, limit)
.await
.map(|groups| {
let returned = groups.iter().map(|group| group.rows.len() as i64).sum::<i64>();
let returned = groups
.iter()
.map(|group| group.rows.len() as i64)
.sum::<i64>();
Json(LeaderboardPage {
groups,
next_offset: offset + returned,
@ -52,7 +55,12 @@ pub async fn api(
})
}
pub async fn build(db: &PgPool, group_id: Option<i64>, offset: i64, limit: i64) -> Result<Vec<LeaderboardGroup>> {
pub async fn build(
db: &PgPool,
group_id: Option<i64>,
offset: i64,
limit: i64,
) -> Result<Vec<LeaderboardGroup>> {
let rows = sqlx::query(
r#"
WITH bucketed AS (

View file

@ -165,12 +165,8 @@ async fn sync_index(
.await
.map_err(http_error)?;
let client =
IntervalsClient::with_api_key(state.config.clone(), session.access_token.clone());
let athletes = client
.athletes()
.await
.map_err(http_error)?;
let client = IntervalsClient::with_api_key(state.config.clone(), session.access_token.clone());
let athletes = client.athletes().await.map_err(http_error)?;
let athletes = athletes
.as_array()
.ok_or_else(|| http_error(anyhow!("athletes response is not an array")))?;
@ -310,11 +306,7 @@ async fn sync_athlete(
*/
let client = IntervalsClient::with_api_key(state.config.clone(), session.access_token.clone());
let target_display_name = ensure_accessible_athlete(
&state,
&client,
&athlete_id,
)
let target_display_name = ensure_accessible_athlete(&state, &client, &athlete_id)
.await
.map_err(http_error)?;
@ -770,11 +762,7 @@ async fn sync_import_activity(
let client = IntervalsClient::with_api_key(state.config.clone(), session.access_token.clone());
ensure_accessible_athlete(
&state,
&client,
&athlete_id,
)
ensure_accessible_athlete(&state, &client, &athlete_id)
.await
.map_err(http_error)?;
@ -876,11 +864,7 @@ async fn sync_import_visible(
let client = IntervalsClient::with_api_key(state.config.clone(), session.access_token.clone());
ensure_accessible_athlete(
&state,
&client,
&athlete_id,
)
ensure_accessible_athlete(&state, &client, &athlete_id)
.await
.map_err(http_error)?;
@ -975,10 +959,7 @@ async fn ensure_accessible_athlete(
client: &IntervalsClient,
athlete_id: &str,
) -> Result<String> {
let athlete = client
.athlete(athlete_id)
.await
.with_context(|| {
let athlete = client.athlete(athlete_id).await.with_context(|| {
format!(
"cannot access Intervals.icu athlete {} with current authentication token",
athlete_id
@ -992,12 +973,7 @@ async fn ensure_accessible_athlete(
.unwrap_or(athlete_id)
.to_string();
db::ensure_local_athlete(
&state.db,
athlete_id,
&display_name,
)
.await?;
db::ensure_local_athlete(&state.db, athlete_id, &display_name).await?;
Ok(display_name)
}

View file

@ -728,7 +728,7 @@ function initLeaderboardElements(root){
const leaderboardList=document.getElementById('leaderboard-list');
if(leaderboardList){
const status=document.getElementById('leaderboard-status'),sentinel=document.getElementById('leaderboard-sentinel'),groups=new Map();
let offset=0,loading=false,hasMore=true; const pageSize=50;
let offset=0,loading=false,hasMore=true; const pageSize=25;
async function loadLeaderboard(){
if(loading||!hasMore)return; loading=true;
try{