Skip to content

Commit

Permalink
Add routes for boosted ride activity (#4)
Browse files Browse the repository at this point in the history
* Remove ls from dockerfile
Fix issue with spotify history repeat inserting

* spotify: Add recent listens route with limit param
github: Add github route for pinned repos
spotify: Stop from querying db every second, *clueless*

* Add boosted api hooks
Add boosted api endpoints
Clean up json responses
  • Loading branch information
dustinrouillard authored Oct 5, 2024
1 parent 1ba3080 commit 8b30c04
Show file tree
Hide file tree
Showing 13 changed files with 221 additions and 8 deletions.
16 changes: 14 additions & 2 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ pub struct Config {
#[envconfig(from = "ENV", default = "dev")]
pub env: String,

#[envconfig(from = "LISTEN_HOST", default = "0.0.0.0")]
#[envconfig(from = "HOST", default = "0.0.0.0")]
pub listen_host: String,

#[envconfig(from = "LISTEN_PORT", default = "8080")]
#[envconfig(from = "PORT", default = "8080")]
pub listen_port: u16,

#[envconfig(from = "METRICS_LISTEN_PORT", default = "8081")]
Expand Down Expand Up @@ -91,4 +91,16 @@ pub struct Config {

#[envconfig(from = "INFLUXDB_BUCKET", default = "api")]
pub influxdb_bucket: String,

#[envconfig(from = "BOOSTED_HOOK_SECRET", default = "")]
pub boosted_hook_token: String,

#[envconfig(
from = "BOOSTED_API_ENDPOINT",
default = "https://boosted-rides.dstn.to"
)]
pub boosted_api_endpoint: String,

#[envconfig(from = "BOOSTED_API_TOKEN", default = "")]
pub boosted_api_token: String,
}
4 changes: 4 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ async fn main() -> Result<(), Box<dyn Error>> {
));
}
});
} else {
tracing::debug!("Spotify runner skipped due to being in DEV Mode")
}

