// 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 }