This commit is contained in:
Jonas Rabenstein 2026-08-13 03:23:00 +02:00
commit 2ab16a68b7
4 changed files with 556 additions and 151 deletions

117
src/db.rs
View file

@ -8,9 +8,7 @@ use crate::model::DetectedCrossing;
pub fn hash_token(token: &str) -> Vec<u8> {
let mut hasher = Sha256::new();
hasher.update(token.as_bytes());
hasher.finalize().to_vec()
}
@ -19,10 +17,7 @@ pub async fn create_session(db: &PgPool, athlete_id: i64, token: &str) -> Result
sqlx::query(
r#"
INSERT INTO sessions (
token_hash,
athlete_id
)
INSERT INTO sessions (token_hash, athlete_id)
VALUES ($1, $2)
"#,
)
@ -39,12 +34,9 @@ pub async fn athlete_for_session(db: &PgPool, token: &str) -> Result<Option<(i64
let row = sqlx::query(
r#"
SELECT
a.id,
a.display_name
SELECT a.id, a.display_name
FROM sessions s
JOIN athletes a
ON a.id = s.athlete_id
JOIN athletes a ON a.id = s.athlete_id
WHERE s.token_hash = $1
"#,
)
@ -56,7 +48,6 @@ pub async fn athlete_for_session(db: &PgPool, token: &str) -> Result<Option<(i64
if let Some(row) = row {
let id: i64 = row.try_get("id")?;
let name: String = row.try_get("display_name")?;
sqlx::query(
@ -201,6 +192,78 @@ pub async fn ensure_dev_athlete(
Ok(row.try_get("id")?)
}
/// Create or update an athlete authenticated with a personal API key.
pub async fn upsert_api_key_athlete(
db: &PgPool,
intervals_athlete_id: &str,
display_name: &str,
api_key: &str,
) -> Result<i64> {
let row = sqlx::query(
r#"
INSERT INTO athletes (
intervals_athlete_id,
display_name,
access_token,
scopes
)
VALUES ($1, $2, $3, 'API_KEY')
ON CONFLICT (intervals_athlete_id)
DO UPDATE SET
display_name = EXCLUDED.display_name,
access_token = EXCLUDED.access_token,
scopes = EXCLUDED.scopes,
updated_at = now()
RETURNING id
"#,
)
.bind(intervals_athlete_id)
.bind(display_name)
.bind(api_key)
.fetch_one(db)
.await?;
use sqlx::Row;
Ok(row.try_get("id")?)
}
/// Create or update an athlete authenticated through OAuth.
pub async fn upsert_oauth_athlete(
db: &PgPool,
intervals_athlete_id: &str,
display_name: &str,
access_token: &str,
scopes: &str,
) -> Result<i64> {
let row = sqlx::query(
r#"
INSERT INTO athletes (
intervals_athlete_id,
display_name,
access_token,
scopes
)
VALUES ($1, $2, $3, $4)
ON CONFLICT (intervals_athlete_id)
DO UPDATE SET
display_name = EXCLUDED.display_name,
access_token = EXCLUDED.access_token,
scopes = EXCLUDED.scopes,
updated_at = now()
RETURNING id
"#,
)
.bind(intervals_athlete_id)
.bind(display_name)
.bind(access_token)
.bind(scopes)
.fetch_one(db)
.await?;
use sqlx::Row;
Ok(row.try_get("id")?)
}
pub async fn ensure_activity(
tx: &mut Transaction<'_, Postgres>,
athlete_id: i64,
@ -217,17 +280,8 @@ pub async fn ensure_activity(
activity_json,
processed_at
)
VALUES (
$1,
$2,
$3,
$4,
NULL
)
ON CONFLICT (
athlete_id,
intervals_activity_id
)
VALUES ($1, $2, $3, $4, NULL)
ON CONFLICT (athlete_id, intervals_activity_id)
DO UPDATE SET
start_time = EXCLUDED.start_time,
activity_json = EXCLUDED.activity_json,
@ -243,7 +297,6 @@ pub async fn ensure_activity(
.await?;
use sqlx::Row;
Ok(row.try_get("id")?)
}
@ -285,10 +338,7 @@ pub async fn save_crossing(
$1,
$2,
$3,
ST_SetSRID(
ST_MakePoint($4, $5),
4326
),
ST_SetSRID(ST_MakePoint($4, $5), 4326),
$6,
$7,
$8
@ -326,3 +376,14 @@ pub async fn mark_activity_processed(
Ok(())
}
pub async fn delete_session(db: &PgPool, token: &str) -> Result<()> {
let hash = hash_token(token);
sqlx::query("DELETE FROM sessions WHERE token_hash = $1")
.bind(&hash)
.execute(db)
.await?;
Ok(())
}