From 1e3c235b787370ea54e4d7c52461e9f34ba41e65 Mon Sep 17 00:00:00 2001 From: aleidk Date: Wed, 7 May 2025 14:36:26 -0400 Subject: [PATCH] feat: add base database management --- .idea/dataSources.xml | 12 +++++++++++ .idea/sqlDataSources.xml | 41 ++++++++++++++++++++++++++++++++++++ .idea/sqldialects.xml | 8 +++++++ Cargo.toml | 2 +- migrations/0001_jobs.sql | 20 ++++++++++++++++++ migrations/0002_statuses.sql | 13 ++++++++++++ migrations/0003_tasks.sql | 19 +++++++++++++++++ src/error.rs | 5 ++++- src/main.rs | 4 ++-- src/readwise.rs | 26 +++++++++-------------- src/sql.rs | 2 ++ 11 files changed, 132 insertions(+), 20 deletions(-) create mode 100644 .idea/dataSources.xml create mode 100644 .idea/sqlDataSources.xml create mode 100644 .idea/sqldialects.xml create mode 100644 migrations/0001_jobs.sql create mode 100644 migrations/0002_statuses.sql create mode 100644 migrations/0003_tasks.sql diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml new file mode 100644 index 0000000..283ef50 --- /dev/null +++ b/.idea/dataSources.xml @@ -0,0 +1,12 @@ + + + + + sqlite.xerial + true + org.sqlite.JDBC + jdbc:sqlite:$USER_HOME$/.local/share/readwise-bulk-upload/db.sql + $ProjectFileDir$ + + + \ No newline at end of file diff --git a/.idea/sqlDataSources.xml b/.idea/sqlDataSources.xml new file mode 100644 index 0000000..ff6c246 --- /dev/null +++ b/.idea/sqlDataSources.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/sqldialects.xml b/.idea/sqldialects.xml new file mode 100644 index 0000000..604ccd2 --- /dev/null +++ b/.idea/sqldialects.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 0943a00..4c7dbb1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,7 @@ edition = "2024" thiserror = "2.0.12" directories = "6.0.0" tokio = { version = "1.45.0", features = ["default", "rt", "rt-multi-thread", "macros"] } -sqlx = { version = "0.8", features = [ "runtime-tokio", "sqlite", "chrono" ] } +sqlx = { version = "0.8", features = [ "runtime-tokio", "sqlite", "chrono", "migrate" ] } clap = { version = "4.5.37", features = ["derive"] } serde = { version = "1.0.219", features = ["derive"] } chrono = {version = "0.4.41", features = ["serde"]} diff --git a/migrations/0001_jobs.sql b/migrations/0001_jobs.sql new file mode 100644 index 0000000..98d5ae6 --- /dev/null +++ b/migrations/0001_jobs.sql @@ -0,0 +1,20 @@ +create table jobs +( + id integer + constraint jobs_pk + primary key autoincrement, + task_id integer not null + constraint jobs_tasks_id_fk + references tasks, + status_id integer not null + constraint jobs_statuses_id_fk + references statuses, + output TEXT, + started_at TEXT default CURRENT_TIMESTAMP not null, + finished_at TEXT, + "order" integer not null +); + +create index jobs_task_id_order_index + on jobs (task_id asc, "order" desc); + diff --git a/migrations/0002_statuses.sql b/migrations/0002_statuses.sql new file mode 100644 index 0000000..2e685f0 --- /dev/null +++ b/migrations/0002_statuses.sql @@ -0,0 +1,13 @@ +create table statuses +( + id integer + constraint task_statuses_pk + primary key autoincrement, + name TEXT not null +); + +insert into statuses (id, name) +values (1, 'Pending'), + (2, 'In progress'), + (3, 'Completed'), + (4, 'Failed'); diff --git a/migrations/0003_tasks.sql b/migrations/0003_tasks.sql new file mode 100644 index 0000000..02a1c26 --- /dev/null +++ b/migrations/0003_tasks.sql @@ -0,0 +1,19 @@ +create table tasks +( + id integer not null + constraint tasks_pk + primary key autoincrement, + payload_key ANY not null + constraint tasks_payload_key + unique, + payload TEXT not null, + status_id integer not null + constraint tasks_task_statuses_id_fk + references statuses, + created_at TEXT default CURRENT_TIMESTAMP not null, + updated_at TEXT +); + +create unique index tasks_payload_key_uindex + on tasks (payload_key); + diff --git a/src/error.rs b/src/error.rs index e6c2532..02b441d 100644 --- a/src/error.rs +++ b/src/error.rs @@ -13,7 +13,10 @@ pub enum Error { #[error(transparent)] Sqlx(#[from] sqlx::Error), - + + #[error(transparent)] + Migration(#[from] sqlx::migrate::MigrateError), + #[error(transparent)] Io(#[from] tokio::io::Error), diff --git a/src/main.rs b/src/main.rs index fbd7100..4129435 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,7 @@ use std::fs::File; use clap::Parser; use readwise_bulk_upload::config::Args; -use readwise_bulk_upload::readwise::Document; +use readwise_bulk_upload::readwise::DocumentPayload; use readwise_bulk_upload::sql::get_database; use readwise_bulk_upload::{Error, Result}; @@ -15,7 +15,7 @@ async fn main() -> Result<()> { args.path().display() )))?; - let documents: Vec = serde_json::from_reader(file)?; + let documents: Vec = serde_json::from_reader(file)?; let db = get_database().await?; diff --git a/src/readwise.rs b/src/readwise.rs index ed70495..e188c21 100644 --- a/src/readwise.rs +++ b/src/readwise.rs @@ -1,29 +1,23 @@ -use serde::{de, Deserialize, Deserializer}; use chrono::{DateTime, Local}; +use serde::{Deserialize, Deserializer, de}; use serde_json::Value; #[derive(Deserialize)] -pub struct Document { - #[serde(deserialize_with = "str_to_int")] - id: u64, - title: String , - note: Option, - excerpt: Option, +pub struct DocumentPayload { + title: String, + summary: Option, url: String, - folder: String, #[serde(deserialize_with = "single_or_vec")] tags: Vec, - created: DateTime, - cover: Option, - #[serde(deserialize_with = "str_to_bool")] - favorite: bool + published_date: DateTime, + location: String, } fn str_to_int<'de, D: Deserializer<'de>>(deserializer: D) -> Result { Ok(match Value::deserialize(deserializer)? { Value::String(s) => s.parse().map_err(de::Error::custom)?, Value::Number(num) => num.as_u64().ok_or(de::Error::custom("Invalid number"))?, - _ => return Err(de::Error::custom("wrong type")) + _ => return Err(de::Error::custom("wrong type")), }) } @@ -31,15 +25,15 @@ fn str_to_bool<'de, D: Deserializer<'de>>(deserializer: D) -> Result s.parse().map_err(de::Error::custom)?, Value::Bool(b) => b, - _ => return Err(de::Error::custom("wrong type")) + _ => return Err(de::Error::custom("wrong type")), }) } fn single_or_vec<'de, D: Deserializer<'de>>(deserializer: D) -> Result, D::Error> { Ok(match Value::deserialize(deserializer)? { - Value::String(s) => vec!(s.parse().map_err(de::Error::custom)?), + Value::String(s) => vec![s.parse().map_err(de::Error::custom)?], Value::Array(arr) => arr.into_iter().map(|a| a.to_string()).collect(), Value::Null => Vec::new(), - _ => return Err(de::Error::custom("wrong type")) + _ => return Err(de::Error::custom("wrong type")), }) } diff --git a/src/sql.rs b/src/sql.rs index 7603143..3c2d1c7 100644 --- a/src/sql.rs +++ b/src/sql.rs @@ -18,6 +18,8 @@ pub async fn get_database() -> crate::Result { .journal_mode(SqliteJournalMode::Wal); let pool = SqlitePool::connect_with(opts).await?; + + sqlx::migrate!("./migrations").run(&pool).await?; Ok(pool) }