Initial db sync work
This commit is contained in:
+136
-8
@@ -1,21 +1,149 @@
|
||||
use std::ops::Sub;
|
||||
use chrono::NaiveDate;
|
||||
use chrono::{NaiveDate, TimeDelta, Utc};
|
||||
use sqlx::{Connection, PgConnection};
|
||||
use toggl::TogglApi;
|
||||
|
||||
mod toggl;
|
||||
mod sensitive;
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
enum AppError {
|
||||
#[error("Database error: {0}")]
|
||||
SqlxError(#[from] sqlx::Error),
|
||||
|
||||
#[error("Toggl error: {0}")]
|
||||
TogglError(#[from] toggl::TogglError),
|
||||
}
|
||||
|
||||
struct Worker {
|
||||
db: PgConnection,
|
||||
toggl_api: TogglApi,
|
||||
}
|
||||
|
||||
struct TableSummary {
|
||||
client_ids: Vec<u64>,
|
||||
workspace_ids: Vec<u64>,
|
||||
project_ids: Vec<u64>,
|
||||
tag_ids: Vec<u64>,
|
||||
}
|
||||
|
||||
impl Worker {
|
||||
async fn get_ids(&mut self) -> Result<TableSummary, AppError> {
|
||||
let client_ids = sqlx::query!("select id from tracking_clients")
|
||||
.fetch_all(&mut self.db)
|
||||
.await?;
|
||||
|
||||
let workspace_ids = sqlx::query!("select id from workspaces")
|
||||
.fetch_all(&mut self.db)
|
||||
.await?;
|
||||
|
||||
let project_ids = sqlx::query!("select id from projects")
|
||||
.fetch_all(&mut self.db)
|
||||
.await?;
|
||||
|
||||
let tag_ids = sqlx::query!("select id from tags")
|
||||
.fetch_all(&mut self.db)
|
||||
.await?;
|
||||
|
||||
Ok(TableSummary {
|
||||
client_ids: client_ids.iter().map(|row| row.id as u64).collect(),
|
||||
workspace_ids: workspace_ids.iter().map(|row| row.id as u64).collect(),
|
||||
project_ids: project_ids.iter().map(|row| row.id as u64).collect(),
|
||||
tag_ids: tag_ids.iter().map(|row| row.id as u64).collect(),
|
||||
})
|
||||
}
|
||||
async fn update(&mut self, default_look_back: TimeDelta) -> Result<(), AppError> {
|
||||
let result = sqlx::query!("select max(updated_at) as last_updated_at from time_entries")
|
||||
.fetch_one(&mut self.db)
|
||||
.await
|
||||
.expect("Could not fetch max updated_at from time_entries");
|
||||
|
||||
let existing_ids = self.get_ids().await?;
|
||||
|
||||
let fetch_since = result.last_updated_at
|
||||
.unwrap_or_else(|| Utc::now() - default_look_back);
|
||||
|
||||
let time_entries = self.toggl_api
|
||||
.get_time_entries_for_user_modified_since(fetch_since).await?;
|
||||
|
||||
let refetch_projects = time_entries.iter()
|
||||
.map(|entry| entry.project_id)
|
||||
.filter_map(|project_id| project_id)
|
||||
.any(|project_id| !existing_ids.project_ids.contains(&project_id));
|
||||
|
||||
let refetch_tags = time_entries.iter()
|
||||
.flat_map(|entry| entry.tag_ids.iter())
|
||||
.any(|tag| !existing_ids.tag_ids.contains(&tag));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn update_projects(&mut self) -> Result<(), AppError> {
|
||||
let projects = self.toggl_api.get_projects().await?;
|
||||
|
||||
for project in projects {
|
||||
sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO projects (id, workspace_id, client_id, name, color, status, active, updated_at, start_date, created_at, server_deleted_at, actual_hours, actual_seconds, can_track_time, permissions)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15)
|
||||
ON CONFLICT (id) DO UPDATE SET
|
||||
workspace_id = excluded.workspace_id,
|
||||
client_id = excluded.client_id,
|
||||
name = excluded.name,
|
||||
color = excluded.color,
|
||||
status = excluded.status,
|
||||
active = excluded.active,
|
||||
updated_at = excluded.updated_at,
|
||||
start_date = excluded.start_date,
|
||||
created_at = excluded.created_at,
|
||||
server_deleted_at = excluded.server_deleted_at,
|
||||
actual_hours = excluded.actual_hours,
|
||||
actual_seconds = excluded.actual_seconds,
|
||||
can_track_time = excluded.can_track_time,
|
||||
permissions = excluded.permissions
|
||||
"#,
|
||||
project.id,
|
||||
project.workspace_id,
|
||||
project.client_id,
|
||||
project.name,
|
||||
project.color,
|
||||
project.status.to_string(),
|
||||
project.active,
|
||||
project.updated_at,
|
||||
project.start_date,
|
||||
project.created_at,
|
||||
project.server_deleted_at,
|
||||
project.actual_hours,
|
||||
project.actual_seconds,
|
||||
project.can_track_time,
|
||||
project.permissions,
|
||||
)
|
||||
.execute(&mut self.db)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
dotenv::dotenv()
|
||||
.expect("Failed to load .env file");
|
||||
|
||||
let api = TogglApi::new(
|
||||
sensitive::API_TOKEN,
|
||||
sensitive::WORKSPACE_ID,
|
||||
);
|
||||
|
||||
dbg!(api.search(toggl::types::TogglReportFilters {
|
||||
start_date: Some(NaiveDate::from_ymd_opt(2024, 07, 10).unwrap()),
|
||||
end_date: Some(NaiveDate::from_ymd_opt(2024, 07, 16).unwrap()),
|
||||
enrich_response: Some(true),
|
||||
..Default::default()
|
||||
}).await);
|
||||
let database_url = std::env::var("DATABASE_URL")
|
||||
.expect("DATABASE_URL must be set");
|
||||
|
||||
let mut worker = Worker {
|
||||
db: PgConnection::connect(&database_url).await.unwrap(),
|
||||
toggl_api: api,
|
||||
};
|
||||
|
||||
worker.update_projects()
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user