Skip to content

Commit

Permalink
use the new libsql crate and make remote connections actually work (#9)
Browse files Browse the repository at this point in the history
  • Loading branch information
cbackas authored Dec 31, 2023
1 parent 4f3308b commit b2ecbb3
Show file tree
Hide file tree
Showing 12 changed files with 408 additions and 599 deletions.
671 changes: 227 additions & 444 deletions Cargo.lock

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,16 @@ dotenv = "0.15.0"
futures-util = "0.3.28"
humantime = "2.1.0"
lazy_static = "1.4.0"
libsql-client = "0.33.2"
libsql = { git = "https://github.com/tursodatabase/libsql" }
reqwest = { version = "0.11.18", features = ["json"] }
serde = { version = "1.0.183", features = ["derive"] }
serde_json = "1.0.104"
sha2 = "0.10.7"
tokio = { version = "1.31.0", features = ["full"] }
tokio-tungstenite = { version = "0.20.0", features = ["native-tls"] }
tokio-tungstenite = { version = "0.20.1", features = ["native-tls"] }
tower = "0.4.13"
tower-http = { version = "0.4.3", features = ["fs"] }
tracing = "0.1.37"
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
tracing = "0.1.40"
tracing-subscriber = { version = "0.3.16", features = ["env-filter"] }
url = "2.4.0"
webhook = "2.1.2"
2 changes: 1 addition & 1 deletion fly.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ primary_region = "ord"

[env]
HOST = "troyonthetrails.com"
RUST_LOG = "troyonthetrails=info"
RUST_LOG = "troyonthetrails=info,tower_http=debug"

[checks]
[checks.health]
Expand Down
224 changes: 119 additions & 105 deletions src/db_service.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,22 @@
use std::{
env,
fmt::{Display, Formatter},
time::{Duration, SystemTime},
};

use anyhow::Context;
use lazy_static::lazy_static;
use libsql_client::{args, Statement, SyncClient};
use tokio::sync::Mutex;
use tracing::{debug, error};
use libsql::{params::IntoParams, Connection};
use tracing::{debug, error, trace};

use crate::strava_api_service::TokenData;
use crate::{strava_api_service::TokenData, DB_SERVICE};

#[derive(Debug)]
pub struct TroyStatus {
pub is_on_trail: bool,
pub trail_status_updated: Option<SystemTime>,
}

enum DBTable {
pub enum DBTable {
TroyStatus,
StravaAuth,
}
Expand All @@ -31,161 +30,176 @@ impl Display for DBTable {
}
}

lazy_static! {
pub static ref DB_SERVICE: Mutex<DbService> = Mutex::new(DbService::default());
}

pub struct DbService {
client: SyncClient,
database: Connection,
}

impl Default for DbService {
fn default() -> Self {
let service = Self::new();
service.init_tables();
service
Self::new()
}
}

impl DbService {
pub fn new() -> Self {
let client = SyncClient::from_env()
.context("Failed to connect to libsql db")
.unwrap();

DbService { client }
trace!("initializing new DbService");

let database = libsql::Database::open_remote(
env::var("LIBSQL_CLIENT_URL").unwrap(),
env::var("LIBSQL_CLIENT_TOKEN").unwrap(),
)
.unwrap()
.connect()
.context("Failed to create database")
.unwrap();

DbService { database }
}

pub fn init_tables(&self) {
if let Err(err) = self.client.execute("CREATE TABLE IF NOT EXISTS troy_status (id INTEGER PRIMARY KEY CHECK (id = 1), is_on_trail INTEGER, trail_status_updated INTEGER)") {
pub async fn init_tables(&self) {
if let Err(err) = self.database.execute("CREATE TABLE IF NOT EXISTS troy_status (id INTEGER PRIMARY KEY CHECK (id = 1), is_on_trail INTEGER, trail_status_updated INTEGER)", libsql::params!()).await {
error!("Failed to create table troy_status: {}", err);
}

if let Err(err) = self.client.execute("CREATE TABLE IF NOT EXISTS strava_auth (id INTEGER PRIMARY KEY CHECK (id = 1), access_token TEXT, refresh_token TEXT, expires_at INTEGER)") {
if let Err(err) = self.database.execute("CREATE TABLE IF NOT EXISTS strava_auth (id INTEGER PRIMARY KEY CHECK (id = 1), access_token TEXT, refresh_token TEXT, expires_at INTEGER)", libsql::params!()).await {
error!("Failed to create table strava_auth: {}", err);
}
}

fn upsert(&self, statement: Statement, table: DBTable) {
let result = self.client.execute(statement);
pub async fn execute(&self, statement: &str, params: impl IntoParams, table: DBTable) {
let result = self.database.execute(statement, params).await;

if result.is_err() {
error!("{}", result.unwrap_err());
return;
}

let result = result.unwrap();
if result.rows_affected != 1 {
if result != 1 {
error!(
"Failed to to db, expected 1 row affected but got {}",
result.rows_affected
result
);
return;
}

debug!("{} upserted to db", table);
}
}

pub fn get_troy_status(&self) -> TroyStatus {
let result = self.client.execute("SELECT * FROM troy_status");
pub async fn get_troy_status() -> TroyStatus {
let result = DB_SERVICE
.lock()
.await
.database
.query("SELECT * FROM troy_status", libsql::params!())
.await;

if result.is_err() {
error!("Failed to get troy status from db");
return TroyStatus {
is_on_trail: false,
trail_status_updated: None,
};
}

if result.is_err() {
error!("Failed to get troy status from db");
return TroyStatus {
is_on_trail: false,
trail_status_updated: None,
};
}
let result = match result.unwrap().next() {
Err(_) => None,
Ok(result) => result,
};

let result = result.unwrap();
if result.is_none() {
error!("Failed to get troy status from db, didn't find any rows",);
return TroyStatus {
is_on_trail: false,
trail_status_updated: None,
};
}

if result.rows.len() != 1 {
error!(
"Failed to get troy status from db, expected 1 row but got {}",
result.rows.len()
);
return TroyStatus {
is_on_trail: false,
trail_status_updated: None,
};
}
#[derive(Debug, serde::Deserialize)]
#[allow(dead_code)]
struct TroyStatusRow {
id: i64,
is_on_trail: u8,
trail_status_updated: u64,
}

let mut result = result.rows;
let result = result.pop().unwrap();

TroyStatus {
is_on_trail: result
.try_column("is_on_trail")
.context("Failed to parse is_on_trail to int")
.unwrap_or(0)
== 1,
trail_status_updated: Some(
SystemTime::UNIX_EPOCH
+ Duration::from_secs(result.try_column("trail_status_updated").unwrap_or(0)),
),
}
let result = result.unwrap();

let thing = libsql::de::from_row::<TroyStatusRow>(&result).unwrap();

TroyStatus {
is_on_trail: thing.is_on_trail == 1,
trail_status_updated: Some(
SystemTime::UNIX_EPOCH + Duration::from_secs(thing.trail_status_updated),
),
}
}

pub fn set_troy_status(&self, is_on_trail: bool) {
let is_on_trail = match is_on_trail {
true => 1,
false => 0,
};
pub async fn set_troy_status(is_on_trail: bool) {
let is_on_trail = match is_on_trail {
true => 1,
false => 0,
};

// get current unix milis timestamp
let current_timestamp: i64 = match SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)
{
Ok(duration) => duration.as_secs() as i64,
Err(_) => 0,
};
// get current unix milis timestamp
let current_timestamp: i64 = match SystemTime::now().duration_since(SystemTime::UNIX_EPOCH) {
Ok(duration) => duration.as_secs() as i64,
Err(_) => 0,
};

self
.upsert(Statement::with_args(
DB_SERVICE.lock().await
.execute(
"INSERT INTO troy_status (id, is_on_trail, trail_status_updated) \
VALUES (1, ?, ?) \
ON CONFLICT (id) \
DO UPDATE SET is_on_trail = excluded.is_on_trail, trail_status_updated = excluded.trail_status_updated",
args!(is_on_trail, current_timestamp),
), DBTable::TroyStatus);
libsql::params!(is_on_trail, current_timestamp),
DBTable::TroyStatus).await;
}

pub async fn get_strava_auth() -> Option<TokenData> {
let result = DB_SERVICE
.lock()
.await
.database
.query("SELECT * FROM strava_auth", libsql::params!())
.await;

if result.is_err() {
error!("Failed to get strava auth from db");
return None;
}

pub fn get_strava_auth(&self) -> Option<TokenData> {
let result = self.client.execute("SELECT * FROM strava_auth");
if result.is_err() {
error!("Failed to get strava auth from db");
return None;
}
let result = match result.unwrap().next() {
Err(_) => None,
Ok(result) => result,
};

let result = result.unwrap();
if result.rows.len() != 1 {
error!(
"Failed to get strava auth from db, expected 1 row but got {}",
result.rows.len()
);
return None;
}
if result.is_none() {
error!("Failed to get strava auth from db, expected 1 row but found none");
return None;
}

let mut result = result.rows;
let result = result.pop().unwrap();
let result = result.unwrap();

Some(TokenData {
access_token: result.try_column("access_token").unwrap_or("").to_string(),
refresh_token: result.try_column("refresh_token").unwrap_or("").to_string(),
expires_at: result.try_column("expires_at").unwrap_or(0),
})
}
Some(TokenData {
access_token: result.get(1).unwrap_or("".to_string()),
refresh_token: result.get(2).unwrap_or("".to_string()),
expires_at: result.get(3).unwrap_or(0),
})
}

pub fn set_strava_auth(&self, token_data: TokenData) {
let access_token = token_data.access_token;
let refresh_token = token_data.refresh_token;
let expires_at = token_data.expires_at as i64;
pub async fn set_strava_auth(token_data: TokenData) {
let access_token = token_data.access_token;
let refresh_token = token_data.refresh_token;
let expires_at = token_data.expires_at as i64;

self.upsert(Statement::with_args(
DB_SERVICE.lock().await.execute(
"INSERT INTO strava_auth (id, access_token, refresh_token, expires_at) \
VALUES (1, ?, ?, ?) \
ON CONFLICT (id) \
DO UPDATE SET access_token = excluded.access_token, refresh_token = excluded.refresh_token, expires_at = excluded.expires_at",
args!(access_token, refresh_token, expires_at),
), DBTable::StravaAuth);
}
libsql::params!(access_token, refresh_token, expires_at),
DBTable::StravaAuth).await;
}
21 changes: 18 additions & 3 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,31 @@ use axum::{
routing::{get, post},
Router,
};
use lazy_static::lazy_static;
use strava_api_service::StravaAPIService;
use tokio::{sync::Mutex, time::Instant};
use tower_http::services::{ServeDir, ServeFile};
use tower_http::{
services::{ServeDir, ServeFile},
trace::TraceLayer,
};
use tracing::{debug, info};
use tracing_subscriber::{
filter::LevelFilter, layer::SubscriberExt, util::SubscriberInitExt, EnvFilter,
};

use crate::strava_api_service::API_SERVICE;
use crate::db_service::DbService;

mod db_service;
mod env_utils;
mod route_handlers;
mod strava_api_service;
mod utils;

lazy_static! {
pub static ref DB_SERVICE: Mutex<DbService> = Mutex::new(DbService::new());
pub static ref API_SERVICE: Mutex<StravaAPIService> = Mutex::new(StravaAPIService::new());
}

#[derive(Default)]
pub struct AppState {
// trail data
Expand All @@ -44,6 +54,10 @@ async fn main() -> anyhow::Result<()> {

debug!("initializing app state ...");

{
let db_service = DB_SERVICE.lock().await;
db_service.init_tables().await;
}
{
let mut strava_api_service = API_SERVICE.lock().await;
strava_api_service.read_strava_auth_from_db().await;
Expand All @@ -59,12 +73,13 @@ async fn main() -> anyhow::Result<()> {
.serve(
get_main_router()
.with_state(SharedAppState::default())
.layer(TraceLayer::new_for_http())
.into_make_service(),
)
.await
.context("error while starting API server")?;

debug!("Server srarted");
info!("Server srarted");

anyhow::Ok(())
}
Expand Down
Loading

0 comments on commit b2ecbb3

Please sign in to comment.