Skip to content

Commit

Permalink
Setup SQL with seed and profile
Browse files Browse the repository at this point in the history
  • Loading branch information
TonyGiorgio committed May 14, 2024
1 parent a84c634 commit 1122e95
Show file tree
Hide file tree
Showing 17 changed files with 221 additions and 9 deletions.
10 changes: 10 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ home = "0.5.9"
rusqlite = { version = "0.31.0", features = ["sqlcipher"] }
diesel = { version = "2.1.6", features = ["sqlite", "chrono", "r2d2"] }
diesel_migrations = { version = "2.1.0", features = ["sqlite"] }
uuid = { version = "1.8", features = ["v4"] }

bitcoin = { version = "0.29.2", features = ["base64"] }
bip39 = "2.0.0"
Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,9 @@ just run
```
just release
```

4. Reset local DB (for init, schema generation, etc.)

```
just reset-db
```
9 changes: 9 additions & 0 deletions diesel.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# For documentation on how to configure this file,
# see https://diesel.rs/guides/configuring-diesel-cli

[print_schema]
file = "src/db_models/schema.rs"
custom_type_derives = ["diesel::query_builder::QueryId"]

[migrations_directory]
dir = "migrations"
1 change: 1 addition & 0 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
pkgs.xorg.libXcursor
pkgs.xorg.libXi
pkgs.xorg.libXrandr
pkgs.diesel-cli
] ++ pkgs.lib.optionals pkgs.stdenv.isDarwin [
pkgs.darwin.apple_sdk.frameworks.AppKit
pkgs.darwin.apple_sdk.frameworks.CoreText
Expand Down
Binary file added harbor.sqlite
Binary file not shown.
3 changes: 3 additions & 0 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,6 @@ release:

clippy:
cargo clippy --all-features --tests -- -D warnings

reset-db:
diesel migration revert --all --database-url=harbor.sqlite && diesel migration run --database-url=harbor.sqlite
Empty file added migrations/.keep
Empty file.
1 change: 1 addition & 0 deletions migrations/2024-05-13-234832_create_config/down.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
DROP TABLE profile;
4 changes: 4 additions & 0 deletions migrations/2024-05-13-234832_create_config/up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
CREATE TABLE profile (
id CHAR(36) PRIMARY KEY NOT NULL,
seed_words CHAR(255) NOT NULL
);
25 changes: 22 additions & 3 deletions src/conf.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
use bip39::{Language, Mnemonic};
use bitcoin::Network;
use log::info;
use std::path::PathBuf;
use std::str::FromStr;
use std::sync::Arc;

use crate::{db::DBConnection, db_models::NewProfile};

/// The directory where all application data is stored
/// Defaults to ~/.harbor, if we're on a test network
Expand All @@ -17,8 +22,22 @@ pub fn data_dir(network: Network) -> PathBuf {
}

// todo store in encrypted database
pub fn get_mnemonic(_network: Network) -> anyhow::Result<Mnemonic> {
let mnemonic = Mnemonic::generate_in(Language::English, 12)?;
pub fn get_mnemonic(db: Arc<dyn DBConnection + Send + Sync>) -> anyhow::Result<Mnemonic> {
match db.get_seed()? {
Some(m) => {
info!("retrieved existing seed");
Ok(Mnemonic::from_str(&m)?)
}
None => {
let new_profile = NewProfile {
id: uuid::Uuid::new_v4().to_string(),
seed_words: Mnemonic::generate_in(Language::English, 12)?.to_string(),
};

let p = db.insert_new_profile(new_profile)?;

Ok(mnemonic)
info!("creating new seed");
Ok(Mnemonic::from_str(&p.seed_words)?)
}
}
}
25 changes: 19 additions & 6 deletions src/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,17 @@ use iced::{
use log::error;
use tokio::time::sleep;

use crate::fedimint_client::{
select_gateway, spawn_internal_payment_subscription, spawn_invoice_payment_subscription,
spawn_invoice_receive_subscription, FedimintClient,
};
use crate::{
bridge::{self, CoreUIMsg, UICoreMsg},
conf, Message,
conf::{self, get_mnemonic},
Message,
};
use crate::{
db::setup_db,
fedimint_client::{
select_gateway, spawn_internal_payment_subscription, spawn_invoice_payment_subscription,
spawn_invoice_receive_subscription, FedimintClient,
},
};

struct HarborCore {
Expand Down Expand Up @@ -175,7 +179,16 @@ pub fn run_core() -> Subscription<Message> {
let path = PathBuf::from(&conf::data_dir(network));
std::fs::create_dir_all(path.clone()).expect("Could not create datadir");

let mnemonic = conf::get_mnemonic(network).expect("Could not get mnemonic");
// Create or get the database
// FIXME: pass in password
let db = setup_db(
path.join("harbor.sqlite")
.to_str()
.expect("path must be correct"),
"password123".to_string(),
);

let mnemonic = get_mnemonic(db).expect("should get seed");

// fixme, properly initialize this
let client = FedimintClient::new(
Expand Down
87 changes: 87 additions & 0 deletions src/db.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
use crate::db_models::{NewProfile, Profile};
use diesel::{
connection::SimpleConnection,
r2d2::{ConnectionManager, Pool},
SqliteConnection,
};
use diesel_migrations::{embed_migrations, EmbeddedMigrations, MigrationHarness};
use std::{sync::Arc, time::Duration};

pub const MIGRATIONS: EmbeddedMigrations = embed_migrations!();

pub(crate) fn setup_db(url: &str, password: String) -> Arc<dyn DBConnection + Send + Sync> {
let manager = ConnectionManager::<SqliteConnection>::new(url);
let pool = Pool::builder()
.max_size(50)
.connection_customizer(Box::new(ConnectionOptions {
key: password,
enable_wal: true,
enable_foreign_keys: true,
busy_timeout: Some(Duration::from_secs(15)),
}))
.test_on_check_out(true)
.build(manager)
.expect("Unable to build DB connection pool");
Arc::new(SQLConnection { db: pool })
}

pub trait DBConnection {
// Gets a seed from the first profile in the DB or returns None
fn get_seed(&self) -> anyhow::Result<Option<String>>;

// Inserts a new profile into the DB
fn insert_new_profile(&self, new_profile: NewProfile) -> anyhow::Result<Profile>;
}

pub(crate) struct SQLConnection {
db: Pool<ConnectionManager<SqliteConnection>>,
}

impl DBConnection for SQLConnection {
fn get_seed(&self) -> anyhow::Result<Option<String>> {
let conn = &mut self.db.get()?;
match Profile::get_first(conn)? {
Some(p) => Ok(Some(p.seed_words)),
None => Ok(None),
}
}

fn insert_new_profile(&self, new_profile: NewProfile) -> anyhow::Result<Profile> {
let conn = &mut self.db.get()?;
new_profile.insert(conn)
}
}

#[derive(Debug)]
pub struct ConnectionOptions {
pub key: String,
pub enable_wal: bool,
pub enable_foreign_keys: bool,
pub busy_timeout: Option<Duration>,
}

impl diesel::r2d2::CustomizeConnection<SqliteConnection, diesel::r2d2::Error>
for ConnectionOptions
{
fn on_acquire(&self, conn: &mut SqliteConnection) -> Result<(), diesel::r2d2::Error> {
(|| {
// FIXME: Special characters might fuck up
conn.batch_execute(&format!("PRAGMA key={}", self.key))?;
if self.enable_wal {
conn.batch_execute("PRAGMA journal_mode = WAL; PRAGMA synchronous = NORMAL;")?;
}
if self.enable_foreign_keys {
conn.batch_execute("PRAGMA foreign_keys = ON;")?;
}
if let Some(d) = self.busy_timeout {
conn.batch_execute(&format!("PRAGMA busy_timeout = {};", d.as_millis()))?;
}

conn.run_pending_migrations(MIGRATIONS)
.expect("Migration has to run successfully");

Ok(())
})()
.map_err(diesel::r2d2::Error::QueryError)
}
}
4 changes: 4 additions & 0 deletions src/db_models/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
pub mod profile;
pub use profile::*;

pub mod schema;
44 changes: 44 additions & 0 deletions src/db_models/profile.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
use crate::db_models::schema::profile;
use diesel::prelude::*;
use serde::{Deserialize, Serialize};

#[derive(
QueryableByName, Queryable, AsChangeset, Serialize, Deserialize, Debug, Clone, PartialEq,
)]
#[diesel(table_name = profile)]
pub struct Profile {
pub id: String,
pub seed_words: String,
}

impl Profile {
pub fn get_first(conn: &mut SqliteConnection) -> anyhow::Result<Option<Profile>> {
Ok(profile::table.first::<Profile>(conn).optional()?)
}
}

#[derive(Insertable)]
#[diesel(table_name = profile)]
pub struct NewProfile {
pub id: String,
pub seed_words: String,
}

impl From<&NewProfile> for Profile {
fn from(new_profile: &NewProfile) -> Self {
Profile {
id: new_profile.id.clone(),
seed_words: new_profile.seed_words.clone(),
}
}
}

impl NewProfile {
pub fn insert(&self, conn: &mut SqliteConnection) -> anyhow::Result<Profile> {
let _ = diesel::insert_into(profile::table)
.values(self)
.execute(conn)?;

Ok(self.into())
}
}
8 changes: 8 additions & 0 deletions src/db_models/schema.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// @generated automatically by Diesel CLI.

diesel::table! {
profile (id) {
id -> Text,
seed_words -> Text,
}
}
2 changes: 2 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ pub mod bridge;
pub mod components;
pub mod conf;
pub mod core;
pub mod db;
pub mod db_models;
mod fedimint_client;
pub mod routes;

Expand Down

0 comments on commit 1122e95

Please sign in to comment.