diff --git a/atuin-server-database/src/lib.rs b/atuin-server-database/src/lib.rs index de33ba44..8d79e894 100644 --- a/atuin-server-database/src/lib.rs +++ b/atuin-server-database/src/lib.rs @@ -13,7 +13,7 @@ use self::{ models::{History, NewHistory, NewSession, NewUser, Session, User}, }; use async_trait::async_trait; -use atuin_common::utils::get_days_from_month; +use atuin_common::{record::Record, utils::get_days_from_month}; use chrono::{Datelike, TimeZone}; use chronoutil::RelativeDuration; use serde::{de::DeserializeOwned, Serialize}; @@ -55,6 +55,11 @@ pub trait Database: Sized + Clone + Send + Sync + 'static { async fn delete_history(&self, user: &User, id: String) -> DbResult<()>; async fn deleted_history(&self, user: &User) -> DbResult>; + async fn add_record(&self, user: &User, record: &[Record]) -> DbResult<()>; + + // Return the tail record ID for each store, so (HostID, Tag, TailRecordID) + async fn tail_records(&self, user: &User) -> DbResult>; + async fn count_history_range( &self, user: &User, diff --git a/atuin-server-postgres/migrations/20230623070418_records.sql b/atuin-server-postgres/migrations/20230623070418_records.sql index 3e5ee00d..f6d3d1e6 100644 --- a/atuin-server-postgres/migrations/20230623070418_records.sql +++ b/atuin-server-postgres/migrations/20230623070418_records.sql @@ -1,6 +1,7 @@ -- Add migration script here create table records ( id uuid primary key, -- remember to use uuidv7 for happy indices <3 + client_id uuid not null, -- I am too uncomfortable with the idea of a client-generated primary key host uuid not null, -- a unique identifier for the host parent uuid not null, -- the ID of the parent record, bearing in mind this is a linked list timestamp bigint not null, -- not a timestamp type, as those do not have nanosecond precision diff --git a/atuin-server-postgres/src/lib.rs b/atuin-server-postgres/src/lib.rs index 0dc51daf..3f1f1165 100644 --- a/atuin-server-postgres/src/lib.rs +++ b/atuin-server-postgres/src/lib.rs @@ -1,4 +1,5 @@ use async_trait::async_trait; +use atuin_common::record::Record; use atuin_server_database::models::{History, NewHistory, NewSession, NewUser, Session, User}; use atuin_server_database::{Database, DbError, DbResult}; use futures_util::TryStreamExt; @@ -329,4 +330,49 @@ impl Database for Postgres { .map_err(fix_error) .map(|DbHistory(h)| h) } + + async fn add_record(&self, user: &User, records: &[Record]) -> DbResult<()> { + let mut tx = self.pool.begin().await.map_err(fix_error)?; + + for i in records { + let id = atuin_common::utils::uuid_v7().as_simple().to_string(); + + sqlx::query( + "insert into records + (id, client_id, host, parent, timestamp, version, tag, data, user_id) + values ($1, $2, $3, $4, $5, $6, $7, $8, $9) + on conflict do nothing + ", + ) + .bind(id) + .bind(&i.id) + .bind(&i.host) + .bind(&i.parent) + .bind(i.timestamp as i64) // throwing away some data, but i64 is still big in terms of time + .bind(&i.version) + .bind(&i.tag) + .bind(&i.data) + .bind(user.id) + .execute(&mut tx) + .await + .map_err(fix_error)?; + } + + tx.commit().await.map_err(fix_error)?; + + Ok(()) + } + + async fn tail_records(&self, user: &User) -> DbResult> { + const TAIL_RECORDS_SQL: &str = "select host, tag, id from records rp where (select count(1) from records where parent=rp.id and user_id = $1) = 0 group by host, tag;"; + + let res = sqlx::query_as(TAIL_RECORDS_SQL) + .bind(user.id) + .fetch(&self.pool) + .try_collect() + .await + .map_err(fix_error)?; + + Ok(res) + } } diff --git a/atuin-server/src/handlers/record.rs b/atuin-server/src/handlers/record.rs new file mode 100644 index 00000000..e69de29b