434 lines
12 KiB
Go
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
|
|
}
|