dev api key
This commit is contained in:
parent
06c837b244
commit
89091a03d3
11 changed files with 791 additions and 762 deletions
122
src/webhook.rs
122
src/webhook.rs
|
|
@ -1,81 +1,71 @@
|
|||
use axum::{
|
||||
extract::State,
|
||||
http::StatusCode,
|
||||
Json,
|
||||
};
|
||||
use axum::{extract::State, http::StatusCode, response::IntoResponse};
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::{
|
||||
AppState,
|
||||
model::WebhookEnvelope,
|
||||
};
|
||||
use crate::AppState;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct WebhookPayload {
|
||||
pub secret: String,
|
||||
|
||||
#[serde(default)]
|
||||
pub athlete_id: Option<String>,
|
||||
|
||||
#[serde(default)]
|
||||
pub activity_id: Option<String>,
|
||||
}
|
||||
|
||||
pub async fn receive(
|
||||
State(state): State<AppState>,
|
||||
Json(payload): Json<WebhookEnvelope>,
|
||||
) -> Result<&'static str, StatusCode> {
|
||||
if payload.secret != state.config.intervals_webhook_secret {
|
||||
tracing::warn!("invalid Intervals.icu webhook secret");
|
||||
return Err(StatusCode::UNAUTHORIZED);
|
||||
axum::Json(payload): axum::Json<WebhookPayload>,
|
||||
) -> impl IntoResponse {
|
||||
let Some(expected_secret) = state.config.intervals_webhook_secret.as_deref() else {
|
||||
tracing::error!("Intervals.icu webhook secret is not configured");
|
||||
|
||||
return (
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
"webhook secret not configured",
|
||||
)
|
||||
.into_response();
|
||||
};
|
||||
|
||||
if payload.secret != expected_secret {
|
||||
tracing::warn!("received webhook with invalid secret");
|
||||
|
||||
return (StatusCode::UNAUTHORIZED, "invalid webhook secret").into_response();
|
||||
}
|
||||
|
||||
for event in payload.events {
|
||||
match event.event_type.as_str() {
|
||||
"ACTIVITY_UPLOADED" |
|
||||
"ACTIVITY_ANALYZED" => {
|
||||
let activity_id = event
|
||||
.activity
|
||||
.as_ref()
|
||||
.and_then(|activity| {
|
||||
activity.get("id")
|
||||
})
|
||||
.and_then(|id| {
|
||||
id.as_str()
|
||||
});
|
||||
let Some(activity_id) = payload.activity_id else {
|
||||
tracing::warn!("received webhook without activity_id");
|
||||
|
||||
let Some(activity_id) = activity_id else {
|
||||
tracing::warn!(
|
||||
athlete_id = %event.athlete_id,
|
||||
"activity webhook without activity id"
|
||||
);
|
||||
return (StatusCode::BAD_REQUEST, "missing activity_id").into_response();
|
||||
};
|
||||
|
||||
continue;
|
||||
};
|
||||
let athlete_id = payload
|
||||
.athlete_id
|
||||
.or_else(|| std::env::var("INTERVALS_ATHLETE_ID").ok());
|
||||
|
||||
if let Err(error) =
|
||||
crate::process_activity(
|
||||
state.clone(),
|
||||
event.athlete_id.clone(),
|
||||
activity_id.to_owned(),
|
||||
).await
|
||||
{
|
||||
tracing::error!(
|
||||
%error,
|
||||
athlete_id = %event.athlete_id,
|
||||
activity_id = %activity_id,
|
||||
"activity processing failed"
|
||||
);
|
||||
let Some(athlete_id) = athlete_id else {
|
||||
tracing::error!(
|
||||
"webhook has no athlete_id and \
|
||||
INTERVALS_ATHLETE_ID is not configured"
|
||||
);
|
||||
|
||||
// Return 500 so Intervals.icu can retry the webhook.
|
||||
return Err(StatusCode::INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
return (StatusCode::BAD_REQUEST, "missing athlete_id").into_response();
|
||||
};
|
||||
|
||||
"APP_SCOPE_CHANGED" => {
|
||||
tracing::info!(
|
||||
athlete_id = %event.athlete_id,
|
||||
"OAuth scopes changed"
|
||||
);
|
||||
}
|
||||
let state_clone = state.clone();
|
||||
|
||||
_ => {
|
||||
tracing::debug!(
|
||||
event_type = %event.event_type,
|
||||
"ignoring Intervals.icu webhook"
|
||||
);
|
||||
}
|
||||
tokio::spawn(async move {
|
||||
if let Err(error) =
|
||||
crate::process_activity(state_clone, athlete_id, activity_id.clone()).await
|
||||
{
|
||||
tracing::error!(
|
||||
%error,
|
||||
activity_id = %activity_id,
|
||||
"failed to process webhook activity"
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Intervals.icu has historically retried on 204; use an ordinary 200.
|
||||
Ok("ok")
|
||||
(StatusCode::ACCEPTED, "activity queued").into_response()
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue