[Fix] Connection Management

This commit is contained in:
Alex Emmet 2026-09-13 20:58:41 +02:00
commit 3f2ac18333
No known key found for this signature in database
122 changed files with 19970 additions and 5263 deletions

View file

@ -50,7 +50,9 @@ impl MessageState {
pub struct StoredMessage {
pub id: i64,
pub external_user: i64,
pub external_principal: Option<iota_identity::PrincipalHandle>,
pub relay_signer_id: Option<i64>,
pub relay_signer_principal: Option<iota_identity::PrincipalHandle>,
pub relay_message_id: Option<String>,
pub message_time: i64,
pub authored_at: Option<i64>,
@ -74,11 +76,13 @@ pub struct StoredMessage {
pub struct NewMessage<'a> {
pub relay_signer_id: i64,
pub relay_signer_principal: iota_identity::PrincipalHandle,
pub relay_message_id: &'a str,
pub authored_at: i64,
pub send_time: i64,
pub storage_owner: i64,
pub external_user: i64,
pub external_principal: iota_identity::PrincipalHandle,
pub sent_by_self: bool,
pub content: &'a str,
pub height: i64,
@ -109,6 +113,26 @@ pub fn edit_message(
update_message_content(
storage_owner,
external_user,
None,
message_time,
editor_id,
new_content,
true,
)
}
pub fn edit_message_for_principal(
storage_owner: i64,
external_user: i64,
external_principal: iota_identity::PrincipalHandle,
message_time: i64,
editor_id: i64,
new_content: &str,
) -> Result<(), StorageError> {
update_message_content(
storage_owner,
external_user,
Some(external_principal),
message_time,
editor_id,
new_content,
@ -133,6 +157,31 @@ pub fn apply_remote_edit(
update_message_content(
storage_owner,
external_user,
None,
message_time,
editor_id,
new_content,
false,
)
}
pub fn apply_remote_edit_for_principal(
storage_owner: i64,
external_user: i64,
external_principal: iota_identity::PrincipalHandle,
message_time: i64,
editor_id: i64,
new_content: &str,
) -> Result<(), StorageError> {
if editor_id != external_user {
return Err(StorageError::Other(
"Remote editor does not match chat partner".into(),
));
}
update_message_content(
storage_owner,
external_user,
Some(external_principal),
message_time,
editor_id,
new_content,
@ -143,6 +192,7 @@ pub fn apply_remote_edit(
fn update_message_content(
storage_owner: i64,
external_user: i64,
external_principal: Option<iota_identity::PrincipalHandle>,
message_time: i64,
editor_id: i64,
new_content: &str,
@ -153,10 +203,20 @@ fn update_message_content(
r#"
SELECT id, content, sent_by_self, history_deleted
FROM messages
WHERE storage_owner = ?1 AND external_user = ?2 AND message_time = ?3
WHERE storage_owner = ?1
AND message_time = ?3
AND (
(?4 IS NULL AND external_user = ?2)
OR external_principal = ?4
)
ORDER BY id DESC LIMIT 1
"#,
params![storage_owner, external_user, message_time],
params![
storage_owner,
external_user,
message_time,
external_principal.map(|principal| principal.0)
],
|row| {
Ok((
row.get::<_, i64>(0)?,
@ -221,16 +281,35 @@ pub fn hard_delete_message(
storage_owner: i64,
external_user: i64,
message_time: i64,
) -> Result<(), StorageError> {
hard_delete_message_with_principal(storage_owner, external_user, None, message_time)
}
fn hard_delete_message_with_principal(
storage_owner: i64,
external_user: i64,
external_principal: Option<iota_identity::PrincipalHandle>,
message_time: i64,
) -> Result<(), StorageError> {
db::with_db(|conn| {
let msg_id = conn
.query_row(
r#"
SELECT id, history_deleted FROM messages
WHERE storage_owner = ?1 AND external_user = ?2 AND message_time = ?3
WHERE storage_owner = ?1
AND message_time = ?3
AND (
(?4 IS NULL AND external_user = ?2)
OR external_principal = ?4
)
ORDER BY id DESC LIMIT 1
"#,
params![storage_owner, external_user, message_time],
params![
storage_owner,
external_user,
message_time,
external_principal.map(|principal| principal.0)
],
|row| row.get::<_, i64>(0),
)
.optional()?;
@ -277,14 +356,14 @@ pub fn remove_message_history_in_tx(
storage_owner: i64,
message_id: i64,
) -> Result<(), StorageError> {
let message: Option<(i64, i64)> = tx
let message: Option<(i64, Option<i64>, i64)> = tx
.query_row(
"SELECT external_user, history_deleted FROM messages WHERE id = ?1 AND storage_owner = ?2",
"SELECT external_user, external_principal, history_deleted FROM messages WHERE id = ?1 AND storage_owner = ?2",
params![message_id, storage_owner],
|row| Ok((row.get(0)?, row.get(1)?)),
|row| Ok((row.get(0)?, row.get(1)?, row.get(2)?)),
)
.optional()?;
let Some((external_user, history_deleted)) = message else {
let Some((external_user, external_principal, history_deleted)) = message else {
return Ok(());
};
if history_deleted != 0 {
@ -295,7 +374,7 @@ pub fn remove_message_history_in_tx(
[message_id],
)?;
tx.execute("DELETE FROM reactions WHERE message_id = ?1", [message_id])?;
tx.execute("DELETE FROM message_receipts WHERE storage_owner = ?1 AND EXISTS (SELECT 1 FROM messages WHERE id = ?2 AND relay_signer_id = message_receipts.target_signer_id AND relay_message_id = message_receipts.target_message_id)", params![storage_owner, message_id])?;
tx.execute("DELETE FROM message_receipts WHERE storage_owner = ?1 AND EXISTS (SELECT 1 FROM messages WHERE id = ?2 AND relay_signer_principal = message_receipts.target_signer_principal AND relay_message_id = message_receipts.target_message_id)", params![storage_owner, message_id])?;
tx.execute("UPDATE messages SET content = '', history_deleted = 1, history_deleted_at = ?2, expires_at = NULL, client_received_at = NULL, client_received_recorded_at = NULL, read_at = NULL, read_recorded_at = NULL WHERE id = ?1", params![message_id, sync::now_millis()])?;
sync::record_event(
tx,
@ -304,7 +383,12 @@ pub fn remove_message_history_in_tx(
message_id,
Operation::Delete,
)?;
update_contact_last_message_in_tx(tx, storage_owner, external_user)?;
update_contact_last_message_in_tx(
tx,
storage_owner,
external_user,
external_principal.map(iota_identity::PrincipalHandle),
)?;
Ok(())
}
@ -312,8 +396,12 @@ fn update_contact_last_message_in_tx(
tx: &Transaction<'_>,
storage_owner: i64,
external_user: i64,
external_principal: Option<iota_identity::PrincipalHandle>,
) -> Result<(), StorageError> {
tx.execute("UPDATE contacts SET last_message_at = (SELECT MAX(COALESCE(destination_iota_received_at, origin_iota_received_at, authored_at, message_time)) FROM messages WHERE storage_owner = ?1 AND external_user = ?2 AND deleted_by_external = 0 AND history_deleted = 0) WHERE storage_owner = ?1 AND user_id = ?2", params![storage_owner, external_user])?;
tx.execute(
"UPDATE contacts SET last_message_at = (SELECT MAX(COALESCE(destination_iota_received_at, origin_iota_received_at, authored_at, message_time)) FROM messages WHERE storage_owner = ?1 AND ((?3 IS NULL AND external_user = ?2) OR external_principal = ?3) AND deleted_by_external = 0 AND history_deleted = 0) WHERE storage_owner = ?1 AND ((?3 IS NULL AND user_id = ?2) OR principal_handle = ?3)",
params![storage_owner, external_user, external_principal.map(|principal| principal.0)],
)?;
Ok(())
}
@ -322,15 +410,17 @@ pub fn purge_message_in_tx(
storage_owner: i64,
message_id: i64,
) -> Result<(), StorageError> {
let (external_user, relay_signer_id, relay_message_id) = tx.query_row(
"SELECT external_user, relay_signer_id, relay_message_id FROM messages WHERE id = ?1 AND storage_owner = ?2",
let (external_user, external_principal, relay_signer_principal, relay_message_id) = tx.query_row(
"SELECT external_user, external_principal, relay_signer_principal, relay_message_id FROM messages WHERE id = ?1 AND storage_owner = ?2",
params![message_id, storage_owner],
|row| Ok((row.get::<_, i64>(0)?, row.get::<_, Option<i64>>(1)?, row.get::<_, Option<String>>(2)?)),
|row| Ok((row.get::<_, i64>(0)?, row.get::<_, Option<i64>>(1)?, row.get::<_, Option<i64>>(2)?, row.get::<_, Option<String>>(3)?)),
)?;
if let (Some(relay_signer_id), Some(relay_message_id)) = (relay_signer_id, relay_message_id) {
if let (Some(relay_signer_principal), Some(relay_message_id)) =
(relay_signer_principal, relay_message_id)
{
tx.execute(
"DELETE FROM message_receipts WHERE storage_owner = ?1 AND target_signer_id = ?2 AND target_message_id = ?3",
params![storage_owner, relay_signer_id, relay_message_id],
"DELETE FROM message_receipts WHERE storage_owner = ?1 AND target_signer_principal = ?2 AND target_message_id = ?3",
params![storage_owner, relay_signer_principal, relay_message_id],
)?;
}
tx.execute(
@ -346,7 +436,12 @@ pub fn purge_message_in_tx(
message_id,
Operation::Delete,
)?;
update_contact_last_message_in_tx(tx, storage_owner, external_user)?;
update_contact_last_message_in_tx(
tx,
storage_owner,
external_user,
external_principal.map(iota_identity::PrincipalHandle),
)?;
Ok(())
}
@ -356,8 +451,50 @@ pub fn delete_message(
external_user: i64,
message_time: i64,
) -> Result<(), StorageError> {
match ensure_message_direction(storage_owner, external_user, message_time, true) {
Ok(()) => hard_delete_message(storage_owner, external_user, message_time),
delete_message_with_principal(storage_owner, external_user, None, message_time, true)
}
pub fn delete_message_for_principal(
storage_owner: i64,
external_user: i64,
external_principal: iota_identity::PrincipalHandle,
message_time: i64,
) -> Result<(), StorageError> {
delete_message_with_principal(
storage_owner,
external_user,
Some(external_principal),
message_time,
true,
)
}
fn delete_message_with_principal(
storage_owner: i64,
external_user: i64,
external_principal: Option<iota_identity::PrincipalHandle>,
message_time: i64,
sent_by_self: bool,
) -> Result<(), StorageError> {
match ensure_message_direction(
storage_owner,
external_user,
external_principal,
message_time,
sent_by_self,
) {
Ok(()) if sent_by_self => hard_delete_message_with_principal(
storage_owner,
external_user,
external_principal,
message_time,
),
Ok(()) => flag_deleted_by_external_with_principal(
storage_owner,
external_user,
external_principal,
message_time,
),
Err(StorageError::Db(rusqlite::Error::QueryReturnedNoRows)) => Ok(()),
Err(error) => Err(error),
}
@ -375,16 +512,34 @@ pub fn apply_remote_delete(
"Remote sender does not match chat partner".into(),
));
}
match ensure_message_direction(storage_owner, external_user, message_time, false) {
Ok(()) => flag_deleted_by_external(storage_owner, external_user, message_time),
Err(StorageError::Db(rusqlite::Error::QueryReturnedNoRows)) => Ok(()),
Err(error) => Err(error),
delete_message_with_principal(storage_owner, external_user, None, message_time, false)
}
pub fn apply_remote_delete_for_principal(
storage_owner: i64,
external_user: i64,
external_principal: iota_identity::PrincipalHandle,
message_time: i64,
sender_id: i64,
) -> Result<(), StorageError> {
if sender_id != external_user {
return Err(StorageError::Other(
"Remote sender does not match chat partner".into(),
));
}
delete_message_with_principal(
storage_owner,
external_user,
Some(external_principal),
message_time,
false,
)
}
fn ensure_message_direction(
storage_owner: i64,
external_user: i64,
external_principal: Option<iota_identity::PrincipalHandle>,
message_time: i64,
expected_sent_by_self: bool,
) -> Result<(), StorageError> {
@ -392,10 +547,20 @@ fn ensure_message_direction(
let sent_by_self: i64 = conn.query_row(
r#"
SELECT sent_by_self FROM messages
WHERE storage_owner = ?1 AND external_user = ?2 AND message_time = ?3
WHERE storage_owner = ?1
AND message_time = ?3
AND (
(?4 IS NULL AND external_user = ?2)
OR external_principal = ?4
)
ORDER BY id DESC LIMIT 1
"#,
params![storage_owner, external_user, message_time],
params![
storage_owner,
external_user,
message_time,
external_principal.map(|principal| principal.0)
],
|row| row.get(0),
)?;
if (sent_by_self != 0) != expected_sent_by_self {
@ -415,6 +580,15 @@ pub fn flag_deleted_by_external(
storage_owner: i64,
external_user: i64,
message_time: i64,
) -> Result<(), StorageError> {
flag_deleted_by_external_with_principal(storage_owner, external_user, None, message_time)
}
fn flag_deleted_by_external_with_principal(
storage_owner: i64,
external_user: i64,
external_principal: Option<iota_identity::PrincipalHandle>,
message_time: i64,
) -> Result<(), StorageError> {
db::with_db(|conn| {
let tx = conn.unchecked_transaction()?;
@ -422,14 +596,28 @@ pub fn flag_deleted_by_external(
r#"
UPDATE messages
SET deleted_by_external = 1
WHERE storage_owner = ?1 AND external_user = ?2 AND message_time = ?3
WHERE storage_owner = ?1
AND message_time = ?3
AND (
(?4 IS NULL AND external_user = ?2)
OR external_principal = ?4
)
"#,
params![storage_owner, external_user, message_time],
params![
storage_owner,
external_user,
message_time,
external_principal.map(|principal| principal.0)
],
)?;
if affected == 0 {
return Err(StorageError::Other("Message not found".into()));
}
let msg_id: i64 = tx.query_row("SELECT id FROM messages WHERE storage_owner = ?1 AND external_user = ?2 AND message_time = ?3 ORDER BY id DESC LIMIT 1", params![storage_owner, external_user, message_time], |r| r.get(0))?;
let msg_id: i64 = tx.query_row(
"SELECT id FROM messages WHERE storage_owner = ?1 AND message_time = ?3 AND ((?4 IS NULL AND external_user = ?2) OR external_principal = ?4) ORDER BY id DESC LIMIT 1",
params![storage_owner, external_user, message_time, external_principal.map(|principal| principal.0)],
|r| r.get(0),
)?;
sync::record_event(
&tx,
storage_owner,
@ -437,7 +625,7 @@ pub fn flag_deleted_by_external(
msg_id,
Operation::Delete,
)?;
update_contact_last_message_in_tx(&tx, storage_owner, external_user)?;
update_contact_last_message_in_tx(&tx, storage_owner, external_user, external_principal)?;
tx.commit()?;
Ok(())
})
@ -490,15 +678,65 @@ pub fn add_reaction(
message_time: i64,
user_id: i64,
reaction: &str,
) -> Result<(), StorageError> {
add_reaction_with_principal(
storage_owner,
external_user,
None,
message_time,
user_id,
None,
reaction,
)
}
pub fn add_reaction_for_principal(
storage_owner: i64,
external_user: i64,
external_principal: iota_identity::PrincipalHandle,
message_time: i64,
user_id: i64,
user_principal: iota_identity::PrincipalHandle,
reaction: &str,
) -> Result<(), StorageError> {
add_reaction_with_principal(
storage_owner,
external_user,
Some(external_principal),
message_time,
user_id,
Some(user_principal),
reaction,
)
}
fn add_reaction_with_principal(
storage_owner: i64,
external_user: i64,
external_principal: Option<iota_identity::PrincipalHandle>,
message_time: i64,
user_id: i64,
user_principal: Option<iota_identity::PrincipalHandle>,
reaction: &str,
) -> Result<(), StorageError> {
db::with_immediate_transaction(|tx| {
let (msg_id, history_deleted): (i64, i64) = tx.query_row(
r#"
SELECT id, history_deleted FROM messages
WHERE storage_owner = ?1 AND external_user = ?2 AND message_time = ?3
WHERE storage_owner = ?1
AND message_time = ?3
AND (
(?4 IS NULL AND external_user = ?2)
OR external_principal = ?4
)
ORDER BY id DESC LIMIT 1
"#,
params![storage_owner, external_user, message_time],
params![
storage_owner,
external_user,
message_time,
external_principal.map(|principal| principal.0)
],
|row| Ok((row.get(0)?, row.get(1)?)),
)?;
if history_deleted != 0 {
@ -528,10 +766,16 @@ pub fn add_reaction(
let inserted = tx.execute(
r#"
INSERT OR IGNORE INTO reactions (message_id, user_id, reaction, created_at)
VALUES (?1, ?2, ?3, ?4)
INSERT OR IGNORE INTO reactions (message_id, user_id, user_principal, reaction, created_at)
VALUES (?1, ?2, ?3, ?4, ?5)
"#,
params![msg_id, user_id, reaction, now],
params![
msg_id,
user_id,
user_principal.map(|principal| principal.0),
reaction,
now
],
)?;
if inserted > 0 {
sync::record_event(
@ -554,15 +798,65 @@ pub fn remove_reaction(
message_time: i64,
user_id: i64,
reaction: &str,
) -> Result<(), StorageError> {
remove_reaction_with_principal(
storage_owner,
external_user,
None,
message_time,
user_id,
None,
reaction,
)
}
pub fn remove_reaction_for_principal(
storage_owner: i64,
external_user: i64,
external_principal: iota_identity::PrincipalHandle,
message_time: i64,
user_id: i64,
user_principal: iota_identity::PrincipalHandle,
reaction: &str,
) -> Result<(), StorageError> {
remove_reaction_with_principal(
storage_owner,
external_user,
Some(external_principal),
message_time,
user_id,
Some(user_principal),
reaction,
)
}
fn remove_reaction_with_principal(
storage_owner: i64,
external_user: i64,
external_principal: Option<iota_identity::PrincipalHandle>,
message_time: i64,
user_id: i64,
user_principal: Option<iota_identity::PrincipalHandle>,
reaction: &str,
) -> Result<(), StorageError> {
db::with_db(|conn| {
let (msg_id, history_deleted): (i64, i64) = conn.query_row(
r#"
SELECT id, history_deleted FROM messages
WHERE storage_owner = ?1 AND external_user = ?2 AND message_time = ?3
WHERE storage_owner = ?1
AND message_time = ?3
AND (
(?4 IS NULL AND external_user = ?2)
OR external_principal = ?4
)
ORDER BY id DESC LIMIT 1
"#,
params![storage_owner, external_user, message_time],
params![
storage_owner,
external_user,
message_time,
external_principal.map(|principal| principal.0)
],
|row| Ok((row.get(0)?, row.get(1)?)),
)?;
if history_deleted != 0 {
@ -571,8 +865,13 @@ pub fn remove_reaction(
let tx = conn.unchecked_transaction()?;
tx.execute(
"DELETE FROM reactions WHERE message_id = ?1 AND user_id = ?2 AND reaction = ?3",
params![msg_id, user_id, reaction],
"DELETE FROM reactions WHERE message_id = ?1 AND ((?3 IS NULL AND user_id = ?2) OR user_principal = ?3) AND reaction = ?4",
params![
msg_id,
user_id,
user_principal.map(|principal| principal.0),
reaction
],
)?;
sync::record_event(
&tx,
@ -589,11 +888,13 @@ pub fn remove_reaction(
pub fn add_message(message: NewMessage<'_>) -> Result<i64, StorageError> {
let NewMessage {
relay_signer_id,
relay_signer_principal,
relay_message_id,
authored_at,
send_time,
storage_owner,
external_user,
external_principal,
sent_by_self,
content,
height,
@ -620,8 +921,9 @@ pub fn add_message(message: NewMessage<'_>) -> Result<i64, StorageError> {
INSERT INTO messages (
storage_owner, external_user, message_time, content, sent_by_self,
message_state, height, key_version, reply_to, relay_signer_id, relay_message_id,
authored_at, origin_iota_received_at, destination_iota_received_at, stored_at, expires_at
) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15, ?16)
authored_at, origin_iota_received_at, destination_iota_received_at, stored_at,
expires_at, external_principal, relay_signer_principal
) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15, ?16, ?17, ?18)
"#,
params![
storage_owner,
@ -640,6 +942,8 @@ pub fn add_message(message: NewMessage<'_>) -> Result<i64, StorageError> {
destination_iota_received_at,
stored_at,
expires_at,
external_principal.0,
relay_signer_principal.0,
],
)?;
let msg_id = tx.last_insert_rowid();
@ -650,7 +954,8 @@ pub fn add_message(message: NewMessage<'_>) -> Result<i64, StorageError> {
msg_id,
Operation::Upsert,
)?;
let mut contact = crate::users::contact::Contact::new(external_user);
let mut contact =
crate::users::contact::Contact::for_principal(external_user, external_principal);
contact.set_last_message_at(
destination_iota_received_at
.or(origin_iota_received_at)
@ -659,8 +964,8 @@ pub fn add_message(message: NewMessage<'_>) -> Result<i64, StorageError> {
crate::util::chats_util::upsert_contact(&tx, storage_owner, &contact)?;
if !sent_by_self {
tx.execute(
"UPDATE contacts SET notifications = CASE WHEN notifications < 9223372036854775807 THEN notifications + 1 ELSE notifications END WHERE storage_owner = ?1 AND user_id = ?2",
params![storage_owner, external_user],
"UPDATE contacts SET notifications = CASE WHEN notifications < 9223372036854775807 THEN notifications + 1 ELSE notifications END WHERE storage_owner = ?1 AND principal_handle = ?2",
params![storage_owner, external_principal.0],
)?;
}
tx.commit()?;
@ -670,7 +975,8 @@ pub fn add_message(message: NewMessage<'_>) -> Result<i64, StorageError> {
pub fn change_message_state_by_relay_id(
storage_owner: i64,
relay_signer_id: i64,
_relay_signer_id: i64,
relay_signer_principal: iota_identity::PrincipalHandle,
relay_message_id: &str,
new_state: MessageState,
) -> Result<(), StorageError> {
@ -678,8 +984,8 @@ pub fn change_message_state_by_relay_id(
let tx = conn.unchecked_transaction()?;
let Some((msg_id, current)) = tx
.query_row(
"SELECT id, message_state FROM messages WHERE storage_owner = ?1 AND relay_signer_id = ?2 AND relay_message_id = ?3",
params![storage_owner, relay_signer_id, relay_message_id],
"SELECT id, message_state FROM messages WHERE storage_owner = ?1 AND relay_signer_principal = ?2 AND relay_message_id = ?3",
params![storage_owner, relay_signer_principal.0, relay_message_id],
|row| Ok((row.get::<_, i64>(0)?, row.get::<_, String>(1)?)),
)
.optional()?
@ -706,8 +1012,10 @@ pub fn change_message_state_by_relay_id(
pub fn record_message_receipt(
storage_owner: i64,
target_signer_id: i64,
target_signer_principal: iota_identity::PrincipalHandle,
target_message_id: &str,
receipt_signer_id: i64,
receipt_signer_principal: iota_identity::PrincipalHandle,
receipt_message_id: &str,
receipt_type: MessageState,
event_at: i64,
@ -720,17 +1028,17 @@ pub fn record_message_receipt(
};
db::with_db(|conn| {
let tx = conn.unchecked_transaction()?;
let Some((message_id, external_user, authored_at, history_deleted)) = tx
let Some((message_id, external_principal, authored_at, history_deleted)) = tx
.query_row(
"SELECT id, external_user, authored_at, history_deleted FROM messages WHERE storage_owner = ?1 AND relay_signer_id = ?2 AND relay_message_id = ?3",
params![storage_owner, target_signer_id, target_message_id],
"SELECT id, external_principal, authored_at, history_deleted FROM messages WHERE storage_owner = ?1 AND relay_signer_principal = ?2 AND relay_message_id = ?3",
params![storage_owner, target_signer_principal.0, target_message_id],
|row| Ok((row.get::<_, i64>(0)?, row.get::<_, i64>(1)?, row.get::<_, Option<i64>>(2)?, row.get::<_, i64>(3)?)),
)
.optional()?
else {
return Err(StorageError::Other("message receipt target was not found".into()));
};
if external_user != receipt_signer_id {
if external_principal != receipt_signer_principal.0 {
return Err(StorageError::Other(
"message receipt signer is not the chat partner".into(),
));
@ -746,8 +1054,8 @@ pub fn record_message_receipt(
return Ok(());
}
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)",
params![storage_owner, target_signer_id, target_message_id, receipt_signer_id, receipt_message_id, receipt_type, event_at, recorded_at],
"INSERT OR IGNORE INTO message_receipts (storage_owner, target_signer_id, target_signer_principal, target_message_id, receipt_signer_id, receipt_signer_principal, receipt_message_id, receipt_type, event_at, recorded_at) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10)",
params![storage_owner, target_signer_id, target_signer_principal.0, target_message_id, receipt_signer_id, receipt_signer_principal.0, receipt_message_id, receipt_type, event_at, recorded_at],
)?;
let (state_column, recorded_column) = if receipt_type == "read" {
("read_at", "read_recorded_at")
@ -780,7 +1088,7 @@ pub fn record_message_receipt(
pub fn record_destination_iota_received(
storage_owner: i64,
relay_signer_id: i64,
relay_signer_principal: iota_identity::PrincipalHandle,
relay_message_id: &str,
accepted_at: i64,
) -> Result<(), StorageError> {
@ -788,8 +1096,8 @@ pub fn record_destination_iota_received(
let tx = conn.unchecked_transaction()?;
let Some(message_id) = tx
.query_row(
"SELECT id FROM messages WHERE storage_owner = ?1 AND relay_signer_id = ?2 AND relay_message_id = ?3",
params![storage_owner, relay_signer_id, relay_message_id],
"SELECT id FROM messages WHERE storage_owner = ?1 AND relay_signer_principal = ?2 AND relay_message_id = ?3",
params![storage_owner, relay_signer_principal.0, relay_message_id],
|row| row.get::<_, i64>(0),
)
.optional()?
@ -814,7 +1122,7 @@ pub fn record_destination_iota_received(
pub fn record_delivery_failure(
storage_owner: i64,
relay_signer_id: i64,
relay_signer_principal: iota_identity::PrincipalHandle,
relay_message_id: &str,
failure: &str,
failed_at: i64,
@ -823,8 +1131,8 @@ pub fn record_delivery_failure(
let tx = conn.unchecked_transaction()?;
let Some(message_id) = tx
.query_row(
"SELECT id FROM messages WHERE storage_owner = ?1 AND relay_signer_id = ?2 AND relay_message_id = ?3",
params![storage_owner, relay_signer_id, relay_message_id],
"SELECT id FROM messages WHERE storage_owner = ?1 AND relay_signer_principal = ?2 AND relay_message_id = ?3",
params![storage_owner, relay_signer_principal.0, relay_message_id],
|row| row.get::<_, i64>(0),
)
.optional()?
@ -957,6 +1265,20 @@ pub fn get_messages(
external_user: i64,
loaded_messages: i64,
amount: i64,
) -> Result<Vec<StoredMessage>, StorageError> {
let Some(principal) = crate::util::chats_util::get_user(storage_owner, external_user)?
.and_then(|contact| contact.principal)
else {
return Ok(Vec::new());
};
get_messages_for_principal(storage_owner, principal, loaded_messages, amount)
}
pub fn get_messages_for_principal(
storage_owner: i64,
external_principal: iota_identity::PrincipalHandle,
loaded_messages: i64,
amount: i64,
) -> Result<Vec<StoredMessage>, StorageError> {
if amount <= 0 || loaded_messages < 0 {
return Ok(Vec::new());
@ -969,21 +1291,25 @@ pub fn get_messages(
origin_iota_received_at, destination_iota_received_at,
client_received_at, client_received_recorded_at, read_at,
read_recorded_at, delivery_failed_at, delivery_failure, content, sent_by_self, message_state, height, key_version,
reply_to, edited_count
reply_to, edited_count, external_user, relay_signer_principal
FROM messages
WHERE storage_owner = ?1 AND external_user = ?2 AND deleted_by_external = 0 AND history_deleted = 0
WHERE storage_owner = ?1 AND external_principal = ?2 AND deleted_by_external = 0 AND history_deleted = 0
ORDER BY COALESCE(destination_iota_received_at, origin_iota_received_at, authored_at, id) DESC, id DESC
LIMIT ?3 OFFSET ?4
"#,
)?;
let rows = stmt.query_map(
params![storage_owner, external_user, amount, loaded_messages],
params![storage_owner, external_principal.0, amount, loaded_messages],
|row| {
Ok(StoredMessage {
id: row.get(0)?,
external_user,
external_user: row.get(20)?,
external_principal: Some(external_principal),
relay_signer_id: row.get(1)?,
relay_signer_principal: row
.get::<_, Option<i64>>(21)?
.map(iota_identity::PrincipalHandle),
relay_message_id: row.get(2)?,
message_time: row.get(3)?,
authored_at: row.get(4)?,
@ -1026,6 +1352,7 @@ pub fn get_message(
storage_owner: i64,
message_time: i64,
external_user: Option<i64>,
external_principal: Option<iota_identity::PrincipalHandle>,
) -> Result<Option<StoredMessage>, StorageError> {
db::with_db(|conn| {
let mut stmt = conn.prepare(
@ -1034,43 +1361,62 @@ pub fn get_message(
origin_iota_received_at, destination_iota_received_at,
client_received_at, client_received_recorded_at, read_at,
read_recorded_at, delivery_failed_at, delivery_failure, content, sent_by_self, message_state, height, key_version,
reply_to, edited_count, external_user
reply_to, edited_count, external_user, external_principal,
relay_signer_principal
FROM messages
WHERE storage_owner = ?1
AND message_time = ?2
AND deleted_by_external = 0
AND history_deleted = 0
AND (?3 IS NULL OR external_user = ?3)
AND (
?3 IS NULL
OR (?4 IS NULL AND external_user = ?3)
OR external_principal = ?4
)
ORDER BY id DESC
"#,
)?;
let rows = stmt.query_map(params![storage_owner, message_time, external_user], |row| {
Ok(StoredMessage {
id: row.get(0)?,
relay_signer_id: row.get(1)?,
relay_message_id: row.get(2)?,
message_time: row.get(3)?,
authored_at: row.get(4)?,
origin_iota_received_at: row.get(5)?,
destination_iota_received_at: row.get(6)?,
client_received_at: row.get(7)?,
client_received_recorded_at: row.get(8)?,
read_at: row.get(9)?,
read_recorded_at: row.get(10)?,
delivery_failed_at: row.get(11)?,
delivery_failure: row.get(12)?,
content: row.get(13)?,
sent_by_self: row.get::<_, i64>(14)? != 0,
message_state: row.get(15)?,
height: row.get(16)?,
key_version: row.get(17)?,
reply_to: row.get(18)?,
edited: row.get::<_, i64>(19)? > 0,
external_user: row.get(20)?,
reactions: Vec::new(),
})
})?;
let rows = stmt.query_map(
params![
storage_owner,
message_time,
external_user,
external_principal.map(|principal| principal.0)
],
|row| {
Ok(StoredMessage {
id: row.get(0)?,
relay_signer_id: row.get(1)?,
relay_signer_principal: row
.get::<_, Option<i64>>(22)?
.map(iota_identity::PrincipalHandle),
relay_message_id: row.get(2)?,
message_time: row.get(3)?,
authored_at: row.get(4)?,
origin_iota_received_at: row.get(5)?,
destination_iota_received_at: row.get(6)?,
client_received_at: row.get(7)?,
client_received_recorded_at: row.get(8)?,
read_at: row.get(9)?,
read_recorded_at: row.get(10)?,
delivery_failed_at: row.get(11)?,
delivery_failure: row.get(12)?,
content: row.get(13)?,
sent_by_self: row.get::<_, i64>(14)? != 0,
message_state: row.get(15)?,
height: row.get(16)?,
key_version: row.get(17)?,
reply_to: row.get(18)?,
edited: row.get::<_, i64>(19)? > 0,
external_user: row.get(20)?,
external_principal: row
.get::<_, Option<i64>>(21)?
.map(iota_identity::PrincipalHandle),
reactions: Vec::new(),
})
},
)?;
let messages: Vec<StoredMessage> = rows.collect::<Result<_, _>>()?;
if messages.is_empty() {
@ -1079,7 +1425,7 @@ pub fn get_message(
if external_user.is_none()
&& messages
.iter()
.map(|message| message.external_user)
.filter_map(|message| message.external_principal)
.collect::<std::collections::HashSet<_>>()
.len()
> 1
@ -1099,7 +1445,18 @@ pub fn get_message_with_offset(
external_user: i64,
message_time: i64,
) -> Result<Option<(StoredMessage, i64)>, StorageError> {
let Some(message) = get_message(storage_owner, message_time, Some(external_user))? else {
let Some(external_principal) = crate::util::chats_util::get_user(storage_owner, external_user)?
.and_then(|contact| contact.principal)
else {
return Ok(None);
};
let Some(message) = get_message(
storage_owner,
message_time,
Some(external_user),
Some(external_principal),
)?
else {
return Ok(None);
};
let offset = db::with_db(|conn| {
@ -1108,7 +1465,7 @@ pub fn get_message_with_offset(
SELECT COUNT(*)
FROM messages
WHERE storage_owner = ?1
AND external_user = ?2
AND external_principal = ?2
AND deleted_by_external = 0
AND history_deleted = 0
AND (
@ -1120,7 +1477,7 @@ pub fn get_message_with_offset(
"#,
params![
storage_owner,
external_user,
external_principal.0,
message.destination_iota_received_at.or(message.origin_iota_received_at).or(message.authored_at),
message.id
],
@ -1142,13 +1499,19 @@ pub fn get_messages_by_ids(
// A journal id uniquely identifies a row. Load all messages for this owner and retain only
// those ids; this keeps reaction hydration identical to normal message loading.
db::with_db(|conn| {
let mut stmt = conn.prepare("SELECT id, relay_signer_id, relay_message_id, message_time, authored_at, origin_iota_received_at, destination_iota_received_at, client_received_at, client_received_recorded_at, read_at, read_recorded_at, delivery_failed_at, delivery_failure, content, sent_by_self, message_state, height, key_version, reply_to, edited_count, external_user FROM messages WHERE storage_owner = ?1 AND deleted_by_external = 0 AND history_deleted = 0")?;
let mut stmt = conn.prepare("SELECT id, relay_signer_id, relay_message_id, message_time, authored_at, origin_iota_received_at, destination_iota_received_at, client_received_at, client_received_recorded_at, read_at, read_recorded_at, delivery_failed_at, delivery_failure, content, sent_by_self, message_state, height, key_version, reply_to, edited_count, external_user, external_principal, relay_signer_principal FROM messages WHERE storage_owner = ?1 AND deleted_by_external = 0 AND history_deleted = 0")?;
let rows = stmt.query_map([storage_owner], |row| {
let external_user: i64 = row.get(20)?;
Ok(StoredMessage {
id: row.get(0)?,
external_user,
external_principal: row
.get::<_, Option<i64>>(21)?
.map(iota_identity::PrincipalHandle),
relay_signer_id: row.get(1)?,
relay_signer_principal: row
.get::<_, Option<i64>>(22)?
.map(iota_identity::PrincipalHandle),
relay_message_id: row.get(2)?,
message_time: row.get(3)?,
authored_at: row.get(4)?,