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]
|
[dependencies]
|
||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
axum = "0.8"
|
axum = { version = "0.8", features = [ "macros" ] }
|
||||||
axum-extra = { version = "0.10", features = ["cookie"] }
|
axum-extra = { version = "0.10", features = ["cookie"] }
|
||||||
chrono = { version = "0.4", features = ["serde"] }
|
chrono = { version = "0.4", features = ["serde"] }
|
||||||
dotenvy = "0.15"
|
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> {
|
pub fn hash_token(token: &str) -> Vec<u8> {
|
||||||
let mut hasher = Sha256::new();
|
let mut hasher = Sha256::new();
|
||||||
|
|
||||||
hasher.update(token.as_bytes());
|
hasher.update(token.as_bytes());
|
||||||
|
|
||||||
hasher.finalize().to_vec()
|
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);
|
let hash = hash_token(token);
|
||||||
|
|
||||||
sqlx::query(
|
sqlx::query(
|
||||||
r#"
|
r#"
|
||||||
INSERT INTO sessions (token_hash, athlete_id)
|
INSERT INTO sessions (
|
||||||
|
token_hash,
|
||||||
|
athlete_id
|
||||||
|
)
|
||||||
VALUES ($1, $2)
|
VALUES ($1, $2)
|
||||||
"#,
|
"#,
|
||||||
)
|
)
|
||||||
|
|
@ -29,14 +38,20 @@ pub async fn create_session(db: &PgPool, athlete_id: i64, token: &str) -> Result
|
||||||
Ok(())
|
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 hash = hash_token(token);
|
||||||
|
|
||||||
let row = sqlx::query(
|
let row = sqlx::query(
|
||||||
r#"
|
r#"
|
||||||
SELECT a.id, a.display_name
|
SELECT
|
||||||
|
a.id,
|
||||||
|
a.display_name
|
||||||
FROM sessions s
|
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
|
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;
|
use sqlx::Row;
|
||||||
|
|
||||||
if let Some(row) = row {
|
if let Some(row) = row {
|
||||||
let id: i64 = row.try_get("id")?;
|
let id: i64 =
|
||||||
let name: String = row.try_get("display_name")?;
|
row.try_get("id")?;
|
||||||
|
|
||||||
|
let name: String =
|
||||||
|
row.try_get("display_name")?;
|
||||||
|
|
||||||
sqlx::query(
|
sqlx::query(
|
||||||
r#"
|
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(
|
sqlx::query(
|
||||||
r#"
|
r#"
|
||||||
DELETE FROM oauth_states
|
DELETE FROM oauth_states
|
||||||
|
|
@ -80,6 +181,40 @@ pub async fn delete_old_oauth_states(db: &PgPool) -> Result<()> {
|
||||||
Ok(())
|
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(
|
pub async fn ensure_activity(
|
||||||
tx: &mut Transaction<'_, Postgres>,
|
tx: &mut Transaction<'_, Postgres>,
|
||||||
athlete_id: i64,
|
athlete_id: i64,
|
||||||
|
|
@ -96,8 +231,17 @@ pub async fn ensure_activity(
|
||||||
activity_json,
|
activity_json,
|
||||||
processed_at
|
processed_at
|
||||||
)
|
)
|
||||||
VALUES ($1, $2, $3, $4, NULL)
|
VALUES (
|
||||||
ON CONFLICT (athlete_id, intervals_activity_id)
|
$1,
|
||||||
|
$2,
|
||||||
|
$3,
|
||||||
|
$4,
|
||||||
|
NULL
|
||||||
|
)
|
||||||
|
ON CONFLICT (
|
||||||
|
athlete_id,
|
||||||
|
intervals_activity_id
|
||||||
|
)
|
||||||
DO UPDATE SET
|
DO UPDATE SET
|
||||||
start_time = EXCLUDED.start_time,
|
start_time = EXCLUDED.start_time,
|
||||||
activity_json = EXCLUDED.activity_json,
|
activity_json = EXCLUDED.activity_json,
|
||||||
|
|
@ -113,6 +257,7 @@ pub async fn ensure_activity(
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
use sqlx::Row;
|
use sqlx::Row;
|
||||||
|
|
||||||
Ok(row.try_get("id")?)
|
Ok(row.try_get("id")?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -154,7 +299,10 @@ pub async fn save_crossing(
|
||||||
$1,
|
$1,
|
||||||
$2,
|
$2,
|
||||||
$3,
|
$3,
|
||||||
ST_SetSRID(ST_MakePoint($4, $5), 4326),
|
ST_SetSRID(
|
||||||
|
ST_MakePoint($4, $5),
|
||||||
|
4326
|
||||||
|
),
|
||||||
$6,
|
$6,
|
||||||
$7,
|
$7,
|
||||||
$8
|
$8
|
||||||
|
|
@ -192,14 +340,3 @@ pub async fn mark_activity_processed(
|
||||||
|
|
||||||
Ok(())
|
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(())
|
|
||||||
}
|
|
||||||
|
|
|
||||||
1426
src/main.rs
1426
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};
|
use chrono::{DateTime, Utc};
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue