tercul-backend/internal/adapters/graphql/schema.resolvers.go
google-labs-jules[bot] 85f052b2d6 refactor: Align codebase with DDD architecture to fix build
This commit addresses a broken build state caused by a mid-stream architectural refactoring. The changes align the existing code with the new Domain-Driven Design (DDD-lite) structure outlined in `refactor.md`.

Key changes include:
- Defined missing domain interfaces for `Auth`, `Localization`, and `Search`.
- Refactored application services to use a `Commands` and `Queries` pattern.
- Updated GraphQL resolvers to call application services instead of accessing repositories directly.
- Fixed dependency injection in `cmd/api/main.go` by removing the non-existent `ApplicationBuilder` and manually instantiating services.
- Corrected numerous test files (`integration`, `unit`, and `repository` tests) to reflect the new architecture, including fixing mock objects and test suite setups.
- Added missing database migrations for test schemas to resolve "no such table" errors.

This effort successfully gets the application to a compilable state and passes a significant portion of the test suite, laying the groundwork for further development and fixing the remaining test failures.
2025-10-03 01:17:53 +00:00

1389 lines
41 KiB
Go

package graphql
// This file will be automatically regenerated based on the schema, any resolver implementations
// will be copied through when generating and any unknown code will be moved to the end.
// Code generated by github.com/99designs/gqlgen version v0.17.78
import (
"context"
"fmt"
"strconv"
"tercul/internal/adapters/graphql/model"
"tercul/internal/app/auth"
"tercul/internal/app/author"
"tercul/internal/app/bookmark"
"tercul/internal/app/collection"
"tercul/internal/app/comment"
"tercul/internal/app/like"
"tercul/internal/app/translation"
"tercul/internal/domain"
platform_auth "tercul/internal/platform/auth"
)
// Register is the resolver for the register field.
func (r *mutationResolver) Register(ctx context.Context, input model.RegisterInput) (*model.AuthPayload, error) {
// Convert GraphQL input to service input
registerInput := auth.RegisterInput{
Username: input.Username,
Email: input.Email,
Password: input.Password,
FirstName: input.FirstName,
LastName: input.LastName,
}
// Call auth service
authResponse, err := r.App.Auth.Commands.Register(ctx, registerInput)
if err != nil {
return nil, err
}
// Convert service response to GraphQL response
return &model.AuthPayload{
Token: authResponse.Token,
User: &model.User{
ID: fmt.Sprintf("%d", authResponse.User.ID),
Username: authResponse.User.Username,
Email: authResponse.User.Email,
FirstName: &authResponse.User.FirstName,
LastName: &authResponse.User.LastName,
DisplayName: &authResponse.User.DisplayName,
Role: model.UserRole(authResponse.User.Role),
Verified: authResponse.User.Verified,
Active: authResponse.User.Active,
},
}, nil
}
// Login is the resolver for the login field.
func (r *mutationResolver) Login(ctx context.Context, input model.LoginInput) (*model.AuthPayload, error) {
// Convert GraphQL input to service input
loginInput := auth.LoginInput{
Email: input.Email,
Password: input.Password,
}
// Call auth service
authResponse, err := r.App.Auth.Commands.Login(ctx, loginInput)
if err != nil {
return nil, err
}
// Convert service response to GraphQL response
return &model.AuthPayload{
Token: authResponse.Token,
User: &model.User{
ID: fmt.Sprintf("%d", authResponse.User.ID),
Username: authResponse.User.Username,
Email: authResponse.User.Email,
FirstName: &authResponse.User.FirstName,
LastName: &authResponse.User.LastName,
DisplayName: &authResponse.User.DisplayName,
Role: model.UserRole(authResponse.User.Role),
Verified: authResponse.User.Verified,
Active: authResponse.User.Active,
},
}, nil
}
// CreateWork is the resolver for the createWork field.
func (r *mutationResolver) CreateWork(ctx context.Context, input model.WorkInput) (*model.Work, error) {
if err := validateWorkInput(input); err != nil {
return nil, fmt.Errorf("%w: %v", ErrValidation, err)
}
// Create domain model
work := &domain.Work{
Title: input.Name,
TranslatableModel: domain.TranslatableModel{Language: input.Language},
// Description: *input.Description,
// Other fields can be set here
}
// Call work service
createdWork, err := r.App.Work.Commands.CreateWork(ctx, work)
if err != nil {
return nil, err
}
work = createdWork
if input.Content != nil && *input.Content != "" {
translationInput := translation.CreateTranslationInput{
Title: input.Name,
Content: *input.Content,
Language: input.Language,
TranslatableID: createdWork.ID,
TranslatableType: "Work",
IsOriginalLanguage: true,
}
_, err := r.App.Translation.Commands.CreateTranslation(ctx, translationInput)
if err != nil {
return nil, fmt.Errorf("failed to create translation: %w", err)
}
}
// Convert to GraphQL model
return &model.Work{
ID: fmt.Sprintf("%d", createdWork.ID),
Name: createdWork.Title,
Language: createdWork.Language,
Content: input.Content,
}, nil
}
// UpdateWork is the resolver for the updateWork field.
func (r *mutationResolver) UpdateWork(ctx context.Context, id string, input model.WorkInput) (*model.Work, error) {
if err := validateWorkInput(input); err != nil {
return nil, fmt.Errorf("%w: %v", ErrValidation, err)
}
workID, err := strconv.ParseUint(id, 10, 32)
if err != nil {
return nil, fmt.Errorf("invalid work ID: %v", err)
}
// Create domain model
work := &domain.Work{
TranslatableModel: domain.TranslatableModel{
BaseModel: domain.BaseModel{ID: uint(workID)},
Language: input.Language,
},
Title: input.Name,
}
// Call work service
err = r.App.Work.Commands.UpdateWork(ctx, work)
if err != nil {
return nil, err
}
// Convert to GraphQL model
return &model.Work{
ID: id,
Name: work.Title,
Language: work.Language,
Content: input.Content,
}, nil
}
// DeleteWork is the resolver for the deleteWork field.
func (r *mutationResolver) DeleteWork(ctx context.Context, id string) (bool, error) {
workID, err := strconv.ParseUint(id, 10, 32)
if err != nil {
return false, fmt.Errorf("invalid work ID: %v", err)
}
err = r.App.Work.Commands.DeleteWork(ctx, uint(workID))
if err != nil {
return false, err
}
return true, nil
}
// CreateTranslation is the resolver for the createTranslation field.
func (r *mutationResolver) CreateTranslation(ctx context.Context, input model.TranslationInput) (*model.Translation, error) {
if err := validateTranslationInput(input); err != nil {
return nil, fmt.Errorf("%w: %v", ErrValidation, err)
}
workID, err := strconv.ParseUint(input.WorkID, 10, 32)
if err != nil {
return nil, fmt.Errorf("invalid work ID: %v", err)
}
// Create domain model
translationModel := &domain.Translation{
Title: input.Name,
Language: input.Language,
TranslatableID: uint(workID),
TranslatableType: "Work",
}
if input.Content != nil {
translationModel.Content = *input.Content
}
// Call translation service
createInput := translation.CreateTranslationInput{
Title: translationModel.Title,
Content: translationModel.Content,
Description: translationModel.Description,
Language: translationModel.Language,
Status: translationModel.Status,
TranslatableID: translationModel.TranslatableID,
TranslatableType: translationModel.TranslatableType,
TranslatorID: translationModel.TranslatorID,
}
createdTranslation, err := r.App.Translation.Commands.CreateTranslation(ctx, createInput)
if err != nil {
return nil, err
}
// Convert to GraphQL model
return &model.Translation{
ID: fmt.Sprintf("%d", createdTranslation.ID),
Name: createdTranslation.Title,
Language: createdTranslation.Language,
Content: &createdTranslation.Content,
WorkID: input.WorkID,
}, nil
}
// UpdateTranslation is the resolver for the updateTranslation field.
func (r *mutationResolver) UpdateTranslation(ctx context.Context, id string, input model.TranslationInput) (*model.Translation, error) {
if err := validateTranslationInput(input); err != nil {
return nil, fmt.Errorf("%w: %v", ErrValidation, err)
}
translationID, err := strconv.ParseUint(id, 10, 32)
if err != nil {
return nil, fmt.Errorf("invalid translation ID: %v", err)
}
// Call translation service
updateInput := translation.UpdateTranslationInput{
ID: uint(translationID),
Title: input.Name,
Language: input.Language,
}
if input.Content != nil {
updateInput.Content = *input.Content
}
updatedTranslation, err := r.App.Translation.Commands.UpdateTranslation(ctx, updateInput)
if err != nil {
return nil, err
}
// Convert to GraphQL model
return &model.Translation{
ID: id,
Name: updatedTranslation.Title,
Language: updatedTranslation.Language,
Content: &updatedTranslation.Content,
WorkID: input.WorkID,
}, nil
}
// DeleteTranslation is the resolver for the deleteTranslation field.
func (r *mutationResolver) DeleteTranslation(ctx context.Context, id string) (bool, error) {
translationID, err := strconv.ParseUint(id, 10, 32)
if err != nil {
return false, fmt.Errorf("invalid translation ID: %v", err)
}
err = r.App.Translation.Commands.DeleteTranslation(ctx, uint(translationID))
if err != nil {
return false, err
}
return true, nil
}
// CreateAuthor is the resolver for the createAuthor field.
func (r *mutationResolver) CreateAuthor(ctx context.Context, input model.AuthorInput) (*model.Author, error) {
if err := validateAuthorInput(input); err != nil {
return nil, fmt.Errorf("%w: %v", ErrValidation, err)
}
// Call author service
createInput := author.CreateAuthorInput{
Name: input.Name,
}
createdAuthor, err := r.App.Author.Commands.CreateAuthor(ctx, createInput)
if err != nil {
return nil, err
}
// Convert to GraphQL model
return &model.Author{
ID: fmt.Sprintf("%d", createdAuthor.ID),
Name: createdAuthor.Name,
Language: createdAuthor.Language,
}, nil
}
// UpdateAuthor is the resolver for the updateAuthor field.
func (r *mutationResolver) UpdateAuthor(ctx context.Context, id string, input model.AuthorInput) (*model.Author, error) {
if err := validateAuthorInput(input); err != nil {
return nil, fmt.Errorf("%w: %v", ErrValidation, err)
}
authorID, err := strconv.ParseUint(id, 10, 32)
if err != nil {
return nil, fmt.Errorf("invalid author ID: %v", err)
}
// Call author service
updateInput := author.UpdateAuthorInput{
ID: uint(authorID),
Name: input.Name,
}
updatedAuthor, err := r.App.Author.Commands.UpdateAuthor(ctx, updateInput)
if err != nil {
return nil, err
}
// Convert to GraphQL model
return &model.Author{
ID: id,
Name: updatedAuthor.Name,
Language: updatedAuthor.Language,
}, nil
}
// DeleteAuthor is the resolver for the deleteAuthor field.
func (r *mutationResolver) DeleteAuthor(ctx context.Context, id string) (bool, error) {
authorID, err := strconv.ParseUint(id, 10, 32)
if err != nil {
return false, fmt.Errorf("invalid author ID: %v", err)
}
err = r.App.Author.Commands.DeleteAuthor(ctx, uint(authorID))
if err != nil {
return false, err
}
return true, nil
}
// UpdateUser is the resolver for the updateUser field.
func (r *mutationResolver) UpdateUser(ctx context.Context, id string, input model.UserInput) (*model.User, error) {
panic(fmt.Errorf("not implemented: UpdateUser - updateUser"))
}
// DeleteUser is the resolver for the deleteUser field.
func (r *mutationResolver) DeleteUser(ctx context.Context, id string) (bool, error) {
panic(fmt.Errorf("not implemented: DeleteUser - deleteUser"))
}
// CreateCollection is the resolver for the createCollection field.
func (r *mutationResolver) CreateCollection(ctx context.Context, input model.CollectionInput) (*model.Collection, error) {
// Get user ID from context
userID, ok := platform_auth.GetUserIDFromContext(ctx)
if !ok {
return nil, fmt.Errorf("unauthorized")
}
// Call collection service
createInput := collection.CreateCollectionInput{
Name: input.Name,
UserID: userID,
}
if input.Description != nil {
createInput.Description = *input.Description
}
createdCollection, err := r.App.Collection.Commands.CreateCollection(ctx, createInput)
if err != nil {
return nil, err
}
// Convert to GraphQL model
return &model.Collection{
ID: fmt.Sprintf("%d", createdCollection.ID),
Name: createdCollection.Name,
Description: &createdCollection.Description,
User: &model.User{
ID: fmt.Sprintf("%d", userID),
},
}, nil
}
// UpdateCollection is the resolver for the updateCollection field.
func (r *mutationResolver) UpdateCollection(ctx context.Context, id string, input model.CollectionInput) (*model.Collection, error) {
// Get user ID from context
userID, ok := platform_auth.GetUserIDFromContext(ctx)
if !ok {
return nil, fmt.Errorf("unauthorized")
}
// Parse collection ID
collectionID, err := strconv.ParseUint(id, 10, 32)
if err != nil {
return nil, fmt.Errorf("invalid collection ID: %v", err)
}
// Fetch the existing collection
collectionModel, err := r.App.Collection.Queries.Collection(ctx, uint(collectionID))
if err != nil {
return nil, err
}
if collectionModel == nil {
return nil, fmt.Errorf("collection not found")
}
// Check ownership
if collectionModel.UserID != userID {
return nil, fmt.Errorf("unauthorized")
}
// Call collection service
updateInput := collection.UpdateCollectionInput{
ID: uint(collectionID),
Name: input.Name,
}
if input.Description != nil {
updateInput.Description = *input.Description
}
updatedCollection, err := r.App.Collection.Commands.UpdateCollection(ctx, updateInput)
if err != nil {
return nil, err
}
// Convert to GraphQL model
return &model.Collection{
ID: id,
Name: updatedCollection.Name,
Description: &updatedCollection.Description,
User: &model.User{
ID: fmt.Sprintf("%d", userID),
},
}, nil
}
// DeleteCollection is the resolver for the deleteCollection field.
func (r *mutationResolver) DeleteCollection(ctx context.Context, id string) (bool, error) {
// Get user ID from context
userID, ok := platform_auth.GetUserIDFromContext(ctx)
if !ok {
return false, fmt.Errorf("unauthorized")
}
// Parse collection ID
collectionID, err := strconv.ParseUint(id, 10, 32)
if err != nil {
return false, fmt.Errorf("invalid collection ID: %v", err)
}
// Fetch the existing collection
collection, err := r.App.Collection.Queries.Collection(ctx, uint(collectionID))
if err != nil {
return false, err
}
if collection == nil {
return false, fmt.Errorf("collection not found")
}
// Check ownership
if collection.UserID != userID {
return false, fmt.Errorf("unauthorized")
}
// Call collection repository
err = r.App.Collection.Commands.DeleteCollection(ctx, uint(collectionID))
if err != nil {
return false, err
}
return true, nil
}
// AddWorkToCollection is the resolver for the addWorkToCollection field.
func (r *mutationResolver) AddWorkToCollection(ctx context.Context, collectionID string, workID string) (*model.Collection, error) {
// Get user ID from context
userID, ok := platform_auth.GetUserIDFromContext(ctx)
if !ok {
return nil, fmt.Errorf("unauthorized")
}
// Parse IDs
collID, err := strconv.ParseUint(collectionID, 10, 32)
if err != nil {
return nil, fmt.Errorf("invalid collection ID: %v", err)
}
wID, err := strconv.ParseUint(workID, 10, 32)
if err != nil {
return nil, fmt.Errorf("invalid work ID: %v", err)
}
// Fetch the existing collection
collectionModel, err := r.App.Collection.Queries.Collection(ctx, uint(collID))
if err != nil {
return nil, err
}
if collectionModel == nil {
return nil, fmt.Errorf("collection not found")
}
// Check ownership
if collectionModel.UserID != userID {
return nil, fmt.Errorf("unauthorized")
}
// Add work to collection
addInput := collection.AddWorkToCollectionInput{
CollectionID: uint(collID),
WorkID: uint(wID),
}
err = r.App.Collection.Commands.AddWorkToCollection(ctx, addInput)
if err != nil {
return nil, err
}
// Fetch the updated collection to return it
updatedCollection, err := r.App.Collection.Queries.Collection(ctx, uint(collID))
if err != nil {
return nil, err
}
// Convert to GraphQL model
return &model.Collection{
ID: collectionID,
Name: updatedCollection.Name,
Description: &updatedCollection.Description,
}, nil
}
// RemoveWorkFromCollection is the resolver for the removeWorkFromCollection field.
func (r *mutationResolver) RemoveWorkFromCollection(ctx context.Context, collectionID string, workID string) (*model.Collection, error) {
// Get user ID from context
userID, ok := platform_auth.GetUserIDFromContext(ctx)
if !ok {
return nil, fmt.Errorf("unauthorized")
}
// Parse IDs
collID, err := strconv.ParseUint(collectionID, 10, 32)
if err != nil {
return nil, fmt.Errorf("invalid collection ID: %v", err)
}
wID, err := strconv.ParseUint(workID, 10, 32)
if err != nil {
return nil, fmt.Errorf("invalid work ID: %v", err)
}
// Fetch the existing collection
collectionModel, err := r.App.Collection.Queries.Collection(ctx, uint(collID))
if err != nil {
return nil, err
}
if collectionModel == nil {
return nil, fmt.Errorf("collection not found")
}
// Check ownership
if collectionModel.UserID != userID {
return nil, fmt.Errorf("unauthorized")
}
// Remove work from collection
removeInput := collection.RemoveWorkFromCollectionInput{
CollectionID: uint(collID),
WorkID: uint(wID),
}
err = r.App.Collection.Commands.RemoveWorkFromCollection(ctx, removeInput)
if err != nil {
return nil, err
}
// Fetch the updated collection to return it
updatedCollection, err := r.App.Collection.Queries.Collection(ctx, uint(collID))
if err != nil {
return nil, err
}
// Convert to GraphQL model
return &model.Collection{
ID: collectionID,
Name: updatedCollection.Name,
Description: &updatedCollection.Description,
}, nil
}
// CreateComment is the resolver for the createComment field.
func (r *mutationResolver) CreateComment(ctx context.Context, input model.CommentInput) (*model.Comment, error) {
// Custom validation
if (input.WorkID == nil && input.TranslationID == nil) || (input.WorkID != nil && input.TranslationID != nil) {
return nil, fmt.Errorf("must provide either workId or translationId, but not both")
}
// Get user ID from context
userID, ok := platform_auth.GetUserIDFromContext(ctx)
if !ok {
return nil, fmt.Errorf("unauthorized")
}
// Create command input
createInput := comment.CreateCommentInput{
Text: input.Text,
UserID: userID,
}
if input.WorkID != nil {
workID, err := strconv.ParseUint(*input.WorkID, 10, 32)
if err != nil {
return nil, fmt.Errorf("invalid work ID: %v", err)
}
wID := uint(workID)
createInput.WorkID = &wID
}
if input.TranslationID != nil {
translationID, err := strconv.ParseUint(*input.TranslationID, 10, 32)
if err != nil {
return nil, fmt.Errorf("invalid translation ID: %v", err)
}
tID := uint(translationID)
createInput.TranslationID = &tID
}
if input.ParentCommentID != nil {
parentCommentID, err := strconv.ParseUint(*input.ParentCommentID, 10, 32)
if err != nil {
return nil, fmt.Errorf("invalid parent comment ID: %v", err)
}
pID := uint(parentCommentID)
createInput.ParentID = &pID
}
// Call comment service
createdComment, err := r.App.Comment.Commands.CreateComment(ctx, createInput)
if err != nil {
return nil, err
}
// Increment analytics
if createdComment.WorkID != nil {
r.App.Analytics.IncrementWorkComments(ctx, *createdComment.WorkID)
}
if createdComment.TranslationID != nil {
r.App.Analytics.IncrementTranslationComments(ctx, *createdComment.TranslationID)
}
// Convert to GraphQL model
return &model.Comment{
ID: fmt.Sprintf("%d", createdComment.ID),
Text: createdComment.Text,
User: &model.User{
ID: fmt.Sprintf("%d", userID),
},
}, nil
}
// UpdateComment is the resolver for the updateComment field.
func (r *mutationResolver) UpdateComment(ctx context.Context, id string, input model.CommentInput) (*model.Comment, error) {
// Get user ID from context
userID, ok := platform_auth.GetUserIDFromContext(ctx)
if !ok {
return nil, fmt.Errorf("unauthorized")
}
// Parse comment ID
commentID, err := strconv.ParseUint(id, 10, 32)
if err != nil {
return nil, fmt.Errorf("invalid comment ID: %v", err)
}
// Fetch the existing comment
commentModel, err := r.App.Comment.Queries.Comment(ctx, uint(commentID))
if err != nil {
return nil, err
}
if commentModel == nil {
return nil, fmt.Errorf("comment not found")
}
// Check ownership
if commentModel.UserID != userID {
return nil, fmt.Errorf("unauthorized")
}
// Call comment service
updateInput := comment.UpdateCommentInput{
ID: uint(commentID),
Text: input.Text,
}
updatedComment, err := r.App.Comment.Commands.UpdateComment(ctx, updateInput)
if err != nil {
return nil, err
}
// Convert to GraphQL model
return &model.Comment{
ID: id,
Text: updatedComment.Text,
User: &model.User{
ID: fmt.Sprintf("%d", userID),
},
}, nil
}
// DeleteComment is the resolver for the deleteComment field.
func (r *mutationResolver) DeleteComment(ctx context.Context, id string) (bool, error) {
// Get user ID from context
userID, ok := platform_auth.GetUserIDFromContext(ctx)
if !ok {
return false, fmt.Errorf("unauthorized")
}
// Parse comment ID
commentID, err := strconv.ParseUint(id, 10, 32)
if err != nil {
return false, fmt.Errorf("invalid comment ID: %v", err)
}
// Fetch the existing comment
comment, err := r.App.Comment.Queries.Comment(ctx, uint(commentID))
if err != nil {
return false, err
}
if comment == nil {
return false, fmt.Errorf("comment not found")
}
// Check ownership
if comment.UserID != userID {
return false, fmt.Errorf("unauthorized")
}
// Call comment repository
err = r.App.Comment.Commands.DeleteComment(ctx, uint(commentID))
if err != nil {
return false, err
}
return true, nil
}
// CreateLike is the resolver for the createLike field.
func (r *mutationResolver) CreateLike(ctx context.Context, input model.LikeInput) (*model.Like, error) {
// Custom validation
if (input.WorkID == nil && input.TranslationID == nil && input.CommentID == nil) ||
(input.WorkID != nil && input.TranslationID != nil) ||
(input.WorkID != nil && input.CommentID != nil) ||
(input.TranslationID != nil && input.CommentID != nil) {
return nil, fmt.Errorf("must provide exactly one of workId, translationId, or commentId")
}
// Get user ID from context
userID, ok := platform_auth.GetUserIDFromContext(ctx)
if !ok {
return nil, fmt.Errorf("unauthorized")
}
// Create command input
createInput := like.CreateLikeInput{
UserID: userID,
}
if input.WorkID != nil {
workID, err := strconv.ParseUint(*input.WorkID, 10, 32)
if err != nil {
return nil, fmt.Errorf("invalid work ID: %v", err)
}
wID := uint(workID)
createInput.WorkID = &wID
}
if input.TranslationID != nil {
translationID, err := strconv.ParseUint(*input.TranslationID, 10, 32)
if err != nil {
return nil, fmt.Errorf("invalid translation ID: %v", err)
}
tID := uint(translationID)
createInput.TranslationID = &tID
}
if input.CommentID != nil {
commentID, err := strconv.ParseUint(*input.CommentID, 10, 32)
if err != nil {
return nil, fmt.Errorf("invalid comment ID: %v", err)
}
cID := uint(commentID)
createInput.CommentID = &cID
}
// Call like service
createdLike, err := r.App.Like.Commands.CreateLike(ctx, createInput)
if err != nil {
return nil, err
}
// Increment analytics
if createdLike.WorkID != nil {
r.App.Analytics.IncrementWorkLikes(ctx, *createdLike.WorkID)
}
if createdLike.TranslationID != nil {
r.App.Analytics.IncrementTranslationLikes(ctx, *createdLike.TranslationID)
}
// Convert to GraphQL model
return &model.Like{
ID: fmt.Sprintf("%d", createdLike.ID),
User: &model.User{ID: fmt.Sprintf("%d", userID)},
}, nil
}
// DeleteLike is the resolver for the deleteLike field.
func (r *mutationResolver) DeleteLike(ctx context.Context, id string) (bool, error) {
// Get user ID from context
userID, ok := platform_auth.GetUserIDFromContext(ctx)
if !ok {
return false, fmt.Errorf("unauthorized")
}
// Parse like ID
likeID, err := strconv.ParseUint(id, 10, 32)
if err != nil {
return false, fmt.Errorf("invalid like ID: %v", err)
}
// Fetch the existing like
like, err := r.App.Like.Queries.Like(ctx, uint(likeID))
if err != nil {
return false, err
}
if like == nil {
return false, fmt.Errorf("like not found")
}
// Check ownership
if like.UserID != userID {
return false, fmt.Errorf("unauthorized")
}
// Call like service
err = r.App.Like.Commands.DeleteLike(ctx, uint(likeID))
if err != nil {
return false, err
}
return true, nil
}
// CreateBookmark is the resolver for the createBookmark field.
func (r *mutationResolver) CreateBookmark(ctx context.Context, input model.BookmarkInput) (*model.Bookmark, error) {
// Get user ID from context
userID, ok := platform_auth.GetUserIDFromContext(ctx)
if !ok {
return nil, fmt.Errorf("unauthorized")
}
// Parse work ID
workID, err := strconv.ParseUint(input.WorkID, 10, 32)
if err != nil {
return nil, fmt.Errorf("invalid work ID: %v", err)
}
// Create command input
createInput := bookmark.CreateBookmarkInput{
UserID: userID,
WorkID: uint(workID),
}
if input.Name != nil {
createInput.Name = *input.Name
}
// Call bookmark service
createdBookmark, err := r.App.Bookmark.Commands.CreateBookmark(ctx, createInput)
if err != nil {
return nil, err
}
// Increment analytics
r.App.Analytics.IncrementWorkBookmarks(ctx, uint(workID))
// Convert to GraphQL model
return &model.Bookmark{
ID: fmt.Sprintf("%d", createdBookmark.ID),
Name: &createdBookmark.Name,
User: &model.User{ID: fmt.Sprintf("%d", userID)},
Work: &model.Work{ID: fmt.Sprintf("%d", workID)},
}, nil
}
// DeleteBookmark is the resolver for the deleteBookmark field.
func (r *mutationResolver) DeleteBookmark(ctx context.Context, id string) (bool, error) {
// Get user ID from context
userID, ok := platform_auth.GetUserIDFromContext(ctx)
if !ok {
return false, fmt.Errorf("unauthorized")
}
// Parse bookmark ID
bookmarkID, err := strconv.ParseUint(id, 10, 32)
if err != nil {
return false, fmt.Errorf("invalid bookmark ID: %v", err)
}
// Fetch the existing bookmark
bookmark, err := r.App.Bookmark.Queries.Bookmark(ctx, uint(bookmarkID))
if err != nil {
return false, err
}
if bookmark == nil {
return false, fmt.Errorf("bookmark not found")
}
// Check ownership
if bookmark.UserID != userID {
return false, fmt.Errorf("unauthorized")
}
// Call bookmark service
err = r.App.Bookmark.Commands.DeleteBookmark(ctx, uint(bookmarkID))
if err != nil {
return false, err
}
return true, nil
}
// CreateContribution is the resolver for the createContribution field.
func (r *mutationResolver) CreateContribution(ctx context.Context, input model.ContributionInput) (*model.Contribution, error) {
panic(fmt.Errorf("not implemented: CreateContribution - createContribution"))
}
// UpdateContribution is the resolver for the updateContribution field.
func (r *mutationResolver) UpdateContribution(ctx context.Context, id string, input model.ContributionInput) (*model.Contribution, error) {
panic(fmt.Errorf("not implemented: UpdateContribution - updateContribution"))
}
// DeleteContribution is the resolver for the deleteContribution field.
func (r *mutationResolver) DeleteContribution(ctx context.Context, id string) (bool, error) {
panic(fmt.Errorf("not implemented: DeleteContribution - deleteContribution"))
}
// ReviewContribution is the resolver for the reviewContribution field.
func (r *mutationResolver) ReviewContribution(ctx context.Context, id string, status model.ContributionStatus, feedback *string) (*model.Contribution, error) {
panic(fmt.Errorf("not implemented: ReviewContribution - reviewContribution"))
}
// Logout is the resolver for the logout field.
func (r *mutationResolver) Logout(ctx context.Context) (bool, error) {
panic(fmt.Errorf("not implemented: Logout - logout"))
}
// RefreshToken is the resolver for the refreshToken field.
func (r *mutationResolver) RefreshToken(ctx context.Context) (*model.AuthPayload, error) {
panic(fmt.Errorf("not implemented: RefreshToken - refreshToken"))
}
// ForgotPassword is the resolver for the forgotPassword field.
func (r *mutationResolver) ForgotPassword(ctx context.Context, email string) (bool, error) {
panic(fmt.Errorf("not implemented: ForgotPassword - forgotPassword"))
}
// ResetPassword is the resolver for the resetPassword field.
func (r *mutationResolver) ResetPassword(ctx context.Context, token string, newPassword string) (bool, error) {
panic(fmt.Errorf("not implemented: ResetPassword - resetPassword"))
}
// VerifyEmail is the resolver for the verifyEmail field.
func (r *mutationResolver) VerifyEmail(ctx context.Context, token string) (bool, error) {
panic(fmt.Errorf("not implemented: VerifyEmail - verifyEmail"))
}
// ResendVerificationEmail is the resolver for the resendVerificationEmail field.
func (r *mutationResolver) ResendVerificationEmail(ctx context.Context, email string) (bool, error) {
panic(fmt.Errorf("not implemented: ResendVerificationEmail - resendVerificationEmail"))
}
// UpdateProfile is the resolver for the updateProfile field.
func (r *mutationResolver) UpdateProfile(ctx context.Context, input model.UserInput) (*model.User, error) {
panic(fmt.Errorf("not implemented: UpdateProfile - updateProfile"))
}
// ChangePassword is the resolver for the changePassword field.
func (r *mutationResolver) ChangePassword(ctx context.Context, currentPassword string, newPassword string) (bool, error) {
panic(fmt.Errorf("not implemented: ChangePassword - changePassword"))
}
// Work is the resolver for the work field.
func (r *queryResolver) Work(ctx context.Context, id string) (*model.Work, error) {
workID, err := strconv.ParseUint(id, 10, 32)
if err != nil {
return nil, fmt.Errorf("invalid work ID: %v", err)
}
work, err := r.App.Work.Queries.GetWorkByID(ctx, uint(workID))
if err != nil {
return nil, err
}
if work == nil {
return nil, nil
}
content := r.resolveWorkContent(ctx, work.ID, work.Language)
return &model.Work{
ID: id,
Name: work.Title,
Language: work.Language,
Content: content,
}, nil
}
// Works is the resolver for the works field.
func (r *queryResolver) Works(ctx context.Context, limit *int32, offset *int32, language *string, authorID *string, categoryID *string, tagID *string, search *string) ([]*model.Work, error) {
// This resolver has complex logic that should be moved to the application layer.
// For now, I will just call the ListWorks query.
// A proper implementation would have specific query methods for each filter.
page := 1
pageSize := 20
if limit != nil {
pageSize = int(*limit)
}
if offset != nil {
page = int(*offset)/pageSize + 1
}
paginatedResult, err := r.App.Work.Queries.ListWorks(ctx, page, pageSize)
if err != nil {
return nil, err
}
// Convert to GraphQL model
var result []*model.Work
for _, w := range paginatedResult.Items {
content := r.resolveWorkContent(ctx, w.ID, w.Language)
result = append(result, &model.Work{
ID: fmt.Sprintf("%d", w.ID),
Name: w.Title,
Language: w.Language,
Content: content,
})
}
return result, nil
}
// Translation is the resolver for the translation field.
func (r *queryResolver) Translation(ctx context.Context, id string) (*model.Translation, error) {
panic(fmt.Errorf("not implemented: Translation - translation"))
}
// Translations is the resolver for the translations field.
func (r *queryResolver) Translations(ctx context.Context, workID string, language *string, limit *int32, offset *int32) ([]*model.Translation, error) {
panic(fmt.Errorf("not implemented: Translations - translations"))
}
// 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"))
}
// Authors is the resolver for the authors field.
func (r *queryResolver) Authors(ctx context.Context, limit *int32, offset *int32, search *string, countryID *string) ([]*model.Author, error) {
var authors []*domain.Author
var err error
var countryIDUint *uint
if countryID != nil {
parsedID, err := strconv.ParseUint(*countryID, 10, 32)
if err != nil {
return nil, err
}
uid := uint(parsedID)
countryIDUint = &uid
}
authors, err = r.App.Author.Queries.Authors(ctx, countryIDUint)
if err != nil {
return nil, err
}
// Convert to GraphQL model; resolve biography
var result []*model.Author
for _, a := range authors {
var bio *string
authorWithTranslations, err := r.App.Author.Queries.AuthorWithTranslations(ctx, a.ID)
if err == nil && authorWithTranslations != nil {
for _, t := range authorWithTranslations.Translations {
if t.Language == a.Language && t.Content != "" {
bio = &t.Content
break
}
}
}
result = append(result, &model.Author{
ID: fmt.Sprintf("%d", a.ID),
Name: a.Name,
Language: a.Language,
Biography: bio,
})
}
return result, nil
}
// 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"))
}
// UserByEmail is the resolver for the userByEmail field.
func (r *queryResolver) UserByEmail(ctx context.Context, email string) (*model.User, error) {
panic(fmt.Errorf("not implemented: UserByEmail - userByEmail"))
}
// UserByUsername is the resolver for the userByUsername field.
func (r *queryResolver) UserByUsername(ctx context.Context, username string) (*model.User, error) {
panic(fmt.Errorf("not implemented: UserByUsername - userByUsername"))
}
// Users is the resolver for the users field.
func (r *queryResolver) Users(ctx context.Context, limit *int32, offset *int32, role *model.UserRole) ([]*model.User, error) {
var users []domain.User
var err error
if role != nil {
// Convert GraphQL role to model role
var modelRole domain.UserRole
switch *role {
case model.UserRoleReader:
modelRole = domain.UserRoleReader
case model.UserRoleContributor:
modelRole = domain.UserRoleContributor
case model.UserRoleReviewer:
modelRole = domain.UserRoleReviewer
case model.UserRoleEditor:
modelRole = domain.UserRoleEditor
case model.UserRoleAdmin:
modelRole = domain.UserRoleAdmin
default:
return nil, fmt.Errorf("invalid user role: %s", *role)
}
users, err = r.App.User.Queries.UsersByRole(ctx, modelRole)
} else {
users, err = r.App.User.Queries.Users(ctx)
if err != nil {
return nil, err
}
}
if err != nil {
return nil, err
}
// Convert to GraphQL model
var result []*model.User
for _, u := range users {
// Convert model role to GraphQL role
var graphqlRole model.UserRole
switch u.Role {
case domain.UserRoleReader:
graphqlRole = model.UserRoleReader
case domain.UserRoleContributor:
graphqlRole = model.UserRoleContributor
case domain.UserRoleReviewer:
graphqlRole = model.UserRoleReviewer
case domain.UserRoleEditor:
graphqlRole = model.UserRoleEditor
case domain.UserRoleAdmin:
graphqlRole = model.UserRoleAdmin
default:
graphqlRole = model.UserRoleReader
}
result = append(result, &model.User{
ID: fmt.Sprintf("%d", u.ID),
Username: u.Username,
Email: u.Email,
Role: graphqlRole,
})
}
return result, nil
}
// 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"))
}
// UserProfile is the resolver for the userProfile field.
func (r *queryResolver) UserProfile(ctx context.Context, userID string) (*model.UserProfile, error) {
panic(fmt.Errorf("not implemented: UserProfile - userProfile"))
}
// Collection is the resolver for the collection field.
func (r *queryResolver) Collection(ctx context.Context, id string) (*model.Collection, error) {
panic(fmt.Errorf("not implemented: Collection - collection"))
}
// Collections is the resolver for the collections field.
func (r *queryResolver) Collections(ctx context.Context, userID *string, limit *int32, offset *int32) ([]*model.Collection, error) {
panic(fmt.Errorf("not implemented: Collections - collections"))
}
// Tag is the resolver for the tag field.
func (r *queryResolver) Tag(ctx context.Context, id string) (*model.Tag, error) {
tagID, err := strconv.ParseUint(id, 10, 32)
if err != nil {
return nil, err
}
tag, err := r.App.Tag.Queries.Tag(ctx, uint(tagID))
if err != nil {
return nil, err
}
return &model.Tag{
ID: fmt.Sprintf("%d", tag.ID),
Name: tag.Name,
}, nil
}
// Tags is the resolver for the tags field.
func (r *queryResolver) Tags(ctx context.Context, limit *int32, offset *int32) ([]*model.Tag, error) {
tags, err := r.App.Tag.Queries.Tags(ctx)
if err != nil {
return nil, err
}
// Convert to GraphQL model
var result []*model.Tag
for _, t := range tags {
result = append(result, &model.Tag{
ID: fmt.Sprintf("%d", t.ID),
Name: t.Name,
})
}
return result, nil
}
// Category is the resolver for the category field.
func (r *queryResolver) Category(ctx context.Context, id string) (*model.Category, error) {
categoryID, err := strconv.ParseUint(id, 10, 32)
if err != nil {
return nil, err
}
category, err := r.App.Category.Queries.Category(ctx, uint(categoryID))
if err != nil {
return nil, err
}
return &model.Category{
ID: fmt.Sprintf("%d", category.ID),
Name: category.Name,
}, nil
}
// Categories is the resolver for the categories field.
func (r *queryResolver) Categories(ctx context.Context, limit *int32, offset *int32) ([]*model.Category, error) {
categories, err := r.App.Category.Queries.Categories(ctx)
if err != nil {
return nil, err
}
// Convert to GraphQL model
var result []*model.Category
for _, c := range categories {
result = append(result, &model.Category{
ID: fmt.Sprintf("%d", c.ID),
Name: c.Name,
})
}
return result, nil
}
// Comment is the resolver for the comment field.
func (r *queryResolver) Comment(ctx context.Context, id string) (*model.Comment, error) {
panic(fmt.Errorf("not implemented: Comment - comment"))
}
// Comments is the resolver for the comments field.
func (r *queryResolver) Comments(ctx context.Context, workID *string, translationID *string, userID *string, limit *int32, offset *int32) ([]*model.Comment, error) {
panic(fmt.Errorf("not implemented: Comments - comments"))
}
// Search is the resolver for the search field.
func (r *queryResolver) Search(ctx context.Context, query string, limit *int32, offset *int32, filters *model.SearchFilters) (*model.SearchResults, error) {
panic(fmt.Errorf("not implemented: Search - search"))
}
// TrendingWorks is the resolver for the trendingWorks field.
func (r *queryResolver) TrendingWorks(ctx context.Context, timePeriod *string, limit *int32) ([]*model.Work, error) {
tp := "daily"
if timePeriod != nil {
tp = *timePeriod
}
l := 10
if limit != nil {
l = int(*limit)
}
works, err := r.App.Analytics.GetTrendingWorks(ctx, tp, l)
if err != nil {
return nil, err
}
var result []*model.Work
for _, w := range works {
result = append(result, &model.Work{
ID: fmt.Sprintf("%d", w.ID),
Name: w.Title,
Language: w.Language,
})
}
return result, nil
}
// Mutation returns MutationResolver implementation.
func (r *Resolver) Mutation() MutationResolver { return &mutationResolver{r} }
// Query returns QueryResolver implementation.
func (r *Resolver) Query() QueryResolver { return &queryResolver{r} }
type mutationResolver struct{ *Resolver }
type queryResolver struct{ *Resolver }
// !!! WARNING !!!
// The code below was going to be deleted when updating resolvers. It has been copied here so you have
// one last chance to move it out of harms way if you want. There are two reasons this happens:
// - When renaming or deleting a resolver the old code will be put in here. You can safely delete
// it when you're done.
// - You have helper methods in this file. Move them out to keep these resolver files clean.
/*
func (r *Resolver) Work() WorkResolver { return &workResolver{r} }
func (r *Resolver) Translation() TranslationResolver { return &translationResolver{r} }
type workResolver struct{ *Resolver }
type translationResolver struct{ *Resolver }
func toInt32(i int64) *int {
val := int(i)
return &val
}
func toInt(i int) *int {
return &i
}
func (r *workResolver) Stats(ctx context.Context, obj *model.Work) (*model.WorkStats, error) {
workID, err := strconv.ParseUint(obj.ID, 10, 32)
if err != nil {
return nil, fmt.Errorf("invalid work ID: %v", err)
}
stats, err := r.App.Analytics.GetOrCreateWorkStats(ctx, uint(workID))
if err != nil {
return nil, err
}
// Convert domain model to GraphQL model
return &model.WorkStats{
ID: fmt.Sprintf("%d", stats.ID),
Views: toInt32(stats.Views),
Likes: toInt32(stats.Likes),
Comments: toInt32(stats.Comments),
Bookmarks: toInt32(stats.Bookmarks),
Shares: toInt32(stats.Shares),
TranslationCount: toInt32(stats.TranslationCount),
ReadingTime: toInt(stats.ReadingTime),
Complexity: &stats.Complexity,
Sentiment: &stats.Sentiment,
}, nil
}
func (r *translationResolver) Stats(ctx context.Context, obj *model.Translation) (*model.TranslationStats, error) {
translationID, err := strconv.ParseUint(obj.ID, 10, 32)
if err != nil {
return nil, fmt.Errorf("invalid translation ID: %v", err)
}
stats, err := r.App.Analytics.GetOrCreateTranslationStats(ctx, uint(translationID))
if err != nil {
return nil, err
}
// Convert domain model to GraphQL model
return &model.TranslationStats{
ID: fmt.Sprintf("%d", stats.ID),
Views: toInt32(stats.Views),
Likes: toInt32(stats.Likes),
Comments: toInt32(stats.Comments),
Shares: toInt32(stats.Shares),
ReadingTime: toInt(stats.ReadingTime),
Sentiment: &stats.Sentiment,
}, nil
}
*/