[fix] VerNum
This commit is contained in:
parent
68cedff1d9
commit
0827882bb3
30 changed files with 1651 additions and 240 deletions
|
|
@ -46,37 +46,7 @@ pub fn try_add_user_with_credential_origin(
|
|||
credential_origin: CredentialOrigin,
|
||||
) -> Result<(), crate::storage_error::StorageError> {
|
||||
db::with_immediate_transaction(|tx| {
|
||||
tx.execute(
|
||||
r#"
|
||||
INSERT INTO users (user_id, username, public_key, private_key_hash, reset_token, created_at, display_name)
|
||||
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)
|
||||
ON CONFLICT(user_id) DO UPDATE SET
|
||||
username = excluded.username,
|
||||
public_key = excluded.public_key,
|
||||
private_key_hash = excluded.private_key_hash,
|
||||
reset_token = excluded.reset_token,
|
||||
display_name = excluded.display_name
|
||||
"#,
|
||||
params![
|
||||
user.user_id,
|
||||
user.username,
|
||||
user.public_key,
|
||||
user.private_key_hash,
|
||||
user.reset_token,
|
||||
user.created_at,
|
||||
user.display_name,
|
||||
],
|
||||
)?;
|
||||
|
||||
for (app_id, app_secret) in &user.trusted_apps {
|
||||
tx.execute(
|
||||
r#"
|
||||
INSERT OR REPLACE INTO trusted_apps (user_id, app_id, app_secret)
|
||||
VALUES (?1, ?2, ?3)
|
||||
"#,
|
||||
params![user.user_id, app_id, app_secret],
|
||||
)?;
|
||||
}
|
||||
persist_user_profile(tx, &user)?;
|
||||
tx.execute(
|
||||
r#"INSERT INTO user_residency (user_id, username, lifecycle_state, data_state, credential_origin, updated_at)
|
||||
VALUES (?1, ?2, 'managed', COALESCE((SELECT data_state FROM user_residency WHERE user_id = ?1), 'present'), ?3, ?4)
|
||||
|
|
@ -88,7 +58,46 @@ pub fn try_add_user_with_credential_origin(
|
|||
}
|
||||
|
||||
pub fn update_user(user: UserProfile) -> Result<(), crate::storage_error::StorageError> {
|
||||
try_add_user(user)
|
||||
db::with_immediate_transaction(|tx| persist_user_profile(tx, &user))
|
||||
}
|
||||
|
||||
/// Persist mutable profile data without modifying lifecycle-managed residency.
|
||||
fn persist_user_profile(
|
||||
tx: &rusqlite::Transaction<'_>,
|
||||
user: &UserProfile,
|
||||
) -> Result<(), crate::storage_error::StorageError> {
|
||||
tx.execute(
|
||||
r#"
|
||||
INSERT INTO users (user_id, username, public_key, private_key_hash, reset_token, created_at, display_name)
|
||||
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)
|
||||
ON CONFLICT(user_id) DO UPDATE SET
|
||||
username = excluded.username,
|
||||
public_key = excluded.public_key,
|
||||
private_key_hash = excluded.private_key_hash,
|
||||
reset_token = excluded.reset_token,
|
||||
display_name = excluded.display_name
|
||||
"#,
|
||||
params![
|
||||
user.user_id,
|
||||
user.username,
|
||||
user.public_key,
|
||||
user.private_key_hash,
|
||||
user.reset_token,
|
||||
user.created_at,
|
||||
user.display_name,
|
||||
],
|
||||
)?;
|
||||
|
||||
for (app_id, app_secret) in &user.trusted_apps {
|
||||
tx.execute(
|
||||
r#"
|
||||
INSERT OR REPLACE INTO trusted_apps (user_id, app_id, app_secret)
|
||||
VALUES (?1, ?2, ?3)
|
||||
"#,
|
||||
params![user.user_id, app_id, app_secret],
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_user_by_username(
|
||||
|
|
@ -659,9 +668,12 @@ pub fn load_users_sync() -> std::io::Result<()> {
|
|||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod purge_tests {
|
||||
use super::{PurgeStage, remaining_purge_stages, run_purge_stages};
|
||||
mod tests {
|
||||
use super::{
|
||||
PurgeStage, UserProfile, persist_user_profile, remaining_purge_stages, run_purge_stages,
|
||||
};
|
||||
use crate::users::pending_operations::PendingUserOperationPhase;
|
||||
use rusqlite::{Connection, params};
|
||||
|
||||
#[test]
|
||||
fn prepared_purge_runs_every_stage_before_completion() {
|
||||
|
|
@ -725,6 +737,82 @@ mod purge_tests {
|
|||
assert!(!data_empty);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn profile_updates_preserve_external_residency_attributes() {
|
||||
let mut connection = Connection::open_in_memory().unwrap();
|
||||
connection
|
||||
.execute_batch(
|
||||
r#"
|
||||
CREATE TABLE users (
|
||||
user_id INTEGER PRIMARY KEY,
|
||||
username TEXT NOT NULL UNIQUE,
|
||||
public_key TEXT NOT NULL,
|
||||
private_key_hash TEXT,
|
||||
reset_token TEXT,
|
||||
created_at INTEGER NOT NULL,
|
||||
display_name TEXT
|
||||
);
|
||||
CREATE TABLE trusted_apps (
|
||||
user_id INTEGER NOT NULL,
|
||||
app_id TEXT NOT NULL,
|
||||
app_secret TEXT NOT NULL,
|
||||
PRIMARY KEY (user_id, app_id)
|
||||
);
|
||||
CREATE TABLE user_residency (
|
||||
user_id INTEGER PRIMARY KEY,
|
||||
username TEXT NOT NULL,
|
||||
lifecycle_state TEXT NOT NULL,
|
||||
data_state TEXT NOT NULL,
|
||||
credential_origin TEXT NOT NULL,
|
||||
updated_at INTEGER NOT NULL
|
||||
);
|
||||
INSERT INTO users VALUES (1, 'alice', 'old-key', NULL, NULL, 1, NULL);
|
||||
INSERT INTO user_residency VALUES (1, 'alice', 'released', 'empty', 'external', 42);
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
let mut user = UserProfile::new_with_created_at(
|
||||
1,
|
||||
"alice".into(),
|
||||
Some("Alice".into()),
|
||||
"new-key".into(),
|
||||
None,
|
||||
None,
|
||||
1,
|
||||
);
|
||||
user.trusted_apps.insert("app".into(), "secret".into());
|
||||
|
||||
let transaction = connection.transaction().unwrap();
|
||||
persist_user_profile(&transaction, &user).unwrap();
|
||||
transaction.commit().unwrap();
|
||||
|
||||
let residency: (String, String, String, String, i64) = connection
|
||||
.query_row(
|
||||
"SELECT username, lifecycle_state, data_state, credential_origin, updated_at FROM user_residency WHERE user_id = ?1",
|
||||
params![1],
|
||||
|row| Ok((row.get(0)?, row.get(1)?, row.get(2)?, row.get(3)?, row.get(4)?)),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
residency,
|
||||
(
|
||||
"alice".into(),
|
||||
"released".into(),
|
||||
"empty".into(),
|
||||
"external".into(),
|
||||
42,
|
||||
)
|
||||
);
|
||||
let trusted_app_count: i64 = connection
|
||||
.query_row(
|
||||
"SELECT COUNT(*) FROM trusted_apps WHERE user_id = ?1",
|
||||
params![1],
|
||||
|row| row.get(0),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(trusted_app_count, 1);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn save_app_data(user_id: i64, app_identifier: &str, data: &str) {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use once_cell::sync::Lazy;
|
||||
use once_cell::sync::OnceCell;
|
||||
use r2d2::ManageConnection;
|
||||
use rusqlite::{Connection, Transaction};
|
||||
use std::path::PathBuf;
|
||||
|
|
@ -10,15 +10,16 @@ use crate::storage_error::StorageError;
|
|||
const DB_NAME: &str = "messages";
|
||||
|
||||
/// A simple r2d2 manager for rusqlite connections.
|
||||
pub struct SqliteManager;
|
||||
pub struct SqliteManager {
|
||||
database_path: PathBuf,
|
||||
}
|
||||
|
||||
impl ManageConnection for SqliteManager {
|
||||
type Connection = Connection;
|
||||
type Error = rusqlite::Error;
|
||||
|
||||
fn connect(&self) -> Result<Connection, rusqlite::Error> {
|
||||
let path = db_file_path(DB_NAME);
|
||||
let conn = Connection::open(path)?;
|
||||
let conn = Connection::open(&self.database_path)?;
|
||||
conn.execute_batch("PRAGMA journal_mode = WAL; PRAGMA synchronous = FULL;")?;
|
||||
conn.busy_timeout(Duration::from_millis(250))?;
|
||||
Ok(conn)
|
||||
|
|
@ -33,18 +34,43 @@ impl ManageConnection for SqliteManager {
|
|||
}
|
||||
}
|
||||
|
||||
static POOL: Lazy<Arc<r2d2::Pool<SqliteManager>>> = Lazy::new(|| {
|
||||
let manager = SqliteManager;
|
||||
static POOL: OnceCell<Arc<r2d2::Pool<SqliteManager>>> = OnceCell::new();
|
||||
|
||||
fn create_pool(database_path: PathBuf) -> Result<Arc<r2d2::Pool<SqliteManager>>, StorageError> {
|
||||
create_pool_with_timeout(database_path, Duration::from_secs(30))
|
||||
}
|
||||
|
||||
fn create_pool_with_timeout(
|
||||
database_path: PathBuf,
|
||||
connection_timeout: Duration,
|
||||
) -> Result<Arc<r2d2::Pool<SqliteManager>>, StorageError> {
|
||||
let manager = SqliteManager { database_path };
|
||||
let pool = r2d2::Pool::builder()
|
||||
.max_size(8)
|
||||
.connection_timeout(connection_timeout)
|
||||
.build(manager)
|
||||
.expect("Failed to create database connection pool");
|
||||
run_migrations(&pool).expect("Failed to run database migrations");
|
||||
Arc::new(pool)
|
||||
});
|
||||
.map_err(|error| StorageError::Pool(error.to_string()))?;
|
||||
run_migrations(&pool)?;
|
||||
Ok(Arc::new(pool))
|
||||
}
|
||||
|
||||
pub fn pool() -> Arc<r2d2::Pool<SqliteManager>> {
|
||||
POOL.clone()
|
||||
/// Opens the SQLite pool and applies all schema migrations.
|
||||
///
|
||||
/// Daemon startup calls this after database verification and before storage is
|
||||
/// reported healthy, so connection and migration failures become a storage
|
||||
/// component failure instead of a lazy-initialization panic.
|
||||
pub fn initialize_database() -> Result<(), StorageError> {
|
||||
let storage_dir = iota_util::file_util::storage_directory();
|
||||
std::fs::create_dir_all(&storage_dir)?;
|
||||
let database_path = storage_dir.join(format!("{DB_NAME}.sqlite3"));
|
||||
POOL.get_or_try_init(|| create_pool(database_path))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn pool() -> Result<Arc<r2d2::Pool<SqliteManager>>, StorageError> {
|
||||
POOL.get()
|
||||
.cloned()
|
||||
.ok_or_else(|| StorageError::Other("database has not been initialized".into()))
|
||||
}
|
||||
|
||||
pub fn with_db<T, F>(f: F) -> Result<T, StorageError>
|
||||
|
|
@ -52,7 +78,8 @@ where
|
|||
F: FnOnce(&Connection) -> Result<T, StorageError>,
|
||||
{
|
||||
blocking_region(|| {
|
||||
let conn = POOL.get().map_err(|e| StorageError::Pool(e.to_string()))?;
|
||||
let pool = pool()?;
|
||||
let conn = pool.get().map_err(|e| StorageError::Pool(e.to_string()))?;
|
||||
f(&conn)
|
||||
})
|
||||
}
|
||||
|
|
@ -62,7 +89,8 @@ where
|
|||
F: FnOnce(&Transaction<'_>) -> Result<T, StorageError>,
|
||||
{
|
||||
blocking_region(|| {
|
||||
let mut conn = POOL.get().map_err(|e| StorageError::Pool(e.to_string()))?;
|
||||
let pool = pool()?;
|
||||
let mut conn = pool.get().map_err(|e| StorageError::Pool(e.to_string()))?;
|
||||
let tx = conn.transaction_with_behavior(rusqlite::TransactionBehavior::Immediate)?;
|
||||
let value = f(&tx)?;
|
||||
tx.commit()?;
|
||||
|
|
@ -962,6 +990,18 @@ pub fn create_general_messages_db() -> Result<Arc<std::sync::Mutex<Connection>>,
|
|||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn pool_creation_failure_is_returned() -> Result<(), StorageError> {
|
||||
let not_a_directory =
|
||||
std::env::temp_dir().join(format!("iota-storage-pool-test-{}", std::process::id()));
|
||||
std::fs::File::create(¬_a_directory)?;
|
||||
let database_path = not_a_directory.join("messages.sqlite3");
|
||||
|
||||
assert!(create_pool_with_timeout(database_path, Duration::from_millis(1)).is_err());
|
||||
std::fs::remove_file(not_a_directory)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resumes_migration_when_height_exists_before_its_version() -> Result<(), StorageError> {
|
||||
let conn = Connection::open_in_memory()?;
|
||||
|
|
|
|||
Loading…
Reference in a new issue