From 7f868711f0a7c77c868a2dd956fcc594d3d95ec8 Mon Sep 17 00:00:00 2001 From: Scotte Zinn Date: Mon, 23 Jun 2025 07:31:55 -0400 Subject: feat: Add sqlite server support for self-hosting (#2770) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Move db_uri setting to DbSettings * WIP: sqlite crate framework * WIP: Migrations * WIP: sqlite implementation * Add sqlite3 to Docker image * verified_at needed for user query * chore(deps): bump debian (#2772) Bumps debian from bookworm-20250428-slim to bookworm-20250520-slim. --- updated-dependencies: - dependency-name: debian dependency-version: bookworm-20250520-slim dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix(doctor): mention the required ble.sh version (#2774) References: https://forum.atuin.sh/t/1047 * fix: Don't print errors in `zsh_autosuggest` helper (#2780) Previously, this would result in long multi-line errors when typing, making it hard to see the shell prompt: ``` $ Error: could not load client settings Caused by: 0: could not create config file 1: failed to create file `/home/jyn/.config/atuin/config.toml` 2: Required key not available (os error 126) Location: atuin-client/src/settings.rs:675:54 fError: could not load client settings Caused by: 0: could not create config file 1: failed to create file `/home/jyn/.config/atuin/config.toml` 2: Required key not available (os error 126) Location: atuin-client/src/settings.rs:675:54 faError: could not load client settings ``` Silence these in autosuggestions, such that they only show up when explicitly invoking atuin. * fix: `atuin.nu` enchancements (#2778) * PR feedback * Remove sqlite3 package * fix(search): prevent panic on malformed format strings (#2776) (#2777) * fix(search): prevent panic on malformed format strings (#2776) - Wrap format operations in panic catcher for graceful error handling - Improve error messages with context-aware guidance for common issues - Let runtime-format parser handle validation to avoid blocking valid formats Fixes crash when using malformed format strings by catching formatting errors gracefully and providing actionable guidance without restricting legitimate format patterns like {command} or {time}. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude * Satisfy cargo fmt * test(search): add regression tests for format string panic (#2776) - Add test for malformed JSON format strings that previously caused panics - Add test to ensure valid format strings continue to work - Prevent future regressions of the format string panic issue 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --------- Co-authored-by: Claude --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Koichi Murase Co-authored-by: jyn Co-authored-by: Tyarel8 <98483313+Tyarel8@users.noreply.github.com> Co-authored-by: Brian Cosgrove Co-authored-by: Claude --- crates/atuin-server-sqlite/src/wrappers.rs | 73 ++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 crates/atuin-server-sqlite/src/wrappers.rs (limited to 'crates/atuin-server-sqlite/src/wrappers.rs') diff --git a/crates/atuin-server-sqlite/src/wrappers.rs b/crates/atuin-server-sqlite/src/wrappers.rs new file mode 100644 index 00000000..3f2262c3 --- /dev/null +++ b/crates/atuin-server-sqlite/src/wrappers.rs @@ -0,0 +1,73 @@ +use ::sqlx::{FromRow, Result}; +use atuin_common::record::{EncryptedData, Host, Record}; +use atuin_server_database::models::{History, Session, User}; +use sqlx::{Row, sqlite::SqliteRow}; + +pub struct DbUser(pub User); +pub struct DbSession(pub Session); +pub struct DbHistory(pub History); +pub struct DbRecord(pub Record); + +impl<'a> FromRow<'a, SqliteRow> for DbUser { + fn from_row(row: &'a SqliteRow) -> Result { + Ok(Self(User { + id: row.try_get("id")?, + username: row.try_get("username")?, + email: row.try_get("email")?, + password: row.try_get("password")?, + verified: row.try_get("verified_at")?, + })) + } +} + +impl<'a> ::sqlx::FromRow<'a, SqliteRow> for DbSession { + fn from_row(row: &'a SqliteRow) -> ::sqlx::Result { + Ok(Self(Session { + id: row.try_get("id")?, + user_id: row.try_get("user_id")?, + token: row.try_get("token")?, + })) + } +} + +impl<'a> ::sqlx::FromRow<'a, SqliteRow> for DbHistory { + fn from_row(row: &'a SqliteRow) -> ::sqlx::Result { + Ok(Self(History { + id: row.try_get("id")?, + client_id: row.try_get("client_id")?, + user_id: row.try_get("user_id")?, + hostname: row.try_get("hostname")?, + timestamp: row.try_get("timestamp")?, + data: row.try_get("data")?, + created_at: row.try_get("created_at")?, + })) + } +} + +impl<'a> ::sqlx::FromRow<'a, SqliteRow> for DbRecord { + fn from_row(row: &'a SqliteRow) -> ::sqlx::Result { + let idx: i64 = row.try_get("idx")?; + let timestamp: i64 = row.try_get("timestamp")?; + + let data = EncryptedData { + data: row.try_get("data")?, + content_encryption_key: row.try_get("cek")?, + }; + + Ok(Self(Record { + id: row.try_get("client_id")?, + host: Host::new(row.try_get("host")?), + idx: idx as u64, + timestamp: timestamp as u64, + version: row.try_get("version")?, + tag: row.try_get("tag")?, + data, + })) + } +} + +impl From for Record { + fn from(other: DbRecord) -> Record { + Record { ..other.0 } + } +} -- cgit v1.3.1