package database import ( "context" "database/sql" "time" "github.com/google/uuid" ) // ChatRepository provides access to chat data type ChatRepository struct { db *DB } // NewChatRepository creates a new chat repository func NewChatRepository(db *DB) *ChatRepository { return &ChatRepository{db: db} } // CreateMessage creates a new message func (r *ChatRepository) CreateMessage(ctx context.Context, message *Message) error { query := ` INSERT INTO messages (room_id, user_id, content, type, parent_id, is_edited, is_deleted, created_at, updated_at) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) RETURNING id ` err := r.db.QueryRowContext(ctx, query, message.RoomID, message.UserID, message.Content, message.Type, message.ParentID, message.IsEdited, message.IsDeleted, message.CreatedAt, message.UpdatedAt, ).Scan(&message.ID) return err } // GetMessages retrieves messages for a room with pagination func (r *ChatRepository) GetMessages(ctx context.Context, roomID uuid.UUID, page, limit int, beforeID *uuid.UUID) ([]*Message, error) { var query string var args []interface{} if beforeID != nil { query = ` SELECT id, room_id, user_id, content, type, parent_id, is_edited, is_deleted, created_at, updated_at FROM messages WHERE room_id = $1 AND id < $2 AND is_deleted = false ORDER BY created_at DESC LIMIT $3 OFFSET $4 ` args = []interface{}{roomID, *beforeID, limit, (page - 1) * limit} } else { query = ` SELECT id, room_id, user_id, content, type, parent_id, is_edited, is_deleted, created_at, updated_at FROM messages WHERE room_id = $1 AND is_deleted = false ORDER BY created_at DESC LIMIT $2 OFFSET $3 ` args = []interface{}{roomID, limit, (page - 1) * limit} } rows, err := r.db.QueryContext(ctx, query, args...) if err != nil { return nil, err } defer rows.Close() var messages []*Message for rows.Next() { msg := &Message{} err := rows.Scan( &msg.ID, &msg.RoomID, &msg.UserID, &msg.Content, &msg.Type, &msg.ParentID, &msg.IsEdited, &msg.IsDeleted, &msg.CreatedAt, &msg.UpdatedAt, ) if err != nil { return nil, err } messages = append(messages, msg) } return messages, nil } // GetMessageByID retrieves a message by ID func (r *ChatRepository) GetMessageByID(ctx context.Context, messageID uuid.UUID) (*Message, error) { query := ` SELECT id, room_id, user_id, content, type, parent_id, is_edited, is_deleted, created_at, updated_at FROM messages WHERE id = $1 ` msg := &Message{} err := r.db.QueryRowContext(ctx, query, messageID).Scan( &msg.ID, &msg.RoomID, &msg.UserID, &msg.Content, &msg.Type, &msg.ParentID, &msg.IsEdited, &msg.IsDeleted, &msg.CreatedAt, &msg.UpdatedAt, ) if err != nil { return nil, err } return msg, nil } // UpdateMessage updates a message func (r *ChatRepository) UpdateMessage(ctx context.Context, message *Message) error { query := ` UPDATE messages SET content = $2, is_edited = $3, is_deleted = $4, updated_at = $5 WHERE id = $1 ` _, err := r.db.ExecContext(ctx, query, message.ID, message.Content, message.IsEdited, message.IsDeleted, message.UpdatedAt, ) return err } // CreateReaction creates a new reaction func (r *ChatRepository) CreateReaction(ctx context.Context, reaction *Reaction) error { query := ` INSERT INTO reactions (message_id, user_id, emoji, created_at) VALUES ($1, $2, $3, $4) RETURNING id ` err := r.db.QueryRowContext(ctx, query, reaction.MessageID, reaction.UserID, reaction.Emoji, reaction.CreatedAt, ).Scan(&reaction.ID) return err } // DeleteReaction removes a reaction func (r *ChatRepository) DeleteReaction(ctx context.Context, messageID, userID uuid.UUID, emoji string) error { query := `DELETE FROM reactions WHERE message_id = $1 AND user_id = $2 AND emoji = $3` _, err := r.db.ExecContext(ctx, query, messageID, userID, emoji) return err } // CreateRoom creates a new room func (r *ChatRepository) CreateRoom(ctx context.Context, room *Room) error { query := ` INSERT INTO rooms (name, description, type, is_private, created_by, created_at, updated_at) VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING id ` err := r.db.QueryRowContext(ctx, query, room.Name, room.Description, room.Type, room.IsPrivate, room.CreatedBy, room.CreatedAt, room.UpdatedAt, ).Scan(&room.ID) return err } // GetRooms retrieves available rooms for a user func (r *ChatRepository) GetRooms(ctx context.Context, userID uuid.UUID, includePrivate bool) ([]*Room, error) { var query string if includePrivate { query = ` SELECT DISTINCT r.id, r.name, r.description, r.type, r.is_private, r.created_by, r.created_at, r.updated_at FROM rooms r LEFT JOIN room_members rm ON r.id = rm.room_id WHERE r.is_private = false OR rm.user_id = $1 ORDER BY r.created_at DESC ` } else { query = ` SELECT id, name, description, type, is_private, created_by, created_at, updated_at FROM rooms WHERE is_private = false ORDER BY created_at DESC ` } var rows *sql.Rows var err error if includePrivate { rows, err = r.db.QueryContext(ctx, query, userID) } else { rows, err = r.db.QueryContext(ctx, query) } if err != nil { return nil, err } defer rows.Close() var rooms []*Room for rows.Next() { room := &Room{} err := rows.Scan( &room.ID, &room.Name, &room.Description, &room.Type, &room.IsPrivate, &room.CreatedBy, &room.CreatedAt, &room.UpdatedAt, ) if err != nil { return nil, err } rooms = append(rooms, room) } return rooms, nil } // GetDirectMessageRoom retrieves or creates a DM room between two users func (r *ChatRepository) GetDirectMessageRoom(ctx context.Context, userID1, userID2 uuid.UUID) (*Room, error) { query := ` SELECT r.id, r.name, r.description, r.type, r.is_private, r.created_by, r.created_at, r.updated_at FROM rooms r JOIN room_members rm1 ON r.id = rm1.room_id JOIN room_members rm2 ON r.id = rm2.room_id WHERE r.type = 'dm' AND rm1.user_id = $1 AND rm2.user_id = $2 LIMIT 1 ` room := &Room{} err := r.db.QueryRowContext(ctx, query, userID1, userID2).Scan( &room.ID, &room.Name, &room.Description, &room.Type, &room.IsPrivate, &room.CreatedBy, &room.CreatedAt, &room.UpdatedAt, ) if err != nil { return nil, err } return room, nil } // AddUserToRoom adds a user to a room func (r *ChatRepository) AddUserToRoom(ctx context.Context, roomID, userID uuid.UUID) error { query := ` INSERT INTO room_members (room_id, user_id, joined_at) VALUES ($1, $2, $3) ON CONFLICT (room_id, user_id) DO NOTHING ` _, err := r.db.ExecContext(ctx, query, roomID, userID, time.Now()) return err } // RemoveUserFromRoom removes a user from a room func (r *ChatRepository) RemoveUserFromRoom(ctx context.Context, roomID, userID uuid.UUID) error { query := `DELETE FROM room_members WHERE room_id = $1 AND user_id = $2` _, err := r.db.ExecContext(ctx, query, roomID, userID) return err } // GetRoomUserCount gets the number of users in a room func (r *ChatRepository) GetRoomUserCount(ctx context.Context, roomID uuid.UUID) (int, error) { query := `SELECT COUNT(*) FROM room_members WHERE room_id = $1` var count int err := r.db.QueryRowContext(ctx, query, roomID).Scan(&count) return count, err } // SearchMessages searches for messages in a room func (r *ChatRepository) SearchMessages(ctx context.Context, roomID uuid.UUID, query string, limit int) ([]*Message, error) { sqlQuery := ` SELECT id, room_id, user_id, content, type, parent_id, is_edited, is_deleted, created_at, updated_at FROM messages WHERE room_id = $1 AND is_deleted = false AND content ILIKE $2 ORDER BY created_at DESC LIMIT $3 ` searchPattern := "%" + query + "%" rows, err := r.db.QueryContext(ctx, sqlQuery, roomID, searchPattern, limit) if err != nil { return nil, err } defer rows.Close() var messages []*Message for rows.Next() { msg := &Message{} err := rows.Scan( &msg.ID, &msg.RoomID, &msg.UserID, &msg.Content, &msg.Type, &msg.ParentID, &msg.IsEdited, &msg.IsDeleted, &msg.CreatedAt, &msg.UpdatedAt, ) if err != nil { return nil, err } messages = append(messages, msg) } return messages, nil }