[Fix] Connections
This commit is contained in:
parent
afc1832fb7
commit
dd69b5bd97
19 changed files with 1010 additions and 341 deletions
|
|
@ -1,3 +1,4 @@
|
|||
pub mod contact;
|
||||
pub mod user_manager;
|
||||
pub mod contact;
|
||||
pub mod pending_operations;
|
||||
pub mod user_manager;
|
||||
pub mod user_profile;
|
||||
|
|
|
|||
173
iota-storage/src/users/pending_operations.rs
Normal file
173
iota-storage/src/users/pending_operations.rs
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
use crate::storage_error::StorageError;
|
||||
use crate::util::db;
|
||||
use rusqlite::params;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum PendingUserOperationKind {
|
||||
Create,
|
||||
Attach,
|
||||
Release,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum PendingUserOperationPhase {
|
||||
Prepared,
|
||||
CredentialWritten,
|
||||
RemoteCommitted,
|
||||
LocalCommitted,
|
||||
}
|
||||
|
||||
impl PendingUserOperationPhase {
|
||||
fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
Self::Prepared => "prepared",
|
||||
Self::CredentialWritten => "credential_written",
|
||||
Self::RemoteCommitted => "remote_committed",
|
||||
Self::LocalCommitted => "local_committed",
|
||||
}
|
||||
}
|
||||
|
||||
fn parse(value: &str) -> Result<Self, StorageError> {
|
||||
match value {
|
||||
"prepared" => Ok(Self::Prepared),
|
||||
"credential_written" => Ok(Self::CredentialWritten),
|
||||
"remote_committed" => Ok(Self::RemoteCommitted),
|
||||
"local_committed" => Ok(Self::LocalCommitted),
|
||||
_ => Err(StorageError::Other(
|
||||
"unknown pending user operation phase".into(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PendingUserOperationKind {
|
||||
fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
Self::Create => "create",
|
||||
Self::Attach => "attach",
|
||||
Self::Release => "release",
|
||||
}
|
||||
}
|
||||
|
||||
fn parse(value: &str) -> Result<Self, StorageError> {
|
||||
match value {
|
||||
"create" => Ok(Self::Create),
|
||||
"attach" => Ok(Self::Attach),
|
||||
"release" => Ok(Self::Release),
|
||||
_ => Err(StorageError::Other("unknown pending user operation".into())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct PendingUserOperation {
|
||||
pub user_id: i64,
|
||||
pub operation: PendingUserOperationKind,
|
||||
pub username: String,
|
||||
pub public_key: Option<String>,
|
||||
pub private_key_hash: Option<String>,
|
||||
pub reset_token: Option<String>,
|
||||
pub registration_token: Option<String>,
|
||||
pub phase: PendingUserOperationPhase,
|
||||
pub created_at: i64,
|
||||
}
|
||||
|
||||
pub fn upsert(operation: &PendingUserOperation) -> Result<(), StorageError> {
|
||||
db::with_immediate_transaction(|tx| {
|
||||
tx.execute(
|
||||
r#"
|
||||
INSERT INTO pending_user_operations (
|
||||
user_id, operation, username, public_key, private_key_hash,
|
||||
reset_token, registration_token, phase, created_at
|
||||
) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9)
|
||||
ON CONFLICT(user_id) DO UPDATE SET
|
||||
operation = excluded.operation,
|
||||
username = excluded.username,
|
||||
public_key = excluded.public_key,
|
||||
private_key_hash = excluded.private_key_hash,
|
||||
reset_token = excluded.reset_token,
|
||||
registration_token = excluded.registration_token,
|
||||
phase = excluded.phase,
|
||||
created_at = excluded.created_at
|
||||
"#,
|
||||
params![
|
||||
operation.user_id,
|
||||
operation.operation.as_str(),
|
||||
operation.username,
|
||||
operation.public_key,
|
||||
operation.private_key_hash,
|
||||
operation.reset_token,
|
||||
operation.registration_token,
|
||||
operation.phase.as_str(),
|
||||
operation.created_at,
|
||||
],
|
||||
)?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_all() -> Result<Vec<PendingUserOperation>, StorageError> {
|
||||
db::with_db(|conn| {
|
||||
let mut statement = conn.prepare(
|
||||
"SELECT user_id, operation, username, public_key, private_key_hash, reset_token, registration_token, phase, created_at FROM pending_user_operations ORDER BY created_at",
|
||||
)?;
|
||||
let rows = statement.query_map([], |row| {
|
||||
Ok((
|
||||
row.get::<_, i64>(0)?,
|
||||
row.get::<_, String>(1)?,
|
||||
row.get::<_, String>(2)?,
|
||||
row.get::<_, Option<String>>(3)?,
|
||||
row.get::<_, Option<String>>(4)?,
|
||||
row.get::<_, Option<String>>(5)?,
|
||||
row.get::<_, Option<String>>(6)?,
|
||||
row.get::<_, String>(7)?,
|
||||
row.get::<_, i64>(8)?,
|
||||
))
|
||||
})?;
|
||||
rows.map(|row| {
|
||||
let (
|
||||
user_id,
|
||||
operation,
|
||||
username,
|
||||
public_key,
|
||||
private_key_hash,
|
||||
reset_token,
|
||||
registration_token,
|
||||
phase,
|
||||
created_at,
|
||||
) = row?;
|
||||
Ok(PendingUserOperation {
|
||||
user_id,
|
||||
operation: PendingUserOperationKind::parse(&operation)?,
|
||||
username,
|
||||
public_key,
|
||||
private_key_hash,
|
||||
reset_token,
|
||||
registration_token,
|
||||
phase: PendingUserOperationPhase::parse(&phase)?,
|
||||
created_at,
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn update_phase(user_id: i64, phase: PendingUserOperationPhase) -> Result<(), StorageError> {
|
||||
db::with_immediate_transaction(|tx| {
|
||||
tx.execute(
|
||||
"UPDATE pending_user_operations SET phase = ?1 WHERE user_id = ?2",
|
||||
params![phase.as_str(), user_id],
|
||||
)?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn remove(user_id: i64) -> Result<(), StorageError> {
|
||||
db::with_immediate_transaction(|tx| {
|
||||
tx.execute(
|
||||
"DELETE FROM pending_user_operations WHERE user_id = ?1",
|
||||
[user_id],
|
||||
)?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
|
@ -32,8 +32,8 @@ pub fn add_user(user: UserProfile) {
|
|||
}
|
||||
|
||||
pub fn try_add_user(user: UserProfile) -> Result<(), crate::storage_error::StorageError> {
|
||||
db::with_db(|conn| {
|
||||
conn.execute(
|
||||
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)
|
||||
|
|
@ -56,7 +56,7 @@ pub fn try_add_user(user: UserProfile) -> Result<(), crate::storage_error::Stora
|
|||
)?;
|
||||
|
||||
for (app_id, app_secret) in &user.trusted_apps {
|
||||
conn.execute(
|
||||
tx.execute(
|
||||
r#"
|
||||
INSERT OR REPLACE INTO trusted_apps (user_id, app_id, app_secret)
|
||||
VALUES (?1, ?2, ?3)
|
||||
|
|
@ -64,7 +64,7 @@ pub fn try_add_user(user: UserProfile) -> Result<(), crate::storage_error::Stora
|
|||
params![user.user_id, app_id, app_secret],
|
||||
)?;
|
||||
}
|
||||
conn.execute(
|
||||
tx.execute(
|
||||
r#"INSERT INTO user_residency (user_id, username, lifecycle_state, data_state, updated_at)
|
||||
VALUES (?1, ?2, 'managed', COALESCE((SELECT data_state FROM user_residency WHERE user_id = ?1), 'present'), ?3)
|
||||
ON CONFLICT(user_id) DO UPDATE SET username = excluded.username, lifecycle_state = 'managed', updated_at = excluded.updated_at"#,
|
||||
|
|
@ -93,7 +93,7 @@ pub fn get_user_by_username(username: &str) -> Option<UserProfile> {
|
|||
private_key_hash: r.get(3)?,
|
||||
created_at: r.get(5)?,
|
||||
reset_token: r.get(4)?,
|
||||
trusted_apps: load_trusted_apps(user_id),
|
||||
trusted_apps: std::collections::HashMap::new(),
|
||||
})
|
||||
},
|
||||
) {
|
||||
|
|
@ -110,8 +110,8 @@ pub fn get_user_by_username(username: &str) -> Option<UserProfile> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn get_user(user_id: i64) -> Option<UserProfile> {
|
||||
match db::with_db(|conn| {
|
||||
pub fn get_user(user_id: i64) -> Result<Option<UserProfile>, crate::storage_error::StorageError> {
|
||||
let user = db::with_db(|conn| {
|
||||
match conn.query_row(
|
||||
"SELECT user_id, username, public_key, private_key_hash, reset_token, created_at, display_name FROM users WHERE user_id = ?1 LIMIT 1",
|
||||
params![user_id],
|
||||
|
|
@ -125,7 +125,7 @@ pub fn get_user(user_id: i64) -> Option<UserProfile> {
|
|||
private_key_hash: r.get(3)?,
|
||||
created_at: r.get(5)?,
|
||||
reset_token: r.get(4)?,
|
||||
trusted_apps: load_trusted_apps(user_id),
|
||||
trusted_apps: std::collections::HashMap::new(),
|
||||
})
|
||||
},
|
||||
) {
|
||||
|
|
@ -133,13 +133,12 @@ pub fn get_user(user_id: i64) -> Option<UserProfile> {
|
|||
Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
|
||||
Err(e) => Err(e.into()),
|
||||
}
|
||||
}) {
|
||||
Ok(opt) => opt,
|
||||
Err(e) => {
|
||||
eprintln!("Error querying user: {}", e);
|
||||
None
|
||||
}
|
||||
}
|
||||
})?;
|
||||
user.map(|mut user| {
|
||||
user.trusted_apps = load_trusted_apps(user_id)?;
|
||||
Ok(user)
|
||||
})
|
||||
.transpose()
|
||||
}
|
||||
|
||||
pub fn get_users() -> Vec<UserProfile> {
|
||||
|
|
@ -177,7 +176,7 @@ pub fn get_users() -> Vec<UserProfile> {
|
|||
for row in rows {
|
||||
match row {
|
||||
Ok(mut user) => {
|
||||
user.trusted_apps = load_trusted_apps(user.user_id);
|
||||
user.trusted_apps = load_trusted_apps(user.user_id)?;
|
||||
out.push(user);
|
||||
}
|
||||
Err(e) => eprintln!("Failed to read user row: {}", e),
|
||||
|
|
@ -193,8 +192,10 @@ pub fn get_users() -> Vec<UserProfile> {
|
|||
}
|
||||
}
|
||||
|
||||
fn load_trusted_apps(user_id: i64) -> std::collections::HashMap<String, String> {
|
||||
match db::with_db(|conn| {
|
||||
fn load_trusted_apps(
|
||||
user_id: i64,
|
||||
) -> Result<std::collections::HashMap<String, String>, crate::storage_error::StorageError> {
|
||||
db::with_db(|conn| {
|
||||
let mut stmt =
|
||||
conn.prepare("SELECT app_id, app_secret FROM trusted_apps WHERE user_id = ?1")?;
|
||||
let rows = stmt.query_map(params![user_id], |r| {
|
||||
|
|
@ -203,18 +204,11 @@ fn load_trusted_apps(user_id: i64) -> std::collections::HashMap<String, String>
|
|||
|
||||
let mut map = std::collections::HashMap::new();
|
||||
for row in rows {
|
||||
if let Ok((k, v)) = row {
|
||||
map.insert(k, v);
|
||||
}
|
||||
let (key, value) = row?;
|
||||
map.insert(key, value);
|
||||
}
|
||||
Ok(map)
|
||||
}) {
|
||||
Ok(m) => m,
|
||||
Err(e) => {
|
||||
eprintln!("Failed to load trusted apps: {}", e);
|
||||
std::collections::HashMap::new()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn remove_user(user_id: i64) {
|
||||
|
|
@ -233,9 +227,11 @@ pub fn remove_user(user_id: i64) {
|
|||
/// Remove only local management authority. Hosted content is intentionally
|
||||
/// retained and is indexed as released for a later purge operation.
|
||||
pub fn release_user(user_id: i64) -> Result<(), crate::storage_error::StorageError> {
|
||||
let username = get_user(user_id).map(|user| user.username).ok_or_else(|| {
|
||||
crate::storage_error::StorageError::Other("managed user was not found".into())
|
||||
})?;
|
||||
let username = get_user(user_id)?
|
||||
.map(|user| user.username)
|
||||
.ok_or_else(|| {
|
||||
crate::storage_error::StorageError::Other("managed user was not found".into())
|
||||
})?;
|
||||
db::with_db(|conn| {
|
||||
let tx = conn.unchecked_transaction()?;
|
||||
tx.execute(
|
||||
|
|
@ -252,7 +248,7 @@ pub fn release_user(user_id: i64) -> Result<(), crate::storage_error::StorageErr
|
|||
tx.commit()?;
|
||||
Ok(())
|
||||
})?;
|
||||
remove_user_credential(user_id)
|
||||
remove_user_credential(user_id, Some(&username))
|
||||
.map_err(|error| crate::storage_error::StorageError::Other(error.to_string()))
|
||||
}
|
||||
|
||||
|
|
@ -312,6 +308,12 @@ pub fn purge_user_data(user_id: i64) -> Result<(), crate::storage_error::Storage
|
|||
/// Complete local erasure is idempotent and is the target for a durable
|
||||
/// Omega-hosted erasure request after account deletion.
|
||||
pub fn erase_user_locally(user_id: i64) -> Result<(), crate::storage_error::StorageError> {
|
||||
let username = get_user(user_id)?.map(|user| user.username).or_else(|| {
|
||||
get_residency()
|
||||
.into_iter()
|
||||
.find(|entry| entry.user_id == user_id)
|
||||
.map(|entry| entry.username)
|
||||
});
|
||||
purge_user_data(user_id)?;
|
||||
db::with_db(|conn| {
|
||||
conn.execute(
|
||||
|
|
@ -325,7 +327,7 @@ pub fn erase_user_locally(user_id: i64) -> Result<(), crate::storage_error::Stor
|
|||
)?;
|
||||
Ok(())
|
||||
})?;
|
||||
remove_user_credential(user_id)
|
||||
remove_user_credential(user_id, username.as_deref())
|
||||
.map_err(|error| crate::storage_error::StorageError::Other(error.to_string()))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -523,7 +523,7 @@ pub fn add_message(message: NewMessage<'_>) -> Result<i64, StorageError> {
|
|||
|
||||
let mut contact = crate::users::contact::Contact::new(external_user);
|
||||
contact.set_last_message_at(send_time);
|
||||
crate::util::chats_util::mod_user(storage_owner, &contact);
|
||||
crate::util::chats_util::mod_user(storage_owner, &contact)?;
|
||||
Ok(msg_id)
|
||||
}
|
||||
|
||||
|
|
@ -546,8 +546,17 @@ pub fn change_message_state_by_relay_id(
|
|||
return Ok(());
|
||||
};
|
||||
let state = MessageState::from_str(¤t).upgrade(new_state).as_str();
|
||||
tx.execute("UPDATE messages SET message_state = ?1 WHERE id = ?2", params![state, msg_id])?;
|
||||
sync::record_event(&tx, storage_owner, EntityType::Message, msg_id, Operation::Upsert)?;
|
||||
tx.execute(
|
||||
"UPDATE messages SET message_state = ?1 WHERE id = ?2",
|
||||
params![state, msg_id],
|
||||
)?;
|
||||
sync::record_event(
|
||||
&tx,
|
||||
storage_owner,
|
||||
EntityType::Message,
|
||||
msg_id,
|
||||
Operation::Upsert,
|
||||
)?;
|
||||
tx.commit()?;
|
||||
Ok(())
|
||||
})
|
||||
|
|
@ -581,7 +590,9 @@ pub fn record_message_receipt(
|
|||
return Err(StorageError::Other("message receipt target was not found".into()));
|
||||
};
|
||||
if external_user != receipt_signer_id {
|
||||
return Err(StorageError::Other("message receipt signer is not the chat partner".into()));
|
||||
return Err(StorageError::Other(
|
||||
"message receipt signer is not the chat partner".into(),
|
||||
));
|
||||
}
|
||||
tx.execute(
|
||||
"INSERT OR IGNORE INTO message_receipts (storage_owner, target_signer_id, target_message_id, receipt_signer_id, receipt_message_id, receipt_type, event_at, recorded_at) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)",
|
||||
|
|
@ -592,9 +603,11 @@ pub fn record_message_receipt(
|
|||
} else {
|
||||
("client_received_at", "client_received_recorded_at")
|
||||
};
|
||||
let state = MessageState::from_str(
|
||||
&tx.query_row("SELECT message_state FROM messages WHERE id = ?1", [message_id], |row| row.get::<_, String>(0))?,
|
||||
)
|
||||
let state = MessageState::from_str(&tx.query_row(
|
||||
"SELECT message_state FROM messages WHERE id = ?1",
|
||||
[message_id],
|
||||
|row| row.get::<_, String>(0),
|
||||
)?)
|
||||
.upgrade(MessageState::from_str(receipt_type))
|
||||
.as_str()
|
||||
.to_string();
|
||||
|
|
@ -602,7 +615,13 @@ pub fn record_message_receipt(
|
|||
&format!("UPDATE messages SET {state_column} = COALESCE({state_column}, ?1), {recorded_column} = COALESCE({recorded_column}, ?2), message_state = ?3 WHERE id = ?4"),
|
||||
params![event_at, recorded_at, state, message_id],
|
||||
)?;
|
||||
sync::record_event(&tx, storage_owner, EntityType::Message, message_id, Operation::Upsert)?;
|
||||
sync::record_event(
|
||||
&tx,
|
||||
storage_owner,
|
||||
EntityType::Message,
|
||||
message_id,
|
||||
Operation::Upsert,
|
||||
)?;
|
||||
tx.commit()?;
|
||||
Ok(())
|
||||
})
|
||||
|
|
@ -630,7 +649,13 @@ pub fn record_destination_iota_received(
|
|||
"UPDATE messages SET destination_iota_received_at = COALESCE(destination_iota_received_at, ?1), message_state = CASE WHEN message_state = 'sending' THEN 'sent' ELSE message_state END WHERE id = ?2",
|
||||
params![accepted_at, message_id],
|
||||
)?;
|
||||
sync::record_event(&tx, storage_owner, EntityType::Message, message_id, Operation::Upsert)?;
|
||||
sync::record_event(
|
||||
&tx,
|
||||
storage_owner,
|
||||
EntityType::Message,
|
||||
message_id,
|
||||
Operation::Upsert,
|
||||
)?;
|
||||
tx.commit()?;
|
||||
Ok(())
|
||||
})
|
||||
|
|
|
|||
|
|
@ -14,9 +14,8 @@ pub fn has_user(storage_owner: i64, user_id: i64) -> Result<bool, StorageError>
|
|||
})
|
||||
}
|
||||
|
||||
pub fn mod_user(storage_owner: i64, contact: &Contact) {
|
||||
if let Err(e) = db::with_db(|conn| {
|
||||
let tx = conn.unchecked_transaction()?;
|
||||
pub fn mod_user(storage_owner: i64, contact: &Contact) -> Result<(), StorageError> {
|
||||
db::with_immediate_transaction(|tx| {
|
||||
tx.execute(
|
||||
r#"
|
||||
INSERT INTO contacts (storage_owner, user_id, user_name, last_message_at)
|
||||
|
|
@ -28,37 +27,34 @@ pub fn mod_user(storage_owner: i64, contact: &Contact) {
|
|||
params![
|
||||
storage_owner,
|
||||
contact.user_id,
|
||||
contact.user_name.clone(),
|
||||
contact.user_name,
|
||||
contact.last_message_at,
|
||||
],
|
||||
)?;
|
||||
sync::record_event(
|
||||
&tx,
|
||||
tx,
|
||||
storage_owner,
|
||||
EntityType::Contact,
|
||||
contact.user_id,
|
||||
Operation::Upsert,
|
||||
)?;
|
||||
tx.commit()?;
|
||||
Ok(())
|
||||
}) {
|
||||
eprintln!("Failed to mod_user: {}", e);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_users_by_ids(storage_owner: i64, ids: &[i64]) -> Vec<Contact> {
|
||||
pub fn get_users_by_ids(storage_owner: i64, ids: &[i64]) -> Result<Vec<Contact>, StorageError> {
|
||||
if ids.is_empty() {
|
||||
return Vec::new();
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
let wanted: std::collections::HashSet<i64> = ids.iter().copied().collect();
|
||||
get_users(storage_owner)
|
||||
Ok(get_users(storage_owner)?
|
||||
.into_iter()
|
||||
.filter(|contact| wanted.contains(&contact.user_id))
|
||||
.collect()
|
||||
.collect())
|
||||
}
|
||||
|
||||
pub fn get_user(storage_owner: i64, user_id: i64) -> Option<Contact> {
|
||||
match db::with_db(|conn| {
|
||||
pub fn get_user(storage_owner: i64, user_id: i64) -> Result<Option<Contact>, StorageError> {
|
||||
db::with_db(|conn| {
|
||||
match conn.query_row(
|
||||
r#"
|
||||
SELECT user_id, user_name, last_message_at
|
||||
|
|
@ -79,17 +75,11 @@ pub fn get_user(storage_owner: i64, user_id: i64) -> Option<Contact> {
|
|||
Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
|
||||
Err(e) => Err(e.into()),
|
||||
}
|
||||
}) {
|
||||
Ok(opt) => opt,
|
||||
Err(e) => {
|
||||
eprintln!("Error querying user in get_user: {}", e);
|
||||
None
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_users(storage_owner: i64) -> Vec<Contact> {
|
||||
match db::with_db(|conn| {
|
||||
pub fn get_users(storage_owner: i64) -> Result<Vec<Contact>, StorageError> {
|
||||
db::with_db(|conn| {
|
||||
let mut stmt = conn.prepare(
|
||||
r#"
|
||||
SELECT user_id, user_name, last_message_at
|
||||
|
|
@ -112,17 +102,8 @@ pub fn get_users(storage_owner: i64) -> Vec<Contact> {
|
|||
|
||||
let mut out = Vec::new();
|
||||
for row in rows {
|
||||
match row {
|
||||
Ok(contact) => out.push(contact),
|
||||
Err(e) => eprintln!("Failed to read contact row: {}", e),
|
||||
}
|
||||
out.push(row?);
|
||||
}
|
||||
Ok(out)
|
||||
}) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
eprintln!("Failed to query contacts in get_users: {}", e);
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -421,9 +421,15 @@ fn run_migrations_on_connection(conn: &Connection) -> Result<(), StorageError> {
|
|||
("relay_message_id", "relay_message_id TEXT"),
|
||||
("authored_at", "authored_at INTEGER"),
|
||||
("origin_iota_received_at", "origin_iota_received_at INTEGER"),
|
||||
("destination_iota_received_at", "destination_iota_received_at INTEGER"),
|
||||
(
|
||||
"destination_iota_received_at",
|
||||
"destination_iota_received_at INTEGER",
|
||||
),
|
||||
("client_received_at", "client_received_at INTEGER"),
|
||||
("client_received_recorded_at", "client_received_recorded_at INTEGER"),
|
||||
(
|
||||
"client_received_recorded_at",
|
||||
"client_received_recorded_at INTEGER",
|
||||
),
|
||||
("read_at", "read_at INTEGER"),
|
||||
("read_recorded_at", "read_recorded_at INTEGER"),
|
||||
] {
|
||||
|
|
@ -489,6 +495,38 @@ fn run_migrations_on_connection(conn: &Connection) -> Result<(), StorageError> {
|
|||
)?;
|
||||
}
|
||||
|
||||
if current_version < 13 {
|
||||
conn.execute_batch(
|
||||
r#"
|
||||
CREATE TABLE IF NOT EXISTS pending_user_operations (
|
||||
user_id INTEGER PRIMARY KEY,
|
||||
operation TEXT NOT NULL
|
||||
CHECK (operation IN ('create', 'attach', 'release')),
|
||||
username TEXT NOT NULL,
|
||||
public_key TEXT,
|
||||
private_key_hash TEXT,
|
||||
reset_token TEXT,
|
||||
registration_token TEXT,
|
||||
created_at INTEGER NOT NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_pending_user_operations_operation
|
||||
ON pending_user_operations (operation, created_at);
|
||||
PRAGMA user_version = 13;
|
||||
"#,
|
||||
)?;
|
||||
}
|
||||
|
||||
if current_version < 14 {
|
||||
conn.execute_batch(
|
||||
r#"
|
||||
ALTER TABLE pending_user_operations
|
||||
ADD COLUMN phase TEXT NOT NULL DEFAULT 'prepared'
|
||||
CHECK (phase IN ('prepared', 'credential_written', 'remote_committed', 'local_committed'));
|
||||
PRAGMA user_version = 14;
|
||||
"#,
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
@ -558,7 +596,7 @@ mod tests {
|
|||
run_migrations_on_connection(&conn)?;
|
||||
|
||||
let version: i64 = conn.pragma_query_value(None, "user_version", |row| row.get(0))?;
|
||||
assert_eq!(version, 12);
|
||||
assert_eq!(version, 13);
|
||||
for column in ["height", "reply_to", "edited_count", "deleted_by_external"] {
|
||||
let mut statement =
|
||||
conn.prepare("SELECT 1 FROM pragma_table_info('messages') WHERE name = ?1")?;
|
||||
|
|
@ -577,7 +615,7 @@ mod tests {
|
|||
run_migrations_on_connection(&conn)?;
|
||||
run_migrations_on_connection(&conn)?;
|
||||
let version: i64 = conn.pragma_query_value(None, "user_version", |row| row.get(0))?;
|
||||
assert_eq!(version, 12);
|
||||
assert_eq!(version, 13);
|
||||
for table in [
|
||||
"sync_heads",
|
||||
"sync_events",
|
||||
|
|
@ -587,6 +625,7 @@ mod tests {
|
|||
"pending_relays",
|
||||
"relay_inbox",
|
||||
"synced_settings",
|
||||
"pending_user_operations",
|
||||
] {
|
||||
let exists: i64 = conn.query_row(
|
||||
"SELECT COUNT(*) FROM sqlite_master WHERE type = 'table' AND name = ?1",
|
||||
|
|
@ -611,8 +650,17 @@ mod tests {
|
|||
run_migrations_on_connection(&conn)?;
|
||||
|
||||
let version: i64 = conn.pragma_query_value(None, "user_version", |row| row.get(0))?;
|
||||
assert_eq!(version, 12);
|
||||
for column in ["id", "user_id", "scope_type", "scope_key", "name", "payload", "revision", "deleted"] {
|
||||
assert_eq!(version, 13);
|
||||
for column in [
|
||||
"id",
|
||||
"user_id",
|
||||
"scope_type",
|
||||
"scope_key",
|
||||
"name",
|
||||
"payload",
|
||||
"revision",
|
||||
"deleted",
|
||||
] {
|
||||
let mut statement =
|
||||
conn.prepare("SELECT 1 FROM pragma_table_info('synced_settings') WHERE name = ?1")?;
|
||||
assert!(statement.exists([column])?);
|
||||
|
|
|
|||
|
|
@ -291,30 +291,15 @@ mod tests {
|
|||
)
|
||||
.unwrap();
|
||||
let transaction = connection.unchecked_transaction().unwrap();
|
||||
let message = super::record_event(
|
||||
&transaction,
|
||||
1,
|
||||
EntityType::Message,
|
||||
10,
|
||||
Operation::Upsert,
|
||||
)
|
||||
.unwrap();
|
||||
let setting = super::record_event(
|
||||
&transaction,
|
||||
1,
|
||||
EntityType::Setting,
|
||||
11,
|
||||
Operation::Upsert,
|
||||
)
|
||||
.unwrap();
|
||||
let contact = super::record_event(
|
||||
&transaction,
|
||||
1,
|
||||
EntityType::Contact,
|
||||
12,
|
||||
Operation::Upsert,
|
||||
)
|
||||
.unwrap();
|
||||
let message =
|
||||
super::record_event(&transaction, 1, EntityType::Message, 10, Operation::Upsert)
|
||||
.unwrap();
|
||||
let setting =
|
||||
super::record_event(&transaction, 1, EntityType::Setting, 11, Operation::Upsert)
|
||||
.unwrap();
|
||||
let contact =
|
||||
super::record_event(&transaction, 1, EntityType::Contact, 12, Operation::Upsert)
|
||||
.unwrap();
|
||||
transaction.commit().unwrap();
|
||||
|
||||
assert_eq!((message, setting, contact), (1, 2, 3));
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use crate::storage_error::StorageError;
|
||||
use crate::util::db;
|
||||
use crate::util::sync::{self, EntityType, Operation};
|
||||
use rusqlite::{params, Connection, OptionalExtension, Row, Transaction};
|
||||
use rusqlite::{Connection, OptionalExtension, Row, Transaction, params};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum SettingScope {
|
||||
|
|
@ -401,8 +401,8 @@ pub(crate) fn delete_scope_in_tx(
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{
|
||||
delete_in_tx, get_from_connection, is_valid_name, list_by_ids_from_connection,
|
||||
list_from_connection, set_in_tx, SettingScope,
|
||||
SettingScope, delete_in_tx, get_from_connection, is_valid_name,
|
||||
list_by_ids_from_connection, list_from_connection, set_in_tx,
|
||||
};
|
||||
use rusqlite::Connection;
|
||||
|
||||
|
|
@ -618,9 +618,11 @@ mod tests {
|
|||
assert!(deleted.changed);
|
||||
assert_eq!(journal_operation, "delete");
|
||||
assert!(list_from_connection(&connection, 1).unwrap().is_empty());
|
||||
assert!(list_by_ids_from_connection(&connection, 1, &[stored.id])
|
||||
.unwrap()
|
||||
.is_empty());
|
||||
assert!(
|
||||
list_by_ids_from_connection(&connection, 1, &[stored.id])
|
||||
.unwrap()
|
||||
.is_empty()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
Loading…
Reference in a new issue