126 lines
3.7 KiB
Go
126 lines
3.7 KiB
Go
// Package postgres implements conversation's domain repositories
|
|
// against the conversation database. One-for-one transplant of
|
|
// internal/db/conversations.go.
|
|
package postgres
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"errors"
|
|
|
|
"enkly/pkg/pgkit"
|
|
"enkly/services/conversation/internal/domain"
|
|
)
|
|
|
|
// DefaultConversationTitle matches the column default in
|
|
// migrations/0001_init.sql.
|
|
const DefaultConversationTitle = "New conversation"
|
|
|
|
const conversationSelect = `
|
|
SELECT c.id, c.user_id, c.title, c.created_at, COALESCE(lm.last, c.created_at)
|
|
FROM conversations c
|
|
LEFT JOIN LATERAL (
|
|
SELECT max(m.created_at) AS last FROM messages m WHERE m.conversation_id = c.id
|
|
) lm ON true`
|
|
|
|
func scanConversation(row interface{ Scan(...any) error }) (*domain.Conversation, error) {
|
|
var c domain.Conversation
|
|
if err := row.Scan(&c.ID, &c.UserID, &c.Title, &c.CreatedAt, &c.LastMessageAt); err != nil {
|
|
if errors.Is(err, sql.ErrNoRows) {
|
|
return nil, domain.ErrNotFound
|
|
}
|
|
return nil, err
|
|
}
|
|
return &c, nil
|
|
}
|
|
|
|
type ConversationRepo struct {
|
|
DB *pgkit.DB
|
|
}
|
|
|
|
func NewConversationRepo(db *pgkit.DB) *ConversationRepo { return &ConversationRepo{DB: db} }
|
|
|
|
func (r *ConversationRepo) Create(ctx context.Context, userID int64, title string) (*domain.Conversation, error) {
|
|
if title == "" {
|
|
title = DefaultConversationTitle
|
|
}
|
|
var id int64
|
|
if err := r.DB.QueryRowContext(ctx,
|
|
`INSERT INTO conversations (user_id, title) VALUES ($1, $2) RETURNING id`,
|
|
userID, title).Scan(&id); err != nil {
|
|
return nil, err
|
|
}
|
|
return r.Get(ctx, userID, id)
|
|
}
|
|
|
|
func (r *ConversationRepo) Get(ctx context.Context, userID, id int64) (*domain.Conversation, error) {
|
|
return scanConversation(r.DB.QueryRowContext(ctx,
|
|
conversationSelect+` WHERE c.id = $1 AND c.user_id = $2`, id, userID))
|
|
}
|
|
|
|
func (r *ConversationRepo) List(ctx context.Context, userID int64) ([]*domain.Conversation, error) {
|
|
rows, err := r.DB.QueryContext(ctx,
|
|
conversationSelect+` WHERE c.user_id = $1 ORDER BY 5 DESC, c.id DESC LIMIT 200`, userID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
var out []*domain.Conversation
|
|
for rows.Next() {
|
|
c, err := scanConversation(rows)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
out = append(out, c)
|
|
}
|
|
return out, rows.Err()
|
|
}
|
|
|
|
func (r *ConversationRepo) MostRecent(ctx context.Context, userID int64) (*domain.Conversation, error) {
|
|
return scanConversation(r.DB.QueryRowContext(ctx,
|
|
conversationSelect+` WHERE c.user_id = $1 ORDER BY 5 DESC, c.id DESC LIMIT 1`, userID))
|
|
}
|
|
|
|
func (r *ConversationRepo) Rename(ctx context.Context, userID, id int64, title string) (*domain.Conversation, error) {
|
|
res, err := r.DB.ExecContext(ctx,
|
|
`UPDATE conversations SET title = $3 WHERE id = $1 AND user_id = $2`, id, userID, title)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if n, _ := res.RowsAffected(); n == 0 {
|
|
return nil, domain.ErrNotFound
|
|
}
|
|
return r.Get(ctx, userID, id)
|
|
}
|
|
|
|
func (r *ConversationRepo) AutoTitle(ctx context.Context, userID, id int64, title string) (bool, error) {
|
|
res, err := r.DB.ExecContext(ctx,
|
|
`UPDATE conversations SET title = $3 WHERE id = $1 AND user_id = $2 AND title = $4`,
|
|
id, userID, title, DefaultConversationTitle)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
n, _ := res.RowsAffected()
|
|
return n > 0, nil
|
|
}
|
|
|
|
func (r *ConversationRepo) Delete(ctx context.Context, userID, id int64) error {
|
|
res, err := r.DB.ExecContext(ctx,
|
|
`DELETE FROM conversations WHERE id = $1 AND user_id = $2`, id, userID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if n, _ := res.RowsAffected(); n == 0 {
|
|
return domain.ErrNotFound
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (r *ConversationRepo) CountMessages(ctx context.Context, conversationID int64) (int, error) {
|
|
var n int
|
|
err := r.DB.QueryRowContext(ctx,
|
|
`SELECT count(*) FROM messages WHERE conversation_id = $1`, conversationID).Scan(&n)
|
|
return n, err
|
|
}
|