(toggl-portal): Database work

This commit is contained in:
Joshua Coles 2023-11-02 16:00:41 +00:00
parent 8f39bf7d23
commit d47174efee
7 changed files with 63 additions and 11 deletions

View File

@ -28,3 +28,5 @@ sea-orm = { version = "0.12", features = [
] } ] }
migration = { path = "./migration" } migration = { path = "./migration" }
chrono = { version = "0.4.31", features = ["serde"] }
futures = "0.3.29"

View File

@ -1,5 +1,9 @@
#!/usr/bin/env just --justfile #!/usr/bin/env just --justfile
reset:
sea-orm-cli migrate down
sea-orm-cli migrate up
migrate: migrate:
sea-orm-cli migrate up sea-orm-cli migrate up

View File

@ -7,8 +7,12 @@ pub struct Migrator;
#[async_trait::async_trait] #[async_trait::async_trait]
impl MigratorTrait for Migrator { impl MigratorTrait for Migrator {
fn migrations() -> Vec<Box<dyn MigrationTrait>> { fn migrations() -> Vec<Box<dyn MigrationTrait>> {
vec![ vec![Box::new(
Box::new(m20231101_172500_create_time_entry_table::Migration), m20231101_172500_create_time_entry_table::Migration,
] )]
}
fn migration_table_name() -> DynIden {
Alias::new("toggl_portal_seaql_migrations").into_iden()
} }
} }

View File

@ -13,15 +13,17 @@ impl MigrationTrait for Migration {
.if_not_exists() .if_not_exists()
.col( .col(
ColumnDef::new(TimeEntry::Id) ColumnDef::new(TimeEntry::Id)
.big_integer() .integer()
.not_null() .not_null()
.auto_increment() .auto_increment()
.primary_key(), .primary_key(),
) )
.col(
ColumnDef::new(TimeEntry::TogglId).big_unsigned().not_null().unique_key())
.col(ColumnDef::new(TimeEntry::Description).string().not_null()) .col(ColumnDef::new(TimeEntry::Description).string().not_null())
.col(ColumnDef::new(TimeEntry::ProjectId).big_integer()) .col(ColumnDef::new(TimeEntry::ProjectId).big_unsigned())
.col(ColumnDef::new(TimeEntry::Start).timestamp().not_null()) .col(ColumnDef::new(TimeEntry::Start).timestamp_with_time_zone().not_null())
.col(ColumnDef::new(TimeEntry::Stop).timestamp().not_null()) .col(ColumnDef::new(TimeEntry::Stop).timestamp_with_time_zone().not_null())
.col(ColumnDef::new(TimeEntry::RawJson).json_binary().not_null()) .col(ColumnDef::new(TimeEntry::RawJson).json_binary().not_null())
.to_owned(), .to_owned(),
) )
@ -39,6 +41,7 @@ impl MigrationTrait for Migration {
enum TimeEntry { enum TimeEntry {
Table, Table,
Id, Id,
TogglId,
Description, Description,
ProjectId, ProjectId,
Start, Start,

17
src/db.rs Normal file
View File

@ -0,0 +1,17 @@
use sea_orm::{NotSet, Set};
use crate::entity::time_entry::ActiveModel;
use crate::types::ReportEntry;
impl ReportEntry {
pub(crate) fn as_models(&self) -> Vec<ActiveModel> {
self.time_entries.iter().map(|inner| ActiveModel {
id: NotSet,
toggl_id: Set(inner.id as i64),
description: Set(self.description.clone()),
project_id: Set(self.project_id.map(|id| id as i64)),
start: Set(chrono::DateTime::parse_from_rfc3339(&inner.start).unwrap()),
stop: Set(chrono::DateTime::parse_from_rfc3339(&inner.start).unwrap()),
raw_json: Set(serde_json::to_value(inner).unwrap()),
}).collect()
}
}

View File

@ -7,11 +7,12 @@ use serde::{Deserialize, Serialize};
#[sea_orm(table_name = "time_entry")] #[sea_orm(table_name = "time_entry")]
pub struct Model { pub struct Model {
#[sea_orm(primary_key)] #[sea_orm(primary_key)]
pub id: i64, pub id: i32,
pub toggl_id: i64,
pub description: String, pub description: String,
pub project_id: Option<i64>, pub project_id: Option<i64>,
pub start: DateTime, pub start: DateTimeWithTimeZone,
pub stop: DateTime, pub stop: DateTimeWithTimeZone,
#[sea_orm(column_type = "JsonBinary")] #[sea_orm(column_type = "JsonBinary")]
pub raw_json: Json, pub raw_json: Json,
} }

View File

@ -12,11 +12,16 @@ use clap::Parser;
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 migration::{Migrator, MigratorTrait};
use crate::entity::time_entry::{self, Entity as TimeEntry};
mod client; mod client;
mod types; mod types;
mod db;
mod entity;
#[derive(Debug, Clone, Parser)] #[derive(Debug, Clone, Parser)]
struct Config { struct Config {
@ -35,9 +40,25 @@ struct Config {
pub async fn report( pub async fn report(
Extension(toggl_client): Extension<TogglClient>, Extension(toggl_client): Extension<TogglClient>,
Extension(db): Extension<DatabaseConnection>,
Json(query): Json<TogglQuery>, Json(query): Json<TogglQuery>,
) -> Result<Json<Vec<ReportEntry>>> { ) -> Result<Json<Vec<ReportEntry>>> {
Ok(toggl_client.full_report(&query).await.map(Json)?) let report = toggl_client.full_report(&query).await?;
let models = report.iter().flat_map(|entry| entry.as_models());
TimeEntry::insert_many(models)
.on_conflict(
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).await?;
Ok(Json(report))
} }
pub async fn current( pub async fn current(