let api_server = HttpServer::new(move || {
Expand Down Expand Up @@ -113,6 +115,8 @@ async fn main() -> Result<(), Box<dyn Error>> {
.service(services::base::health)
.service(services::analytics::factory::analytics_factory())
.service(services::weather::factory::weather_factory())
.service(services::hooks::factory::hooks_factory())
.service(services::boosted::factory::boosted_factory())
.service(services::uploads::factory::uploads_factory())
.service(services::spotify::factory::spotify_factory())
.service(services::github::factory::github_factory())
Expand Down
3 changes: 0 additions & 3 deletions src/modules/spotify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,6 @@ use crate::{
ServerState,
};

extern crate chrono;
extern crate serde_json;

pub(crate) async fn fetch_spotify_current(data: web::Data<ServerState>) {
let valkey = &mut data.valkey.clone();
let rabbit = &mut data.rabbit.clone();
Expand Down
7 changes: 7 additions & 0 deletions src/services/boosted/factory.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
use actix_web::{web, Scope};

use crate::services;

pub fn boosted_factory() -> Scope {
web::scope("/boosted").service(services::boosted::routes::ride_stats)
}
3 changes: 3 additions & 0 deletions src/services/boosted/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pub mod factory;
pub mod routes;
pub mod structs;
39 changes: 39 additions & 0 deletions src/services/boosted/routes.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
use actix_web::{get, http::Error, web, HttpResponse};
use envconfig::Envconfig;
use redis::aio::ConnectionManager;
use serde_json::json;

use crate::{
config::Config, services::boosted::structs::BoostedStats, ServerState,
};

#[get("/stats")]
async fn ride_stats(
state: web::Data<ServerState>,
) -> Result<HttpResponse, Error> {
let config = Config::init_from_env().unwrap();
let valkey = &mut state.valkey.clone();

let in_ride = redis::cmd("GET")
.arg("boosted/in-ride")
.query_async::<ConnectionManager, String>(&mut valkey.cm)
.await
.unwrap_or(String::from("false"))
== "true";

let client = reqwest::Client::new();
let res = client
.get(format!("{}/v1/users/stats", config.boosted_api_endpoint))
.header("Authorization", config.boosted_api_token)
.send()
.await
.unwrap();

let json = res.json::<BoostedStats>().await.unwrap();

Ok(HttpResponse::Ok().json(json!({"boosted": {
"riding": in_ride,
"latest_ride": json.latest_ride,
"stats": json.stats
}})))
}
35 changes: 35 additions & 0 deletions src/services/boosted/structs.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize)]
pub struct BoostedStats {
pub latest_ride: RideStats,
pub stats: Stats,
}

#[derive(Serialize, Deserialize)]
pub struct RideStats {
pub started_at: String,
pub ended_at: String,
pub duration: f64,
pub distance: f64,
}

#[derive(Serialize, Deserialize)]
pub struct Stats {
pub boards: Boards,
pub rides: ValueEntry,
pub duration: ValueEntry,
pub distance: ValueEntry,
}

#[derive(Serialize, Deserialize)]
pub struct Boards {
pub distance: f64,
}

#[derive(Serialize, Deserialize)]
pub struct ValueEntry {
pub day: f64,
pub week: f64,
pub month: f64,
}
55 changes: 55 additions & 0 deletions src/services/hooks/boosted.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
use actix_web::{http::Error, post, web, HttpRequest, HttpResponse};
use envconfig::Envconfig as _;
use redis::aio::ConnectionManager;

use crate::{
config::Config,
services::hooks::structs::{BoostedHookPayload, BoostedHookType},
ServerState,
};

#[post("/boosted")]
async fn execute(
req: HttpRequest,
state: web::Data<ServerState>,
payload: web::Json<BoostedHookPayload>,
) -> Result<HttpResponse, Error> {
let config = Config::init_from_env().unwrap();

let auth_header = req.headers().get("authorization");
if auth_header.is_none() {
return Ok(HttpResponse::BadRequest().finish());
}

let auth_header = auth_header.unwrap().to_str().unwrap();

if auth_header != config.boosted_hook_token {
return Ok(HttpResponse::BadRequest().finish());
}

let valkey = &mut state.valkey.clone();

match payload.hook_type {
BoostedHookType::RideStarted => {
let _ = redis::cmd("SET")
.arg("boosted/in-ride")
.arg("true")
.query_async::<ConnectionManager, String>(&mut valkey.cm)
.await;
}
BoostedHookType::RideEnded => {
let _ = redis::cmd("DEL")
.arg("boosted/in-ride")
.query_async::<ConnectionManager, String>(&mut valkey.cm)
.await;
}
BoostedHookType::RideDiscarded => {
let _ = redis::cmd("DEL")
.arg("boosted/in-ride")
.query_async::<ConnectionManager, String>(&mut valkey.cm)
.await;
}
}

Ok(HttpResponse::NoContent().finish())
}
7 changes: 7 additions & 0 deletions src/services/hooks/factory.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
use actix_web::{web, Scope};

use crate::services;

pub fn hooks_factory() -> Scope {
web::scope("/hooks").service(services::hooks::boosted::execute)
}
3 changes: 3 additions & 0 deletions src/services/hooks/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pub mod boosted;
pub mod factory;
pub mod structs;
49 changes: 49 additions & 0 deletions src/services/hooks/structs.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
use chrono::{DateTime, FixedOffset};
use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize)]
pub enum BoostedHookType {
#[serde(rename = "ride_started")]
RideStarted,
#[serde(rename = "ride_ended")]
RideEnded,
#[serde(rename = "ride_discarded")]
RideDiscarded,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct RideSummary {
pub ride_id: i64,
pub distance: f64,
pub max_speed: f64,
pub avg_speed: f64,
pub elevation_gain: Option<f64>,
pub elevation_loss: Option<f64>,
pub ride_points: usize,
pub start_time: DateTime<FixedOffset>,
pub end_time: DateTime<FixedOffset>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct RideStartedHookBody {
pub ride_id: i64,
pub started_at: String,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct RideEndedHookBody {
pub ride_id: i64,
pub summary: RideSummary,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct RideDiscardedHookBody {
pub ride_id: i64,
pub code: String,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct BoostedHookPayload {
pub hook_type: BoostedHookType,
pub body: serde_json::Value,
}
2 changes: 2 additions & 0 deletions src/services/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
pub mod analytics;
pub mod base;
pub mod blog;
pub mod boosted;
pub mod github;
pub mod hooks;
pub mod spotify;
pub mod uploads;
pub mod weather;
6 changes: 3 additions & 3 deletions src/services/spotify/routes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ async fn authorize(
"message": "Spotify is already setup."
});

return Ok(HttpResponse::BadRequest().body(json.to_string()));
return Ok(HttpResponse::BadRequest().json(json));
}

let config = Config::init_from_env().unwrap();
Expand All @@ -101,7 +101,7 @@ async fn authorize(
let url = format!("https://accounts.spotify.com/authorize?client_id={}&response_type=code&scope={}&redirect_uri={}", config.spotify_client_id, scope, redirect_uri);
let json = json!({ "url": url });

Ok(HttpResponse::Ok().body(json.to_string()))
Ok(HttpResponse::Ok().json(json))
}

#[get("/setup")]
Expand All @@ -123,7 +123,7 @@ async fn setup(
"message": "Spotify is already setup."
});

return Ok(HttpResponse::BadRequest().body(json.to_string()));
return Ok(HttpResponse::BadRequest().json(json));
}

let config = Config::init_from_env().unwrap();
Expand Down

0 comments on commit 8b30c04

Please sign in to comment.