feat: enforce youth mode policy and improve search
This commit is contained in:
@@ -2703,10 +2703,6 @@ func (a *adapter) TenantMessageSearch(id t.TenantID, topic string, uid t.Uid, qu
|
||||
args = append(args, parsed)
|
||||
}
|
||||
queryText = strings.TrimSpace(queryText)
|
||||
if queryText != "" {
|
||||
where = append(where, "CAST(m.content AS CHAR) LIKE ?")
|
||||
args = append(args, "%"+queryText+"%")
|
||||
}
|
||||
if messageType != "" && messageType != "all" {
|
||||
where = append(where, "(JSON_UNQUOTE(JSON_EXTRACT(m.head,'$.type'))=? OR JSON_UNQUOTE(JSON_EXTRACT(m.head,'$.\"x-im-type\"'))=?)")
|
||||
args = append(args, messageType, messageType)
|
||||
@@ -2715,8 +2711,9 @@ func (a *adapter) TenantMessageSearch(id t.TenantID, topic string, uid t.Uid, qu
|
||||
if cancel != nil {
|
||||
defer cancel()
|
||||
}
|
||||
scanLimit := messageSearchScanLimit(limit, queryText)
|
||||
queryArgs := append([]any{decodeUID(uid)}, args...)
|
||||
queryArgs = append(queryArgs, limit+1)
|
||||
queryArgs = append(queryArgs, scanLimit)
|
||||
rows, err := a.db.QueryxContext(ctx,
|
||||
"SELECT m.createdat,m.updatedat,m.deletedat,m.delid,m.seqid,m.topic,m.`from`,IFNULL(m.client_msg_id,'') AS client_msg_id,m.version,m.recalled_at,m.recalled_by,m.head,m.content "+
|
||||
"FROM im_messages AS m LEFT JOIN im_dellog AS d ON d.topic=m.topic "+
|
||||
@@ -2729,6 +2726,7 @@ func (a *adapter) TenantMessageSearch(id t.TenantID, topic string, uid t.Uid, qu
|
||||
msgs := make([]t.Message, 0, limit)
|
||||
nextCursor := ""
|
||||
lastSeq := int64(0)
|
||||
scanned := 0
|
||||
for rows.Next() {
|
||||
row := struct {
|
||||
t.Message
|
||||
@@ -2738,16 +2736,24 @@ func (a *adapter) TenantMessageSearch(id t.TenantID, topic string, uid t.Uid, qu
|
||||
return nil, "", err
|
||||
}
|
||||
msg := finalizeTenantMessage(id, row.Message, row.RecalledBy)
|
||||
if len(msgs) == limit {
|
||||
nextCursor = strconv.FormatInt(lastSeq, 10)
|
||||
scanned++
|
||||
lastSeq = msg.SeqId
|
||||
if !messageMatchesSearch(msg, queryText) {
|
||||
continue
|
||||
}
|
||||
if len(msgs) == limit {
|
||||
lastSeq = msgs[len(msgs)-1].SeqId
|
||||
nextCursor = strconv.FormatInt(lastSeq, 10)
|
||||
break
|
||||
}
|
||||
msgs = append(msgs, msg)
|
||||
lastSeq = msg.SeqId
|
||||
}
|
||||
if err = rows.Err(); err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
if nextCursor == "" && scanned == scanLimit && lastSeq > 0 {
|
||||
nextCursor = strconv.FormatInt(lastSeq, 10)
|
||||
}
|
||||
return msgs, nextCursor, nil
|
||||
}
|
||||
|
||||
@@ -2763,10 +2769,6 @@ func (a *adapter) tenantGroupMessageSearch(id t.TenantID, topic string, uid t.Ui
|
||||
args = append(args, parsed)
|
||||
}
|
||||
queryText = strings.TrimSpace(queryText)
|
||||
if queryText != "" {
|
||||
where = append(where, "CAST(m.content AS CHAR) LIKE ?")
|
||||
args = append(args, "%"+queryText+"%")
|
||||
}
|
||||
messageType = strings.TrimSpace(messageType)
|
||||
if messageType != "" && messageType != "all" {
|
||||
where = append(where, "m.msg_type=?")
|
||||
@@ -2780,10 +2782,11 @@ func (a *adapter) tenantGroupMessageSearch(id t.TenantID, topic string, uid t.Ui
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
msgs := make([]t.Message, 0, limit+1)
|
||||
seen := make(map[int64]struct{}, limit+1)
|
||||
scanLimit := messageSearchScanLimit(limit, queryText)
|
||||
msgs := make([]t.Message, 0, scanLimit)
|
||||
seen := make(map[int64]struct{}, scanLimit)
|
||||
for _, table := range tables {
|
||||
if len(msgs) > limit {
|
||||
if len(seen) >= scanLimit {
|
||||
break
|
||||
}
|
||||
month := groupMessageMonthFromTable(table)
|
||||
@@ -2792,7 +2795,7 @@ func (a *adapter) tenantGroupMessageSearch(id t.TenantID, topic string, uid t.Ui
|
||||
idColumn = "message_id"
|
||||
}
|
||||
queryArgs := append([]any{store.DecodeUid(uid)}, args...)
|
||||
queryArgs = append(queryArgs, limit+1-len(msgs))
|
||||
queryArgs = append(queryArgs, scanLimit-len(seen))
|
||||
query := fmt.Sprintf("SELECT m.%s AS 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`,IFNULL(m.client_msg_id,'') AS client_msg_id,m.version,m.recalled_at,m.recalled_by,m.head,m.content,'%s' AS shard_month FROM %s 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=? WHERE %s AND d.deletedfor IS NULL ORDER BY m.seqid DESC LIMIT ?", idColumn, month, table, strings.Join(where, " AND "))
|
||||
rows, queryErr := a.db.QueryxContext(ctx, query, queryArgs...)
|
||||
if queryErr != nil {
|
||||
@@ -2811,7 +2814,10 @@ func (a *adapter) tenantGroupMessageSearch(id t.TenantID, topic string, uid t.Ui
|
||||
continue
|
||||
}
|
||||
seen[row.SeqId] = struct{}{}
|
||||
msgs = append(msgs, normalizeGroupMessage(id, row.Message, row.RecalledBy))
|
||||
msg := normalizeGroupMessage(id, row.Message, row.RecalledBy)
|
||||
if messageMatchesSearch(msg, queryText) {
|
||||
msgs = append(msgs, msg)
|
||||
}
|
||||
}
|
||||
if err = rows.Err(); err != nil {
|
||||
rows.Close()
|
||||
@@ -2825,10 +2831,112 @@ func (a *adapter) tenantGroupMessageSearch(id t.TenantID, topic string, uid t.Ui
|
||||
msgs = msgs[:limit]
|
||||
lastSeq := msgs[len(msgs)-1].SeqId
|
||||
nextCursor = strconv.FormatInt(lastSeq, 10)
|
||||
} else if len(seen) >= scanLimit {
|
||||
lastScanned := int64(0)
|
||||
for seq := range seen {
|
||||
if lastScanned == 0 || seq < lastScanned {
|
||||
lastScanned = seq
|
||||
}
|
||||
}
|
||||
if lastScanned > 0 {
|
||||
nextCursor = strconv.FormatInt(lastScanned, 10)
|
||||
}
|
||||
}
|
||||
return msgs, nextCursor, nil
|
||||
}
|
||||
|
||||
func messageSearchScanLimit(limit int, queryText string) int {
|
||||
if limit <= 0 || limit > 100 {
|
||||
limit = 50
|
||||
}
|
||||
if strings.TrimSpace(queryText) == "" {
|
||||
return limit + 1
|
||||
}
|
||||
scanLimit := limit * 20
|
||||
if scanLimit < 200 {
|
||||
scanLimit = 200
|
||||
}
|
||||
if scanLimit > 1000 {
|
||||
scanLimit = 1000
|
||||
}
|
||||
return scanLimit
|
||||
}
|
||||
|
||||
func messageMatchesSearch(msg t.Message, queryText string) bool {
|
||||
queryText = normalizeMessageSearchText(queryText)
|
||||
if queryText == "" {
|
||||
return true
|
||||
}
|
||||
text := normalizeMessageSearchText(messageSearchText(msg.Content))
|
||||
if strings.Contains(text, queryText) {
|
||||
return true
|
||||
}
|
||||
terms := strings.Fields(queryText)
|
||||
if len(terms) == 0 {
|
||||
return true
|
||||
}
|
||||
for _, term := range terms {
|
||||
if !strings.Contains(text, term) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func messageSearchText(content any) string {
|
||||
switch value := content.(type) {
|
||||
case nil:
|
||||
return ""
|
||||
case string:
|
||||
return value
|
||||
case []byte:
|
||||
var decoded any
|
||||
if json.Unmarshal(value, &decoded) == nil {
|
||||
return messageSearchText(decoded)
|
||||
}
|
||||
return string(value)
|
||||
case map[string]any:
|
||||
return messageSearchMapText(value)
|
||||
case t.KVMap:
|
||||
return messageSearchMapText(map[string]any(value))
|
||||
default:
|
||||
raw, err := json.Marshal(value)
|
||||
if err != nil {
|
||||
return fmt.Sprint(value)
|
||||
}
|
||||
var decoded any
|
||||
if json.Unmarshal(raw, &decoded) == nil && fmt.Sprintf("%T", decoded) != fmt.Sprintf("%T", value) {
|
||||
return messageSearchText(decoded)
|
||||
}
|
||||
return string(raw)
|
||||
}
|
||||
}
|
||||
|
||||
func messageSearchMapText(value map[string]any) string {
|
||||
parts := make([]string, 0, len(value))
|
||||
for _, key := range []string{"text", "txt", "content", "title", "summary", "name", "file_name", "filename", "url"} {
|
||||
if text := strings.TrimSpace(fmt.Sprint(value[key])); text != "" && text != "<nil>" {
|
||||
parts = append(parts, text)
|
||||
}
|
||||
}
|
||||
for _, key := range []string{"file", "attachment", "snapshot", "forward", "link", "card"} {
|
||||
if nested, ok := value[key]; ok {
|
||||
if text := strings.TrimSpace(messageSearchText(nested)); text != "" {
|
||||
parts = append(parts, text)
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(parts) == 0 {
|
||||
raw, _ := json.Marshal(value)
|
||||
return string(raw)
|
||||
}
|
||||
return strings.Join(parts, " ")
|
||||
}
|
||||
|
||||
func normalizeMessageSearchText(value string) string {
|
||||
return strings.ToLower(strings.Join(strings.Fields(strings.TrimSpace(value)), " "))
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
Reference in New Issue
Block a user