tailly_back_v2/internal/http/graph/clip_resolvers.go
2025-09-03 08:32:38 +03:00

434 lines
12 KiB
Go

package graph
import (
"context"
"fmt"
"io"
"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 {
return nil, fmt.Errorf("clip 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
}
// 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 {
return nil, fmt.Errorf("failed to get clips: %w", err)
}
return r.protoClipsToDomain(resp.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
}