package repository import ( "context" "database/sql" "errors" "fmt" "tailly_clips/internal/domain" "time" ) var ( ErrCommentNotFound = errors.New("comment not found") ) type CommentRepository interface { Create(ctx context.Context, comment *domain.ClipComment) error GetByID(ctx context.Context, id int) (*domain.ClipComment, error) GetByClipID(ctx context.Context, clipID, limit, offset int) ([]*domain.ClipComment, int, error) GetByAuthorID(ctx context.Context, authorID, limit, offset int) ([]*domain.ClipComment, int, error) Update(ctx context.Context, comment *domain.ClipComment) error Delete(ctx context.Context, id int) error DeleteByClipID(ctx context.Context, clipID int) error DeleteByAuthorID(ctx context.Context, authorID int) error } type commentRepository struct { db *sql.DB } func NewCommentRepository(db *sql.DB) CommentRepository { return &commentRepository{db: db} } func (r *commentRepository) Create(ctx context.Context, comment *domain.ClipComment) error { query := ` INSERT INTO clip_comments (clip_id, author_id, content, created_at, updated_at) VALUES ($1, $2, $3, $4, $5) RETURNING id ` err := r.db.QueryRowContext(ctx, query, comment.ClipID, comment.AuthorID, comment.Content, comment.CreatedAt, comment.UpdatedAt, ).Scan(&comment.ID) if err != nil { return fmt.Errorf("failed to create comment: %w", err) } return nil } func (r *commentRepository) GetByID(ctx context.Context, id int) (*domain.ClipComment, error) { query := ` SELECT id, clip_id, author_id, content, created_at, updated_at FROM clip_comments WHERE id = $1 AND deleted_at IS NULL ` comment := &domain.ClipComment{} err := r.db.QueryRowContext(ctx, query, id).Scan( &comment.ID, &comment.ClipID, &comment.AuthorID, &comment.Content, &comment.CreatedAt, &comment.UpdatedAt, ) if err == sql.ErrNoRows { return nil, ErrCommentNotFound } if err != nil { return nil, fmt.Errorf("failed to get comment: %w", err) } return comment, nil } func (r *commentRepository) GetByClipID(ctx context.Context, clipID, limit, offset int) ([]*domain.ClipComment, int, error) { // Получаем общее количество countQuery := `SELECT COUNT(*) FROM clip_comments WHERE clip_id = $1 AND deleted_at IS NULL` var totalCount int err := r.db.QueryRowContext(ctx, countQuery, clipID).Scan(&totalCount) if err != nil { return nil, 0, fmt.Errorf("failed to get total count: %w", err) } // Получаем комментарии query := ` SELECT id, clip_id, author_id, content, created_at, updated_at FROM clip_comments WHERE clip_id = $1 AND deleted_at IS NULL ORDER BY created_at ASC LIMIT $2 OFFSET $3 ` rows, err := r.db.QueryContext(ctx, query, clipID, limit, offset) if err != nil { return nil, 0, fmt.Errorf("failed to get comments: %w", err) } defer rows.Close() var comments []*domain.ClipComment for rows.Next() { comment := &domain.ClipComment{} err := rows.Scan( &comment.ID, &comment.ClipID, &comment.AuthorID, &comment.Content, &comment.CreatedAt, &comment.UpdatedAt, ) if err != nil { return nil, 0, fmt.Errorf("failed to scan comment: %w", err) } comments = append(comments, comment) } if err = rows.Err(); err != nil { return nil, 0, fmt.Errorf("rows error: %w", err) } return comments, totalCount, nil } func (r *commentRepository) GetByAuthorID(ctx context.Context, authorID, limit, offset int) ([]*domain.ClipComment, int, error) { // Получаем общее количество countQuery := `SELECT COUNT(*) FROM clip_comments WHERE author_id = $1 AND deleted_at IS NULL` var totalCount int err := r.db.QueryRowContext(ctx, countQuery, authorID).Scan(&totalCount) if err != nil { return nil, 0, fmt.Errorf("failed to get total count: %w", err) } // Получаем комментарии query := ` SELECT id, clip_id, author_id, content, created_at, updated_at FROM clip_comments WHERE author_id = $1 AND deleted_at IS NULL ORDER BY created_at DESC LIMIT $2 OFFSET $3 ` rows, err := r.db.QueryContext(ctx, query, authorID, limit, offset) if err != nil { return nil, 0, fmt.Errorf("failed to get comments: %w", err) } defer rows.Close() var comments []*domain.ClipComment for rows.Next() { comment := &domain.ClipComment{} err := rows.Scan( &comment.ID, &comment.ClipID, &comment.AuthorID, &comment.Content, &comment.CreatedAt, &comment.UpdatedAt, ) if err != nil { return nil, 0, fmt.Errorf("failed to scan comment: %w", err) } comments = append(comments, comment) } if err = rows.Err(); err != nil { return nil, 0, fmt.Errorf("rows error: %w", err) } return comments, totalCount, nil } func (r *commentRepository) Update(ctx context.Context, comment *domain.ClipComment) error { query := ` UPDATE clip_comments SET content = $1, updated_at = $2 WHERE id = $3 AND deleted_at IS NULL ` result, err := r.db.ExecContext(ctx, query, comment.Content, time.Now(), comment.ID, ) if err != nil { return fmt.Errorf("failed to update comment: %w", err) } rowsAffected, err := result.RowsAffected() if err != nil { return fmt.Errorf("failed to get rows affected: %w", err) } if rowsAffected == 0 { return ErrCommentNotFound } comment.UpdatedAt = time.Now() return nil } func (r *commentRepository) Delete(ctx context.Context, id int) error { query := ` UPDATE clip_comments SET deleted_at = $1 WHERE id = $2 AND deleted_at IS NULL ` result, err := r.db.ExecContext(ctx, query, time.Now(), id) if err != nil { return fmt.Errorf("failed to delete comment: %w", err) } rowsAffected, err := result.RowsAffected() if err != nil { return fmt.Errorf("failed to get rows affected: %w", err) } if rowsAffected == 0 { return ErrCommentNotFound } return nil } func (r *commentRepository) DeleteByClipID(ctx context.Context, clipID int) error { query := ` UPDATE clip_comments SET deleted_at = $1 WHERE clip_id = $2 AND deleted_at IS NULL ` _, err := r.db.ExecContext(ctx, query, time.Now(), clipID) if err != nil { return fmt.Errorf("failed to delete comments by clip ID: %w", err) } return nil } func (r *commentRepository) DeleteByAuthorID(ctx context.Context, authorID int) error { query := ` UPDATE clip_comments SET deleted_at = $1 WHERE author_id = $2 AND deleted_at IS NULL ` _, err := r.db.ExecContext(ctx, query, time.Now(), authorID) if err != nil { return fmt.Errorf("failed to delete comments by author ID: %w", err) } return nil }