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")?)
|
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(
|
pub async fn ensure_activity(
|
||||||
tx: &mut Transaction<'_, Postgres>,
|
tx: &mut Transaction<'_, Postgres>,
|
||||||
athlete_id: i64,
|
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
|
* tracked per local athlete row, so they're resolved against
|
||||||
* athlete_id, not the session.
|
* 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
|
.await
|
||||||
.map_err(http_error)?;
|
.map_err(http_error)?
|
||||||
|
.map(|(id, _)| id);
|
||||||
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());
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Default:
|
* Default:
|
||||||
|
|
@ -285,8 +289,6 @@ async fn sync_athlete(
|
||||||
|
|
||||||
let newest = parse_datetime_local(query.newest.as_deref()).unwrap_or_else(|| Utc::now());
|
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
|
let activities = client
|
||||||
.activities(
|
.activities(
|
||||||
&athlete_id,
|
&athlete_id,
|
||||||
|
|
@ -714,6 +716,14 @@ async fn sync_import_activity(
|
||||||
|
|
||||||
let client = IntervalsClient::with_api_key(state.config.clone(), session.access_token.clone());
|
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(
|
match process_activity_with_client(
|
||||||
state.clone(),
|
state.clone(),
|
||||||
athlete_id.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());
|
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)
|
let target_athlete_id = local_athlete(&state.db, &athlete_id)
|
||||||
.await
|
.await
|
||||||
.map_err(http_error)?
|
.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> {
|
async fn activity_exists(db: &PgPool, athlete_id: i64, activity_id: &str) -> Result<bool> {
|
||||||
let row = sqlx::query(
|
let row = sqlx::query(
|
||||||
r#"
|
r#"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue