package store import ( "context" ) // Reaction represents a single emoji reaction on a message. type Reaction struct { MessageID int64 UserID int64 Emoji string } // AddReaction adds a reaction to a message; it is a no-op if it already exists. func (s *Store) AddReaction(ctx context.Context, messageID, userID int64, emoji string) error { _, err := s.pool.Exec(ctx, ` INSERT INTO message_reactions(message_id, user_id, emoji) VALUES($1, $2, $3) ON CONFLICT (message_id, user_id, emoji) DO NOTHING `, messageID, userID, emoji) return err } // RemoveReaction removes a reaction from a message; it is a no-op if absent. func (s *Store) RemoveReaction(ctx context.Context, messageID, userID int64, emoji string) error { _, err := s.pool.Exec(ctx, ` DELETE FROM message_reactions WHERE message_id=$1 AND user_id=$2 AND emoji=$3 `, messageID, userID, emoji) return err } // ReactionsForMessages returns all reactions for the given message IDs. func (s *Store) ReactionsForMessages(ctx context.Context, messageIDs []int64) ([]Reaction, error) { if len(messageIDs) == 0 { return nil, nil } rows, err := s.pool.Query(ctx, ` SELECT message_id, user_id, emoji FROM message_reactions WHERE message_id = ANY($1) ORDER BY message_id, created_at `, messageIDs) if err != nil { return nil, err } defer rows.Close() var reactions []Reaction for rows.Next() { var r Reaction if err := rows.Scan(&r.MessageID, &r.UserID, &r.Emoji); err != nil { return nil, err } reactions = append(reactions, r) } return reactions, nil } // MessageBelongsToConversation reports whether a message belongs to a conversation. func (s *Store) MessageBelongsToConversation(ctx context.Context, conversationID, messageID int64) (bool, error) { var exists bool err := s.pool.QueryRow(ctx, ` SELECT EXISTS( SELECT 1 FROM messages WHERE id=$1 AND conversation_id=$2 ) `, messageID, conversationID).Scan(&exists) if err != nil { return false, err } return exists, nil }