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.