package models import ( "crypto/sha512" "encoding/hex" "fmt" "gogs.mikescher.com/BlackForestBytes/goext/dataext" "gogs.mikescher.com/BlackForestBytes/goext/langext" "gogs.mikescher.com/BlackForestBytes/goext/mathext" "gogs.mikescher.com/BlackForestBytes/goext/sq" "strconv" "strings" "time" ) type MessageFilter struct { ConfirmedSubscriptionBy *UserID SearchString *[]string Sender *[]UserID ChannelNameCS *[]string // case-sensitive ChannelNameCI *[]string // case-insensitive ChannelID *[]ChannelID SenderNameCS *[]string // case-sensitive SenderNameCI *[]string // case-insensitive SenderIP *[]string TimestampCoalesce *time.Time TimestampCoalesceAfter *time.Time TimestampCoalesceBefore *time.Time TimestampReal *time.Time TimestampRealAfter *time.Time TimestampRealBefore *time.Time TimestampClient *time.Time TimestampClientAfter *time.Time TimestampClientBefore *time.Time TitleCS *string // case-sensitive TitleCI *string // case-insensitive Priority *[]int UserMessageID *[]string OnlyDeleted bool IncludeDeleted bool CompatAcknowledged *bool UsedKeyID *[]KeyTokenID } func (f MessageFilter) SQL() (string, string, sq.PP, error) { joinClause := "" if f.ConfirmedSubscriptionBy != nil { joinClause += " LEFT JOIN subscriptions AS subs on messages.channel_id = subs.channel_id " } if f.SearchString != nil { joinClause += " JOIN messages_fts AS mfts on (mfts.rowid = messages.rowid) " } sqlClauses := make([]string, 0) params := sq.PP{} if f.OnlyDeleted { sqlClauses = append(sqlClauses, "(deleted=1)") } else if f.IncludeDeleted { // nothing, return all } else { sqlClauses = append(sqlClauses, "(deleted=0)") // default } if f.ConfirmedSubscriptionBy != nil { sqlClauses = append(sqlClauses, "(subs.subscriber_user_id = :sub_uid AND subs.confirmed = 1)") params["sub_uid"] = *f.ConfirmedSubscriptionBy } if f.Sender != nil { filter := make([]string, 0) for i, v := range *f.Sender { filter = append(filter, fmt.Sprintf("(sender_user_id = :sender_%d)", i)) params[fmt.Sprintf("sender_%d", i)] = v } sqlClauses = append(sqlClauses, "("+strings.Join(filter, " OR ")+")") } if f.ChannelNameCI != nil { filter := make([]string, 0) for i, v := range *f.ChannelNameCI { filter = append(filter, fmt.Sprintf("(messages.channel_internal_name = :channelnameci_%d COLLATE NOCASE)", i)) params[fmt.Sprintf("channelnameci_%d", i)] = v } sqlClauses = append(sqlClauses, "("+strings.Join(filter, " OR ")+")") } if f.ChannelNameCS != nil { filter := make([]string, 0) for i, v := range *f.ChannelNameCS { filter = append(filter, fmt.Sprintf("(messages.channel_internal_name = :channelnamecs_%d COLLATE BINARY)", i)) params[fmt.Sprintf("channelnamecs_%d", i)] = v } sqlClauses = append(sqlClauses, "("+strings.Join(filter, " OR ")+")") } if f.ChannelID != nil { filter := make([]string, 0) for i, v := range *f.ChannelID { filter = append(filter, fmt.Sprintf("(messages.channel_id = :channelid_%d)", i)) params[fmt.Sprintf("channelid_%d", i)] = v } sqlClauses = append(sqlClauses, "("+strings.Join(filter, " OR ")+")") } if f.SenderNameCI != nil { filter := make([]string, 0) for i, v := range *f.SenderNameCI { filter = append(filter, fmt.Sprintf("(sender_name = :sendernameci_%d COLLATE NOCASE)", i)) params[fmt.Sprintf("sendernameci_%d", i)] = v } sqlClauses = append(sqlClauses, "(sender_name IS NOT NULL AND ("+strings.Join(filter, " OR ")+"))") } if f.SenderNameCS != nil { filter := make([]string, 0) for i, v := range *f.SenderNameCS { filter = append(filter, fmt.Sprintf("(sender_name = :sendernamecs_%d COLLATE BINARY)", i)) params[fmt.Sprintf("sendernamecs_%d", i)] = v } sqlClauses = append(sqlClauses, "(sender_name IS NOT NULL AND ("+strings.Join(filter, " OR ")+"))") } if f.SenderIP != nil { filter := make([]string, 0) for i, v := range *f.SenderIP { filter = append(filter, fmt.Sprintf("(sender_ip = :senderip_%d)", i)) params[fmt.Sprintf("senderip_%d", i)] = v } sqlClauses = append(sqlClauses, "("+strings.Join(filter, " OR ")+")") } if f.TimestampCoalesce != nil { sqlClauses = append(sqlClauses, "(COALESCE(timestamp_client, timestamp_real) = :ts_equals)") params["ts_equals"] = (*f.TimestampCoalesce).UnixMilli() } if f.TimestampCoalesceAfter != nil { sqlClauses = append(sqlClauses, "(COALESCE(timestamp_client, timestamp_real) > :ts_after)") params["ts_after"] = (*f.TimestampCoalesceAfter).UnixMilli() } if f.TimestampCoalesceBefore != nil { sqlClauses = append(sqlClauses, "(COALESCE(timestamp_client, timestamp_real) < :ts_before)") params["ts_before"] = (*f.TimestampCoalesceBefore).UnixMilli() } if f.TimestampReal != nil { sqlClauses = append(sqlClauses, "(timestamp_real = :ts_real_equals)") params["ts_real_equals"] = (*f.TimestampRealAfter).UnixMilli() } if f.TimestampRealAfter != nil { sqlClauses = append(sqlClauses, "(timestamp_real > :ts_real_after)") params["ts_real_after"] = (*f.TimestampRealAfter).UnixMilli() } if f.TimestampRealBefore != nil { sqlClauses = append(sqlClauses, "(timestamp_real < :ts_real_before)") params["ts_real_before"] = (*f.TimestampRealBefore).UnixMilli() } if f.TimestampClient != nil { sqlClauses = append(sqlClauses, "(timestamp_client IS NOT NULL AND timestamp_client = :ts_client_equals)") params["ts_client_equals"] = (*f.TimestampClient).UnixMilli() } if f.TimestampClientAfter != nil { sqlClauses = append(sqlClauses, "(timestamp_client IS NOT NULL AND timestamp_client > :ts_client_after)") params["ts_client_after"] = (*f.TimestampClientAfter).UnixMilli() } if f.TimestampClientBefore != nil { sqlClauses = append(sqlClauses, "(timestamp_client IS NOT NULL AND timestamp_client < :ts_client_before)") params["ts_client_before"] = (*f.TimestampClientBefore).UnixMilli() } if f.TitleCI != nil { sqlClauses = append(sqlClauses, "(title = :titleci COLLATE NOCASE)") params["titleci"] = *f.TitleCI } if f.TitleCS != nil { sqlClauses = append(sqlClauses, "(title = :titleci COLLATE BINARY)") params["titleci"] = *f.TitleCI } if f.Priority != nil { prioList := "(" + strings.Join(langext.ArrMap(*f.Priority, func(p int) string { return strconv.Itoa(p) }), ", ") + ")" sqlClauses = append(sqlClauses, "(priority IN "+prioList+")") } if f.UserMessageID != nil { filter := make([]string, 0) for i, v := range *f.UserMessageID { filter = append(filter, fmt.Sprintf("(usr_message_id = :usermessageid_%d)", i)) params[fmt.Sprintf("usermessageid_%d", i)] = v } sqlClauses = append(sqlClauses, "(usr_message_id IS NOT NULL AND ("+strings.Join(filter, " OR ")+"))") } if f.CompatAcknowledged != nil { joinClause += " LEFT JOIN compat_acks AS filter_compatack_compat_acks on messages.message_id = filter_compatack_compat_acks.message_id " if *f.CompatAcknowledged { sqlClauses = append(sqlClauses, "(filter_compatack_compat_acks.message_id IS NOT NULL)") } else { sqlClauses = append(sqlClauses, "(filter_compatack_compat_acks.message_id IS NULL)") } } if f.UsedKeyID != nil { filter := make([]string, 0) for i, v := range *f.UsedKeyID { filter = append(filter, fmt.Sprintf("(used_key_id = :usedkeyid_%d)", i)) params[fmt.Sprintf("usedkeyid_%d", i)] = v } sqlClauses = append(sqlClauses, "("+strings.Join(filter, " OR ")+")") } if f.SearchString != nil { filter := make([]string, 0) for i, v := range *f.SearchString { filter = append(filter, fmt.Sprintf("(messages_fts match :searchstring_%d)", i)) params[fmt.Sprintf("searchstring_%d", i)] = v } sqlClauses = append(sqlClauses, "("+strings.Join(filter, " OR ")+")") } sqlClause := "" if len(sqlClauses) > 0 { sqlClause = strings.Join(sqlClauses, " AND ") } else { sqlClause = "1=1" } return sqlClause, joinClause, params, nil } func (f MessageFilter) Hash() string { bh, err := dataext.StructHash(f, dataext.StructHashOptions{HashAlgo: sha512.New()}) if err != nil { return "00000000" } str := hex.EncodeToString(bh) return str[0:mathext.Min(8, len(bh))] }