378 lines
11 KiB
Go
378 lines
11 KiB
Go
package calendar
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"time"
|
|
|
|
"github.com/jackc/pgx/v5"
|
|
)
|
|
|
|
var ErrChatMessageNotFound = errors.New("chat message not found")
|
|
|
|
const (
|
|
ChatChannelExternal = "external"
|
|
ChatChannelInternal = "internal"
|
|
)
|
|
|
|
type ChatMessage struct {
|
|
ID string `json:"id"`
|
|
CalendarItemID string `json:"calendar_item_id"`
|
|
Channel string `json:"channel"`
|
|
Body string `json:"body"`
|
|
CreatedBy *string `json:"created_by"`
|
|
AuthorName string `json:"author_name"`
|
|
AuthorRole string `json:"author_role"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
EditedAt *time.Time `json:"edited_at"`
|
|
DeletedAt *time.Time `json:"deleted_at"`
|
|
Attachments []ChatAttachment `json:"attachments"`
|
|
}
|
|
|
|
type ChatAttachment struct {
|
|
ID string `json:"id"`
|
|
ChatMessageID string `json:"chat_message_id"`
|
|
OriginalFilename string `json:"original_filename"`
|
|
StoredFilename string `json:"stored_filename"`
|
|
MimeType string `json:"mime_type"`
|
|
SizeBytes int64 `json:"size_bytes"`
|
|
StorageDriver string `json:"storage_driver"`
|
|
StoragePath string `json:"-"`
|
|
CreatedBy *string `json:"created_by"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
}
|
|
|
|
type CreateChatMessageInput struct {
|
|
CalendarItemID string
|
|
Channel string
|
|
Body string
|
|
CreatedBy string
|
|
}
|
|
|
|
type CreateChatAttachmentInput struct {
|
|
ChatMessageID string
|
|
OriginalFilename string
|
|
StoredFilename string
|
|
MimeType string
|
|
SizeBytes int64
|
|
StorageDriver string
|
|
StoragePath string
|
|
CreatedBy string
|
|
}
|
|
|
|
func (s Store) ListChatMessages(ctx context.Context, calendarItemID string, channel string) ([]ChatMessage, error) {
|
|
rows, err := s.db.Query(ctx, `
|
|
SELECT
|
|
calendar_item_chat_messages.id::text,
|
|
calendar_item_chat_messages.calendar_item_id::text,
|
|
calendar_item_chat_messages.channel,
|
|
calendar_item_chat_messages.body,
|
|
calendar_item_chat_messages.created_by::text,
|
|
COALESCE(users.name, 'Usuario removido'),
|
|
COALESCE(users.role, ''),
|
|
calendar_item_chat_messages.created_at,
|
|
calendar_item_chat_messages.edited_at,
|
|
calendar_item_chat_messages.deleted_at
|
|
FROM calendar_item_chat_messages
|
|
LEFT JOIN users ON users.id = calendar_item_chat_messages.created_by
|
|
WHERE calendar_item_chat_messages.calendar_item_id = $1::uuid
|
|
AND calendar_item_chat_messages.channel = $2
|
|
ORDER BY calendar_item_chat_messages.created_at ASC
|
|
`, calendarItemID, channel)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
messages := []ChatMessage{}
|
|
for rows.Next() {
|
|
message, err := scanChatMessage(rows)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
messages = append(messages, message)
|
|
}
|
|
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := s.attachChatAttachments(ctx, messages); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return messages, nil
|
|
}
|
|
|
|
func (s Store) CreateChatMessage(ctx context.Context, input CreateChatMessageInput) (ChatMessage, error) {
|
|
row := s.db.QueryRow(ctx, `
|
|
INSERT INTO calendar_item_chat_messages (
|
|
calendar_item_id,
|
|
channel,
|
|
body,
|
|
created_by
|
|
)
|
|
VALUES ($1::uuid, $2, $3, NULLIF($4, '')::uuid)
|
|
RETURNING
|
|
calendar_item_chat_messages.id::text,
|
|
calendar_item_chat_messages.calendar_item_id::text,
|
|
calendar_item_chat_messages.channel,
|
|
calendar_item_chat_messages.body,
|
|
calendar_item_chat_messages.created_by::text,
|
|
COALESCE((SELECT users.name FROM users WHERE users.id = calendar_item_chat_messages.created_by), 'Usuario removido'),
|
|
COALESCE((SELECT users.role FROM users WHERE users.id = calendar_item_chat_messages.created_by), ''),
|
|
calendar_item_chat_messages.created_at,
|
|
calendar_item_chat_messages.edited_at,
|
|
calendar_item_chat_messages.deleted_at
|
|
`, input.CalendarItemID, input.Channel, input.Body, input.CreatedBy)
|
|
|
|
return scanChatMessage(row)
|
|
}
|
|
|
|
func (s Store) FindChatMessage(ctx context.Context, id string) (ChatMessage, error) {
|
|
row := s.db.QueryRow(ctx, `
|
|
SELECT
|
|
calendar_item_chat_messages.id::text,
|
|
calendar_item_chat_messages.calendar_item_id::text,
|
|
calendar_item_chat_messages.channel,
|
|
calendar_item_chat_messages.body,
|
|
calendar_item_chat_messages.created_by::text,
|
|
COALESCE(users.name, 'Usuario removido'),
|
|
COALESCE(users.role, ''),
|
|
calendar_item_chat_messages.created_at,
|
|
calendar_item_chat_messages.edited_at,
|
|
calendar_item_chat_messages.deleted_at
|
|
FROM calendar_item_chat_messages
|
|
LEFT JOIN users ON users.id = calendar_item_chat_messages.created_by
|
|
WHERE calendar_item_chat_messages.id = $1::uuid
|
|
`, id)
|
|
|
|
message, err := scanChatMessage(row)
|
|
if err != nil {
|
|
return ChatMessage{}, err
|
|
}
|
|
messages := []ChatMessage{message}
|
|
if err := s.attachChatAttachments(ctx, messages); err != nil {
|
|
return ChatMessage{}, err
|
|
}
|
|
return messages[0], nil
|
|
}
|
|
|
|
func (s Store) UpdateChatMessage(ctx context.Context, id string, body string) (ChatMessage, error) {
|
|
row := s.db.QueryRow(ctx, `
|
|
UPDATE calendar_item_chat_messages
|
|
SET body = $2, edited_at = now()
|
|
WHERE id = $1::uuid
|
|
AND deleted_at IS NULL
|
|
RETURNING
|
|
calendar_item_chat_messages.id::text,
|
|
calendar_item_chat_messages.calendar_item_id::text,
|
|
calendar_item_chat_messages.channel,
|
|
calendar_item_chat_messages.body,
|
|
calendar_item_chat_messages.created_by::text,
|
|
COALESCE((SELECT users.name FROM users WHERE users.id = calendar_item_chat_messages.created_by), 'Usuario removido'),
|
|
COALESCE((SELECT users.role FROM users WHERE users.id = calendar_item_chat_messages.created_by), ''),
|
|
calendar_item_chat_messages.created_at,
|
|
calendar_item_chat_messages.edited_at,
|
|
calendar_item_chat_messages.deleted_at
|
|
`, id, body)
|
|
|
|
message, err := scanChatMessage(row)
|
|
if err != nil {
|
|
return ChatMessage{}, err
|
|
}
|
|
messages := []ChatMessage{message}
|
|
if err := s.attachChatAttachments(ctx, messages); err != nil {
|
|
return ChatMessage{}, err
|
|
}
|
|
return messages[0], nil
|
|
}
|
|
|
|
func (s Store) DeleteChatMessage(ctx context.Context, id string) (ChatMessage, error) {
|
|
row := s.db.QueryRow(ctx, `
|
|
UPDATE calendar_item_chat_messages
|
|
SET deleted_at = now(), body = ''
|
|
WHERE id = $1::uuid
|
|
AND deleted_at IS NULL
|
|
RETURNING
|
|
calendar_item_chat_messages.id::text,
|
|
calendar_item_chat_messages.calendar_item_id::text,
|
|
calendar_item_chat_messages.channel,
|
|
calendar_item_chat_messages.body,
|
|
calendar_item_chat_messages.created_by::text,
|
|
COALESCE((SELECT users.name FROM users WHERE users.id = calendar_item_chat_messages.created_by), 'Usuario removido'),
|
|
COALESCE((SELECT users.role FROM users WHERE users.id = calendar_item_chat_messages.created_by), ''),
|
|
calendar_item_chat_messages.created_at,
|
|
calendar_item_chat_messages.edited_at,
|
|
calendar_item_chat_messages.deleted_at
|
|
`, id)
|
|
|
|
return scanChatMessage(row)
|
|
}
|
|
|
|
func (s Store) CreateChatAttachment(ctx context.Context, input CreateChatAttachmentInput) (ChatAttachment, error) {
|
|
row := s.db.QueryRow(ctx, `
|
|
INSERT INTO calendar_item_chat_attachments (
|
|
chat_message_id,
|
|
original_filename,
|
|
stored_filename,
|
|
mime_type,
|
|
size_bytes,
|
|
storage_driver,
|
|
storage_path,
|
|
created_by
|
|
)
|
|
VALUES ($1::uuid, $2, $3, $4, $5, $6, $7, NULLIF($8, '')::uuid)
|
|
RETURNING
|
|
id::text,
|
|
chat_message_id::text,
|
|
original_filename,
|
|
stored_filename,
|
|
mime_type,
|
|
size_bytes,
|
|
storage_driver,
|
|
storage_path,
|
|
created_by::text,
|
|
created_at
|
|
`, input.ChatMessageID, input.OriginalFilename, input.StoredFilename, input.MimeType, input.SizeBytes, input.StorageDriver, input.StoragePath, input.CreatedBy)
|
|
|
|
return scanChatAttachment(row)
|
|
}
|
|
|
|
func (s Store) FindChatAttachment(ctx context.Context, id string, viewerUserID string) (ChatAttachment, error) {
|
|
row := s.db.QueryRow(ctx, `
|
|
SELECT
|
|
calendar_item_chat_attachments.id::text,
|
|
calendar_item_chat_attachments.chat_message_id::text,
|
|
calendar_item_chat_attachments.original_filename,
|
|
calendar_item_chat_attachments.stored_filename,
|
|
calendar_item_chat_attachments.mime_type,
|
|
calendar_item_chat_attachments.size_bytes,
|
|
calendar_item_chat_attachments.storage_driver,
|
|
calendar_item_chat_attachments.storage_path,
|
|
calendar_item_chat_attachments.created_by::text,
|
|
calendar_item_chat_attachments.created_at
|
|
FROM calendar_item_chat_attachments
|
|
INNER JOIN calendar_item_chat_messages ON calendar_item_chat_messages.id = calendar_item_chat_attachments.chat_message_id
|
|
INNER JOIN calendar_items ON calendar_items.id = calendar_item_chat_messages.calendar_item_id
|
|
WHERE calendar_item_chat_attachments.id = $1::uuid
|
|
AND calendar_item_chat_messages.deleted_at IS NULL
|
|
AND (
|
|
$2 = ''
|
|
OR (
|
|
calendar_item_chat_messages.channel = 'external'
|
|
AND EXISTS (
|
|
SELECT 1
|
|
FROM client_users
|
|
WHERE client_users.client_id = calendar_items.client_id
|
|
AND client_users.user_id = $2::uuid
|
|
)
|
|
)
|
|
)
|
|
`, id, viewerUserID)
|
|
|
|
return scanChatAttachment(row)
|
|
}
|
|
|
|
func (s Store) attachChatAttachments(ctx context.Context, messages []ChatMessage) error {
|
|
if len(messages) == 0 {
|
|
return nil
|
|
}
|
|
|
|
ids := make([]string, 0, len(messages))
|
|
indexByID := map[string]int{}
|
|
for index, message := range messages {
|
|
ids = append(ids, message.ID)
|
|
indexByID[message.ID] = index
|
|
}
|
|
|
|
rows, err := s.db.Query(ctx, `
|
|
SELECT
|
|
id::text,
|
|
chat_message_id::text,
|
|
original_filename,
|
|
stored_filename,
|
|
mime_type,
|
|
size_bytes,
|
|
storage_driver,
|
|
storage_path,
|
|
created_by::text,
|
|
created_at
|
|
FROM calendar_item_chat_attachments
|
|
WHERE chat_message_id::text = ANY($1)
|
|
ORDER BY created_at ASC
|
|
`, ids)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer rows.Close()
|
|
|
|
for rows.Next() {
|
|
attachment, err := scanChatAttachment(rows)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
index, ok := indexByID[attachment.ChatMessageID]
|
|
if ok {
|
|
messages[index].Attachments = append(messages[index].Attachments, attachment)
|
|
}
|
|
}
|
|
|
|
return rows.Err()
|
|
}
|
|
|
|
type chatMessageScanner interface {
|
|
Scan(dest ...any) error
|
|
}
|
|
|
|
func scanChatMessage(row chatMessageScanner) (ChatMessage, error) {
|
|
var message ChatMessage
|
|
err := row.Scan(
|
|
&message.ID,
|
|
&message.CalendarItemID,
|
|
&message.Channel,
|
|
&message.Body,
|
|
&message.CreatedBy,
|
|
&message.AuthorName,
|
|
&message.AuthorRole,
|
|
&message.CreatedAt,
|
|
&message.EditedAt,
|
|
&message.DeletedAt,
|
|
)
|
|
if errors.Is(err, pgx.ErrNoRows) {
|
|
return ChatMessage{}, ErrChatMessageNotFound
|
|
}
|
|
if err != nil {
|
|
return ChatMessage{}, err
|
|
}
|
|
message.Attachments = []ChatAttachment{}
|
|
return message, nil
|
|
}
|
|
|
|
type chatAttachmentScanner interface {
|
|
Scan(dest ...any) error
|
|
}
|
|
|
|
func scanChatAttachment(row chatAttachmentScanner) (ChatAttachment, error) {
|
|
var attachment ChatAttachment
|
|
err := row.Scan(
|
|
&attachment.ID,
|
|
&attachment.ChatMessageID,
|
|
&attachment.OriginalFilename,
|
|
&attachment.StoredFilename,
|
|
&attachment.MimeType,
|
|
&attachment.SizeBytes,
|
|
&attachment.StorageDriver,
|
|
&attachment.StoragePath,
|
|
&attachment.CreatedBy,
|
|
&attachment.CreatedAt,
|
|
)
|
|
if errors.Is(err, pgx.ErrNoRows) {
|
|
return ChatAttachment{}, ErrAttachmentNotFound
|
|
}
|
|
if err != nil {
|
|
return ChatAttachment{}, err
|
|
}
|
|
return attachment, nil
|
|
}
|