feat: add redis cache and fanout support

This commit is contained in:
richjohnrambo
2026-08-25 20:54:03 +08:00
parent 07376c70e3
commit f900a08a01
68 changed files with 3949 additions and 5696 deletions
+9 -9
View File
@@ -754,7 +754,7 @@ func (a *adapter) CreateDb(reset bool) error {
usebt TINYINT DEFAULT 0,
owner BIGINT NOT NULL DEFAULT 0,
access JSON,
seqid INT NOT NULL DEFAULT 0,
seqid BIGINT NOT NULL DEFAULT 0,
delid INT DEFAULT 0,
subcnt INT DEFAULT 0,
public JSON,
@@ -799,8 +799,8 @@ func (a *adapter) CreateDb(reset bool) error {
userid BIGINT NOT NULL,
topic CHAR(25) NOT NULL,
delid INT DEFAULT 0,
recvseqid INT DEFAULT 0,
readseqid INT DEFAULT 0,
recvseqid BIGINT DEFAULT 0,
readseqid BIGINT DEFAULT 0,
modewant CHAR(8),
modegiven CHAR(8),
private JSON,
@@ -822,7 +822,7 @@ func (a *adapter) CreateDb(reset bool) error {
updatedat DATETIME(3) NOT NULL,
deletedat DATETIME(3),
delid INT DEFAULT 0,
seqid INT NOT NULL,
seqid BIGINT NOT NULL,
topic CHAR(25) NOT NULL,` +
"`from` BIGINT NOT NULL," +
`client_msg_id VARCHAR(64),
@@ -845,7 +845,7 @@ func (a *adapter) CreateDb(reset bool) error {
createdat DATETIME(3) NOT NULL,
updatedat DATETIME(3) NOT NULL,
topic CHAR(25) NOT NULL,
seqid INT NOT NULL,
seqid BIGINT NOT NULL,
version INT NOT NULL,
actor BIGINT NOT NULL,
head JSON,
@@ -1050,7 +1050,7 @@ func createIMCallTables(tx *sql.Tx) error {
id BIGINT NOT NULL AUTO_INCREMENT,
call_id VARCHAR(64) NOT NULL,
topic CHAR(25) NOT NULL,
seq_id INT NOT NULL,
seq_id BIGINT NOT NULL,
caller_id BIGINT NOT NULL,
callee_id BIGINT NOT NULL,
call_type VARCHAR(16) NOT NULL,
@@ -1403,7 +1403,7 @@ func createIMUserProductTables(tx *sql.Tx) error {
target_type VARCHAR(16) NOT NULL,
target_user_id BIGINT NOT NULL DEFAULT 0,
target_topic VARCHAR(32) NOT NULL DEFAULT '',
target_seq INT NOT NULL DEFAULT 0,
target_seq BIGINT NOT NULL DEFAULT 0,
reason_code VARCHAR(64) NOT NULL,
description TEXT NOT NULL,
evidence_file_ids JSON,
@@ -1481,7 +1481,7 @@ func upgradeIMMessageActions(a *adapter) error {
createdat DATETIME(3) NOT NULL,
updatedat DATETIME(3) NOT NULL,
topic CHAR(25) NOT NULL,
seqid INT NOT NULL,
seqid BIGINT NOT NULL,
version INT NOT NULL,
actor BIGINT NOT NULL,
head JSON,
@@ -1554,7 +1554,7 @@ func createIMRelationTables(exec sqlExecutor) error {
user_id BIGINT NOT NULL,
item_type VARCHAR(16) NOT NULL,
source_topic CHAR(25) NOT NULL,
source_seq INT NOT NULL,
source_seq BIGINT NOT NULL,
snapshot JSON,
attachment_refs JSON,
availability VARCHAR(16) NOT NULL DEFAULT 'available',
+21 -1
View File
@@ -23,6 +23,26 @@ func ensureIMGroupBusinessTables(exec sqlExecutor) error {
INDEX idx_im_group_settings_owner(owner_user_id, group_status),
INDEX idx_im_group_settings_status_updated(group_status, updated_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci`,
`CREATE TABLE IF NOT EXISTS im_group_messages (
id BIGINT NOT NULL AUTO_INCREMENT, topic VARCHAR(64) NOT NULL, group_id BIGINT NOT NULL,
seqid BIGINT NOT NULL, from_user BIGINT NOT NULL, client_msg_id VARCHAR(64),
msg_type VARCHAR(32) NOT NULL DEFAULT 'text', status VARCHAR(32) NOT NULL DEFAULT 'normal',
content JSON, head JSON, mentioned_user_ids JSON, mention_all TINYINT(1) NOT NULL DEFAULT 0,
reply_seqid BIGINT, root_seqid BIGINT, file_count INT NOT NULL DEFAULT 0,
content_size INT NOT NULL DEFAULT 0, has_risk TINYINT(1) NOT NULL DEFAULT 0,
audit_status VARCHAR(32) NOT NULL DEFAULT 'pass', version INT NOT NULL DEFAULT 1,
recalled_at DATETIME(3), recalled_by BIGINT, deleted_at DATETIME(3), expires_at DATETIME(3),
created_at DATETIME(3) NOT NULL, updated_at DATETIME(3) NOT NULL,
PRIMARY KEY(id), UNIQUE INDEX uk_im_group_messages_group_seq(group_id, seqid),
UNIQUE INDEX uk_im_group_messages_topic_seq(topic, seqid),
UNIQUE INDEX uk_im_group_messages_topic_sender_client_mid(topic, from_user, client_msg_id),
INDEX idx_im_group_messages_group_created(group_id, created_at),
INDEX idx_im_group_messages_group_status_seq(group_id, status, seqid),
INDEX idx_im_group_messages_topic_created(topic, created_at),
INDEX idx_im_group_messages_topic_from_seq(topic, from_user, seqid),
INDEX idx_im_group_messages_audit(audit_status, created_at),
INDEX idx_im_group_messages_expires(expires_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci`,
`CREATE TABLE IF NOT EXISTS im_group_members (
id BIGINT NOT NULL AUTO_INCREMENT, topic VARCHAR(32) NOT NULL, user_id BIGINT NOT NULL,
role VARCHAR(16) NOT NULL DEFAULT 'member', member_status VARCHAR(16) NOT NULL DEFAULT 'active',
@@ -105,7 +125,7 @@ func ensureIMGroupBusinessTables(exec sqlExecutor) error {
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci`,
`CREATE TABLE IF NOT EXISTS im_group_files (
id BIGINT NOT NULL AUTO_INCREMENT, group_file_id VARCHAR(64) NOT NULL, topic VARCHAR(32) NOT NULL,
file_id VARCHAR(64) NOT NULL, source_topic VARCHAR(32) NOT NULL DEFAULT '', source_seq INT NOT NULL DEFAULT 0,
file_id VARCHAR(64) NOT NULL, source_topic VARCHAR(32) NOT NULL DEFAULT '', source_seq BIGINT NOT NULL DEFAULT 0,
uploader_id BIGINT NOT NULL, file_name VARCHAR(255) NOT NULL DEFAULT '', mime_type VARCHAR(128) NOT NULL DEFAULT '',
file_type VARCHAR(32) NOT NULL DEFAULT 'file', file_size BIGINT NOT NULL DEFAULT 0,
status VARCHAR(16) NOT NULL DEFAULT 'active', deleted_by BIGINT, deleted_at DATETIME(3),
+434 -10
View File
@@ -92,6 +92,56 @@ func finalizeTenantMessage(id t.TenantID, msg t.Message, recalledBy sql.NullInt6
return msg
}
func normalizeGroupMessage(id t.TenantID, msg t.Message, recalledBy sql.NullInt64) t.Message {
msg.TenantID = id
msg.From = common.EncodeUidString(msg.From).String()
if recalledBy.Valid {
msg.RecalledBy = common.EncodeUidString(strconv.FormatInt(recalledBy.Int64, 10)).String()
}
msg.Content = common.FromJSON(msg.Content)
return msg
}
func messageHeadType(head t.KVMap) string {
if head == nil {
return "text"
}
for _, key := range []string{"x-im-type", "type"} {
if value, ok := head[key].(string); ok {
value = strings.TrimSpace(value)
if value != "" {
return value
}
}
}
return "text"
}
func messageHeadJSON(head t.KVMap, key string) []byte {
if head == nil {
return nil
}
if value, ok := head[key]; ok {
return common.ToJSON(value)
}
return nil
}
func messageHeadBool(head t.KVMap, key string) bool {
if head == nil {
return false
}
switch value := head[key].(type) {
case bool:
return value
case string:
parsed, _ := strconv.ParseBool(value)
return parsed
default:
return false
}
}
func stampTenantFile(id t.TenantID, file *t.FileDef) *t.FileDef {
if file != nil {
file.TenantID = id
@@ -1105,6 +1155,63 @@ func tenantMessageDeleteList(tx *sqlx.Tx, tenantID t.TenantID, topic string, toD
return nil
}
func groupMessageDeleteList(tx *sqlx.Tx, topic string, toDel *t.DelMessage) error {
var err error
if toDel == nil {
if _, err = tx.Exec("DELETE FROM im_dellog WHERE topic=?", topic); err != nil {
return err
}
_, err = tx.Exec("DELETE FROM im_group_messages WHERE topic=?", topic)
return err
}
delRanges := toDel.SeqIdRanges
if toDel.DeletedFor == "" {
where := "m.topic=?"
args := []any{topic}
if len(delRanges) > 0 {
rSQL, rArgs := common.RangesToSql(delRanges)
where += " AND m.seqid " + rSQL
args = append(args, rArgs...)
}
where += " AND m.deleted_at IS NULL"
if newerThan := toDel.GetNewerThan(); newerThan != nil {
where += " AND m.created_at>?"
args = append(args, newerThan)
}
var seqIDs []int
if err = tx.Select(&seqIDs, "SELECT seqid FROM im_group_messages AS m WHERE "+where, args...); err != nil {
return err
}
if len(seqIDs) == 0 {
return nil
}
sort.Ints(seqIDs)
delRanges = t.SliceToRanges(seqIDs)
rSQL, rArgs := common.RangesToSql(delRanges)
where = "m.topic=? AND m.seqid " + rSQL
args = append([]any{topic}, rArgs...)
if _, err = tx.Exec("UPDATE im_group_messages AS m SET m.updated_at=?,m.deleted_at=?,m.status='deleted',m.from_user=0,m.head=NULL,m.content=NULL,m.content_size=0 WHERE "+
where, append([]any{t.TimeNow(), t.TimeNow()}, args...)...); err != nil {
return err
}
}
stmt, err := tx.Prepare("INSERT INTO im_dellog(topic,deletedfor,delid,low,hi) VALUES(?,?,?,?,?)")
if err != nil {
return err
}
defer stmt.Close()
forUser := common.DecodeUidString(toDel.DeletedFor)
for _, rng := range delRanges {
if rng.Hi == 0 {
rng.Hi = rng.Low + 1
}
if _, err = stmt.Exec(topic, forUser, toDel.DelId, rng.Low, rng.Hi); err != nil {
return err
}
}
return nil
}
func (a *adapter) TenantTopicDelete(id t.TenantID, topic string, isChan, hard bool) error {
tenantAdp, err := a.routedTenantAdapter(id)
if err != nil || topic == "" {
@@ -1138,7 +1245,12 @@ func (a *adapter) TenantTopicDelete(id t.TenantID, topic string, isChan, hard bo
if _, err = tx.Exec(tx.Rebind(q), args...); err != nil {
return err
}
if err = tenantMessageDeleteList(tx, id, topic, nil); err != nil {
if t.GetTopicCat(topic) == t.TopicCatGrp {
err = groupMessageDeleteList(tx, topic, nil)
} else {
err = tenantMessageDeleteList(tx, id, topic, nil)
}
if err != nil {
return err
}
if _, err = tx.Exec("DELETE FROM im_topictags WHERE tenant_id=? AND topic=?", id, topic); err != nil {
@@ -1652,6 +1764,42 @@ func (a *adapter) TenantMessageSave(id t.TenantID, msg *t.Message) error {
return err
}
func (a *adapter) TenantGroupMessageSave(id t.TenantID, msg *t.Message) error {
tenantAdp, err := a.routedTenantAdapter(id)
if err != nil || msg == nil || msg.TenantID != id || msg.Topic == "" || msg.SeqId <= 0 {
return t.ErrMalformed
}
if tenantAdp != a {
return tenantAdp.TenantGroupMessageSave(id, msg)
}
ctx, cancel := a.getContext()
if cancel != nil {
defer cancel()
}
groupID, err := a.groupIDByTopic(ctx, msg.Topic)
if err != nil {
return err
}
clientMsgID := sql.NullString{String: msg.ClientMsgID, Valid: msg.ClientMsgID != ""}
contentJSON := common.ToJSON(msg.Content)
res, err := a.db.ExecContext(ctx,
`INSERT INTO im_group_messages
(topic,group_id,seqid,from_user,client_msg_id,msg_type,status,content,head,mentioned_user_ids,mention_all,
file_count,content_size,version,created_at,updated_at)
VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`,
msg.Topic, groupID, msg.SeqId, decodeUID(t.ParseUid(msg.From)), clientMsgID, messageHeadType(msg.Head), "normal",
contentJSON, msg.Head, messageHeadJSON(msg.Head, "mentioned_user_ids"), messageHeadBool(msg.Head, "mention_all"),
0, len(contentJSON), messageVersion(msg.Version), msg.CreatedAt, msg.UpdatedAt)
if err == nil {
lastID, _ := res.LastInsertId()
msg.SetUid(t.Uid(lastID))
}
if isDupe(err) {
return t.ErrDuplicate
}
return err
}
func (a *adapter) TenantMessageGetByClientID(id t.TenantID, from t.Uid, clientMsgID string) (*t.Message, error) {
if !a.tenantScopedDB {
tenantAdp, err := a.routedTenantAdapter(id)
@@ -1687,7 +1835,40 @@ func (a *adapter) TenantMessageGetByClientID(id t.TenantID, from t.Uid, clientMs
return &msg, nil
}
func (a *adapter) TenantMessageGetOne(id t.TenantID, topic string, seq int) (*t.Message, error) {
func (a *adapter) TenantGroupMessageGetByClientID(id t.TenantID, topic string, from t.Uid, clientMsgID string) (*t.Message, error) {
tenantAdp, err := a.routedTenantAdapter(id)
if err != nil {
return nil, err
}
if tenantAdp != a {
return tenantAdp.TenantGroupMessageGetByClientID(id, topic, from, clientMsgID)
}
if topic == "" || from.IsZero() || clientMsgID == "" {
return nil, t.ErrMalformed
}
ctx, cancel := a.getContext()
if cancel != nil {
defer cancel()
}
row := struct {
t.Message
RecalledBy sql.NullInt64 `db:"recalled_by"`
}{}
err = a.db.QueryRowxContext(ctx,
"SELECT id,created_at AS createdat,updated_at AS updatedat,deleted_at AS deletedat,0 AS delid,seqid,topic,from_user AS `from`,client_msg_id,version,recalled_at,recalled_by,head,content "+
"FROM im_group_messages WHERE topic=? AND from_user=? AND client_msg_id=?",
topic, decodeUID(from), clientMsgID).StructScan(&row)
if err == sql.ErrNoRows {
return nil, nil
}
if err != nil {
return nil, err
}
msg := normalizeGroupMessage(id, row.Message, row.RecalledBy)
return &msg, nil
}
func (a *adapter) TenantMessageGetOne(id t.TenantID, topic string, seq int64) (*t.Message, error) {
if !a.tenantScopedDB {
tenantAdp, err := a.routedTenantAdapter(id)
if err != nil {
@@ -1711,7 +1892,29 @@ func (a *adapter) TenantMessageGetOne(id t.TenantID, topic string, seq int) (*t.
return msg, err
}
func (a *adapter) TenantMessageRecall(id t.TenantID, topic string, seq int, actor t.Uid, expectedVersion int, recalledAt time.Time) (*t.Message, error) {
func (a *adapter) TenantGroupMessageGetOne(id t.TenantID, topic string, seq int64) (*t.Message, error) {
tenantAdp, err := a.routedTenantAdapter(id)
if err != nil {
return nil, err
}
if tenantAdp != a {
return tenantAdp.TenantGroupMessageGetOne(id, topic, seq)
}
if topic == "" || seq <= 0 {
return nil, t.ErrMalformed
}
ctx, cancel := a.getContext()
if cancel != nil {
defer cancel()
}
msg, err := tenantGroupMessageGetOne(a.db, ctx, id, topic, seq, "")
if err == sql.ErrNoRows {
return nil, t.ErrNotFound
}
return msg, err
}
func (a *adapter) TenantMessageRecall(id t.TenantID, topic string, seq int64, actor t.Uid, expectedVersion int, recalledAt time.Time) (*t.Message, error) {
if !a.tenantScopedDB {
tenantAdp, err := a.routedTenantAdapter(id)
if err != nil {
@@ -1763,7 +1966,57 @@ func (a *adapter) TenantMessageRecall(id t.TenantID, topic string, seq int, acto
return a.TenantMessageGetOne(id, topic, seq)
}
func (a *adapter) TenantMessageEdit(id t.TenantID, topic string, seq int, actor t.Uid, expectedVersion int, head t.KVMap, content any, reason string, editedAt time.Time) (*t.Message, error) {
func (a *adapter) TenantGroupMessageRecall(id t.TenantID, topic string, seq int64, actor t.Uid, expectedVersion int, recalledAt time.Time) (*t.Message, error) {
tenantAdp, err := a.routedTenantAdapter(id)
if err != nil {
return nil, err
}
if tenantAdp != a {
return tenantAdp.TenantGroupMessageRecall(id, topic, seq, actor, expectedVersion, recalledAt)
}
if topic == "" || seq <= 0 || actor.IsZero() || expectedVersion <= 0 {
return nil, t.ErrMalformed
}
ctx, cancel := a.getContextForTx()
if cancel != nil {
defer cancel()
}
tx, err := a.db.BeginTxx(ctx, nil)
if err != nil {
return nil, err
}
defer func() {
if err != nil {
tx.Rollback()
}
}()
msg, err := tenantGroupMessageGetOne(tx, ctx, id, topic, seq, " FOR UPDATE")
if err != nil {
if err == sql.ErrNoRows {
return nil, t.ErrNotFound
}
return nil, err
}
if msg.Version != expectedVersion || msg.RecalledAt != nil || msg.DeletedAt != nil {
return nil, t.ErrDuplicate
}
_, err = tx.ExecContext(ctx,
"UPDATE im_group_messages SET updated_at=?,version=version+1,status='recalled',recalled_at=?,recalled_by=?,head=NULL,content=NULL WHERE topic=? AND seqid=? AND version=?",
recalledAt, recalledAt, decodeUID(actor), topic, seq, expectedVersion)
if err != nil {
return nil, err
}
if err = insertBusinessEventTx(tx, t.StableEventID("message", topic, seq, expectedVersion+1, "recalled"),
"topic", topic, actor, "message.recalled", map[string]any{"topic": topic, "seq": seq, "version": expectedVersion + 1}); err != nil {
return nil, err
}
if err = tx.Commit(); err != nil {
return nil, err
}
return a.TenantGroupMessageGetOne(id, topic, seq)
}
func (a *adapter) TenantMessageEdit(id t.TenantID, topic string, seq int64, actor t.Uid, expectedVersion int, head t.KVMap, content any, reason string, editedAt time.Time) (*t.Message, error) {
if !a.tenantScopedDB {
tenantAdp, err := a.routedTenantAdapter(id)
if err != nil {
@@ -1820,6 +2073,62 @@ func (a *adapter) TenantMessageEdit(id t.TenantID, topic string, seq int, actor
return a.TenantMessageGetOne(id, topic, seq)
}
func (a *adapter) TenantGroupMessageEdit(id t.TenantID, topic string, seq int64, actor t.Uid, expectedVersion int, head t.KVMap, content any, reason string, editedAt time.Time) (*t.Message, error) {
tenantAdp, err := a.routedTenantAdapter(id)
if err != nil {
return nil, err
}
if tenantAdp != a {
return tenantAdp.TenantGroupMessageEdit(id, topic, seq, actor, expectedVersion, head, content, reason, editedAt)
}
if topic == "" || seq <= 0 || actor.IsZero() || expectedVersion <= 0 {
return nil, t.ErrMalformed
}
ctx, cancel := a.getContextForTx()
if cancel != nil {
defer cancel()
}
tx, err := a.db.BeginTxx(ctx, nil)
if err != nil {
return nil, err
}
defer func() {
if err != nil {
tx.Rollback()
}
}()
msg, err := tenantGroupMessageGetOne(tx, ctx, id, topic, seq, " FOR UPDATE")
if err != nil {
if err == sql.ErrNoRows {
return nil, t.ErrNotFound
}
return nil, err
}
if msg.Version != expectedVersion || msg.RecalledAt != nil || msg.DeletedAt != nil {
return nil, t.ErrDuplicate
}
if _, err = tx.ExecContext(ctx,
"INSERT INTO im_message_revisions(createdat,updatedat,topic,seqid,version,actor,head,content,reason) VALUES(?,?,?,?,?,?,?,?,?)",
editedAt, editedAt, topic, seq, msg.Version, decodeUID(actor), msg.Head, common.ToJSON(msg.Content), strings.TrimSpace(reason)); err != nil {
return nil, err
}
contentJSON := common.ToJSON(content)
_, err = tx.ExecContext(ctx,
"UPDATE im_group_messages SET updated_at=?,version=version+1,msg_type=?,head=?,content=?,content_size=? WHERE topic=? AND seqid=? AND version=?",
editedAt, messageHeadType(head), head, contentJSON, len(contentJSON), topic, seq, expectedVersion)
if err != nil {
return nil, err
}
if err = insertBusinessEventTx(tx, t.StableEventID("message", topic, seq, expectedVersion+1, "edited"),
"topic", topic, actor, "message.edited", map[string]any{"topic": topic, "seq": seq, "version": expectedVersion + 1}); err != nil {
return nil, err
}
if err = tx.Commit(); err != nil {
return nil, err
}
return a.TenantGroupMessageGetOne(id, topic, seq)
}
func (a *adapter) TenantMessageGetAll(id t.TenantID, topic string, uid t.Uid, opts *t.QueryOpt) ([]t.Message, error) {
tenantAdp, err := a.routedTenantAdapter(id)
if err != nil || topic == "" {
@@ -1888,6 +2197,69 @@ func (a *adapter) TenantMessageGetAll(id t.TenantID, topic string, uid t.Uid, op
return msgs, rows.Err()
}
func (a *adapter) TenantGroupMessageGetAll(id t.TenantID, topic string, uid t.Uid, opts *t.QueryOpt) ([]t.Message, error) {
tenantAdp, err := a.routedTenantAdapter(id)
if err != nil || topic == "" {
return nil, t.ErrMalformed
}
if tenantAdp != a {
return tenantAdp.TenantGroupMessageGetAll(id, topic, uid, opts)
}
limit := a.maxMessageResults
args := []any{store.DecodeUid(uid), topic}
seq := ""
if opts != nil {
seq = "AND m.seqid "
if len(opts.IdRanges) > 0 {
constr, addArgs := common.RangesToSql(opts.IdRanges)
seq += constr
args = append(args, addArgs...)
} else {
seq += "BETWEEN ? AND ?"
if opts.Since > 0 {
args = append(args, opts.Since)
} else {
args = append(args, 0)
}
if opts.Before > 1 {
args = append(args, opts.Before-1)
} else {
args = append(args, 1<<31-1)
}
}
if opts.Limit > 0 && opts.Limit < limit {
limit = opts.Limit
}
}
args = append(args, limit)
ctx, cancel := a.getContext()
if cancel != nil {
defer cancel()
}
rows, err := a.db.QueryxContext(ctx,
"SELECT m.id,m.created_at AS createdat,m.updated_at AS updatedat,m.deleted_at AS deletedat,0 AS delid,m.seqid,m.topic,m.from_user AS `from`,m.client_msg_id,m.version,m.recalled_at,m.recalled_by,m.head,m.content "+
"FROM im_group_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=? "+
"WHERE m.status!='deleted' AND m.topic=? "+seq+" AND d.deletedfor IS NULL ORDER BY m.seqid DESC LIMIT ?",
args...)
if err != nil {
return nil, err
}
defer rows.Close()
msgs := make([]t.Message, 0, limit)
for rows.Next() {
row := struct {
t.Message
RecalledBy sql.NullInt64 `db:"recalled_by"`
}{}
if err = rows.StructScan(&row); err != nil {
return nil, err
}
msgs = append(msgs, normalizeGroupMessage(id, row.Message, row.RecalledBy))
}
return msgs, rows.Err()
}
func (a *adapter) TenantMessageSearch(id t.TenantID, topic string, uid t.Uid, queryText, messageType, cursor string, limit int) ([]t.Message, string, error) {
if !a.tenantScopedDB {
tenantAdp, err := a.routedTenantAdapter(id)
@@ -1951,7 +2323,7 @@ func (a *adapter) TenantMessageSearch(id t.TenantID, topic string, uid t.Uid, qu
msg := finalizeTenantMessage(id, row.Message, row.RecalledBy)
msg.Head = kvMapFromJSON(msg.Head)
if len(msgs) == limit {
nextCursor = strconv.Itoa(msg.SeqId)
nextCursor = strconv.FormatInt(msg.SeqId, 10)
continue
}
msgs = append(msgs, msg)
@@ -2001,7 +2373,7 @@ func (a *adapter) TenantCallCreate(id t.TenantID, record *t.CallRecord) error {
return err
}
func (a *adapter) TenantCallAccept(id t.TenantID, callID string, seq int, acceptedAt time.Time) error {
func (a *adapter) TenantCallAccept(id t.TenantID, callID string, seq int64, acceptedAt time.Time) error {
if !a.tenantScopedDB {
tenantAdp, err := a.routedTenantAdapter(id)
if err != nil {
@@ -2026,7 +2398,7 @@ func (a *adapter) TenantCallAccept(id t.TenantID, callID string, seq int, accept
return err
}
func (a *adapter) TenantCallFinish(id t.TenantID, callID string, seq int, status string, durationMS int64, finishedAt time.Time) error {
func (a *adapter) TenantCallFinish(id t.TenantID, callID string, seq int64, status string, durationMS int64, finishedAt time.Time) error {
if !a.tenantScopedDB {
tenantAdp, err := a.routedTenantAdapter(id)
if err != nil {
@@ -2102,7 +2474,7 @@ func (a *adapter) TenantCallList(id t.TenantID, uid t.Uid, topic, cursor string,
ID int64
CallID string `db:"call_id"`
Topic string
SeqID int `db:"seq_id"`
SeqID int64 `db:"seq_id"`
CallerID int64 `db:"caller_id"`
CalleeID int64 `db:"callee_id"`
CallType string `db:"call_type"`
@@ -4509,7 +4881,7 @@ func (a *adapter) abuseReportByIdempotency(ctx context.Context, uid t.Uid, idemp
TargetType string `db:"target_type"`
TargetUserID int64 `db:"target_user_id"`
TargetTopic string `db:"target_topic"`
TargetSeq int `db:"target_seq"`
TargetSeq int64 `db:"target_seq"`
ReasonCode string `db:"reason_code"`
Description string `db:"description"`
EvidenceFileIDs []byte `db:"evidence_file_ids"`
@@ -4699,7 +5071,7 @@ func isAllowedWorkbenchRoute(route string) bool {
}
}
func tenantMessageGetOne(q sqlx.QueryerContext, ctx context.Context, tenantID t.TenantID, topic string, seq int, suffix string) (*t.Message, error) {
func tenantMessageGetOne(q sqlx.QueryerContext, ctx context.Context, tenantID t.TenantID, topic string, seq int64, suffix string) (*t.Message, error) {
row := struct {
t.Message
RecalledBy sql.NullInt64 `db:"recalled_by"`
@@ -4721,6 +5093,31 @@ func tenantMessageGetOne(q sqlx.QueryerContext, ctx context.Context, tenantID t.
return &msg, nil
}
func (a *adapter) groupIDByTopic(ctx context.Context, topic string) (int64, error) {
var groupID int64
err := a.db.QueryRowContext(ctx, "SELECT id FROM im_group_settings WHERE topic=? LIMIT 1", topic).Scan(&groupID)
if err == sql.ErrNoRows {
return 0, t.ErrNotFound
}
return groupID, err
}
func tenantGroupMessageGetOne(q sqlx.QueryerContext, ctx context.Context, tenantID t.TenantID, topic string, seq int64, suffix string) (*t.Message, error) {
row := struct {
t.Message
RecalledBy sql.NullInt64 `db:"recalled_by"`
}{}
err := sqlx.GetContext(ctx, q, &row,
"SELECT id,created_at AS createdat,updated_at AS updatedat,deleted_at AS deletedat,0 AS delid,seqid,topic,from_user AS `from`,client_msg_id,version,recalled_at,recalled_by,head,content "+
"FROM im_group_messages WHERE topic=? AND seqid=?"+suffix,
topic, seq)
if err != nil {
return nil, err
}
msg := normalizeGroupMessage(tenantID, row.Message, row.RecalledBy)
return &msg, nil
}
func messageVersion(version int) int {
if version <= 0 {
return 1
@@ -5174,6 +5571,33 @@ func (a *adapter) TenantMessageDeleteList(id t.TenantID, topic string, del *t.De
return tx.Commit()
}
func (a *adapter) TenantGroupMessageDeleteList(id t.TenantID, topic string, del *t.DelMessage) error {
tenantAdp, err := a.routedTenantAdapter(id)
if err != nil || topic == "" || (del != nil && del.TenantID != id) {
return t.ErrMalformed
}
if tenantAdp != a {
return tenantAdp.TenantGroupMessageDeleteList(id, topic, del)
}
ctx, cancel := a.getContextForTx()
if cancel != nil {
defer cancel()
}
tx, err := a.db.BeginTxx(ctx, nil)
if err != nil {
return err
}
defer func() {
if err != nil {
tx.Rollback()
}
}()
if err = groupMessageDeleteList(tx, topic, del); err != nil {
return err
}
return tx.Commit()
}
func (a *adapter) TenantMessageGetDeleted(id t.TenantID, topic string, uid t.Uid, opts *t.QueryOpt) ([]t.DelMessage, error) {
tenantAdp, err := a.routedTenantAdapter(id)
if err != nil || topic == "" {
+1 -1
View File
@@ -1201,7 +1201,7 @@ func TestTopicUpdateOnMessage(t *testing.T) {
}
var got struct {
TouchedAt time.Time
SeqId int
SeqId int64
}
err = db.QueryRow("SELECT touchedat, seqid FROM im_topics WHERE name=?", testData.Topics[2].Id).
Scan(&got.TouchedAt, &got.SeqId)