allow importing accessible foreign athletes
This commit is contained in:
parent
e52056ae60
commit
30542807b8
2 changed files with 99 additions and 10 deletions
35
src/db.rs
35
src/db.rs
|
|
@ -232,6 +232,41 @@ 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
|
||||
/// IntervalsClient for API requests.
|
||||
pub async fn ensure_local_athlete(
|
||||
db: &PgPool,
|
||||
intervals_athlete_id: &str,
|
||||
display_name: &str,
|
||||
) -> Result<i64> {
|
||||
let row = sqlx::query(
|
||||
r#"
|
||||
INSERT INTO athletes (
|
||||
intervals_athlete_id,
|
||||
display_name,
|
||||
access_token,
|
||||
scopes
|
||||
)
|
||||
VALUES ($1, $2, '', 'SESSION_ACCESS')
|
||||
ON CONFLICT (intervals_athlete_id)
|
||||
DO UPDATE SET
|
||||
display_name = EXCLUDED.display_name,
|
||||
updated_at = now()
|
||||
RETURNING id
|
||||
"#,
|
||||
)
|
||||
.bind(intervals_athlete_id)
|
||||
.bind(display_name)
|
||||
.fetch_one(db)
|
||||
.await?;
|
||||
|
||||
use sqlx::Row;
|
||||
Ok(row.try_get("id")?)
|
||||
}
|
||||
|
||||
pub async fn ensure_activity(
|
||||
tx: &mut Transaction<'_, Postgres>,
|
||||
athlete_id: i64,
|
||||
|
|
|
|||
76
src/main.rs
76
src/main.rs
|
|
@ -254,16 +254,20 @@ async fn sync_athlete(
|
|||
* tracked per local athlete row, so they're resolved against
|
||||
* athlete_id, not the session.
|
||||
*/
|
||||
let target_athlete = local_athlete(&state.db, &athlete_id)
|
||||
let client = IntervalsClient::with_api_key(state.config.clone(), session.access_token.clone());
|
||||
|
||||
let target_display_name = ensure_accessible_athlete(
|
||||
&state,
|
||||
&client,
|
||||
&athlete_id,
|
||||
)
|
||||
.await
|
||||
.map_err(http_error)?;
|
||||
|
||||
let target_athlete_id = local_athlete(&state.db, &athlete_id)
|
||||
.await
|
||||
.map_err(http_error)?;
|
||||
|
||||
let target_athlete_id = target_athlete.as_ref().map(|(id, _)| *id);
|
||||
|
||||
let target_display_name = target_athlete
|
||||
.as_ref()
|
||||
.map(|(_, name)| name.clone())
|
||||
.unwrap_or_else(|| athlete_id.clone());
|
||||
.map_err(http_error)?
|
||||
.map(|(id, _)| id);
|
||||
|
||||
/*
|
||||
* Default:
|
||||
|
|
@ -285,8 +289,6 @@ async fn sync_athlete(
|
|||
|
||||
let newest = parse_datetime_local(query.newest.as_deref()).unwrap_or_else(|| Utc::now());
|
||||
|
||||
let client = IntervalsClient::with_api_key(state.config.clone(), session.access_token.clone());
|
||||
|
||||
let activities = client
|
||||
.activities(
|
||||
&athlete_id,
|
||||
|
|
@ -714,6 +716,14 @@ 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,
|
||||
)
|
||||
.await
|
||||
.map_err(http_error)?;
|
||||
|
||||
match process_activity_with_client(
|
||||
state.clone(),
|
||||
athlete_id.clone(),
|
||||
|
|
@ -812,6 +822,14 @@ 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,
|
||||
)
|
||||
.await
|
||||
.map_err(http_error)?;
|
||||
|
||||
let target_athlete_id = local_athlete(&state.db, &athlete_id)
|
||||
.await
|
||||
.map_err(http_error)?
|
||||
|
|
@ -894,6 +912,42 @@ Aktivitäten werden importiert …
|
|||
)))
|
||||
}
|
||||
|
||||
/// Verify that the current authentication token can access an athlete and
|
||||
/// ensure that the athlete exists in the local database. This is required for
|
||||
/// importing activities belonging to athletes that have never authenticated
|
||||
/// with county-sprints themselves.
|
||||
async fn ensure_accessible_athlete(
|
||||
state: &AppState,
|
||||
client: &IntervalsClient,
|
||||
athlete_id: &str,
|
||||
) -> Result<String> {
|
||||
let athlete = client
|
||||
.athlete(athlete_id)
|
||||
.await
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"cannot access Intervals.icu athlete {} with current authentication token",
|
||||
athlete_id
|
||||
)
|
||||
})?;
|
||||
|
||||
let display_name = athlete
|
||||
.get("name")
|
||||
.or_else(|| athlete.get("display_name"))
|
||||
.and_then(Value::as_str)
|
||||
.unwrap_or(athlete_id)
|
||||
.to_string();
|
||||
|
||||
db::ensure_local_athlete(
|
||||
&state.db,
|
||||
athlete_id,
|
||||
&display_name,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(display_name)
|
||||
}
|
||||
|
||||
async fn activity_exists(db: &PgPool, athlete_id: i64, activity_id: &str) -> Result<bool> {
|
||||
let row = sqlx::query(
|
||||
r#"
|
||||
|
|
|
|||
Loading…
Reference in a new issue