tailly_back_v2/internal/http/graph/user_resolvers.go
admin dcf9b4bcbf
All checks were successful
continuous-integration/drone/push Build is passing
v0.0.33 Уведомления о лайках
2025-09-18 10:13:33 +03:00

327 lines
9.5 KiB
Go
Raw Permalink 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.

// user_resolvers.go
package graph
import (
"context"
"fmt"
"tailly_back_v2/internal/domain"
"tailly_back_v2/internal/service"
"tailly_back_v2/proto"
"time"
)
type userResolver struct{ *Resolver }
// User is the resolver for the user field.
func (r *queryResolver) User(ctx context.Context, id int) (*domain.User, error) {
user, err := r.Services.User.GetByID(ctx, id)
if err != nil {
return nil, fmt.Errorf("failed to get user: %w", err)
}
return user, nil
}
// Me is the resolver for the me field.
func (r *queryResolver) Me(ctx context.Context) (*domain.User, error) {
userID, err := getUserIDFromContext(ctx)
if err != nil {
return nil, err
}
user, err := r.Services.User.GetByID(ctx, userID)
if err != nil {
return nil, fmt.Errorf("failed to get current user: %w", err)
}
return user, nil
}
// User returns UserResolver implementation.
func (r *Resolver) User() UserResolver { return &userResolver{r} }
// EmailConfirmedAt is the resolver for the emailConfirmedAt field.
func (r *userResolver) EmailConfirmedAt(ctx context.Context, obj *domain.User) (*string, error) {
if obj.EmailConfirmedAt == nil {
return nil, nil
}
formatted := obj.EmailConfirmedAt.Format(time.RFC3339)
return &formatted, nil
}
// CreatedAt is the resolver for the createdAt field.
func (r *userResolver) CreatedAt(ctx context.Context, obj *domain.User) (string, error) {
return obj.CreatedAt.Format(time.RFC3339), nil
}
// UpdatedAt is the resolver for the updatedAt field.
func (r *userResolver) UpdatedAt(ctx context.Context, obj *domain.User) (string, error) {
return obj.UpdatedAt.Format(time.RFC3339), nil
}
// UpdateProfile is the resolver for the updateProfile field.
func (r *mutationResolver) UpdateProfile(ctx context.Context, username string, email string, avatar string) (*domain.User, error) {
userID, err := getUserIDFromContext(ctx)
if err != nil {
return nil, err
}
user, err := r.Services.User.UpdateProfile(ctx, userID, username, email, avatar)
if err != nil {
return nil, fmt.Errorf("failed to update profile: %w", err)
}
return user, nil
}
// ChangePassword is the resolver for the changePassword field.
func (r *mutationResolver) ChangePassword(ctx context.Context, oldPassword string, newPassword string) (bool, error) {
userID, err := getUserIDFromContext(ctx)
if err != nil {
return false, err
}
if err := r.Services.User.ChangePassword(ctx, userID, oldPassword, newPassword); err != nil {
return false, fmt.Errorf("failed to change password: %w", err)
}
return true, nil
}
// Register is the resolver for the register field.
func (r *mutationResolver) Register(ctx context.Context, input domain.RegisterInput) (*domain.User, error) {
user, err := r.Services.Auth.Register(ctx, service.RegisterInput{
Username: input.Username,
Email: input.Email,
Password: input.Password,
})
if err != nil {
return nil, fmt.Errorf("failed to register: %w", err)
}
return user, nil
}
// Login is the resolver for the login field.
func (r *mutationResolver) Login(ctx context.Context, input domain.LoginInput) (*domain.Tokens, error) {
tokens, err := r.Services.Auth.Login(ctx, input.Email, input.Password)
if err != nil {
return nil, fmt.Errorf("failed to login: %w", err)
}
return tokens, nil
}
// Users is the resolver for the users field.
func (r *queryResolver) Users(ctx context.Context) ([]*domain.User, error) {
userID, err := getUserIDFromContext(ctx)
users, err := r.Services.User.GetAll(ctx, userID)
if err != nil {
return nil, fmt.Errorf("failed to get users: %w", err)
}
return users, nil
}
// FollowersCount is the resolver for the followersCount field.
func (r *userResolver) FollowersCount(ctx context.Context, obj *domain.User) (int, error) {
res, err := r.SubscribeClient.GetFollowersCount(ctx, &proto.GetCountRequest{
UserId: int32(obj.ID),
})
if err != nil {
return 0, fmt.Errorf("failed to get followers count: %w", err)
}
return int(res.Count), nil
}
// FollowingCount is the resolver for the followingCount field.
func (r *userResolver) FollowingCount(ctx context.Context, obj *domain.User) (int, error) {
res, err := r.SubscribeClient.GetFollowingCount(ctx, &proto.GetCountRequest{
UserId: int32(obj.ID),
})
if err != nil {
return 0, fmt.Errorf("failed to get following count: %w", err)
}
return int(res.Count), nil
}
// IsFollowing is the resolver for the isFollowing field.
func (r *userResolver) IsFollowing(ctx context.Context, obj *domain.User, userID int) (bool, error) {
currentUserID, err := getUserIDFromContext(ctx)
if err != nil {
return false, fmt.Errorf("authentication required: %w", err)
}
res, err := r.SubscribeClient.IsFollowing(ctx, &proto.IsFollowingRequest{
FollowerId: int32(currentUserID),
FollowingId: int32(userID),
})
if err != nil {
return false, fmt.Errorf("failed to check subscription status: %w", err)
}
return res.IsFollowing, nil
}
// Followers is the resolver for the followers field.
func (r *userResolver) Followers(ctx context.Context, obj *domain.User, limit *int, offset *int) (*FollowersResponse, error) {
limitVal := 20
if limit != nil {
limitVal = *limit
}
offsetVal := 0
if offset != nil {
offsetVal = *offset
}
res, err := r.SubscribeClient.GetFollowers(ctx, &proto.GetFollowersRequest{
UserId: int32(obj.ID),
Limit: int32(limitVal),
Offset: int32(offsetVal),
})
if err != nil {
return nil, fmt.Errorf("failed to get followers: %w", err)
}
return &FollowersResponse{
Followers: convertProtoFollowersToGraphQL(res.Followers),
TotalCount: int(res.TotalCount),
}, nil
}
// Following is the resolver for the following field.
func (r *userResolver) Following(ctx context.Context, obj *domain.User, limit *int, offset *int) (*FollowingResponse, error) {
limitVal := 20
if limit != nil {
limitVal = *limit
}
offsetVal := 0
if offset != nil {
offsetVal = *offset
}
res, err := r.SubscribeClient.GetFollowing(ctx, &proto.GetFollowingRequest{
UserId: int32(obj.ID),
Limit: int32(limitVal),
Offset: int32(offsetVal),
})
if err != nil {
return nil, fmt.Errorf("failed to get following: %w", err)
}
return &FollowingResponse{
Following: convertProtoFollowingToGraphQL(res.Following),
TotalCount: int(res.TotalCount),
}, nil
}
// SubscriptionNotifications is the resolver for the subscriptionNotifications field.
func (r *userResolver) SubscriptionNotifications(ctx context.Context, obj *domain.User, unreadOnly *bool, limit *int, offset *int) (*NotificationsResponse, error) {
currentUserID, err := getUserIDFromContext(ctx)
if err != nil {
return nil, fmt.Errorf("authentication required: %w", err)
}
// Можно смотреть только свои уведомления
if obj.ID != currentUserID {
return nil, fmt.Errorf("access denied: can only view your own notifications")
}
unreadOnlyVal := false
if unreadOnly != nil {
unreadOnlyVal = *unreadOnly
}
limitVal := 20
if limit != nil {
limitVal = *limit
}
offsetVal := 0
if offset != nil {
offsetVal = *offset
}
res, err := r.SubscribeClient.GetSubscriptionNotifications(ctx, &proto.GetNotificationsRequest{
UserId: int32(obj.ID),
UnreadOnly: unreadOnlyVal,
Limit: int32(limitVal),
Offset: int32(offsetVal),
})
if err != nil {
return nil, fmt.Errorf("failed to get notifications: %w", err)
}
return &NotificationsResponse{
Notifications: convertProtoNotificationsToGraphQL(res.Notifications),
TotalCount: int(res.TotalCount),
UnreadCount: int(res.UnreadCount),
}, nil
}
// LikeNotifications is the resolver for the likeNotifications field.
func (r *userResolver) LikeNotifications(ctx context.Context, obj *domain.User, unreadOnly *bool, limit *int, offset *int) (*LikeNotificationsResponse, error) {
currentUserID, err := getUserIDFromContext(ctx)
if err != nil {
return nil, fmt.Errorf("authentication required: %w", err)
}
// Можно смотреть только свои уведомления
if obj.ID != currentUserID {
return nil, fmt.Errorf("access denied: can only view your own notifications")
}
unreadOnlyVal := false
if unreadOnly != nil {
unreadOnlyVal = *unreadOnly
}
limitVal := 20
if limit != nil {
limitVal = *limit
}
offsetVal := 0
if offset != nil {
offsetVal = *offset
}
notifications, totalCount, unreadCount, err := r.Services.Like.GetLikeNotifications(ctx, obj.ID, unreadOnlyVal, limitVal, offsetVal)
if err != nil {
return nil, fmt.Errorf("failed to get like notifications: %w", err)
}
// Заполняем дополнительные поля для GraphQL
for _, notification := range notifications {
// Получаем информацию о пользователе, который поставил лайк
likerUser, err := r.Services.User.GetByID(ctx, notification.LikerID)
if err != nil {
return nil, fmt.Errorf("failed to get liker user: %w", err)
}
notification.SetLiker(likerUser)
// Получаем информацию о посте
post, err := r.Services.Post.GetByID(ctx, notification.PostID)
if err != nil {
return nil, fmt.Errorf("failed to get post: %w", err)
}
// Получаем автора поста
postAuthor, err := r.Services.User.GetByID(ctx, post.AuthorID)
if err != nil {
return nil, fmt.Errorf("failed to get post author: %w", err)
}
// Устанавливаем автора поста
post.Author = postAuthor
notification.SetPost(post)
// Форматируем дату
notification.SetCreatedAtStr()
}
return &LikeNotificationsResponse{
Notifications: notifications,
TotalCount: totalCount,
UnreadCount: unreadCount,
}, nil
}