(toggl-portal): Poll
This commit is contained in:
parent
d47174efee
commit
7e2848a0c1
38
src/main.rs
38
src/main.rs
@ -1,4 +1,5 @@
|
|||||||
use crate::client::TogglClient;
|
use crate::client::TogglClient;
|
||||||
|
use crate::entity::time_entry::{self, ActiveModel, Entity as TimeEntry};
|
||||||
use crate::types::{Current, ReportEntry, TogglQuery};
|
use crate::types::{Current, ReportEntry, TogglQuery};
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use axum::http::StatusCode;
|
use axum::http::StatusCode;
|
||||||
@ -9,19 +10,19 @@ use base64::engine::general_purpose::STANDARD;
|
|||||||
use base64::Engine;
|
use base64::Engine;
|
||||||
use beachhead::{shutdown_signal, Result};
|
use beachhead::{shutdown_signal, Result};
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
use migration::{Migrator, MigratorTrait};
|
||||||
|
use sea_orm::sea_query::OnConflict;
|
||||||
|
use sea_orm::{DatabaseConnection, EntityTrait};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
use sea_orm::{DatabaseConnection, EntityTrait};
|
|
||||||
use sea_orm::sea_query::OnConflict;
|
|
||||||
use tower_http::trace::TraceLayer;
|
use tower_http::trace::TraceLayer;
|
||||||
use migration::{Migrator, MigratorTrait};
|
|
||||||
use crate::entity::time_entry::{self, Entity as TimeEntry};
|
|
||||||
|
|
||||||
mod client;
|
mod client;
|
||||||
mod types;
|
|
||||||
mod db;
|
mod db;
|
||||||
mod entity;
|
mod entity;
|
||||||
|
mod poll;
|
||||||
|
mod types;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Parser)]
|
#[derive(Debug, Clone, Parser)]
|
||||||
struct Config {
|
struct Config {
|
||||||
@ -44,7 +45,16 @@ pub async fn report(
|
|||||||
Json(query): Json<TogglQuery>,
|
Json(query): Json<TogglQuery>,
|
||||||
) -> Result<Json<Vec<ReportEntry>>> {
|
) -> Result<Json<Vec<ReportEntry>>> {
|
||||||
let report = toggl_client.full_report(&query).await?;
|
let report = toggl_client.full_report(&query).await?;
|
||||||
let models = report.iter().flat_map(|entry| entry.as_models());
|
cache_report(&db, &report).await?;
|
||||||
|
|
||||||
|
Ok(Json(report))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn cache_report(
|
||||||
|
db: &DatabaseConnection,
|
||||||
|
models: &Vec<ReportEntry>,
|
||||||
|
) -> Result<()> {
|
||||||
|
let models = models.iter().flat_map(|entry| entry.as_models());
|
||||||
|
|
||||||
TimeEntry::insert_many(models)
|
TimeEntry::insert_many(models)
|
||||||
.on_conflict(
|
.on_conflict(
|
||||||
@ -55,10 +65,12 @@ pub async fn report(
|
|||||||
time_entry::Column::Start,
|
time_entry::Column::Start,
|
||||||
time_entry::Column::Stop,
|
time_entry::Column::Stop,
|
||||||
time_entry::Column::RawJson,
|
time_entry::Column::RawJson,
|
||||||
]).to_owned()
|
])
|
||||||
).exec(&db).await?;
|
.to_owned(),
|
||||||
|
)
|
||||||
Ok(Json(report))
|
.exec(db)
|
||||||
|
.await?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn current(
|
pub async fn current(
|
||||||
@ -99,9 +111,9 @@ async fn main() -> Result<()> {
|
|||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
Migrator::up(&db, None)
|
Migrator::up(&db, None).await.expect("Failed to migrate");
|
||||||
.await
|
|
||||||
.expect("Failed to migrate");
|
tokio::spawn(poll::poll_job(toggl_client.clone(), db.clone()));
|
||||||
|
|
||||||
// build our application with a route
|
// build our application with a route
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
|
|||||||
52
src/poll.rs
Normal file
52
src/poll.rs
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
use crate::client::TogglClient;
|
||||||
|
use crate::types::TogglQuery;
|
||||||
|
use sea_orm::DatabaseConnection;
|
||||||
|
use tracing::instrument;
|
||||||
|
|
||||||
|
#[tracing::instrument(skip(client, db))]
|
||||||
|
pub async fn poll_job(client: TogglClient, db: DatabaseConnection) {
|
||||||
|
// Every 2h, poll the Toggl API for new time entries for today to cache them in the database
|
||||||
|
let period = tokio::time::Duration::from_secs(60 * 60 * 2);
|
||||||
|
|
||||||
|
loop {
|
||||||
|
tracing::info!("Polling Toggl API");
|
||||||
|
match perform_poll(&client, &db).await {
|
||||||
|
Ok(report_entries_count) => {
|
||||||
|
tracing::info!(
|
||||||
|
"Successfully polled Toggl API: {:?} entries retrieved",
|
||||||
|
report_entries_count
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(error) => {
|
||||||
|
tracing::error!("Failed to poll Toggl API: {:?}", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tokio::time::sleep(period).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[instrument(skip(client, db))]
|
||||||
|
async fn perform_poll(client: &TogglClient, db: &DatabaseConnection) -> beachhead::Result<usize> {
|
||||||
|
let report = client
|
||||||
|
.full_report(&TogglQuery {
|
||||||
|
start_date: Some(
|
||||||
|
chrono::Utc::now()
|
||||||
|
.date_naive()
|
||||||
|
.format("%Y-%m-%d")
|
||||||
|
.to_string(),
|
||||||
|
),
|
||||||
|
end_date: Some(
|
||||||
|
chrono::Utc::now()
|
||||||
|
.date_naive()
|
||||||
|
.format("%Y-%m-%d")
|
||||||
|
.to_string(),
|
||||||
|
),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
crate::cache_report(&db, &report).await?;
|
||||||
|
Ok(report.len())
|
||||||
|
}
|
||||||
@ -58,7 +58,7 @@ pub struct Project {
|
|||||||
|
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
#[skip_serializing_none]
|
#[skip_serializing_none]
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
||||||
pub struct TogglQuery {
|
pub struct TogglQuery {
|
||||||
pub billable: Option<bool>,
|
pub billable: Option<bool>,
|
||||||
pub client_ids: Option<Vec<u64>>,
|
pub client_ids: Option<Vec<u64>>,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user