veza/veza-backend-api/internal/database/chat_repository.go
2025-12-03 20:29:37 +01:00

342 lines
8.1 KiB
Go

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
}