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)
}