tailly_clips/internal/repository/clip_repository.go
admin 7bfc7eb9a8
All checks were successful
continuous-integration/drone/push Build is passing
v.0.0.2 Убран duration из клипов
2025-09-02 23:03:30 +03:00

331 lines
8.0 KiB
Go

package repository
import (
"context"
"database/sql"
"errors"
"fmt"
"tailly_clips/internal/domain"
"time"
)
var (
ErrClipNotFound = errors.New("clip not found")
)
type ClipRepository interface {
Create(ctx context.Context, clip *domain.Clip) error
GetByID(ctx context.Context, id int) (*domain.Clip, error)
GetByAuthorID(ctx context.Context, authorID, limit, offset int) ([]*domain.Clip, int, error)
GetAll(ctx context.Context, limit, offset int) ([]*domain.Clip, int, error)
Delete(ctx context.Context, id int) error
IncrementLikesCount(ctx context.Context, clipID int) error
DecrementLikesCount(ctx context.Context, clipID int) error
IncrementCommentsCount(ctx context.Context, clipID int) error
DecrementCommentsCount(ctx context.Context, clipID int) error
GetClipURLs(ctx context.Context, clipID int) (string, string, error)
GetClipWithURLs(ctx context.Context, clipID int) (*domain.Clip, error)
}
type clipRepository struct {
db *sql.DB
}
func NewClipRepository(db *sql.DB) ClipRepository {
return &clipRepository{db: db}
}
func (r *clipRepository) Create(ctx context.Context, clip *domain.Clip) error {
query := `
INSERT INTO clips (title, video_url, thumbnail_url, author_id, likes_count, comments_count, created_at, updated_at)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
RETURNING id
`
err := r.db.QueryRowContext(ctx, query,
clip.Title,
clip.VideoURL,
clip.ThumbnailURL,
clip.AuthorID,
clip.LikesCount,
clip.CommentsCount,
clip.CreatedAt,
clip.UpdatedAt,
).Scan(&clip.ID)
if err != nil {
return fmt.Errorf("failed to create clip: %w", err)
}
return nil
}
func (r *clipRepository) GetByID(ctx context.Context, id int) (*domain.Clip, error) {
query := `
SELECT id, title, video_url, thumbnail_url, author_id,
likes_count, comments_count, created_at, updated_at
FROM clips
WHERE id = $1 AND deleted_at IS NULL
`
clip := &domain.Clip{}
err := r.db.QueryRowContext(ctx, query, id).Scan(
&clip.ID,
&clip.Title,
&clip.VideoURL,
&clip.ThumbnailURL,
&clip.AuthorID,
&clip.LikesCount,
&clip.CommentsCount,
&clip.CreatedAt,
&clip.UpdatedAt,
)
if err == sql.ErrNoRows {
return nil, ErrClipNotFound
}
if err != nil {
return nil, fmt.Errorf("failed to get clip: %w", err)
}
return clip, nil
}
func (r *clipRepository) GetByAuthorID(ctx context.Context, authorID, limit, offset int) ([]*domain.Clip, int, error) {
// Получаем общее количество
countQuery := `SELECT COUNT(*) FROM clips 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, title, video_url, thumbnail_url, author_id,
likes_count, comments_count, created_at, updated_at
FROM clips
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 clips: %w", err)
}
defer rows.Close()
var clips []*domain.Clip
for rows.Next() {
clip := &domain.Clip{}
err := rows.Scan(
&clip.ID,
&clip.Title,
&clip.VideoURL,
&clip.ThumbnailURL,
&clip.AuthorID,
&clip.LikesCount,
&clip.CommentsCount,
&clip.CreatedAt,
&clip.UpdatedAt,
)
if err != nil {
return nil, 0, fmt.Errorf("failed to scan clip: %w", err)
}
clips = append(clips, clip)
}
if err = rows.Err(); err != nil {
return nil, 0, fmt.Errorf("rows error: %w", err)
}
return clips, totalCount, nil
}
func (r *clipRepository) GetAll(ctx context.Context, limit, offset int) ([]*domain.Clip, int, error) {
// Получаем общее количество
countQuery := `SELECT COUNT(*) FROM clips WHERE deleted_at IS NULL`
var totalCount int
err := r.db.QueryRowContext(ctx, countQuery).Scan(&totalCount)
if err != nil {
return nil, 0, fmt.Errorf("failed to get total count: %w", err)
}
// Получаем клипы
query := `
SELECT id, title, video_url, thumbnail_url, author_id,
likes_count, comments_count, created_at, updated_at
FROM clips
WHERE deleted_at IS NULL
ORDER BY created_at DESC
LIMIT $1 OFFSET $2
`
rows, err := r.db.QueryContext(ctx, query, limit, offset)
if err != nil {
return nil, 0, fmt.Errorf("failed to get clips: %w", err)
}
defer rows.Close()
var clips []*domain.Clip
for rows.Next() {
clip := &domain.Clip{}
err := rows.Scan(
&clip.ID,
&clip.Title,
&clip.VideoURL,
&clip.ThumbnailURL,
&clip.AuthorID,
&clip.LikesCount,
&clip.CommentsCount,
&clip.CreatedAt,
&clip.UpdatedAt,
)
if err != nil {
return nil, 0, fmt.Errorf("failed to scan clip: %w", err)
}
clips = append(clips, clip)
}
if err = rows.Err(); err != nil {
return nil, 0, fmt.Errorf("rows error: %w", err)
}
return clips, totalCount, nil
}
func (r *clipRepository) Delete(ctx context.Context, id int) error {
query := `
UPDATE clips
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 clip: %w", err)
}
rowsAffected, err := result.RowsAffected()
if err != nil {
return fmt.Errorf("failed to get rows affected: %w", err)
}
if rowsAffected == 0 {
return ErrClipNotFound
}
return nil
}
func (r *clipRepository) IncrementLikesCount(ctx context.Context, clipID int) error {
query := `
UPDATE clips
SET likes_count = likes_count + 1, updated_at = $1
WHERE id = $2 AND deleted_at IS NULL
`
_, err := r.db.ExecContext(ctx, query, time.Now(), clipID)
if err != nil {
return fmt.Errorf("failed to increment likes count: %w", err)
}
return nil
}
func (r *clipRepository) DecrementLikesCount(ctx context.Context, clipID int) error {
query := `
UPDATE clips
SET likes_count = GREATEST(likes_count - 1, 0), updated_at = $1
WHERE id = $2 AND deleted_at IS NULL
`
_, err := r.db.ExecContext(ctx, query, time.Now(), clipID)
if err != nil {
return fmt.Errorf("failed to decrement likes count: %w", err)
}
return nil
}
func (r *clipRepository) IncrementCommentsCount(ctx context.Context, clipID int) error {
query := `
UPDATE clips
SET comments_count = comments_count + 1, updated_at = $1
WHERE id = $2 AND deleted_at IS NULL
`
_, err := r.db.ExecContext(ctx, query, time.Now(), clipID)
if err != nil {
return fmt.Errorf("failed to increment comments count: %w", err)
}
return nil
}
func (r *clipRepository) DecrementCommentsCount(ctx context.Context, clipID int) error {
query := `
UPDATE clips
SET comments_count = GREATEST(comments_count - 1, 0), updated_at = $1
WHERE id = $2 AND deleted_at IS NULL
`
_, err := r.db.ExecContext(ctx, query, time.Now(), clipID)
if err != nil {
return fmt.Errorf("failed to decrement comments count: %w", err)
}
return nil
}
func (r *clipRepository) GetClipURLs(ctx context.Context, clipID int) (string, string, error) {
query := `
SELECT video_url, thumbnail_url
FROM clips
WHERE id = $1 AND deleted_at IS NULL
`
var videoURL, thumbnailURL string
err := r.db.QueryRowContext(ctx, query, clipID).Scan(&videoURL, &thumbnailURL)
if err == sql.ErrNoRows {
return "", "", ErrClipNotFound
}
if err != nil {
return "", "", fmt.Errorf("failed to get clip URLs: %w", err)
}
return videoURL, thumbnailURL, nil
}
func (r *clipRepository) GetClipWithURLs(ctx context.Context, clipID int) (*domain.Clip, error) {
query := `
SELECT id, title, video_url, thumbnail_url, author_id,
likes_count, comments_count, created_at, updated_at
FROM clips
WHERE id = $1 AND deleted_at IS NULL
`
clip := &domain.Clip{}
err := r.db.QueryRowContext(ctx, query, clipID).Scan(
&clip.ID,
&clip.Title,
&clip.VideoURL,
&clip.ThumbnailURL,
&clip.AuthorID,
&clip.LikesCount,
&clip.CommentsCount,
&clip.CreatedAt,
&clip.UpdatedAt,
)
if err == sql.ErrNoRows {
return nil, ErrClipNotFound
}
if err != nil {
return nil, fmt.Errorf("failed to get clip: %w", err)
}
return clip, nil
}