tailly_back_v2/internal/http/graph/clip_resolvers.go
2025-09-02 23:35:28 +03:00

466 lines
13 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package graph
import (
"context"
"fmt"
"io"
"log"
"tailly_back_v2/internal/domain"
"tailly_back_v2/proto"
"time"
"github.com/99designs/gqlgen/graphql"
)
// IsLiked is the resolver for the isLiked field.
func (r *clipResolver) IsLiked(ctx context.Context, obj *domain.Clip) (bool, error) {
userID, err := getUserIDFromContext(ctx)
if err != nil {
return false, fmt.Errorf("user not authenticated: %w", err)
}
resp, err := r.ClipClient.CheckIfLiked(ctx, &proto.CheckIfLikedRequest{
ClipId: int32(obj.ID),
UserId: int32(userID),
})
if err != nil {
return false, fmt.Errorf("failed to check like status: %w", err)
}
return resp.IsLiked, nil
}
func (r *clipResolver) Author(ctx context.Context, obj *domain.Clip) (*domain.User, error) {
if obj.AuthorID == 0 {
log.Printf("ERROR: Clip %d has no author ID", obj.ID)
return &domain.User{
ID: 0,
Username: "Unknown",
Avatar: "/img/logo.png",
}, nil
// Не возвращаем ошибку, чтобы не ломать весь запрос
}
author, err := r.Services.User.GetByID(ctx, obj.AuthorID)
if err != nil {
log.Printf("ERROR: Failed to get author %d for clip %d: %v", obj.AuthorID, obj.ID, err)
// Возвращаем заглушку вместо ошибки
return &domain.User{
ID: obj.AuthorID,
Username: "Unknown",
Avatar: "/img/logo.png",
}, nil
}
if author == nil {
log.Printf("ERROR: Author %d not found for clip %d", obj.AuthorID, obj.ID)
return &domain.User{
ID: obj.AuthorID,
Username: "Deleted User",
Avatar: "/img/logo.png",
}, nil
}
return author, nil
}
// CreatedAt is the resolver for the createdAt field.
func (r *clipResolver) CreatedAt(ctx context.Context, obj *domain.Clip) (string, error) {
return obj.CreatedAt.Format(time.RFC3339), nil
}
// UpdatedAt is the resolver for the updatedAt field.
func (r *clipResolver) UpdatedAt(ctx context.Context, obj *domain.Clip) (string, error) {
return obj.UpdatedAt.Format(time.RFC3339), nil
}
// CreatedAt is the resolver for the createdAt field.
func (r *clipCommentResolver) CreatedAt(ctx context.Context, obj *domain.ClipComment) (string, error) {
return obj.CreatedAt.Format(time.RFC3339), nil
}
// UpdatedAt is the resolver for the updatedAt field.
func (r *clipCommentResolver) UpdatedAt(ctx context.Context, obj *domain.ClipComment) (string, error) {
return obj.UpdatedAt.Format(time.RFC3339), nil
}
// CreatedAt is the resolver for the createdAt field.
func (r *clipLikeResolver) CreatedAt(ctx context.Context, obj *domain.ClipLike) (string, error) {
return obj.CreatedAt.Format(time.RFC3339), nil
}
// CreateClip is the resolver for the createClip field.
func (r *mutationResolver) CreateClip(ctx context.Context, title *string, video graphql.Upload) (*domain.Clip, error) {
userID, err := getUserIDFromContext(ctx)
if err != nil {
return nil, fmt.Errorf("user not authenticated: %w", err)
}
var clipTitle string
if title != nil {
clipTitle = *title
}
// Читаем данные видео
videoData, err := io.ReadAll(video.File)
if err != nil {
return nil, fmt.Errorf("failed to read video data: %w", err)
}
resp, err := r.ClipClient.CreateClip(ctx, &proto.CreateClipRequest{
UserId: int32(userID),
Title: clipTitle,
VideoData: videoData,
FileName: video.Filename,
ContentType: video.ContentType,
})
if err != nil {
return nil, fmt.Errorf("failed to create clip: %w", err)
}
return r.protoClipToDomain(resp.Clip), nil
}
// DeleteClip is the resolver for the deleteClip field.
func (r *mutationResolver) DeleteClip(ctx context.Context, id int) (bool, error) {
userID, err := getUserIDFromContext(ctx)
if err != nil {
return false, fmt.Errorf("user not authenticated: %w", err)
}
_, err = r.ClipClient.DeleteClip(ctx, &proto.DeleteClipRequest{
ClipId: int32(id),
UserId: int32(userID),
})
if err != nil {
return false, fmt.Errorf("failed to delete clip: %w", err)
}
return true, nil
}
// LikeClip is the resolver for the likeClip field.
func (r *mutationResolver) LikeClip(ctx context.Context, clipID int) (*domain.ClipLike, error) {
userID, err := getUserIDFromContext(ctx)
if err != nil {
return nil, fmt.Errorf("user not authenticated: %w", err)
}
resp, err := r.ClipClient.LikeClip(ctx, &proto.LikeClipRequest{
ClipId: int32(clipID),
UserId: int32(userID),
})
if err != nil {
return nil, fmt.Errorf("failed to like clip: %w", err)
}
return r.protoLikeToDomain(resp.Like), nil
}
// UnlikeClip is the resolver for the unlikeClip field.
func (r *mutationResolver) UnlikeClip(ctx context.Context, clipID int) (bool, error) {
userID, err := getUserIDFromContext(ctx)
if err != nil {
return false, fmt.Errorf("user not authenticated: %w", err)
}
_, err = r.ClipClient.UnlikeClip(ctx, &proto.UnlikeClipRequest{
ClipId: int32(clipID),
UserId: int32(userID),
})
if err != nil {
return false, fmt.Errorf("failed to unlike clip: %w", err)
}
return true, nil
}
// CreateClipComment is the resolver for the createClipComment field.
func (r *mutationResolver) CreateClipComment(ctx context.Context, clipID int, content string) (*domain.ClipComment, error) {
userID, err := getUserIDFromContext(ctx)
if err != nil {
return nil, fmt.Errorf("user not authenticated: %w", err)
}
resp, err := r.ClipClient.CreateComment(ctx, &proto.CreateCommentRequest{
ClipId: int32(clipID),
UserId: int32(userID),
Content: content,
})
if err != nil {
return nil, fmt.Errorf("failed to create comment: %w", err)
}
return r.protoCommentToDomain(resp.Comment), nil
}
// DeleteClipComment is the resolver for the deleteClipComment field.
func (r *mutationResolver) DeleteClipComment(ctx context.Context, commentID int) (bool, error) {
userID, err := getUserIDFromContext(ctx)
if err != nil {
return false, fmt.Errorf("user not authenticated: %w", err)
}
_, err = r.ClipClient.DeleteComment(ctx, &proto.DeleteCommentRequest{
CommentId: int32(commentID),
UserId: int32(userID),
})
if err != nil {
return false, fmt.Errorf("failed to delete comment: %w", err)
}
return true, nil
}
// Clip is the resolver for the clip field.
func (r *queryResolver) Clip(ctx context.Context, id int) (*domain.Clip, error) {
resp, err := r.ClipClient.GetClip(ctx, &proto.GetClipRequest{
ClipId: int32(id),
})
if err != nil {
return nil, fmt.Errorf("failed to get clip: %w", err)
}
return r.protoClipToDomain(resp.Clip), nil
}
// Clips is the resolver for the clips field.
func (r *queryResolver) Clips(ctx context.Context, limit *int, offset *int) ([]*domain.Clip, error) {
limitVal := 20
if limit != nil {
limitVal = *limit
}
offsetVal := 0
if offset != nil {
offsetVal = *offset
}
resp, err := r.ClipClient.GetClips(ctx, &proto.GetClipsRequest{
Limit: int32(limitVal),
Offset: int32(offsetVal),
})
if err != nil {
log.Printf("ERROR: Failed to get clips from gRPC service: %v", err)
return nil, fmt.Errorf("failed to get clips: %w", err)
}
clips := r.protoClipsToDomain(resp.Clips)
// Добавьте логирование для дебага
log.Printf("Retrieved %d clips from gRPC service", len(clips))
for i, clip := range clips {
log.Printf("Clip %d: ID=%d, Title=%s, VideoURL=%s, AuthorID=%d",
i, clip.ID, clip.Title, clip.VideoURL, clip.AuthorID)
}
return clips, nil
}
// UserClips is the resolver for the userClips field.
func (r *queryResolver) UserClips(ctx context.Context, userID int, limit *int, offset *int) ([]*domain.Clip, error) {
limitVal := 20
if limit != nil {
limitVal = *limit
}
offsetVal := 0
if offset != nil {
offsetVal = *offset
}
resp, err := r.ClipClient.GetUserClips(ctx, &proto.GetUserClipsRequest{
UserId: int32(userID),
Limit: int32(limitVal),
Offset: int32(offsetVal),
})
if err != nil {
return nil, fmt.Errorf("failed to get user clips: %w", err)
}
return r.protoClipsToDomain(resp.Clips), nil
}
// ClipComments is the resolver for the clipComments field.
func (r *queryResolver) ClipComments(ctx context.Context, clipID int, limit *int, offset *int) ([]*domain.ClipComment, error) {
limitVal := 20
if limit != nil {
limitVal = *limit
}
offsetVal := 0
if offset != nil {
offsetVal = *offset
}
resp, err := r.ClipClient.GetClipComments(ctx, &proto.GetClipCommentsRequest{
ClipId: int32(clipID),
Limit: int32(limitVal),
Offset: int32(offsetVal),
})
if err != nil {
return nil, fmt.Errorf("failed to get clip comments: %w", err)
}
return r.protoCommentsToDomain(resp.Comments), nil
}
// ClipLikes is the resolver for the clipLikes field.
func (r *queryResolver) ClipLikes(ctx context.Context, clipID int, limit *int, offset *int) ([]*domain.ClipLike, error) {
limitVal := 20
if limit != nil {
limitVal = *limit
}
offsetVal := 0
if offset != nil {
offsetVal = *offset
}
resp, err := r.ClipClient.GetClipLikes(ctx, &proto.GetClipLikesRequest{
ClipId: int32(clipID),
Limit: int32(limitVal),
Offset: int32(offsetVal),
})
if err != nil {
return nil, fmt.Errorf("failed to get clip likes: %w", err)
}
return r.protoLikesToDomain(resp.Likes), nil
}
// IsLiked is the resolver for the isLiked field.
func (r *queryResolver) IsLiked(ctx context.Context, clipID int) (bool, error) {
userID, err := getUserIDFromContext(ctx)
if err != nil {
return false, fmt.Errorf("user not authenticated: %w", err)
}
resp, err := r.ClipClient.CheckIfLiked(ctx, &proto.CheckIfLikedRequest{
ClipId: int32(clipID),
UserId: int32(userID),
})
if err != nil {
return false, fmt.Errorf("failed to check like status: %w", err)
}
return resp.IsLiked, nil
}
// ClipCreated is the resolver for the clipCreated field.
func (r *subscriptionResolver) ClipCreated(ctx context.Context) (<-chan *domain.Clip, error) {
// Реализация подписки требует дополнительной инфраструктуры (Redis, Kafka и т.д.)
// Возвращаем ошибку, так как это не реализовано в gRPC сервисе
return nil, fmt.Errorf("subscriptions not implemented")
}
// ClipLiked is the resolver for the clipLiked field.
func (r *subscriptionResolver) ClipLiked(ctx context.Context, clipID int) (<-chan *domain.ClipLike, error) {
// Реализация подписки требует дополнительной инфраструктуры
return nil, fmt.Errorf("subscriptions not implemented")
}
// CommentClipCreated is the resolver for the commentClipCreated field.
func (r *subscriptionResolver) CommentClipCreated(ctx context.Context, clipID int) (<-chan *domain.ClipComment, error) {
// Реализация подписки требует дополнительной инфраструктуры
return nil, fmt.Errorf("subscriptions not implemented")
}
// Вспомогательные методы для конвертации
func (r *Resolver) protoClipToDomain(protoClip *proto.Clip) *domain.Clip {
if protoClip == nil {
return nil
}
return &domain.Clip{
ID: int(protoClip.Id),
Title: protoClip.Title,
VideoURL: protoClip.VideoUrl,
ThumbnailURL: protoClip.ThumbnailUrl,
AuthorID: int(protoClip.AuthorId),
LikesCount: int(protoClip.LikesCount),
CommentsCount: int(protoClip.CommentsCount),
CreatedAt: protoClip.CreatedAt.AsTime(),
UpdatedAt: protoClip.UpdatedAt.AsTime(),
}
}
func (r *Resolver) protoClipsToDomain(protoClips []*proto.Clip) []*domain.Clip {
var clips []*domain.Clip
for _, protoClip := range protoClips {
clips = append(clips, r.protoClipToDomain(protoClip))
}
return clips
}
func (r *Resolver) protoLikeToDomain(protoLike *proto.ClipLike) *domain.ClipLike {
if protoLike == nil {
return nil
}
return &domain.ClipLike{
ID: int(protoLike.Id),
ClipID: int(protoLike.ClipId),
UserID: int(protoLike.UserId),
CreatedAt: protoLike.CreatedAt.AsTime(),
}
}
func (r *Resolver) protoLikesToDomain(protoLikes []*proto.ClipLike) []*domain.ClipLike {
var likes []*domain.ClipLike
for _, protoLike := range protoLikes {
likes = append(likes, r.protoLikeToDomain(protoLike))
}
return likes
}
func (r *Resolver) protoCommentToDomain(protoComment *proto.ClipComment) *domain.ClipComment {
if protoComment == nil {
return nil
}
return &domain.ClipComment{
ID: int(protoComment.Id),
ClipID: int(protoComment.ClipId),
AuthorID: int(protoComment.AuthorId),
Content: protoComment.Content,
CreatedAt: protoComment.CreatedAt.AsTime(),
UpdatedAt: protoComment.UpdatedAt.AsTime(),
}
}
func (r *Resolver) protoCommentsToDomain(protoComments []*proto.ClipComment) []*domain.ClipComment {
var comments []*domain.ClipComment
for _, protoComment := range protoComments {
comments = append(comments, r.protoCommentToDomain(protoComment))
}
return comments
}
// Clip returns ClipResolver implementation.
func (r *Resolver) Clip() ClipResolver { return &clipResolver{r} }
// ClipComment returns ClipCommentResolver implementation.
func (r *Resolver) ClipComment() ClipCommentResolver { return &clipCommentResolver{r} }
// ClipLike returns ClipLikeResolver implementation.
func (r *Resolver) ClipLike() ClipLikeResolver { return &clipLikeResolver{r} }
type clipResolver struct{ *Resolver }
type clipCommentResolver struct{ *Resolver }
type clipLikeResolver struct{ *Resolver }
func (r *clipCommentResolver) Author(ctx context.Context, obj *domain.ClipComment) (*domain.User, error) {
if obj.AuthorID == 0 {
return nil, fmt.Errorf("comment has no author ID")
}
author, err := r.Services.User.GetByID(ctx, obj.AuthorID)
if err != nil {
return nil, fmt.Errorf("failed to get author: %w", err)
}
return author, nil
}