package repository import ( "context" "database/sql" "errors" "tailly_back_v2/internal/domain" ) var ( ErrLikeNotFound = errors.New("like not found") ErrLikeAlreadyExists = errors.New("like already exists") ) type LikeRepository interface { Create(ctx context.Context, like *domain.Like) error GetByID(ctx context.Context, id int) (*domain.Like, error) GetByPostID(ctx context.Context, postID int) ([]*domain.Like, error) GetByUserAndPost(ctx context.Context, userID, postID int) (*domain.Like, error) Delete(ctx context.Context, id int) error DeleteByUserAndPost(ctx context.Context, userID, postID int) error // Новые методы для уведомлений GetUnreadLikeNotifications(ctx context.Context, userID int, limit, offset int) ([]*domain.LikeNotification, error) GetAllLikeNotifications(ctx context.Context, userID int, limit, offset int) ([]*domain.LikeNotification, error) MarkLikeNotificationAsRead(ctx context.Context, notificationID, userID int) error MarkAllLikeNotificationsAsRead(ctx context.Context, userID int) error GetLikeNotificationCounts(ctx context.Context, userID int) (totalCount, unreadCount int, err error) } type likeRepository struct { db *sql.DB } func NewLikeRepository(db *sql.DB) *likeRepository { return &likeRepository{db: db} } func (r *likeRepository) Create(ctx context.Context, like *domain.Like) error { // Проверяем, существует ли уже лайк _, err := r.GetByUserAndPost(ctx, like.UserID, like.PostID) if err == nil { return ErrLikeAlreadyExists } else if err != ErrLikeNotFound { return err } query := ` INSERT INTO likes (post_id, user_id, created_at, is_read, notified_at) VALUES ($1, $2, $3, $4, $5) RETURNING id ` err = r.db.QueryRowContext(ctx, query, like.PostID, like.UserID, like.CreatedAt, like.IsRead, like.NotifiedAt, ).Scan(&like.ID) return err } func (r *likeRepository) GetByID(ctx context.Context, id int) (*domain.Like, error) { query := ` SELECT id, post_id, user_id, created_at, is_read, notified_at FROM likes WHERE id = $1 ` like := &domain.Like{} err := r.db.QueryRowContext(ctx, query, id).Scan( &like.ID, &like.PostID, &like.UserID, &like.CreatedAt, &like.IsRead, &like.NotifiedAt, ) if err != nil { if errors.Is(err, sql.ErrNoRows) { return nil, ErrLikeNotFound } return nil, err } return like, nil } func (r *likeRepository) GetByPostID(ctx context.Context, postID int) ([]*domain.Like, error) { query := ` SELECT id, post_id, user_id, created_at, is_read, notified_at FROM likes WHERE post_id = $1 ORDER BY created_at DESC ` rows, err := r.db.QueryContext(ctx, query, postID) if err != nil { return nil, err } defer rows.Close() var likes []*domain.Like for rows.Next() { like := &domain.Like{} err := rows.Scan( &like.ID, &like.PostID, &like.UserID, &like.CreatedAt, &like.IsRead, &like.NotifiedAt, ) if err != nil { return nil, err } likes = append(likes, like) } return likes, nil } func (r *likeRepository) GetByUserAndPost(ctx context.Context, userID, postID int) (*domain.Like, error) { query := ` SELECT id, post_id, user_id, created_at, is_read, notified_at FROM likes WHERE user_id = $1 AND post_id = $2 LIMIT 1 ` like := &domain.Like{} err := r.db.QueryRowContext(ctx, query, userID, postID).Scan( &like.ID, &like.PostID, &like.UserID, &like.CreatedAt, &like.IsRead, &like.NotifiedAt, ) if err != nil { if errors.Is(err, sql.ErrNoRows) { return nil, ErrLikeNotFound } return nil, err } return like, nil } func (r *likeRepository) Delete(ctx context.Context, id int) error { query := `DELETE FROM likes WHERE id = $1` _, err := r.db.ExecContext(ctx, query, id) return err } func (r *likeRepository) DeleteByUserAndPost(ctx context.Context, userID, postID int) error { query := `DELETE FROM likes WHERE user_id = $1 AND post_id = $2` _, err := r.db.ExecContext(ctx, query, userID, postID) return err } // Новые методы для уведомлений func (r *likeRepository) GetUnreadLikeNotifications(ctx context.Context, userID int, limit, offset int) ([]*domain.LikeNotification, error) { query := ` SELECT l.id, l.post_id, l.user_id as liker_id, l.created_at, l.is_read, u.username as liker_username, u.avatar as liker_avatar, p.title as post_title, p.author_id as post_author_id FROM likes l JOIN users u ON l.user_id = u.id JOIN posts p ON l.post_id = p.id WHERE p.author_id = $1 AND l.is_read = false ORDER BY l.created_at DESC LIMIT $2 OFFSET $3 ` rows, err := r.db.QueryContext(ctx, query, userID, limit, offset) if err != nil { return nil, err } defer rows.Close() var notifications []*domain.LikeNotification for rows.Next() { notification := &domain.LikeNotification{} err := rows.Scan( ¬ification.ID, ¬ification.PostID, ¬ification.LikerID, ¬ification.CreatedAt, ¬ification.IsRead, ¬ification.LikerUsername, ¬ification.LikerAvatar, ¬ification.PostTitle, ¬ification.PostAuthorID, ) if err != nil { return nil, err } notifications = append(notifications, notification) } return notifications, nil } func (r *likeRepository) GetAllLikeNotifications(ctx context.Context, userID int, limit, offset int) ([]*domain.LikeNotification, error) { query := ` SELECT l.id, l.post_id, l.user_id as liker_id, l.created_at, l.is_read, u.username as liker_username, u.avatar as liker_avatar, p.title as post_title, p.author_id as post_author_id FROM likes l JOIN users u ON l.user_id = u.id JOIN posts p ON l.post_id = p.id WHERE p.author_id = $1 ORDER BY l.created_at DESC LIMIT $2 OFFSET $3 ` rows, err := r.db.QueryContext(ctx, query, userID, limit, offset) if err != nil { return nil, err } defer rows.Close() var notifications []*domain.LikeNotification for rows.Next() { notification := &domain.LikeNotification{} err := rows.Scan( ¬ification.ID, ¬ification.PostID, ¬ification.LikerID, ¬ification.CreatedAt, ¬ification.IsRead, ¬ification.LikerUsername, ¬ification.LikerAvatar, ¬ification.PostTitle, ¬ification.PostAuthorID, ) if err != nil { return nil, err } notifications = append(notifications, notification) } return notifications, nil } func (r *likeRepository) MarkLikeNotificationAsRead(ctx context.Context, notificationID, userID int) error { query := ` UPDATE likes SET is_read = true WHERE id = $1 AND post_id IN ( SELECT id FROM posts WHERE author_id = $2 ) ` result, err := r.db.ExecContext(ctx, query, notificationID, userID) if err != nil { return err } rowsAffected, err := result.RowsAffected() if err != nil { return err } if rowsAffected == 0 { return ErrLikeNotFound } return nil } func (r *likeRepository) MarkAllLikeNotificationsAsRead(ctx context.Context, userID int) error { query := ` UPDATE likes SET is_read = true WHERE post_id IN ( SELECT id FROM posts WHERE author_id = $1 ) AND is_read = false ` _, err := r.db.ExecContext(ctx, query, userID) return err } func (r *likeRepository) GetLikeNotificationCounts(ctx context.Context, userID int) (totalCount, unreadCount int, err error) { queryTotal := ` SELECT COUNT(*) FROM likes l JOIN posts p ON l.post_id = p.id WHERE p.author_id = $1 ` queryUnread := ` SELECT COUNT(*) FROM likes l JOIN posts p ON l.post_id = p.id WHERE p.author_id = $1 AND l.is_read = false ` err = r.db.QueryRowContext(ctx, queryTotal, userID).Scan(&totalCount) if err != nil { return 0, 0, err } err = r.db.QueryRowContext(ctx, queryUnread, userID).Scan(&unreadCount) if err != nil { return 0, 0, err } return totalCount, unreadCount, nil }