(toggl-portal): Fetch projects and clients when there is an update.
This commit is contained in:
parent
3eb3316c31
commit
8e2356ac85
@ -29,13 +29,11 @@ impl MigrationTrait for Migration {
|
|||||||
#[derive(DeriveIden)]
|
#[derive(DeriveIden)]
|
||||||
enum TimeEntry {
|
enum TimeEntry {
|
||||||
Table,
|
Table,
|
||||||
Id,
|
|
||||||
ProjectId,
|
ProjectId,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(DeriveIden)]
|
#[derive(DeriveIden)]
|
||||||
enum Project {
|
enum Project {
|
||||||
Table,
|
Table,
|
||||||
Id,
|
|
||||||
TogglId,
|
TogglId,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,7 +2,6 @@ use reqwest::Client;
|
|||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use axum::http::StatusCode;
|
|
||||||
use hyper::HeaderMap;
|
use hyper::HeaderMap;
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
use tracing::log::debug;
|
use tracing::log::debug;
|
||||||
|
|||||||
62
src/db.rs
62
src/db.rs
@ -1,18 +1,34 @@
|
|||||||
use sea_orm::{NotSet, Set};
|
use crate::entity::{client, project, time_entry};
|
||||||
use crate::entity::{time_entry, client, project};
|
|
||||||
use crate::types::{Project, ProjectClient, ReportEntry};
|
use crate::types::{Project, ProjectClient, ReportEntry};
|
||||||
|
use sea_orm::sea_query::OnConflict;
|
||||||
|
use sea_orm::{NotSet, Set};
|
||||||
|
|
||||||
impl ReportEntry {
|
impl ReportEntry {
|
||||||
pub(crate) fn as_models(&self) -> Vec<time_entry::ActiveModel> {
|
pub(crate) fn as_models(&self) -> Vec<time_entry::ActiveModel> {
|
||||||
self.time_entries.iter().map(|inner| time_entry::ActiveModel {
|
self.time_entries
|
||||||
id: NotSet,
|
.iter()
|
||||||
toggl_id: Set(inner.id as i64),
|
.map(|inner| time_entry::ActiveModel {
|
||||||
description: Set(self.description.clone()),
|
id: NotSet,
|
||||||
project_id: Set(self.project_id.map(|id| id as i64)),
|
toggl_id: Set(inner.id as i64),
|
||||||
start: Set(chrono::DateTime::parse_from_rfc3339(&inner.start).unwrap()),
|
description: Set(self.description.clone()),
|
||||||
stop: Set(chrono::DateTime::parse_from_rfc3339(&inner.start).unwrap()),
|
project_id: Set(self.project_id.map(|id| id as i64)),
|
||||||
raw_json: Set(serde_json::to_value(inner).unwrap()),
|
start: Set(chrono::DateTime::parse_from_rfc3339(&inner.start).unwrap()),
|
||||||
}).collect()
|
stop: Set(chrono::DateTime::parse_from_rfc3339(&inner.start).unwrap()),
|
||||||
|
raw_json: Set(serde_json::to_value(inner).unwrap()),
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn grafting_conflict_statement() -> OnConflict {
|
||||||
|
OnConflict::column(time_entry::Column::TogglId)
|
||||||
|
.update_columns(vec![
|
||||||
|
time_entry::Column::Description,
|
||||||
|
time_entry::Column::ProjectId,
|
||||||
|
time_entry::Column::Start,
|
||||||
|
time_entry::Column::Stop,
|
||||||
|
time_entry::Column::RawJson,
|
||||||
|
])
|
||||||
|
.to_owned()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -27,6 +43,18 @@ impl ProjectClient {
|
|||||||
server_deleted_at: Set(self.server_deleted_at.clone().map(|dt| dt.fixed_offset())),
|
server_deleted_at: Set(self.server_deleted_at.clone().map(|dt| dt.fixed_offset())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn grafting_conflict_statement() -> OnConflict {
|
||||||
|
OnConflict::column(client::Column::Id)
|
||||||
|
.update_columns(vec![
|
||||||
|
client::Column::Name,
|
||||||
|
client::Column::Archived,
|
||||||
|
client::Column::WorkspaceId,
|
||||||
|
client::Column::At,
|
||||||
|
client::Column::ServerDeletedAt,
|
||||||
|
])
|
||||||
|
.to_owned()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Project {
|
impl Project {
|
||||||
@ -41,4 +69,16 @@ impl Project {
|
|||||||
raw_json: Set(serde_json::to_value(self).unwrap()),
|
raw_json: Set(serde_json::to_value(self).unwrap()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn grafting_conflict_statement() -> OnConflict {
|
||||||
|
OnConflict::column(project::Column::TogglId)
|
||||||
|
.update_columns(vec![
|
||||||
|
project::Column::Name,
|
||||||
|
project::Column::Active,
|
||||||
|
project::Column::ClientId,
|
||||||
|
project::Column::WorkspaceId,
|
||||||
|
project::Column::RawJson,
|
||||||
|
])
|
||||||
|
.to_owned()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
51
src/main.rs
51
src/main.rs
@ -1,5 +1,5 @@
|
|||||||
use crate::client::TogglClient;
|
use crate::client::TogglClient;
|
||||||
use crate::entity::time_entry::{self, Entity as TimeEntry};
|
use crate::entity::prelude::TimeEntry;
|
||||||
use crate::types::{Current, Project, ProjectClient, ReportEntry, TogglQuery};
|
use crate::types::{Current, Project, ProjectClient, ReportEntry, TogglQuery};
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use axum::http::StatusCode;
|
use axum::http::StatusCode;
|
||||||
@ -11,7 +11,6 @@ use base64::Engine;
|
|||||||
use beachhead::{shutdown_signal, Result};
|
use beachhead::{shutdown_signal, Result};
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use migration::{Migrator, MigratorTrait};
|
use migration::{Migrator, MigratorTrait};
|
||||||
use sea_orm::sea_query::OnConflict;
|
|
||||||
use sea_orm::{DatabaseConnection, EntityTrait};
|
use sea_orm::{DatabaseConnection, EntityTrait};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
@ -54,17 +53,7 @@ async fn cache_report(db: &DatabaseConnection, models: &Vec<ReportEntry>) -> Res
|
|||||||
let models = models.iter().flat_map(|entry| entry.as_models());
|
let models = models.iter().flat_map(|entry| entry.as_models());
|
||||||
|
|
||||||
TimeEntry::insert_many(models)
|
TimeEntry::insert_many(models)
|
||||||
.on_conflict(
|
.on_conflict(ReportEntry::grafting_conflict_statement())
|
||||||
OnConflict::column(time_entry::Column::TogglId)
|
|
||||||
.update_columns(vec![
|
|
||||||
time_entry::Column::Description,
|
|
||||||
time_entry::Column::ProjectId,
|
|
||||||
time_entry::Column::Start,
|
|
||||||
time_entry::Column::Stop,
|
|
||||||
time_entry::Column::RawJson,
|
|
||||||
])
|
|
||||||
.to_owned(),
|
|
||||||
)
|
|
||||||
.exec(db)
|
.exec(db)
|
||||||
.await?;
|
.await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -87,45 +76,27 @@ pub async fn start_time_entry(
|
|||||||
|
|
||||||
async fn projects(
|
async fn projects(
|
||||||
Extension(db): Extension<DatabaseConnection>,
|
Extension(db): Extension<DatabaseConnection>,
|
||||||
Extension(toggl_client): Extension<TogglClient>
|
Extension(toggl_client): Extension<TogglClient>,
|
||||||
) -> Result<Json<Vec<Project>>> {
|
) -> Result<Json<Vec<Project>>> {
|
||||||
let projects = toggl_client.fetch_projects().await?;
|
let projects = toggl_client.fetch_projects().await?;
|
||||||
|
|
||||||
entity::project::Entity::insert_many(projects.iter().map(Project::as_model))
|
entity::project::Entity::insert_many(projects.iter().map(Project::as_model))
|
||||||
.on_conflict(
|
.on_conflict(Project::grafting_conflict_statement())
|
||||||
OnConflict::column(entity::project::Column::TogglId)
|
.exec(&db)
|
||||||
.update_columns(vec![
|
.await?;
|
||||||
entity::project::Column::Name,
|
|
||||||
entity::project::Column::Active,
|
|
||||||
entity::project::Column::ClientId,
|
|
||||||
entity::project::Column::WorkspaceId,
|
|
||||||
entity::project::Column::RawJson,
|
|
||||||
])
|
|
||||||
.to_owned(),
|
|
||||||
)
|
|
||||||
.exec(&db).await?;
|
|
||||||
|
|
||||||
Ok(Json(projects))
|
Ok(Json(projects))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn clients(
|
async fn clients(
|
||||||
Extension(db): Extension<DatabaseConnection>,
|
Extension(db): Extension<DatabaseConnection>,
|
||||||
Extension(toggl_client): Extension<TogglClient>
|
Extension(toggl_client): Extension<TogglClient>,
|
||||||
) -> Result<Json<Vec<ProjectClient>>> {
|
) -> Result<Json<Vec<ProjectClient>>> {
|
||||||
let clients = toggl_client.fetch_clients().await?;
|
let clients = toggl_client.fetch_clients().await?;
|
||||||
entity::client::Entity::insert_many(clients.iter().map(ProjectClient::as_model))
|
entity::client::Entity::insert_many(clients.iter().map(ProjectClient::as_model))
|
||||||
.on_conflict(
|
.on_conflict(ProjectClient::grafting_conflict_statement())
|
||||||
OnConflict::column(entity::client::Column::Id)
|
.exec(&db)
|
||||||
.update_columns(vec![
|
.await?;
|
||||||
entity::client::Column::Name,
|
|
||||||
entity::client::Column::Archived,
|
|
||||||
entity::client::Column::At,
|
|
||||||
entity::client::Column::ServerDeletedAt,
|
|
||||||
entity::client::Column::WorkspaceId,
|
|
||||||
])
|
|
||||||
.to_owned(),
|
|
||||||
)
|
|
||||||
.exec(&db).await?;
|
|
||||||
|
|
||||||
Ok(Json(clients))
|
Ok(Json(clients))
|
||||||
}
|
}
|
||||||
|
|||||||
32
src/poll.rs
32
src/poll.rs
@ -1,7 +1,8 @@
|
|||||||
use crate::client::TogglClient;
|
use crate::client::TogglClient;
|
||||||
use crate::types::TogglQuery;
|
use crate::types::{Project, ProjectClient, TogglQuery};
|
||||||
use sea_orm::DatabaseConnection;
|
use sea_orm::{DatabaseConnection, EntityTrait, QuerySelect};
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
use crate::entity::{project, client};
|
||||||
|
|
||||||
#[tracing::instrument(skip(client, db))]
|
#[tracing::instrument(skip(client, db))]
|
||||||
pub async fn poll_job(client: TogglClient, db: DatabaseConnection) {
|
pub async fn poll_job(client: TogglClient, db: DatabaseConnection) {
|
||||||
@ -50,6 +51,33 @@ pub async fn perform_poll(
|
|||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
let existing_project_ids = project::Entity::find()
|
||||||
|
.select_only()
|
||||||
|
.column(project::Column::TogglId)
|
||||||
|
.into_tuple::<i64>()
|
||||||
|
.all(db)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let new_projects = report.iter()
|
||||||
|
.filter_map(|entry| entry.project_id)
|
||||||
|
.any(|project_id| !existing_project_ids.contains(&(project_id as i64)));
|
||||||
|
|
||||||
|
if new_projects {
|
||||||
|
let clients = client.fetch_clients().await?;
|
||||||
|
|
||||||
|
client::Entity::insert_many(clients.iter().map(ProjectClient::as_model))
|
||||||
|
.on_conflict(ProjectClient::grafting_conflict_statement())
|
||||||
|
.exec(db)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let projects = client.fetch_projects().await?;
|
||||||
|
|
||||||
|
project::Entity::insert_many(projects.iter().map(Project::as_model))
|
||||||
|
.on_conflict(Project::grafting_conflict_statement())
|
||||||
|
.exec(db)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
crate::cache_report(&db, &report).await?;
|
crate::cache_report(&db, &report).await?;
|
||||||
Ok(report.len())
|
Ok(report.len())
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user