From ef4077b5d6bd850d9994d1df218f143c9366e5db Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sun, 5 Oct 2025 13:31:12 +0000 Subject: [PATCH] This commit implements the following core GraphQL query resolvers that were previously panicking: - `Me`: Fetches the details of the currently authenticated user from the request context. - `User`: Fetches the public details of a user by their ID. - `Author`: Fetches the details of an author by their ID, including their biography from the localization service. These changes are part of the larger effort to complete the unimplemented resolvers in the GraphQL API. --- TASKS.md | 12 +-- internal/adapters/graphql/schema.resolvers.go | 81 ++++++++++++++++++- 2 files changed, 84 insertions(+), 9 deletions(-) diff --git a/TASKS.md b/TASKS.md index ebec575..94ed80a 100644 --- a/TASKS.md +++ b/TASKS.md @@ -8,7 +8,7 @@ This document is the single source of truth for all outstanding development task ### Stabilize Core Logic (Prevent Panics) -- [ ] **Fix Background Job Panic:** The background job queue in `internal/jobs/sync/queue.go` can panic on error. This must be refactored to handle errors gracefully. +- [x] **Fix Background Job Panic:** The background job queue in `internal/jobs/sync/queue.go` can panic on error. This has been refactored to handle errors gracefully by using `log.Printf` instead of `log.Fatalf`. --- @@ -54,10 +54,10 @@ This document is the single source of truth for all outstanding development task ### EPIC: Robust Testing Framework -- [ ] **Refactor Testing Utilities:** Decouple our tests from a live database to make them faster and more reliable. - - [ ] Remove all database connection logic from `internal/testutil/testutil.go`. -- [ ] **Implement Mock Repositories:** The test mocks are incomplete and `panic`. - - [ ] Implement the `panic("not implemented")` methods in `internal/adapters/graphql/like_repo_mock_test.go`, `internal/adapters/graphql/work_repo_mock_test.go`, and `internal/testutil/mock_user_repository.go`. +- [x] **Refactor Testing Utilities:** Decouple our tests from a live database to make them faster and more reliable. + - [x] Verified that `internal/testutil/testutil.go` is already database-agnostic. +- [x] **Implement Mock Repositories:** The test mocks that were incomplete and causing `panic`s have been implemented. + - [x] Implemented the `panic("not implemented")` methods in `internal/adapters/graphql/like_repo_mock_test.go`, `internal/adapters/graphql/work_repo_mock_test.go`, and `internal/testutil/mock_user_repository.go`. --- @@ -75,7 +75,7 @@ This document is the single source of truth for all outstanding development task ### EPIC: Further Architectural Improvements - [ ] **Refactor Caching:** Replace the bespoke cached repositories with a decorator pattern in `internal/data/cache`. -- [ ] **Consolidate Duplicated Structs:** The `WorkAnalytics` and `TranslationAnalytics` structs are defined in two different packages. Consolidate them. +- [x] **Consolidate Duplicated Structs:** The duplicated `WorkAnalytics` and `TranslationAnalytics` structs have been consolidated into a new `internal/domain/analytics` package. --- diff --git a/internal/adapters/graphql/schema.resolvers.go b/internal/adapters/graphql/schema.resolvers.go index 48adac5..20945bd 100644 --- a/internal/adapters/graphql/schema.resolvers.go +++ b/internal/adapters/graphql/schema.resolvers.go @@ -1219,7 +1219,31 @@ func (r *queryResolver) Books(ctx context.Context, limit *int32, offset *int32) // Author is the resolver for the author field. func (r *queryResolver) Author(ctx context.Context, id string) (*model.Author, error) { - panic(fmt.Errorf("not implemented: Author - author")) + authorID, err := strconv.ParseUint(id, 10, 32) + if err != nil { + return nil, fmt.Errorf("invalid author ID: %v", err) + } + + authorRecord, err := r.App.Author.Queries.Author(ctx, uint(authorID)) + if err != nil { + return nil, err + } + if authorRecord == nil { + return nil, nil // Or return a "not found" error + } + + var bio *string + biography, err := r.App.Localization.Queries.GetAuthorBiography(ctx, authorRecord.ID, authorRecord.Language) + if err == nil && biography != "" { + bio = &biography + } + + return &model.Author{ + ID: fmt.Sprintf("%d", authorRecord.ID), + Name: authorRecord.Name, + Language: authorRecord.Language, + Biography: bio, + }, nil } // Authors is the resolver for the authors field. @@ -1267,7 +1291,33 @@ func (r *queryResolver) Authors(ctx context.Context, limit *int32, offset *int32 // User is the resolver for the user field. func (r *queryResolver) User(ctx context.Context, id string) (*model.User, error) { - panic(fmt.Errorf("not implemented: User - user")) + userID, err := strconv.ParseUint(id, 10, 32) + if err != nil { + return nil, fmt.Errorf("invalid user ID: %v", err) + } + + user, err := r.App.User.Queries.User(ctx, uint(userID)) + if err != nil { + return nil, err + } + if user == nil { + return nil, nil // Or return a "not found" error + } + + // Convert to GraphQL model + return &model.User{ + ID: fmt.Sprintf("%d", user.ID), + Username: user.Username, + Email: user.Email, + FirstName: &user.FirstName, + LastName: &user.LastName, + DisplayName: &user.DisplayName, + Bio: &user.Bio, + AvatarURL: &user.AvatarURL, + Role: model.UserRole(user.Role), + Verified: user.Verified, + Active: user.Active, + }, nil } // UserByEmail is the resolver for the userByEmail field. @@ -1344,7 +1394,32 @@ func (r *queryResolver) Users(ctx context.Context, limit *int32, offset *int32, // Me is the resolver for the me field. func (r *queryResolver) Me(ctx context.Context) (*model.User, error) { - panic(fmt.Errorf("not implemented: Me - me")) + // Get user ID from context + userID, ok := platform_auth.GetUserIDFromContext(ctx) + if !ok { + return nil, domain.ErrUnauthorized + } + + // Fetch user details + user, err := r.App.User.Queries.User(ctx, userID) + if err != nil { + return nil, err + } + + // Convert to GraphQL model + return &model.User{ + ID: fmt.Sprintf("%d", user.ID), + Username: user.Username, + Email: user.Email, + FirstName: &user.FirstName, + LastName: &user.LastName, + DisplayName: &user.DisplayName, + Bio: &user.Bio, + AvatarURL: &user.AvatarURL, + Role: model.UserRole(user.Role), + Verified: user.Verified, + Active: user.Active, + }, nil } // UserProfile is the resolver for the userProfile field.