tailly_clips/internal/handler/grpc_handler.go
2025-09-09 19:33:10 +03:00

270 lines
7.9 KiB
Go

package handler
import (
"bytes"
"context"
"io"
"tailly_clips/internal/domain"
"tailly_clips/internal/service"
"tailly_clips/proto"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/emptypb"
"google.golang.org/protobuf/types/known/timestamppb"
)
type GRPCHandler struct {
proto.UnimplementedClipServiceServer
clipService service.ClipService
likeService service.LikeService
commentService service.CommentService
}
func NewGRPCHandler(
clipService service.ClipService,
likeService service.LikeService,
commentService service.CommentService,
) *GRPCHandler {
return &GRPCHandler{
clipService: clipService,
likeService: likeService,
commentService: commentService,
}
}
func (h *GRPCHandler) CreateClip(stream proto.ClipService_CreateClipServer) error {
var metadata *proto.Metadata
var videoData bytes.Buffer
// Получаем потоковые данные
for {
req, err := stream.Recv()
if err == io.EOF {
break
}
if err != nil {
return status.Errorf(codes.Internal, "failed to receive chunk: %v", err)
}
switch data := req.Data.(type) {
case *proto.CreateClipRequest_Metadata:
metadata = data.Metadata
case *proto.CreateClipRequest_Chunk:
videoData.Write(data.Chunk)
}
}
if metadata == nil {
return status.Errorf(codes.InvalidArgument, "metadata is required")
}
createReq := domain.CreateClipRequest{
UserID: int(metadata.UserId),
Title: metadata.Title,
VideoData: videoData.Bytes(),
FileName: metadata.FileName,
ContentType: metadata.ContentType,
}
clip, err := h.clipService.CreateClip(stream.Context(), createReq)
if err != nil {
return status.Errorf(codes.Internal, "failed to create clip: %v", err)
}
return stream.SendAndClose(&proto.CreateClipResponse{
Clip: h.clipToProto(clip),
})
}
func (h *GRPCHandler) GetClip(ctx context.Context, req *proto.GetClipRequest) (*proto.GetClipResponse, error) {
clip, err := h.clipService.GetClip(ctx, int(req.ClipId))
if err != nil {
return nil, status.Errorf(codes.NotFound, "clip not found: %v", err)
}
return &proto.GetClipResponse{
Clip: h.clipToProto(clip),
}, nil
}
func (h *GRPCHandler) GetUserClips(ctx context.Context, req *proto.GetUserClipsRequest) (*proto.GetUserClipsResponse, error) {
clips, totalCount, err := h.clipService.GetUserClips(ctx, int(req.UserId), int(req.Limit), int(req.Offset))
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to get user clips: %v", err)
}
return &proto.GetUserClipsResponse{
Clips: h.clipsToProto(clips),
TotalCount: int32(totalCount),
}, nil
}
func (h *GRPCHandler) GetClips(ctx context.Context, req *proto.GetClipsRequest) (*proto.GetClipsResponse, error) {
clips, totalCount, err := h.clipService.GetClips(ctx, int(req.Limit), int(req.Offset))
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to get clips: %v", err)
}
return &proto.GetClipsResponse{
Clips: h.clipsToProto(clips),
TotalCount: int32(totalCount),
}, nil
}
func (h *GRPCHandler) DeleteClip(ctx context.Context, req *proto.DeleteClipRequest) (*emptypb.Empty, error) {
err := h.clipService.DeleteClip(ctx, int(req.ClipId), int(req.UserId))
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to delete clip: %v", err)
}
return &emptypb.Empty{}, nil
}
func (h *GRPCHandler) LikeClip(ctx context.Context, req *proto.LikeClipRequest) (*proto.LikeClipResponse, error) {
like, err := h.likeService.LikeClip(ctx, int(req.ClipId), int(req.UserId))
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to like clip: %v", err)
}
return &proto.LikeClipResponse{
Like: h.likeToProto(like),
}, nil
}
func (h *GRPCHandler) UnlikeClip(ctx context.Context, req *proto.UnlikeClipRequest) (*emptypb.Empty, error) {
err := h.likeService.UnlikeClip(ctx, int(req.ClipId), int(req.UserId))
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to unlike clip: %v", err)
}
return &emptypb.Empty{}, nil
}
func (h *GRPCHandler) GetClipLikes(ctx context.Context, req *proto.GetClipLikesRequest) (*proto.GetClipLikesResponse, error) {
likes, totalCount, err := h.likeService.GetClipLikes(ctx, int(req.ClipId), int(req.Limit), int(req.Offset))
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to get clip likes: %v", err)
}
return &proto.GetClipLikesResponse{
Likes: h.likesToProto(likes),
TotalCount: int32(totalCount),
}, nil
}
func (h *GRPCHandler) CheckIfLiked(ctx context.Context, req *proto.CheckIfLikedRequest) (*proto.CheckIfLikedResponse, error) {
isLiked, err := h.likeService.CheckIfLiked(ctx, int(req.ClipId), int(req.UserId))
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to check like status: %v", err)
}
return &proto.CheckIfLikedResponse{
IsLiked: isLiked,
}, nil
}
func (h *GRPCHandler) CreateComment(ctx context.Context, req *proto.CreateCommentRequest) (*proto.CreateCommentResponse, error) {
comment, err := h.commentService.CreateComment(ctx, int(req.ClipId), int(req.UserId), req.Content)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to create comment: %v", err)
}
return &proto.CreateCommentResponse{
Comment: h.commentToProto(comment),
}, nil
}
func (h *GRPCHandler) GetClipComments(ctx context.Context, req *proto.GetClipCommentsRequest) (*proto.GetClipCommentsResponse, error) {
comments, totalCount, err := h.commentService.GetClipComments(ctx, int(req.ClipId), int(req.Limit), int(req.Offset))
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to get clip comments: %v", err)
}
return &proto.GetClipCommentsResponse{
Comments: h.commentsToProto(comments),
TotalCount: int32(totalCount),
}, nil
}
func (h *GRPCHandler) DeleteComment(ctx context.Context, req *proto.DeleteCommentRequest) (*emptypb.Empty, error) {
err := h.commentService.DeleteComment(ctx, int(req.CommentId), int(req.UserId))
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to delete comment: %v", err)
}
return &emptypb.Empty{}, nil
}
// Вспомогательные методы для конвертации
func (h *GRPCHandler) clipToProto(clip *domain.Clip) *proto.Clip {
if clip == nil {
return nil
}
return &proto.Clip{
Id: int32(clip.ID),
Title: clip.Title,
VideoUrl: clip.VideoURL,
ThumbnailUrl: clip.ThumbnailURL,
AuthorId: int32(clip.AuthorID),
LikesCount: int32(clip.LikesCount),
CommentsCount: int32(clip.CommentsCount),
CreatedAt: timestamppb.New(clip.CreatedAt),
UpdatedAt: timestamppb.New(clip.UpdatedAt),
}
}
func (h *GRPCHandler) clipsToProto(clips []*domain.Clip) []*proto.Clip {
var protoClips []*proto.Clip
for _, clip := range clips {
protoClips = append(protoClips, h.clipToProto(clip))
}
return protoClips
}
func (h *GRPCHandler) likeToProto(like *domain.ClipLike) *proto.ClipLike {
if like == nil {
return nil
}
return &proto.ClipLike{
Id: int32(like.ID),
ClipId: int32(like.ClipID),
UserId: int32(like.UserID),
CreatedAt: timestamppb.New(like.CreatedAt),
}
}
func (h *GRPCHandler) likesToProto(likes []*domain.ClipLike) []*proto.ClipLike {
var protoLikes []*proto.ClipLike
for _, like := range likes {
protoLikes = append(protoLikes, h.likeToProto(like))
}
return protoLikes
}
func (h *GRPCHandler) commentToProto(comment *domain.ClipComment) *proto.ClipComment {
if comment == nil {
return nil
}
return &proto.ClipComment{
Id: int32(comment.ID),
ClipId: int32(comment.ClipID),
AuthorId: int32(comment.AuthorID),
Content: comment.Content,
CreatedAt: timestamppb.New(comment.CreatedAt),
UpdatedAt: timestamppb.New(comment.UpdatedAt),
}
}
func (h *GRPCHandler) commentsToProto(comments []*domain.ClipComment) []*proto.ClipComment {
var protoComments []*proto.ClipComment
for _, comment := range comments {
protoComments = append(protoComments, h.commentToProto(comment))
}
return protoComments
}