feat: add message mutation sync
This commit is contained in:
@@ -2747,7 +2747,7 @@ func (a *adapter) TopicGet(topic string) (*t.Topic, error) {
|
||||
// Fetch topic by name
|
||||
var tt = new(t.Topic)
|
||||
if err := a.db.GetContext(ctx, tt,
|
||||
"SELECT createdat,updatedat,state,stateat,touchedat,name AS id,usebt,access,owner,seqid,delid,subcnt,public,trusted,tags,aux "+
|
||||
"SELECT createdat,updatedat,state,stateat,touchedat,name AS id,usebt,access,owner,seqid,delid,subcnt,IFNULL(mutation_version,0) AS mutation_version,public,trusted,tags,aux "+
|
||||
"FROM im_topics WHERE name=?", topic); err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
// Nothing found - clear the error
|
||||
@@ -2901,7 +2901,7 @@ func (a *adapter) TopicsForUser(uid t.Uid, keepDeleted bool, opts *t.QueryOpt) (
|
||||
|
||||
// Fetch grp topics and join to subscriptions.
|
||||
if len(topq) > 0 {
|
||||
q = "SELECT updatedat,state,touchedat,name AS id,usebt,access,seqid,delid,subcnt,public,trusted " +
|
||||
q = "SELECT updatedat,state,touchedat,name AS id,usebt,access,seqid,delid,subcnt,IFNULL(mutation_version,0) AS mutation_version,public,trusted " +
|
||||
"FROM im_topics WHERE name IN (?)"
|
||||
q, args, _ = sqlx.In(q, topq)
|
||||
|
||||
@@ -2943,6 +2943,7 @@ func (a *adapter) TopicsForUser(uid t.Uid, keepDeleted bool, opts *t.QueryOpt) (
|
||||
sub.SetState(top.State)
|
||||
sub.SetTouchedAt(top.TouchedAt)
|
||||
sub.SetSeqId(top.SeqId)
|
||||
sub.Private = ensurePrivateMutationVersion(sub.Private, top.MutationVersion)
|
||||
if t.GetTopicCat(sub.Topic) == t.TopicCatGrp {
|
||||
sub.SetSubCnt(top.SubCnt)
|
||||
sub.SetPublic(common.FromJSON(top.Public))
|
||||
|
||||
@@ -172,6 +172,15 @@ func ensureP2PPrivatePeerRemark(private any, peerID, remark string) any {
|
||||
return data
|
||||
}
|
||||
|
||||
func ensurePrivateMutationVersion(private any, version int64) any {
|
||||
data, _ := private.(map[string]any)
|
||||
if data == nil {
|
||||
data = map[string]any{}
|
||||
}
|
||||
data["mutation_version"] = version
|
||||
return data
|
||||
}
|
||||
|
||||
func clearProfilePublicFields(public map[string]any) {
|
||||
for _, key := range []string{"display_name", "nickname", "fn", "name", "avatar_file_id", "photo", "avatar", "signature", "gender", "region", "public_account"} {
|
||||
delete(public, key)
|
||||
@@ -309,6 +318,17 @@ func messageHeadBool(head t.KVMap, key string) bool {
|
||||
}
|
||||
}
|
||||
|
||||
func (a *adapter) tenantTopicBumpMutationVersionTx(ctx context.Context, tx *sqlx.Tx, id t.TenantID, topic string, at time.Time) error {
|
||||
if a.tenantScopedDB {
|
||||
_, err := tx.ExecContext(ctx, "UPDATE im_topics SET mutation_version=mutation_version+1,updatedat=?,touchedat=? WHERE name=?",
|
||||
at, at, topic)
|
||||
return err
|
||||
}
|
||||
_, err := tx.ExecContext(ctx, "UPDATE im_topics SET mutation_version=mutation_version+1,updatedat=?,touchedat=? WHERE tenant_id=? AND name=?",
|
||||
at, at, id, topic)
|
||||
return err
|
||||
}
|
||||
|
||||
func stampTenantFile(id t.TenantID, file *t.FileDef) *t.FileDef {
|
||||
if file != nil {
|
||||
file.TenantID = id
|
||||
@@ -888,7 +908,7 @@ func (a *adapter) TenantTopicGet(id t.TenantID, topic string) (*t.Topic, error)
|
||||
}
|
||||
var top t.Topic
|
||||
err = a.db.GetContext(ctx, &top,
|
||||
"SELECT tenant_id,createdat,updatedat,state,stateat,touchedat,name AS id,usebt,access,owner,seqid,delid,subcnt,public,trusted,tags,aux "+
|
||||
"SELECT tenant_id,createdat,updatedat,state,stateat,touchedat,name AS id,usebt,access,owner,seqid,delid,subcnt,IFNULL(mutation_version,0) AS mutation_version,public,trusted,tags,aux "+
|
||||
"FROM im_topics WHERE tenant_id=? AND name=?", id, topic)
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
@@ -1013,7 +1033,7 @@ func (a *adapter) TenantTopicsForUser(id t.TenantID, uid t.Uid, keepDeleted bool
|
||||
return nil, err
|
||||
}
|
||||
if len(topq) > 0 {
|
||||
q = "SELECT tenant_id,updatedat,state,touchedat,name AS id,usebt,access,seqid,delid,subcnt,public,trusted FROM im_topics WHERE tenant_id=? AND name IN (?)"
|
||||
q = "SELECT tenant_id,updatedat,state,touchedat,name AS id,usebt,access,seqid,delid,subcnt,IFNULL(mutation_version,0) AS mutation_version,public,trusted FROM im_topics WHERE tenant_id=? AND name IN (?)"
|
||||
q, args, _ = sqlx.In(q, id, topq)
|
||||
if !keepDeleted {
|
||||
q += " AND state!=?"
|
||||
@@ -1042,6 +1062,7 @@ func (a *adapter) TenantTopicsForUser(id t.TenantID, uid t.Uid, keepDeleted bool
|
||||
sub.SetState(top.State)
|
||||
sub.SetTouchedAt(top.TouchedAt)
|
||||
sub.SetSeqId(top.SeqId)
|
||||
sub.Private = ensurePrivateMutationVersion(sub.Private, top.MutationVersion)
|
||||
if t.GetTopicCat(sub.Topic) == t.TopicCatGrp {
|
||||
sub.SetSubCnt(top.SubCnt)
|
||||
sub.SetPublic(common.FromJSON(top.Public))
|
||||
@@ -1575,6 +1596,49 @@ func (a *adapter) TenantTopicUpdate(id t.TenantID, topic string, update map[stri
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
func (a *adapter) TenantTopicBumpMutationVersion(id t.TenantID, topic string, at time.Time) (int64, error) {
|
||||
tenantAdp, err := a.routedTenantAdapter(id)
|
||||
if err != nil || topic == "" {
|
||||
return 0, t.ErrMalformed
|
||||
}
|
||||
if tenantAdp != a {
|
||||
return tenantAdp.TenantTopicBumpMutationVersion(id, topic, at)
|
||||
}
|
||||
if at.IsZero() {
|
||||
at = time.Now().UTC().Round(time.Millisecond)
|
||||
}
|
||||
ctx, cancel := a.getContextForTx()
|
||||
if cancel != nil {
|
||||
defer cancel()
|
||||
}
|
||||
tx, err := a.db.BeginTxx(ctx, nil)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer func() {
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
}
|
||||
}()
|
||||
if err = a.tenantTopicBumpMutationVersionTx(ctx, tx, id, topic, at); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
var version int64
|
||||
query := "SELECT mutation_version FROM im_topics WHERE name=?"
|
||||
args := []any{topic}
|
||||
if !a.tenantScopedDB {
|
||||
query = "SELECT mutation_version FROM im_topics WHERE tenant_id=? AND name=?"
|
||||
args = []any{id, topic}
|
||||
}
|
||||
if err = tx.GetContext(ctx, &version, query, args...); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if err = tx.Commit(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return version, nil
|
||||
}
|
||||
|
||||
func (a *adapter) TenantTopicOwnerChange(id t.TenantID, topic string, owner t.Uid) error {
|
||||
tenantAdp, err := a.routedTenantAdapter(id)
|
||||
if err != nil || topic == "" {
|
||||
@@ -2173,6 +2237,9 @@ func (a *adapter) TenantMessageRecall(id t.TenantID, topic string, seq int64, ac
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = a.tenantTopicBumpMutationVersionTx(ctx, tx, id, topic, recalledAt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = tx.Commit(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -2219,6 +2286,9 @@ func (a *adapter) TenantGroupMessageRecall(id t.TenantID, topic string, seq int6
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = a.tenantTopicBumpMutationVersionTx(ctx, tx, id, topic, recalledAt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = tx.Commit(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -2272,6 +2342,9 @@ func (a *adapter) TenantMessageEdit(id t.TenantID, topic string, seq int64, acto
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = a.tenantTopicBumpMutationVersionTx(ctx, tx, id, topic, editedAt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = tx.Commit(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -2324,6 +2397,9 @@ func (a *adapter) TenantGroupMessageEdit(id t.TenantID, topic string, seq int64,
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = a.tenantTopicBumpMutationVersionTx(ctx, tx, id, topic, editedAt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = tx.Commit(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -2601,6 +2677,129 @@ func (a *adapter) tenantGroupMessageSearch(id t.TenantID, topic string, uid t.Ui
|
||||
return msgs, nextCursor, nil
|
||||
}
|
||||
|
||||
func (a *adapter) TenantMessageMutations(id t.TenantID, topic string, uid t.Uid, sinceVersion int64, limit int) ([]t.MessageMutation, string, bool, int64, error) {
|
||||
if !a.tenantScopedDB {
|
||||
tenantAdp, err := a.routedTenantAdapter(id)
|
||||
if err != nil {
|
||||
return nil, "", false, 0, err
|
||||
}
|
||||
if tenantAdp != a {
|
||||
return tenantAdp.TenantMessageMutations(id, topic, uid, sinceVersion, limit)
|
||||
}
|
||||
}
|
||||
if err := validTenant(id); err != nil || topic == "" || uid.IsZero() || sinceVersion < 0 {
|
||||
return nil, "", false, 0, t.ErrMalformed
|
||||
}
|
||||
if limit <= 0 || limit > 100 {
|
||||
limit = 50
|
||||
}
|
||||
ctx, cancel := a.getContext()
|
||||
if cancel != nil {
|
||||
defer cancel()
|
||||
}
|
||||
var currentVersion int64
|
||||
if a.tenantScopedDB {
|
||||
if err := a.db.GetContext(ctx, ¤tVersion, "SELECT IFNULL(mutation_version,0) FROM im_topics WHERE name=?", topic); err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, "", false, 0, t.ErrNotFound
|
||||
}
|
||||
return nil, "", false, 0, err
|
||||
}
|
||||
} else {
|
||||
if err := a.db.GetContext(ctx, ¤tVersion, "SELECT IFNULL(mutation_version,0) FROM im_topics WHERE tenant_id=? AND name=?", id, topic); err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, "", false, 0, t.ErrNotFound
|
||||
}
|
||||
return nil, "", false, 0, err
|
||||
}
|
||||
}
|
||||
if sinceVersion >= currentVersion {
|
||||
return []t.MessageMutation{}, strconv.FormatInt(currentVersion, 10), false, currentVersion, nil
|
||||
}
|
||||
if t.GetTopicCat(topic) == t.TopicCatGrp {
|
||||
return a.tenantGroupMessageMutations(ctx, id, topic, uid, limit, currentVersion)
|
||||
}
|
||||
return a.tenantP2PMessageMutations(ctx, id, topic, uid, limit, currentVersion)
|
||||
}
|
||||
|
||||
func (a *adapter) tenantP2PMessageMutations(ctx context.Context, id t.TenantID, topic string, uid t.Uid, limit int, currentVersion int64) ([]t.MessageMutation, string, bool, int64, error) {
|
||||
rows, err := a.db.QueryxContext(ctx,
|
||||
"SELECT m.seqid,m.version,m.updatedat AS mutated_at,m.recalled_at,m.recalled_by,rev.actor "+
|
||||
"FROM im_messages AS m "+
|
||||
"LEFT JOIN im_dellog AS d ON d.topic=m.topic AND m.seqid BETWEEN d.low AND d.hi-1 AND d.deletedfor=? "+
|
||||
"LEFT JOIN im_message_revisions AS rev ON rev.topic=m.topic AND rev.seqid=m.seqid AND rev.version=m.version-1 "+
|
||||
"WHERE m.topic=? AND d.deletedfor IS NULL AND (m.version>1 OR m.recalled_at IS NOT NULL OR m.deletedat IS NOT NULL) "+
|
||||
"ORDER BY m.updatedat DESC,m.seqid DESC LIMIT ?",
|
||||
decodeUID(uid), topic, limit+1)
|
||||
if err != nil {
|
||||
return nil, "", false, 0, err
|
||||
}
|
||||
defer rows.Close()
|
||||
return scanTenantMessageMutations(rows, limit, currentVersion)
|
||||
}
|
||||
|
||||
func (a *adapter) tenantGroupMessageMutations(ctx context.Context, id t.TenantID, topic string, uid t.Uid, limit int, currentVersion int64) ([]t.MessageMutation, string, bool, int64, error) {
|
||||
rows, err := a.db.QueryxContext(ctx,
|
||||
"SELECT m.seqid,m.version,m.updated_at AS mutated_at,m.recalled_at,m.recalled_by,rev.actor "+
|
||||
"FROM im_group_messages AS m "+
|
||||
"LEFT JOIN im_dellog AS d ON d.topic COLLATE utf8mb4_unicode_ci=m.topic AND m.seqid BETWEEN d.low AND d.hi-1 AND d.deletedfor=? "+
|
||||
"LEFT JOIN im_message_revisions AS rev ON rev.topic=m.topic AND rev.seqid=m.seqid AND rev.version=m.version-1 "+
|
||||
"WHERE m.topic=? AND d.deletedfor IS NULL AND (m.version>1 OR m.recalled_at IS NOT NULL OR m.deleted_at IS NOT NULL OR m.status='recalled') "+
|
||||
"ORDER BY m.updated_at DESC,m.seqid DESC LIMIT ?",
|
||||
decodeUID(uid), topic, limit+1)
|
||||
if err != nil {
|
||||
return nil, "", false, 0, err
|
||||
}
|
||||
defer rows.Close()
|
||||
return scanTenantMessageMutations(rows, limit, currentVersion)
|
||||
}
|
||||
|
||||
func scanTenantMessageMutations(rows *sqlx.Rows, limit int, currentVersion int64) ([]t.MessageMutation, string, bool, int64, error) {
|
||||
items := make([]t.MessageMutation, 0, limit)
|
||||
hasMore := false
|
||||
for rows.Next() {
|
||||
row := struct {
|
||||
SeqId int64 `db:"seqid"`
|
||||
Version int `db:"version"`
|
||||
MutatedAt time.Time `db:"mutated_at"`
|
||||
RecalledAt sql.NullTime `db:"recalled_at"`
|
||||
RecalledBy sql.NullInt64 `db:"recalled_by"`
|
||||
Actor sql.NullInt64 `db:"actor"`
|
||||
}{}
|
||||
if err := rows.StructScan(&row); err != nil {
|
||||
return nil, "", false, 0, err
|
||||
}
|
||||
if len(items) == limit {
|
||||
hasMore = true
|
||||
continue
|
||||
}
|
||||
event := "message.edited"
|
||||
operator := ""
|
||||
if row.Actor.Valid {
|
||||
operator = store.EncodeUid(row.Actor.Int64).String()
|
||||
}
|
||||
if row.RecalledAt.Valid {
|
||||
event = "message.recalled"
|
||||
if row.RecalledBy.Valid {
|
||||
operator = store.EncodeUid(row.RecalledBy.Int64).String()
|
||||
}
|
||||
row.MutatedAt = row.RecalledAt.Time
|
||||
}
|
||||
items = append(items, t.MessageMutation{
|
||||
SeqId: row.SeqId,
|
||||
Version: row.Version,
|
||||
Event: event,
|
||||
Operator: operator,
|
||||
MutatedAt: row.MutatedAt,
|
||||
MutationVersion: currentVersion,
|
||||
})
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, "", false, 0, err
|
||||
}
|
||||
return items, strconv.FormatInt(currentVersion, 10), hasMore, currentVersion, nil
|
||||
}
|
||||
|
||||
func (a *adapter) TenantCallCreate(id t.TenantID, record *t.CallRecord) error {
|
||||
if !a.tenantScopedDB {
|
||||
tenantAdp, err := a.routedTenantAdapter(id)
|
||||
|
||||
Reference in New Issue
Block a user