Add additional validation
Some checks failed
Build and Publish / Build and Test (push) Failing after 10m39s

This commit is contained in:
Joshua Coles 2025-02-01 19:45:43 +00:00
parent 5286c49bd3
commit 3f439c8a31
3 changed files with 107 additions and 15 deletions

View File

@ -1,5 +1,5 @@
use axum::response::IntoResponse;
use sqlx::{Connection, PgPool};
use sqlx::{PgPool};
use toggl::TogglApi;
use worker::Worker;
@ -97,7 +97,7 @@ async fn main() {
let cli = Cli::parse();
let toggl_api = TogglApi::new(&cli.api_token, cli.default_workspace_id);
let mut db = PgPool::connect(&cli.database_url).await.unwrap();
let db = PgPool::connect(&cli.database_url).await.unwrap();
sqlx::migrate!("./migrations")
.run(&db)

View File

@ -1,14 +1,73 @@
use axum::response::IntoResponse;
use axum::{
http::StatusCode,
routing::{get, post},
Extension, Json, Router,
};
use chrono::TimeDelta;
use std::net::IpAddr;
use crate::worker::Worker;
use crate::AppError;
use axum::response::IntoResponse;
use axum::{
routing::{get, post},
Extension, Router,
};
use chrono::TimeDelta;
use sqlx::postgres::PgListener;
use std::net::IpAddr;
#[derive(Debug, serde::Deserialize)]
struct Payload(Vec<PayloadItem>);
#[derive(Debug, serde::Deserialize)]
enum PayloadItem {
Num(i32),
Str(String),
}
impl PayloadItem {
fn to_i32(self) -> Option<i32> {
match self {
PayloadItem::Num(n) => Some(n),
PayloadItem::Str(s) => s.parse().ok(),
}
}
}
async fn listen_for_changes(pool: sqlx::PgPool, worker: Worker) {
let mut listener = PgListener::connect_with(&pool)
.await
.expect("Failed to connect to database");
// Enable LISTEN on the channel
listener
.listen("toggl_external_changes")
.await
.expect("Failed to LISTEN on channel");
while let Ok(notification) = listener.recv().await {
let payload = notification.payload();
if payload == "" {
worker
.update(TimeDelta::minutes(30))
.await
.expect("Failed to sync");
continue;
}
match serde_json::from_str::<Payload>(payload) {
Ok(payload) => {
let ids: Vec<i32> = payload
.0
.into_iter()
.filter_map(|v| v.to_i32())
.collect();
if !ids.is_empty() {
if let Err(e) = worker.fetch_time_entries_by_ids(&ids).await {
eprintln!("Error fetching time entries: {}", e);
}
}
}
Err(e) => eprintln!("Error parsing notification payload: {}", e),
}
}
}
async fn sync(Extension(worker): Extension<Worker>) -> Result<impl IntoResponse, AppError> {
worker.update(TimeDelta::days(30)).await?;
@ -17,6 +76,15 @@ async fn sync(Extension(worker): Extension<Worker>) -> Result<impl IntoResponse,
}
pub async fn serve(worker: Worker, ip: IpAddr, port: u16) -> Result<(), AppError> {
// Clone the pool and worker for the notification listener
let notification_pool = worker.db.clone();
let notification_worker = worker.clone();
// Spawn the notification listener
tokio::spawn(async move {
listen_for_changes(notification_pool, notification_worker).await;
});
// build our application with a route
let app = Router::new()
.route("/health", get(|| async { "Ok" }))

View File

@ -28,6 +28,30 @@ pub struct Worker {
}
impl Worker {
pub async fn fetch_time_entries_by_ids(&self, ids: &[i32]) -> Result<(), AppError> {
// Convert i32 IDs to u64 as that's what the Toggl API expects
let ids: Vec<u64> = ids.iter().map(|&id| id as u64).collect();
// Use the search API with time_entry_ids filter
let time_entries = self
.toggl_api
.search(
self.toggl_api.workspace_id,
TogglReportFilters {
time_entry_ids: Some(ids),
..Default::default()
},
)
.await?;
let time_entries = time_entries
.into_iter()
.map(|entry| entry.into_time_entry(self.toggl_api.workspace_id))
.collect::<Vec<_>>();
self.update_database(time_entries).await
}
async fn get_ids(&self) -> Result<TableSummary, AppError> {
let client_ids = sqlx::query!("select id from tracking_clients")
.fetch_all(&self.db)
@ -75,10 +99,6 @@ impl Worker {
}
pub async fn fetch_changed_since(&self, look_back: TimeDelta) -> Result<(), AppError> {
if look_back > TimeDelta::days(90) {
return Err(AppError::LookBackTooLarge);
}
self.update_time_entries(Utc::now() - look_back).await
}
@ -96,6 +116,10 @@ impl Worker {
}
async fn update_time_entries(&self, fetch_since: DateTime<Utc>) -> Result<(), AppError> {
if Utc::now() - fetch_since > TimeDelta::days(90) {
return Err(AppError::LookBackTooLarge);
}
let time_entries = self
.toggl_api
.get_time_entries_for_user_modified_since(fetch_since)