owpengram-server/internal/store/postgres/channel_reaction_catalog.go

190 lines
6.1 KiB
Go

package postgres
import (
"context"
"fmt"
"strings"
"telesrv/internal/domain"
)
func (s *ChannelStore) ListTopMessageReactions(ctx context.Context, userID int64, limit int) ([]domain.MessageReaction, error) {
if userID == 0 {
return nil, domain.ErrChannelInvalid
}
if limit <= 0 {
return []domain.MessageReaction{}, nil
}
if limit > domain.MaxTopMessageReactions {
limit = domain.MaxTopMessageReactions
}
rows, err := s.db.Query(ctx, `
SELECT reaction_type, reaction_value
FROM user_top_reactions
WHERE user_id = $1
ORDER BY reaction_count DESC, reaction_date DESC, updated_at DESC, reaction_type ASC, reaction_value ASC
LIMIT $2`, userID, limit)
if err != nil {
return nil, fmt.Errorf("list top message reactions: %w", err)
}
defer rows.Close()
out := make([]domain.MessageReaction, 0, limit)
for rows.Next() {
var reactionType, reactionValue string
if err := rows.Scan(&reactionType, &reactionValue); err != nil {
return nil, err
}
if reaction, ok := domain.MessageReactionFromValue(domain.MessageReactionType(reactionType), reactionValue); ok {
out = append(out, reaction)
}
}
if err := rows.Err(); err != nil {
return nil, err
}
return out, nil
}
func (s *ChannelStore) ListRecentMessageReactions(ctx context.Context, userID int64, limit int) ([]domain.MessageReaction, error) {
if userID == 0 {
return nil, domain.ErrChannelInvalid
}
if limit <= 0 {
return []domain.MessageReaction{}, nil
}
if limit > domain.MaxRecentMessageReactions {
limit = domain.MaxRecentMessageReactions
}
rows, err := s.db.Query(ctx, `
SELECT reaction_type, reaction_value
FROM user_recent_reactions
WHERE user_id = $1
ORDER BY reaction_date DESC, updated_at DESC, reaction_type ASC, reaction_value ASC
LIMIT $2`, userID, limit)
if err != nil {
return nil, fmt.Errorf("list recent message reactions: %w", err)
}
defer rows.Close()
out := make([]domain.MessageReaction, 0, limit)
for rows.Next() {
var reactionType, reactionValue string
if err := rows.Scan(&reactionType, &reactionValue); err != nil {
return nil, err
}
if reaction, ok := domain.MessageReactionFromValue(domain.MessageReactionType(reactionType), reactionValue); ok {
out = append(out, reaction)
}
}
if err := rows.Err(); err != nil {
return nil, err
}
return out, nil
}
func (s *ChannelStore) RecordMessageReactionUse(ctx context.Context, userID int64, reactions []domain.MessageReaction, addToRecent bool, date int) error {
if userID == 0 || len(reactions) == 0 {
return nil
}
if date <= 0 {
date = nowUnix()
}
for _, reaction := range reactions {
if reaction.Type != domain.MessageReactionEmoji || strings.TrimSpace(reaction.Emoticon) == "" || len(reaction.Emoticon) > domain.MaxChannelReactionEmoticonLength {
continue
}
if addToRecent {
if _, err := s.db.Exec(ctx, `
INSERT INTO user_recent_reactions (user_id, reaction_type, reaction_value, reaction_date)
VALUES ($1,$2,$3,$4)
ON CONFLICT (user_id, reaction_type, reaction_value)
DO UPDATE SET reaction_date = EXCLUDED.reaction_date, updated_at = now()`,
userID, string(reaction.Type), reaction.Value(), date); err != nil {
return fmt.Errorf("record recent message reaction: %w", err)
}
}
if _, err := s.db.Exec(ctx, `
INSERT INTO user_top_reactions (user_id, reaction_type, reaction_value, reaction_count, reaction_date)
VALUES ($1,$2,$3,1,$4)
ON CONFLICT (user_id, reaction_type, reaction_value)
DO UPDATE SET reaction_count = user_top_reactions.reaction_count + 1, reaction_date = EXCLUDED.reaction_date, updated_at = now()`,
userID, string(reaction.Type), reaction.Value(), date); err != nil {
return fmt.Errorf("record top message reaction: %w", err)
}
}
return nil
}
func (s *ChannelStore) ClearRecentMessageReactions(ctx context.Context, userID int64) error {
if userID == 0 {
return domain.ErrChannelInvalid
}
if _, err := s.db.Exec(ctx, `DELETE FROM user_recent_reactions WHERE user_id = $1`, userID); err != nil {
return fmt.Errorf("clear recent message reactions: %w", err)
}
return nil
}
func (s *ChannelStore) ListSavedReactionTags(ctx context.Context, userID int64, limit int) ([]domain.SavedReactionTag, error) {
if userID == 0 {
return nil, domain.ErrChannelInvalid
}
if limit <= 0 {
return []domain.SavedReactionTag{}, nil
}
if limit > domain.MaxSavedReactionTags {
limit = domain.MaxSavedReactionTags
}
rows, err := s.db.Query(ctx, `
SELECT reaction_type, reaction_value, title, reaction_count
FROM user_saved_reaction_tags
WHERE user_id = $1
ORDER BY reaction_count DESC, updated_at DESC, reaction_type ASC, reaction_value ASC
LIMIT $2`, userID, limit)
if err != nil {
return nil, fmt.Errorf("list saved reaction tags: %w", err)
}
defer rows.Close()
out := make([]domain.SavedReactionTag, 0, limit)
for rows.Next() {
var reactionType, reactionValue, title string
var count int
if err := rows.Scan(&reactionType, &reactionValue, &title, &count); err != nil {
return nil, err
}
reaction, ok := domain.MessageReactionFromValue(domain.MessageReactionType(reactionType), reactionValue)
if !ok {
continue
}
out = append(out, domain.SavedReactionTag{
UserID: userID,
Reaction: reaction,
Title: title,
Count: count,
})
}
if err := rows.Err(); err != nil {
return nil, err
}
return out, nil
}
func (s *ChannelStore) UpsertSavedReactionTag(ctx context.Context, tag domain.SavedReactionTag) error {
if tag.UserID == 0 || tag.Reaction.Type != domain.MessageReactionEmoji {
return domain.ErrChannelInvalid
}
reactionValue := strings.TrimSpace(tag.Reaction.Emoticon)
if reactionValue == "" {
return domain.ErrChannelInvalid
}
if tag.Count < 0 {
tag.Count = 0
}
if _, err := s.db.Exec(ctx, `
INSERT INTO user_saved_reaction_tags (user_id, reaction_type, reaction_value, title, reaction_count)
VALUES ($1, $2, $3, $4, $5)
ON CONFLICT (user_id, reaction_type, reaction_value) DO UPDATE SET
title = EXCLUDED.title,
reaction_count = GREATEST(user_saved_reaction_tags.reaction_count, EXCLUDED.reaction_count),
updated_at = now()`, tag.UserID, string(tag.Reaction.Type), reactionValue, tag.Title, tag.Count); err != nil {
return fmt.Errorf("upsert saved reaction tag: %w", err)
}
return nil
}