sync
This commit is contained in:
parent
3cfd5bdd6c
commit
859603399a
4 changed files with 1284 additions and 313 deletions
|
|
@ -5,7 +5,7 @@ edition = "2024"
|
|||
|
||||
[dependencies]
|
||||
anyhow = "1"
|
||||
axum = "0.8"
|
||||
axum = { version = "0.8", features = [ "macros" ] }
|
||||
axum-extra = { version = "0.10", features = ["cookie"] }
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
dotenvy = "0.15"
|
||||
|
|
|
|||
181
src/db.rs
181
src/db.rs
|
|
@ -8,16 +8,25 @@ 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()
|
||||
}
|
||||
|
||||
pub async fn create_session(db: &PgPool, athlete_id: i64, token: &str) -> Result<()> {
|
||||
pub async fn create_session(
|
||||
db: &PgPool,
|
||||
athlete_id: i64,
|
||||
token: &str,
|
||||
) -> Result<()> {
|
||||
let hash = hash_token(token);
|
||||
|
||||
sqlx::query(
|
||||
r#"
|
||||
INSERT INTO sessions (token_hash, athlete_id)
|
||||
INSERT INTO sessions (
|
||||
token_hash,
|
||||
athlete_id
|
||||
)
|
||||
VALUES ($1, $2)
|
||||
"#,
|
||||
)
|
||||
|
|
@ -29,14 +38,20 @@ pub async fn create_session(db: &PgPool, athlete_id: i64, token: &str) -> Result
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn athlete_for_session(db: &PgPool, token: &str) -> Result<Option<(i64, String)>> {
|
||||
pub async fn athlete_for_session(
|
||||
db: &PgPool,
|
||||
token: &str,
|
||||
) -> Result<Option<(i64, String)>> {
|
||||
let hash = hash_token(token);
|
||||
|
||||
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
|
||||
"#,
|
||||
)
|
||||
|
|
@ -47,8 +62,11 @@ pub async fn athlete_for_session(db: &PgPool, token: &str) -> Result<Option<(i64
|
|||
use sqlx::Row;
|
||||
|
||||
if let Some(row) = row {
|
||||
let id: i64 = row.try_get("id")?;
|
||||
let name: String = row.try_get("display_name")?;
|
||||
let id: i64 =
|
||||
row.try_get("id")?;
|
||||
|
||||
let name: String =
|
||||
row.try_get("display_name")?;
|
||||
|
||||
sqlx::query(
|
||||
r#"
|
||||
|
|
@ -67,7 +85,90 @@ pub async fn athlete_for_session(db: &PgPool, token: &str) -> Result<Option<(i64
|
|||
}
|
||||
}
|
||||
|
||||
pub async fn delete_old_oauth_states(db: &PgPool) -> Result<()> {
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SessionAthlete {
|
||||
pub id: i64,
|
||||
pub intervals_athlete_id: String,
|
||||
pub display_name: String,
|
||||
pub access_token: String,
|
||||
}
|
||||
|
||||
pub async fn session_athlete(
|
||||
db: &PgPool,
|
||||
token: &str,
|
||||
) -> Result<Option<SessionAthlete>> {
|
||||
let hash = hash_token(token);
|
||||
|
||||
let row = sqlx::query(
|
||||
r#"
|
||||
SELECT
|
||||
a.id,
|
||||
a.intervals_athlete_id,
|
||||
a.display_name,
|
||||
a.access_token
|
||||
FROM sessions s
|
||||
JOIN athletes a
|
||||
ON a.id = s.athlete_id
|
||||
WHERE s.token_hash = $1
|
||||
"#,
|
||||
)
|
||||
.bind(&hash)
|
||||
.fetch_optional(db)
|
||||
.await?;
|
||||
|
||||
use sqlx::Row;
|
||||
|
||||
let Some(row) = row else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
sqlx::query(
|
||||
r#"
|
||||
UPDATE sessions
|
||||
SET last_seen_at = now()
|
||||
WHERE token_hash = $1
|
||||
"#,
|
||||
)
|
||||
.bind(&hash)
|
||||
.execute(db)
|
||||
.await?;
|
||||
|
||||
Ok(Some(SessionAthlete {
|
||||
id: row.try_get("id")?,
|
||||
intervals_athlete_id: row.try_get("intervals_athlete_id")?,
|
||||
display_name: row.try_get("display_name")?,
|
||||
access_token: row.try_get("access_token")?,
|
||||
}))
|
||||
}
|
||||
|
||||
/// Return the start time of the most recently imported activity
|
||||
/// for an athlete.
|
||||
///
|
||||
/// This is used as the default `oldest` value on the sync page.
|
||||
pub async fn last_synced_activity_time(
|
||||
db: &PgPool,
|
||||
athlete_id: i64,
|
||||
) -> Result<Option<DateTime<Utc>>> {
|
||||
let row = sqlx::query(
|
||||
r#"
|
||||
SELECT MAX(start_time) AS last_synced
|
||||
FROM activities
|
||||
WHERE athlete_id = $1
|
||||
AND processed_at IS NOT NULL
|
||||
"#,
|
||||
)
|
||||
.bind(athlete_id)
|
||||
.fetch_one(db)
|
||||
.await?;
|
||||
|
||||
use sqlx::Row;
|
||||
|
||||
Ok(row.try_get("last_synced")?)
|
||||
}
|
||||
|
||||
pub async fn delete_old_oauth_states(
|
||||
db: &PgPool,
|
||||
) -> Result<()> {
|
||||
sqlx::query(
|
||||
r#"
|
||||
DELETE FROM oauth_states
|
||||
|
|
@ -80,6 +181,40 @@ pub async fn delete_old_oauth_states(db: &PgPool) -> Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn ensure_dev_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, 'dev')
|
||||
ON CONFLICT (intervals_athlete_id)
|
||||
DO UPDATE SET
|
||||
display_name = EXCLUDED.display_name,
|
||||
access_token = EXCLUDED.access_token,
|
||||
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")?)
|
||||
}
|
||||
|
||||
pub async fn ensure_activity(
|
||||
tx: &mut Transaction<'_, Postgres>,
|
||||
athlete_id: i64,
|
||||
|
|
@ -96,8 +231,17 @@ 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,
|
||||
|
|
@ -113,6 +257,7 @@ pub async fn ensure_activity(
|
|||
.await?;
|
||||
|
||||
use sqlx::Row;
|
||||
|
||||
Ok(row.try_get("id")?)
|
||||
}
|
||||
|
||||
|
|
@ -154,7 +299,10 @@ 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
|
||||
|
|
@ -192,14 +340,3 @@ 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(())
|
||||
}
|
||||
|
|
|
|||
1456
src/main.rs
1456
src/main.rs
File diff suppressed because it is too large
Load diff
|
|
@ -1,4 +1,4 @@
|
|||
use std::collections::{HashMap, HashSet};
|
||||
use std::collections::HashSet;
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue