Skip to content

Feat/persistence db init - #76

Merged
grunch merged 3 commits into
mainfrom
feat/persistence-db-init
Apr 2, 2026
Merged

Feat/persistence db init#76
grunch merged 3 commits into
mainfrom
feat/persistence-db-init

Conversation

@grunch

@grunch grunch commented Apr 2, 2026

Copy link
Copy Markdown
Member
  • Add trade_keys table to SQLite schema (schema v2) to persist the
    BIP-32 key index used per order across restarts
  • Extend Storage trait with save_trade_key, get_trade_key, and
    get_trade_by_order_id; implement in SqliteStorage, stub in IndexedDbStorage
  • Add db/app_db.rs: global OnceCell<AppStorage> singleton with
    init_db(path) / db() accessors
  • Expose init_db(path: String) to Flutter via flutter_rust_bridge
  • Make store_trade_key_index / get_trade_key_index async; write-through
    to DB on store, DB fallback with cache-warm on miss
  • Save TradeInfo to DB after successful publish in take_order
  • Add get_trade_role(order_id) public API that queries the persisted trade
  • Dart: add tradeRoleFromDbProvider (FutureProvider.family) that calls
    getTradeRole and maps TradeRole → bool
  • TradeDetailScreen falls back to tradeRoleFromDbProvider when the
    in-memory tradeRoleProvider map has no entry for the order, so
    reopened trades after an app restart show the correct buyer/seller actions

Summary by CodeRabbit

  • New Features

    • Persistent local database for trade data so trades and keys survive app restarts, improving continuity and reliability.
    • Trade detail now falls back to persisted buyer/seller role when in-memory state is missing, reducing incorrect role displays after restart.
  • Chores

    • Updated localization source formatting for German, Spanish, French, and Italian strings.

grunch added 2 commits April 2, 2026 11:30
…overy

- Add `trade_keys` table to SQLite schema (schema v2) to persist the
  BIP-32 key index used per order across restarts
- Extend `Storage` trait with `save_trade_key`, `get_trade_key`, and
  `get_trade_by_order_id`; implement in SqliteStorage, stub in IndexedDbStorage
- Add `db/app_db.rs`: global `OnceCell<AppStorage>` singleton with
  `init_db(path)` / `db()` accessors
- Expose `init_db(path: String)` to Flutter via flutter_rust_bridge
- Make `store_trade_key_index` / `get_trade_key_index` async; write-through
  to DB on store, DB fallback with cache-warm on miss
- Save `TradeInfo` to DB after successful publish in `take_order`
- Add `get_trade_role(order_id)` public API that queries the persisted trade
- Dart: add `tradeRoleFromDbProvider` (FutureProvider.family) that calls
  `getTradeRole` and maps `TradeRole` → bool
- `TradeDetailScreen` falls back to `tradeRoleFromDbProvider` when the
  in-memory `tradeRoleProvider` map has no entry for the order, so
  reopened trades after an app restart show the correct buyer/seller actions
- Move `init_db` from `lib.rs` to `crate::api` so flutter_rust_bridge
  picks it up (frb.yaml scans `crate::api` only)
- Re-run `flutter_rust_bridge_codegen generate` so `init_db` and
  `get_trade_role` appear in the generated bindings
- Call `initDb` in `main()` right after `RustLib.init()`, passing the
  app documents directory path; skipped on web (IndexedDB stub is not
  yet implemented)

Without this call, `app_db::db()` always returned `None` and all
trade-key / trade-role persistence was silently a no-op.
@coderabbitai

coderabbitai Bot commented Apr 2, 2026

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 324e0262-e94a-4197-a65f-ac0cb241db37

📥 Commits

Reviewing files that changed from the base of the PR and between 6a308ba and f754b0a.

📒 Files selected for processing (5)
  • lib/main.dart
  • rust/src/api/orders.rs
  • rust/src/db/app_db.rs
  • rust/src/db/indexeddb.rs
  • rust/src/db/schema.rs
🚧 Files skipped from review as they are similar to previous changes (5)
  • rust/src/db/schema.rs
  • rust/src/db/indexeddb.rs
  • rust/src/db/app_db.rs
  • rust/src/api/orders.rs
  • lib/main.dart

Walkthrough

Adds persistent storage for trade data (SQLite/IndexedDB), exposes a Rust async DB initializer, extends storage APIs for trade keys and trade lookup, updates Flutter to initialize the DB at startup, and adds a DB-backed Riverpod provider to fall back from in-memory trade role state.

Changes

Cohort / File(s) Summary
DB init & app startup
lib/main.dart, rust/src/api/mod.rs, rust/src/db/app_db.rs
App now calls rust_api.initDb(path) on startup (non-web). Added public init_db API and a global storage singleton initializer.
Storage trait & schema
rust/src/db/mod.rs, rust/src/db/schema.rs
Exported app_db module; extended Storage trait with save_trade_key, get_trade_key, get_trade_by_order_id; bumped SCHEMA_VERSION to 2 and added trade_keys table DDL.
SQLite implementation
rust/src/db/sqlite.rs
Implemented save_trade_key, get_trade_key, and get_trade_by_order_id using trade_keys table and JSON extraction from trades table.
IndexedDB implementation (WASM)
rust/src/db/indexeddb.rs
Added non-failing stubs for save_trade_key (no-op), get_trade_key and get_trade_by_order_id (return None).
Rust orders API & FRB wiring
rust/src/api/orders.rs, rust/src/frb_generated.rs
Made trade-key persistence/lookups async with DB fallback; persist trade records after publish; added get_trade_role API; updated FRB-generated handlers and encoders for new APIs.
Flutter providers & UI
lib/features/order/providers/trade_state_provider.dart, lib/features/trades/screens/trade_detail_screen.dart
Added tradeRoleFromDbProvider (FutureProvider.family.autoDispose<bool?, String>) to read persisted role; TradeDetailScreen now prefers in-memory role, falling back to DB provider (valueOrNull) and defaults to true while loading.
Localization formatting
lib/l10n/app_localizations_de.dart, lib/l10n/app_localizations_es.dart, lib/l10n/app_localizations_fr.dart, lib/l10n/app_localizations_it.dart
Reformatted several localization getters to multi-line string literals; no text changes.
Miscellaneous
rust/src/lib.rs
Trivial whitespace change (extra blank line).

Sequence Diagram(s)

sequenceDiagram
    participant App as Flutter App
    participant UI as TradeDetailScreen
    participant Provider as Riverpod Provider
    participant Rust as Rust API
    participant DB as SQLite / IndexedDB

    App->>Rust: init_db(path)
    Rust->>DB: open storage backend
    DB-->>Rust: ready
    Rust-->>App: init complete

    UI->>Provider: read roleMap[orderId]
    alt In-memory hit
        Provider-->>UI: return in-memory role
    else Cache miss
        Provider->>Rust: get_trade_role(orderId)
        Rust->>DB: query trade by order_id
        DB-->>Rust: TradeRole or None
        Rust-->>Provider: Option<TradeRole>
        Provider-->>UI: role (or null → default true while loading)
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐇 I dug a burrow, found a key,

DB now keeps our trades for me,
Cache hops first, then asks below,
If none — the DB will let us know,
Crunching carrots, code, and glee. 🥕

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Feat/persistence db init' accurately and directly describes the primary change: adding persistent database initialization for the application.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/persistence-db-init

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (1)
rust/src/db/app_db.rs (1)

25-34: Consider using get_or_try_init to avoid redundant initialization attempts.

The current pattern has a race window where concurrent callers both pass the is_none() check and open separate storage instances (first wins, second is dropped). While correct, OnceCell::get_or_try_init handles this more elegantly.

♻️ Suggested simplification
 pub async fn init_db(path: &str) -> Result<()> {
-    if APP_DB.get().is_none() {
-        let storage = AppStorage::open(path).await?;
-        // A concurrent init racing here is harmless — OnceCell guarantees
-        // only the first value is stored.
-        let _ = APP_DB.set(storage);
-        log::info!("[db] persistent store initialised (path={})", path);
-    }
+    APP_DB
+        .get_or_try_init(|| async {
+            let storage = AppStorage::open(path).await?;
+            log::info!("[db] persistent store initialised (path={})", path);
+            Ok(storage)
+        })
+        .await?;
     Ok(())
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rust/src/db/app_db.rs` around lines 25 - 34, Replace the current
check-then-set pattern in init_db with OnceCell's get_or_try_init to avoid
redundant concurrent opens: call APP_DB.get_or_try_init with an async closure
that calls AppStorage::open(path).await, propagate the Result so callers receive
any error, and move the log::info! after successful initialization (i.e., after
get_or_try_init returns Ok) so you only log when the cell was initialized or
already contains the storage; keep the function signature and Err handling
unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@lib/main.dart`:
- Around line 20-25: Wrap the database initialization that runs when kIsWeb is
false (the block using getApplicationDocumentsDirectory(), p.join(...,
'mostro.db') and rust_api.initDb) in a try-catch so initDb failures don’t crash
startup; on catch, log the error (include the caught exception and stacktrace)
and set a clear degraded-state or fallback path (e.g., a boolean flag or
in-memory-only mode) so the rest of the app can continue to run gracefully
instead of crashing.

In `@rust/src/api/orders.rs`:
- Around line 53-77: Change get_trade_key_index to return Option<u32> (or
Result<Option<u32>, _> if you prefer) instead of silently returning 0: update
the function signature for get_trade_key_index(order_id: &str) to return
Option<u32>, return None when the key is neither in trade_key_map() nor found in
db::app_db::db().get_trade_key, and on DB errors propagate or log and return
None; then update all callers to explicitly handle the None case (e.g.,
early-return an error or prompt the user) rather than assuming index 0 so
signature verification won't silently fail. Ensure you update any code that
writes to the cache (trade_key_map().write()) to still insert the found index
and adjust await/unwrap handling to the new return type.

In `@rust/src/db/indexeddb.rs`:
- Around line 83-97: The IndexedDB stubs currently return Err(...) which causes
callers to silently fall back to incorrect behavior on WASM; change the
implementations to no-op success semantics: in save_trade_key return Ok(())
instead of Err(...), in get_trade_key return Ok(None) (not an Err) to let
callers handle "no persisted key" correctly, and in get_trade_by_order_id return
Ok(None) so role lookups remain None without an internal error; update the
function bodies for save_trade_key, get_trade_key, and get_trade_by_order_id to
return these values (or alternatively gate with a compile-time error for web
builds if you prefer to block) so behavior matches the intended "no persistence"
fallback.

In `@rust/src/db/schema.rs`:
- Line 2: Update the comment in schema.rs to accurately state that
SQLITE_INIT_SQL is executed unconditionally on every SqliteStorage::open() call
(SqliteStorage::open) rather than only when SCHEMA_VERSION changes; mention that
the DDL uses idempotent CREATE TABLE IF NOT EXISTS so this is safe, and note
that SCHEMA_VERSION is currently defined but unused—either document intended
future use for version checks/optimizations or remove it if unnecessary.

---

Nitpick comments:
In `@rust/src/db/app_db.rs`:
- Around line 25-34: Replace the current check-then-set pattern in init_db with
OnceCell's get_or_try_init to avoid redundant concurrent opens: call
APP_DB.get_or_try_init with an async closure that calls
AppStorage::open(path).await, propagate the Result so callers receive any error,
and move the log::info! after successful initialization (i.e., after
get_or_try_init returns Ok) so you only log when the cell was initialized or
already contains the storage; keep the function signature and Err handling
unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3fbd9fdc-a73c-4560-87e1-e6aa4f8a9c3a

📥 Commits

Reviewing files that changed from the base of the PR and between 6463851 and 6a308ba.

📒 Files selected for processing (16)
  • lib/features/order/providers/trade_state_provider.dart
  • lib/features/trades/screens/trade_detail_screen.dart
  • lib/l10n/app_localizations_de.dart
  • lib/l10n/app_localizations_es.dart
  • lib/l10n/app_localizations_fr.dart
  • lib/l10n/app_localizations_it.dart
  • lib/main.dart
  • rust/src/api/mod.rs
  • rust/src/api/orders.rs
  • rust/src/db/app_db.rs
  • rust/src/db/indexeddb.rs
  • rust/src/db/mod.rs
  • rust/src/db/schema.rs
  • rust/src/db/sqlite.rs
  • rust/src/frb_generated.rs
  • rust/src/lib.rs

Comment thread lib/main.dart
Comment thread rust/src/api/orders.rs Outdated
Comment thread rust/src/db/indexeddb.rs
Comment thread rust/src/db/schema.rs
- Wrap initDb() call in main() with try/catch so a DB failure degrades
  to memory-only mode instead of crashing startup
- Change get_trade_key_index() to return Option<u32>; all four callers
  now propagate an explicit error when no key is found rather than
  silently falling back to index 0 (which would cause signature
  verification failures on the daemon side)
- Fix IndexedDB stubs for save_trade_key, get_trade_key, and
  get_trade_by_order_id to return no-op Ok / Ok(None) instead of Err,
  so callers behave correctly on WASM without spurious error logs
- Replace check-then-set pattern in init_db() with get_or_try_init()
  to avoid redundant concurrent opens
- Update schema.rs comments to accurately reflect that SQLITE_INIT_SQL
  runs unconditionally (safe due to IF NOT EXISTS) and that
  SCHEMA_VERSION is currently unused at runtime
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant