use crate::sync_session::EventEnvelope;
use helix_core::effect::StorageOp;
pub fn event_to_upsert_op(ev: &EventEnvelope) -> StorageOp {
event_to_upsert_op_with_policy(ev, false, false)
}
pub fn event_to_sync_upsert_op(ev: &EventEnvelope) -> StorageOp {
let mut op = event_to_upsert_op_with_policy(ev, false, true);
if ev.fields.read_bits.is_empty() {
if let StorageOp::BatchUpsert(spec) = &mut op {
if !spec.exclude_from_update.contains(&"read_bits") {
spec.exclude_from_update.push("read_bits");
}
}
}
op
}
pub fn event_to_online_upsert_op(ev: &EventEnvelope) -> StorageOp {
event_to_upsert_op_with_policy(ev, true, false)
}
pub fn event_to_readback_upsert_op(ev: &EventEnvelope) -> StorageOp {
let mut op = event_to_upsert_op_with_policy(ev, false, true);
if ev.fields.read_bits.is_empty() {
if let StorageOp::BatchUpsert(spec) = &mut op {
if !spec.exclude_from_update.contains(&"read_bits") {
spec.exclude_from_update.push("read_bits");
}
}
}
op
}
fn event_to_upsert_op_with_policy(
ev: &EventEnvelope,
preserve_empty_online_fields: bool,
preserve_missing_readback_fields: bool,
) -> StorageOp {
use helix_core::effect::{SqlValue, UpsertSpec};
let f = &ev.fields;
let channel_id = if f.channel_id.is_empty() {
ev.channel_id.as_str().to_string()
} else {
f.channel_id.clone()
};
let pk = if !f.temporary_id.is_empty() {
f.temporary_id.clone()
} else if !f.id.is_empty() {
f.id.clone()
} else {
format!("evt:{}:{}", ev.channel_id.as_str(), ev.seq.0)
};
let mut exclude_from_update = vec!["send_status"];
if ev.seq.0 == 0 {
exclude_from_update.push("event_seq");
}
if preserve_empty_online_fields {
let reply_messages_missing = online_json_field_missing(&f.reply_messages);
let preserve = [
("id", f.id.is_empty()),
("user_id", f.user_id.is_empty()),
("type", f.msg_type.is_empty()),
("message", f.message.is_empty()),
("viewers", f.viewers.is_empty()),
("user_snapshot", online_json_field_missing(&f.user_snapshot)),
("mentions", f.mentions.is_empty()),
("expedite_map", online_json_field_missing(&f.expedite_map)),
("topic", online_json_field_missing(&f.topic)),
("simple_message", f.simple_message.is_empty()),
("reply_id", f.reply_id.is_empty()),
("reply_root_id", f.reply_root_id.is_empty()),
("reply_first_level_id", f.reply_first_level_id.is_empty()),
(
"replied_message",
online_json_field_missing(&f.replied_message),
),
("reply_messages", reply_messages_missing),
("reply_count", f.reply_count == 0 && reply_messages_missing),
("read_bits", f.read_bits.is_empty()),
("snapshot_id", f.snapshot_id.is_empty()),
("create_at", f.create_at == 0),
("update_at", f.update_at == 0),
];
exclude_from_update.extend(
preserve
.into_iter()
.filter_map(|(column, should_preserve)| should_preserve.then_some(column)),
);
}
if preserve_missing_readback_fields {
let preserve = [
("id", !f.has_field(crate::sync_session::POST_FIELD_ID)),
(
"channel_id",
!f.has_field(crate::sync_session::POST_FIELD_CHANNEL_ID),
),
(
"user_id",
!f.has_field(crate::sync_session::POST_FIELD_USER_ID),
),
("type", !f.has_field(crate::sync_session::POST_FIELD_TYPE)),
(
"message",
!f.has_field(crate::sync_session::POST_FIELD_MESSAGE),
),
(
"simple_message",
!f.has_field(crate::sync_session::POST_FIELD_SIMPLE_MESSAGE),
),
("props", !f.has_field(crate::sync_session::POST_FIELD_PROPS)),
(
"user_snapshot",
!f.has_field(crate::sync_session::POST_FIELD_USER_SNAPSHOT),
),
(
"viewers",
!f.has_field(crate::sync_session::POST_FIELD_VIEWERS),
),
(
"mentions",
!f.has_field(crate::sync_session::POST_FIELD_MENTIONS),
),
(
"expedite_map",
!f.has_field(crate::sync_session::POST_FIELD_EXPEDITE_MAP),
),
("topic", !f.has_field(crate::sync_session::POST_FIELD_TOPIC)),
(
"quick_reply",
!f.has_field(crate::sync_session::POST_FIELD_QUICK_REPLY),
),
(
"reply_id",
!f.has_field(crate::sync_session::POST_FIELD_REPLY_ID),
),
(
"reply_root_id",
!f.has_field(crate::sync_session::POST_FIELD_REPLY_ROOT_ID),
),
(
"reply_first_level_id",
!f.has_field(crate::sync_session::POST_FIELD_REPLY_FIRST_LEVEL_ID),
),
(
"replied_message",
!f.has_field(crate::sync_session::POST_FIELD_REPLIED_MESSAGE),
),
(
"reply_messages",
!f.has_field(crate::sync_session::POST_FIELD_REPLY_MESSAGES),
),
(
"reply_count",
!f.has_field(crate::sync_session::POST_FIELD_REPLY_COUNT),
),
(
"read_bits",
!f.has_field(crate::sync_session::POST_FIELD_READ_BITS),
),
(
"snapshot_id",
!f.has_field(crate::sync_session::POST_FIELD_SNAPSHOT_ID),
),
(
"create_at",
!f.has_field(crate::sync_session::POST_FIELD_CREATE_AT),
),
(
"update_at",
!f.has_field(crate::sync_session::POST_FIELD_UPDATE_AT),
),
];
exclude_from_update.extend(
preserve
.into_iter()
.filter_map(|(column, should_preserve)| should_preserve.then_some(column)),
);
}
let mut spec = UpsertSpec {
version_column: None,
update_guard: None,
table: "message",
rows: vec![vec![
("temporary_id".to_string(), SqlValue::Text(pk)),
("id".to_string(), SqlValue::Text(f.id.clone())),
("channel_id".to_string(), SqlValue::Text(channel_id)),
("user_id".to_string(), SqlValue::Text(f.user_id.clone())),
("type".to_string(), SqlValue::Text(f.msg_type.clone())),
("message".to_string(), SqlValue::Text(f.message.clone())),
("props".to_string(), SqlValue::Text(f.props.clone())),
(
"viewers".to_string(),
SqlValue::Text(serde_json::json!(f.viewers.clone()).to_string()),
),
(
"user_snapshot".to_string(),
SqlValue::Text(f.user_snapshot.clone()),
),
(
"mentions".to_string(),
SqlValue::Text(serde_json::json!(f.mentions.clone()).to_string()),
),
(
"expedite_map".to_string(),
SqlValue::Text(f.expedite_map.clone()),
),
("topic".to_string(), SqlValue::Text(f.topic.clone())),
(
"simple_message".to_string(),
SqlValue::Text(f.simple_message.clone()),
),
("reply_id".to_string(), SqlValue::Text(f.reply_id.clone())),
(
"reply_root_id".to_string(),
SqlValue::Text(f.reply_root_id.clone()),
),
(
"reply_first_level_id".to_string(),
SqlValue::Text(f.reply_first_level_id.clone()),
),
(
"replied_message".to_string(),
SqlValue::Text(f.replied_message.clone()),
),
(
"reply_messages".to_string(),
SqlValue::Text(f.reply_messages.clone()),
),
("reply_count".to_string(), SqlValue::Integer(f.reply_count)),
("read_bits".to_string(), SqlValue::Text(f.read_bits.clone())),
(
"snapshot_id".to_string(),
SqlValue::Text(f.snapshot_id.clone()),
),
("event_seq".to_string(), SqlValue::Integer(ev.seq.0 as i64)),
(
"send_status".to_string(),
SqlValue::Text("sent".to_string()),
),
("create_at".to_string(), SqlValue::Integer(f.create_at)),
("update_at".to_string(), SqlValue::Integer(f.update_at)),
]],
conflict_key: Some("temporary_id"),
exclude_from_update,
};
crate::category_chain::post::guard_upsert(&mut spec, f);
StorageOp::BatchUpsert(spec)
}
fn online_json_field_missing(value: &str) -> bool {
value.is_empty() || value == "{}" || value == "[]"
}
pub fn edit_content_op(msg_id: &str, fields: &crate::sync_session::PostFields) -> StorageOp {
use helix_core::effect::{BatchUpdateSpec, SqlValue};
let mut patch = vec![
("type".to_string(), SqlValue::Text(fields.msg_type.clone())),
(
"message".to_string(),
SqlValue::Text(fields.message.clone()),
),
("props".to_string(), SqlValue::Text(fields.props.clone())),
];
if !fields.expedite_map.is_empty() {
patch.push((
"expedite_map".to_string(),
SqlValue::Text(fields.expedite_map.clone()),
));
}
if !fields.topic.is_empty() {
patch.push(("topic".to_string(), SqlValue::Text(fields.topic.clone())));
}
if !fields.reply_id.is_empty() {
patch.push((
"reply_id".to_string(),
SqlValue::Text(fields.reply_id.clone()),
));
}
if !fields.reply_root_id.is_empty() {
patch.push((
"reply_root_id".to_string(),
SqlValue::Text(fields.reply_root_id.clone()),
));
}
if !fields.reply_first_level_id.is_empty() {
patch.push((
"reply_first_level_id".to_string(),
SqlValue::Text(fields.reply_first_level_id.clone()),
));
}
if !fields.replied_message.is_empty() {
patch.push((
"replied_message".to_string(),
SqlValue::Text(fields.replied_message.clone()),
));
}
if !fields.reply_messages.is_empty() {
patch.push((
"reply_messages".to_string(),
SqlValue::Text(fields.reply_messages.clone()),
));
patch.push((
"reply_count".to_string(),
SqlValue::Integer(fields.reply_count),
));
}
if fields.msg_type == "CATEGORY_CHAIN" {
return crate::category_chain::post::guarded_edit(fields, patch);
}
StorageOp::BatchUpdate(BatchUpdateSpec {
table: "message",
key_col: "id",
key_vals: vec![SqlValue::Text(msg_id.to_string())],
patch,
})
}
pub fn posts_update_edit_op(
msg_id: &str,
fields: &crate::sync_session::PostFields,
event_seq: u64,
) -> StorageOp {
posts_update_mutation_op(msg_id, fields, event_seq, false)
}
pub fn revoke_op(msg_id: &str) -> StorageOp {
use helix_core::effect::{BatchUpdateSpec, SqlValue};
StorageOp::BatchUpdate(BatchUpdateSpec {
table: "message",
key_col: "id",
key_vals: vec![SqlValue::Text(msg_id.to_string())],
patch: vec![("revoke".to_string(), SqlValue::Integer(1))],
})
}
pub fn revoke_authority_op(
msg_id: &str,
fields: crate::sync_session::PostFields,
event_seq: u64,
) -> StorageOp {
posts_update_mutation_op(msg_id, &fields, event_seq, true)
}
fn posts_update_mutation_op(
msg_id: &str,
fields: &crate::sync_session::PostFields,
event_seq: u64,
revoke: bool,
) -> StorageOp {
use helix_core::effect::{BatchUpdateSpec, SqlValue};
let mut patch = Vec::new();
if fields.has_field(crate::sync_session::POST_FIELD_TYPE) {
patch.push(("type".to_string(), SqlValue::Text(fields.msg_type.clone())));
}
if fields.has_field(crate::sync_session::POST_FIELD_MESSAGE) {
patch.push((
"message".to_string(),
SqlValue::Text(fields.message.clone()),
));
}
if fields.has_field(crate::sync_session::POST_FIELD_SIMPLE_MESSAGE) {
patch.push((
"simple_message".to_string(),
SqlValue::Text(fields.simple_message.clone()),
));
}
if fields.has_field(crate::sync_session::POST_FIELD_PROPS) {
patch.push(("props".to_string(), SqlValue::Text(fields.props.clone())));
}
if fields.has_field(crate::sync_session::POST_FIELD_VIEWERS) {
patch.push((
"viewers".to_string(),
SqlValue::Text(serde_json::json!(fields.viewers).to_string()),
));
}
if fields.has_field(crate::sync_session::POST_FIELD_MENTIONS) {
patch.push((
"mentions".to_string(),
SqlValue::Text(serde_json::json!(fields.mentions).to_string()),
));
}
if fields.has_field(crate::sync_session::POST_FIELD_EXPEDITE_MAP) {
patch.push((
"expedite_map".to_string(),
SqlValue::Text(fields.expedite_map.clone()),
));
}
if fields.has_field(crate::sync_session::POST_FIELD_QUICK_REPLY) {
patch.push((
"quick_reply".to_string(),
SqlValue::Text(fields.quick_reply.clone()),
));
}
if fields.has_field(crate::sync_session::POST_FIELD_TOPIC) {
patch.push(("topic".to_string(), SqlValue::Text(fields.topic.clone())));
}
if fields.has_field(crate::sync_session::POST_FIELD_REPLY_ID) {
patch.push((
"reply_id".to_string(),
SqlValue::Text(fields.reply_id.clone()),
));
}
if fields.has_field(crate::sync_session::POST_FIELD_REPLY_ROOT_ID) {
patch.push((
"reply_root_id".to_string(),
SqlValue::Text(fields.reply_root_id.clone()),
));
}
if fields.has_field(crate::sync_session::POST_FIELD_REPLY_FIRST_LEVEL_ID) {
patch.push((
"reply_first_level_id".to_string(),
SqlValue::Text(fields.reply_first_level_id.clone()),
));
}
if fields.has_field(crate::sync_session::POST_FIELD_REPLIED_MESSAGE) {
patch.push((
"replied_message".to_string(),
SqlValue::Text(clear_json_null(&fields.replied_message)),
));
}
if fields.has_field(crate::sync_session::POST_FIELD_REPLY_MESSAGES) {
patch.push((
"reply_messages".to_string(),
SqlValue::Text(clear_json_null_object(&fields.reply_messages)),
));
}
if fields.has_field(crate::sync_session::POST_FIELD_REPLY_COUNT) {
patch.push((
"reply_count".to_string(),
SqlValue::Integer(fields.reply_count),
));
}
if fields.has_field(crate::sync_session::POST_FIELD_UPDATE_AT) {
patch.push(("update_at".to_string(), SqlValue::Integer(fields.update_at)));
}
patch.push(("event_seq".to_string(), SqlValue::Integer(event_seq as i64)));
if revoke {
patch.push(("revoke".to_string(), SqlValue::Integer(1)));
}
if fields.msg_type == "CATEGORY_CHAIN" {
return crate::category_chain::post::guarded_edit(fields, patch);
}
StorageOp::BatchUpdate(BatchUpdateSpec {
table: "message",
key_col: "id",
key_vals: vec![SqlValue::Text(msg_id.to_string())],
patch,
})
}
fn clear_json_null(value: &str) -> String {
if value == "null" {
String::new()
} else {
value.to_string()
}
}
fn clear_json_null_object(value: &str) -> String {
if value.is_empty() || value == "null" {
"{}".to_string()
} else {
value.to_string()
}
}
pub fn apply_read_op(msg_id: &str, read_bits: &str) -> StorageOp {
use helix_core::effect::{BatchUpdateSpec, SqlValue};
StorageOp::BatchUpdate(BatchUpdateSpec {
table: "message",
key_col: "id",
key_vals: vec![SqlValue::Text(msg_id.to_string())],
patch: vec![(
"read_bits".to_string(),
SqlValue::Text(read_bits.to_string()),
)],
})
}
pub fn pin_state_op(msg_id: &str, pinned: bool) -> StorageOp {
use helix_core::effect::{BatchUpdateSpec, SqlValue};
let props = serde_json::json!({ "pinned": pinned }).to_string();
StorageOp::BatchUpdate(BatchUpdateSpec {
table: "message",
key_col: "id",
key_vals: vec![SqlValue::Text(msg_id.to_string())],
patch: vec![("props".to_string(), SqlValue::Text(props))],
})
}
pub fn event_to_storage_op(ev: &EventEnvelope) -> StorageOp {
use crate::sync_session::EventKind;
let msg_id = ev
.msg_id
.as_deref()
.filter(|s| !s.is_empty())
.unwrap_or(ev.fields.id.as_str());
match ev.kind {
EventKind::PostUpsert => event_to_online_upsert_op(ev),
EventKind::PostEdit => edit_content_op(msg_id, &ev.fields),
EventKind::PostRevoke => revoke_op(msg_id),
EventKind::PostRead
if ev
.fields
.has_field(crate::sync_session::POST_FIELD_READ_BITS) =>
{
apply_read_op(msg_id, ev.fields.read_bits.as_str())
}
EventKind::PostRead => StorageOp::BatchUpdate(helix_core::effect::BatchUpdateSpec {
table: "message",
key_col: "id",
key_vals: Vec::new(),
patch: Vec::new(),
}),
EventKind::ChannelTerminalClosed => {
StorageOp::BatchUpdate(helix_core::effect::BatchUpdateSpec {
table: "message",
key_col: "id",
key_vals: Vec::new(),
patch: Vec::new(),
})
}
EventKind::Other(_) => StorageOp::BatchUpdate(helix_core::effect::BatchUpdateSpec {
table: "message",
key_col: "id",
key_vals: vec![helix_core::effect::SqlValue::Text(msg_id.to_string())],
patch: Vec::new(),
}),
}
}