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.72 import ( "context" "errors" "fmt" "strconv" "strings" "tercul/internal/adapters/graphql/model" "tercul/internal/app/auth" "tercul/internal/app/author" "tercul/internal/app/book" "tercul/internal/app/bookmark" "tercul/internal/app/collection" "tercul/internal/app/comment" "tercul/internal/app/contribution" "tercul/internal/app/like" "tercul/internal/app/translation" "tercul/internal/app/user" "tercul/internal/domain" domainsearch "tercul/internal/domain/search" platform_auth "tercul/internal/platform/auth" "tercul/internal/platform/log" "time" ) func toModelUserRole(role domain.UserRole) model.UserRole { switch strings.ToLower(string(role)) { case "reader": return model.UserRoleReader case "contributor": return model.UserRoleContributor case "reviewer": return model.UserRoleReviewer case "editor": return model.UserRoleEditor case "admin": return model.UserRoleAdmin default: return model.UserRoleReader } } // 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: toModelUserRole(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: toModelUserRole(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 := Validate(input); err != nil { return nil, err } // Create domain model workModel := &domain.Work{ Title: input.Name, TranslatableModel: domain.TranslatableModel{Language: input.Language}, } // Call work service createdWork, err := r.App.Work.Commands.CreateWork(ctx, workModel) if err != nil { return nil, err } if input.Content != nil && *input.Content != "" { translationInput := translation.CreateOrUpdateTranslationInput{ Title: input.Name, Content: *input.Content, Language: input.Language, TranslatableID: createdWork.ID, TranslatableType: "works", IsOriginalLanguage: true, } _, err := r.App.Translation.Commands.CreateOrUpdateTranslation(ctx, translationInput) if err != nil { return nil, fmt.Errorf("failed to create initial 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 := Validate(input); err != nil { return nil, err } workID, err := strconv.ParseUint(id, 10, 32) if err != nil { return nil, fmt.Errorf("%w: invalid work ID", domain.ErrValidation) } // Create domain model workModel := &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, workModel) if err != nil { return nil, err } // Convert to GraphQL model return &model.Work{ ID: id, Name: workModel.Title, Language: workModel.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("%w: invalid work ID", domain.ErrValidation) } 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 := Validate(input); err != nil { return nil, err } workID, err := strconv.ParseUint(input.WorkID, 10, 32) if err != nil { return nil, fmt.Errorf("%w: invalid work ID", domain.ErrValidation) } var content string if input.Content != nil { content = *input.Content } createInput := translation.CreateOrUpdateTranslationInput{ Title: input.Name, Content: content, Language: input.Language, TranslatableID: uint(workID), TranslatableType: "works", } createdTranslation, err := r.App.Translation.Commands.CreateOrUpdateTranslation(ctx, createInput) if err != nil { return nil, err } go func() { if err := r.App.Analytics.IncrementWorkTranslationCount(context.Background(), uint(workID)); err != nil { log.Error(err, "failed to increment work translation count") } }() 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 := Validate(input); err != nil { return nil, err } workID, err := strconv.ParseUint(input.WorkID, 10, 32) if err != nil { return nil, fmt.Errorf("%w: invalid work ID", domain.ErrValidation) } var content string if input.Content != nil { content = *input.Content } updateInput := translation.CreateOrUpdateTranslationInput{ Title: input.Name, Content: content, Language: input.Language, TranslatableID: uint(workID), TranslatableType: "works", } updatedTranslation, err := r.App.Translation.Commands.CreateOrUpdateTranslation(ctx, updateInput) if err != nil { return nil, err } return &model.Translation{ ID: fmt.Sprintf("%d", updatedTranslation.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 } // CreateBook is the resolver for the createBook field. func (r *mutationResolver) CreateBook(ctx context.Context, input model.BookInput) (*model.Book, error) { if err := Validate(input); err != nil { return nil, err } createInput := book.CreateBookInput{ Title: input.Name, Description: *input.Description, Language: input.Language, ISBN: input.Isbn, } createdBook, err := r.App.Book.Commands.CreateBook(ctx, createInput) if err != nil { return nil, err } return &model.Book{ ID: fmt.Sprintf("%d", createdBook.ID), Name: createdBook.Title, Language: createdBook.Language, Description: &createdBook.Description, Isbn: &createdBook.ISBN, }, nil } // UpdateBook is the resolver for the updateBook field. func (r *mutationResolver) UpdateBook(ctx context.Context, id string, input model.BookInput) (*model.Book, error) { if err := Validate(input); err != nil { return nil, err } bookID, err := strconv.ParseUint(id, 10, 32) if err != nil { return nil, fmt.Errorf("%w: invalid book ID", domain.ErrValidation) } updateInput := book.UpdateBookInput{ ID: uint(bookID), Title: &input.Name, Description: input.Description, Language: &input.Language, ISBN: input.Isbn, } updatedBook, err := r.App.Book.Commands.UpdateBook(ctx, updateInput) if err != nil { return nil, err } return &model.Book{ ID: id, Name: updatedBook.Title, Language: updatedBook.Language, Description: &updatedBook.Description, Isbn: &updatedBook.ISBN, }, nil } // DeleteBook is the resolver for the deleteBook field. func (r *mutationResolver) DeleteBook(ctx context.Context, id string) (bool, error) { bookID, err := strconv.ParseUint(id, 10, 32) if err != nil { return false, fmt.Errorf("%w: invalid book ID", domain.ErrValidation) } err = r.App.Book.Commands.DeleteBook(ctx, uint(bookID)) 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 := Validate(input); err != nil { return nil, err } createInput := author.CreateAuthorInput{ Name: input.Name, } createdAuthor, err := r.App.Author.Commands.CreateAuthor(ctx, createInput) if err != nil { return nil, err } 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 := Validate(input); err != nil { return nil, err } authorID, err := strconv.ParseUint(id, 10, 32) if err != nil { return nil, fmt.Errorf("invalid author ID: %v", err) } updateInput := author.UpdateAuthorInput{ ID: uint(authorID), Name: input.Name, } updatedAuthor, err := r.App.Author.Commands.UpdateAuthor(ctx, updateInput) if err != nil { return nil, err } 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) { if err := Validate(input); err != nil { return nil, err } userID, err := strconv.ParseUint(id, 10, 32) if err != nil { return nil, fmt.Errorf("invalid user ID: %v", err) } updateInput := user.UpdateUserInput{ ID: uint(userID), Username: input.Username, Email: input.Email, Password: input.Password, FirstName: input.FirstName, LastName: input.LastName, DisplayName: input.DisplayName, Bio: input.Bio, AvatarURL: input.AvatarURL, Verified: input.Verified, Active: input.Active, } if input.Role != nil { role := domain.UserRole(input.Role.String()) updateInput.Role = &role } if input.CountryID != nil { countryID, err := strconv.ParseUint(*input.CountryID, 10, 32) if err != nil { return nil, fmt.Errorf("invalid country ID: %v", err) } uid := uint(countryID) updateInput.CountryID = &uid } if input.CityID != nil { cityID, err := strconv.ParseUint(*input.CityID, 10, 32) if err != nil { return nil, fmt.Errorf("invalid city ID: %v", err) } uid := uint(cityID) updateInput.CityID = &uid } if input.AddressID != nil { addressID, err := strconv.ParseUint(*input.AddressID, 10, 32) if err != nil { return nil, fmt.Errorf("invalid address ID: %v", err) } uid := uint(addressID) updateInput.AddressID = &uid } updatedUser, err := r.App.User.Commands.UpdateUser(ctx, updateInput) if err != nil { return nil, err } return &model.User{ ID: fmt.Sprintf("%d", updatedUser.ID), Username: updatedUser.Username, Email: updatedUser.Email, FirstName: &updatedUser.FirstName, LastName: &updatedUser.LastName, DisplayName: &updatedUser.DisplayName, Bio: &updatedUser.Bio, AvatarURL: &updatedUser.AvatarURL, Role: toModelUserRole(updatedUser.Role), Verified: updatedUser.Verified, Active: updatedUser.Active, }, nil } // DeleteUser is the resolver for the deleteUser field. func (r *mutationResolver) DeleteUser(ctx context.Context, id string) (bool, error) { userID, err := strconv.ParseUint(id, 10, 32) if err != nil { return false, fmt.Errorf("%w: invalid user ID", domain.ErrValidation) } err = r.App.User.Commands.DeleteUser(ctx, uint(userID)) if err != nil { return false, err } return true, nil } // CreateCollection is the resolver for the createCollection field. func (r *mutationResolver) CreateCollection(ctx context.Context, input model.CollectionInput) (*model.Collection, error) { userID, ok := platform_auth.GetUserIDFromContext(ctx) if !ok { return nil, fmt.Errorf("unauthorized") } 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 } 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) { userID, ok := platform_auth.GetUserIDFromContext(ctx) if !ok { return nil, fmt.Errorf("unauthorized") } collectionID, err := strconv.ParseUint(id, 10, 32) if err != nil { return nil, fmt.Errorf("invalid collection ID: %v", err) } updateInput := collection.UpdateCollectionInput{ ID: uint(collectionID), Name: input.Name, UserID: userID, } if input.Description != nil { updateInput.Description = *input.Description } updatedCollection, err := r.App.Collection.Commands.UpdateCollection(ctx, updateInput) if err != nil { return nil, err } 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) { userID, ok := platform_auth.GetUserIDFromContext(ctx) if !ok { return false, fmt.Errorf("unauthorized") } collectionID, err := strconv.ParseUint(id, 10, 32) if err != nil { return false, fmt.Errorf("invalid collection ID: %v", err) } err = r.App.Collection.Commands.DeleteCollection(ctx, uint(collectionID), userID) 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) { userID, ok := platform_auth.GetUserIDFromContext(ctx) if !ok { return nil, fmt.Errorf("unauthorized") } 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) } addInput := collection.AddWorkToCollectionInput{ CollectionID: uint(collID), WorkID: uint(wID), UserID: userID, } err = r.App.Collection.Commands.AddWorkToCollection(ctx, addInput) if err != nil { return nil, err } updatedCollection, err := r.App.Collection.Queries.Collection(ctx, uint(collID)) if err != nil { return nil, err } 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) { userID, ok := platform_auth.GetUserIDFromContext(ctx) if !ok { return nil, fmt.Errorf("unauthorized") } 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) } removeInput := collection.RemoveWorkFromCollectionInput{ CollectionID: uint(collID), WorkID: uint(wID), UserID: userID, } err = r.App.Collection.Commands.RemoveWorkFromCollection(ctx, removeInput) if err != nil { return nil, err } updatedCollection, err := r.App.Collection.Queries.Collection(ctx, uint(collID)) if err != nil { return nil, err } 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) { 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") } userID, ok := platform_auth.GetUserIDFromContext(ctx) if !ok { return nil, fmt.Errorf("unauthorized") } 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 } createdComment, err := r.App.Comment.Commands.CreateComment(ctx, createInput) if err != nil { return nil, err } if createdComment.WorkID != nil { if err := r.App.Analytics.IncrementWorkComments(ctx, *createdComment.WorkID); err != nil { log.FromContext(ctx).Error(err, "failed to increment work comments") } } if createdComment.TranslationID != nil { if err := r.App.Analytics.IncrementTranslationComments(ctx, *createdComment.TranslationID); err != nil { log.FromContext(ctx).Error(err, "failed to increment translation comments") } } 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) { userID, ok := platform_auth.GetUserIDFromContext(ctx) if !ok { return nil, fmt.Errorf("unauthorized") } commentID, err := strconv.ParseUint(id, 10, 32) if err != nil { return nil, fmt.Errorf("invalid comment ID: %v", err) } 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") } if commentModel.UserID != userID { return nil, fmt.Errorf("unauthorized") } updateInput := comment.UpdateCommentInput{ ID: uint(commentID), Text: input.Text, } updatedComment, err := r.App.Comment.Commands.UpdateComment(ctx, updateInput) if err != nil { return nil, err } 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) { userID, ok := platform_auth.GetUserIDFromContext(ctx) if !ok { return false, fmt.Errorf("unauthorized") } commentID, err := strconv.ParseUint(id, 10, 32) if err != nil { return false, fmt.Errorf("invalid comment ID: %v", err) } 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") } if comment.UserID != userID { return false, fmt.Errorf("unauthorized") } 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) { 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") } userID, ok := platform_auth.GetUserIDFromContext(ctx) if !ok { return nil, fmt.Errorf("unauthorized") } 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 } createdLike, err := r.App.Like.Commands.CreateLike(ctx, createInput) if err != nil { return nil, err } if createdLike.WorkID != nil { if err := r.App.Analytics.IncrementWorkLikes(ctx, *createdLike.WorkID); err != nil { log.FromContext(ctx).Error(err, "failed to increment work likes") } } if createdLike.TranslationID != nil { if err := r.App.Analytics.IncrementTranslationLikes(ctx, *createdLike.TranslationID); err != nil { log.FromContext(ctx).Error(err, "failed to increment translation likes") } } 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) { userID, ok := platform_auth.GetUserIDFromContext(ctx) if !ok { return false, fmt.Errorf("unauthorized") } likeID, err := strconv.ParseUint(id, 10, 32) if err != nil { return false, fmt.Errorf("invalid like ID: %v", err) } 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") } if like.UserID != userID { return false, fmt.Errorf("unauthorized") } 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) { userID, ok := platform_auth.GetUserIDFromContext(ctx) if !ok { return nil, fmt.Errorf("unauthorized") } workID, err := strconv.ParseUint(input.WorkID, 10, 32) if err != nil { return nil, fmt.Errorf("invalid work ID: %v", err) } createInput := bookmark.CreateBookmarkInput{ UserID: userID, WorkID: uint(workID), } if input.Name != nil { createInput.Name = *input.Name } createdBookmark, err := r.App.Bookmark.Commands.CreateBookmark(ctx, createInput) if err != nil { return nil, err } if err := r.App.Analytics.IncrementWorkBookmarks(ctx, uint(workID)); err != nil { log.FromContext(ctx).Error(err, "failed to increment work bookmarks") } 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) { userID, ok := platform_auth.GetUserIDFromContext(ctx) if !ok { return false, fmt.Errorf("unauthorized") } bookmarkID, err := strconv.ParseUint(id, 10, 32) if err != nil { return false, fmt.Errorf("invalid bookmark ID: %v", err) } 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") } if bookmark.UserID != userID { return false, fmt.Errorf("unauthorized") } 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) { userID, ok := platform_auth.GetUserIDFromContext(ctx) if !ok { return nil, fmt.Errorf("unauthorized") } createInput := contribution.CreateContributionInput{ Name: input.Name, } 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.Status != nil { createInput.Status = input.Status.String() } else { createInput.Status = "DRAFT" // Default status } createdContribution, err := r.App.Contribution.Commands.CreateContribution(ctx, createInput) if err != nil { return nil, err } return &model.Contribution{ ID: fmt.Sprintf("%d", createdContribution.ID), Name: createdContribution.Name, Status: model.ContributionStatus(createdContribution.Status), User: &model.User{ ID: fmt.Sprintf("%d", userID), }, }, nil } // UpdateContribution is the resolver for the updateContribution field. func (r *mutationResolver) UpdateContribution(ctx context.Context, id string, input model.ContributionInput) (*model.Contribution, error) { userID, ok := platform_auth.GetUserIDFromContext(ctx) if !ok { return nil, domain.ErrUnauthorized } contributionID, err := strconv.ParseUint(id, 10, 32) if err != nil { return nil, fmt.Errorf("%w: invalid contribution ID", domain.ErrValidation) } updateInput := contribution.UpdateContributionInput{ ID: uint(contributionID), UserID: userID, Name: &input.Name, } if input.Status != nil { status := input.Status.String() updateInput.Status = &status } updatedContribution, err := r.App.Contribution.Commands.UpdateContribution(ctx, updateInput) if err != nil { return nil, err } return &model.Contribution{ ID: fmt.Sprintf("%d", updatedContribution.ID), Name: updatedContribution.Name, Status: model.ContributionStatus(updatedContribution.Status), User: &model.User{ ID: fmt.Sprintf("%d", updatedContribution.UserID), }, }, nil } // DeleteContribution is the resolver for the deleteContribution field. func (r *mutationResolver) DeleteContribution(ctx context.Context, id string) (bool, error) { userID, ok := platform_auth.GetUserIDFromContext(ctx) if !ok { return false, domain.ErrUnauthorized } contributionID, err := strconv.ParseUint(id, 10, 32) if err != nil { return false, fmt.Errorf("%w: invalid contribution ID", domain.ErrValidation) } err = r.App.Contribution.Commands.DeleteContribution(ctx, uint(contributionID), userID) if err != nil { return false, err } return true, nil } // 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) { contributionID, err := strconv.ParseUint(id, 10, 32) if err != nil { return nil, fmt.Errorf("%w: invalid contribution ID", domain.ErrValidation) } reviewInput := contribution.ReviewContributionInput{ ID: uint(contributionID), Status: status.String(), Feedback: feedback, } reviewedContribution, err := r.App.Contribution.Commands.ReviewContribution(ctx, reviewInput) if err != nil { return nil, err } return &model.Contribution{ ID: fmt.Sprintf("%d", reviewedContribution.ID), Name: reviewedContribution.Name, Status: model.ContributionStatus(reviewedContribution.Status), User: &model.User{ ID: fmt.Sprintf("%d", reviewedContribution.UserID), }, }, nil } // Logout is the resolver for the logout field. func (r *mutationResolver) Logout(ctx context.Context) (bool, error) { err := r.App.Auth.Commands.Logout(ctx) if err != nil { return false, err } return true, nil } // RefreshToken is the resolver for the refreshToken field. func (r *mutationResolver) RefreshToken(ctx context.Context) (*model.AuthPayload, error) { authResponse, err := r.App.Auth.Commands.RefreshToken(ctx) if err != nil { return nil, err } 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: toModelUserRole(authResponse.User.Role), Verified: authResponse.User.Verified, Active: authResponse.User.Active, }, }, nil } // ForgotPassword is the resolver for the forgotPassword field. func (r *mutationResolver) ForgotPassword(ctx context.Context, email string) (bool, error) { err := r.App.Auth.Commands.ForgotPassword(ctx, email) if err != nil { return true, nil } return true, nil } // ResetPassword is the resolver for the resetPassword field. func (r *mutationResolver) ResetPassword(ctx context.Context, token string, newPassword string) (bool, error) { resetInput := auth.ResetPasswordInput{ Token: token, NewPassword: newPassword, } err := r.App.Auth.Commands.ResetPassword(ctx, resetInput) if err != nil { return false, err } return true, nil } // VerifyEmail is the resolver for the verifyEmail field. func (r *mutationResolver) VerifyEmail(ctx context.Context, token string) (bool, error) { err := r.App.Auth.Commands.VerifyEmail(ctx, token) if err != nil { return false, err } return true, nil } // ResendVerificationEmail is the resolver for the resendVerificationEmail field. func (r *mutationResolver) ResendVerificationEmail(ctx context.Context, email string) (bool, error) { err := r.App.Auth.Commands.ResendVerificationEmail(ctx, email) if err != nil { return true, nil } return true, nil } // UpdateProfile is the resolver for the updateProfile field. func (r *mutationResolver) UpdateProfile(ctx context.Context, input model.UserInput) (*model.User, error) { userID, ok := platform_auth.GetUserIDFromContext(ctx) if !ok { return nil, domain.ErrUnauthorized } updateInput := user.UpdateUserInput{ ID: userID, FirstName: input.FirstName, LastName: input.LastName, DisplayName: input.DisplayName, Bio: input.Bio, AvatarURL: input.AvatarURL, } if input.CountryID != nil { countryID, err := strconv.ParseUint(*input.CountryID, 10, 32) if err != nil { return nil, fmt.Errorf("invalid country ID: %v", err) } uid := uint(countryID) updateInput.CountryID = &uid } if input.CityID != nil { cityID, err := strconv.ParseUint(*input.CityID, 10, 32) if err != nil { return nil, fmt.Errorf("invalid city ID: %v", err) } uid := uint(cityID) updateInput.CityID = &uid } if input.AddressID != nil { addressID, err := strconv.ParseUint(*input.AddressID, 10, 32) if err != nil { return nil, fmt.Errorf("invalid address ID: %v", err) } uid := uint(addressID) updateInput.AddressID = &uid } updatedUser, err := r.App.User.Commands.UpdateUser(ctx, updateInput) if err != nil { return nil, err } return &model.User{ ID: fmt.Sprintf("%d", updatedUser.ID), Username: updatedUser.Username, Email: updatedUser.Email, FirstName: &updatedUser.FirstName, LastName: &updatedUser.LastName, DisplayName: &updatedUser.DisplayName, Bio: &updatedUser.Bio, AvatarURL: &updatedUser.AvatarURL, Role: toModelUserRole(updatedUser.Role), Verified: updatedUser.Verified, Active: updatedUser.Active, }, nil } // ChangePassword is the resolver for the changePassword field. func (r *mutationResolver) ChangePassword(ctx context.Context, currentPassword string, newPassword string) (bool, error) { userID, ok := platform_auth.GetUserIDFromContext(ctx) if !ok { return false, domain.ErrUnauthorized } changeInput := auth.ChangePasswordInput{ UserID: userID, CurrentPassword: currentPassword, NewPassword: newPassword, } err := r.App.Auth.Commands.ChangePassword(ctx, changeInput) if err != nil { return false, err } return true, nil } // 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) } workDTO, err := r.App.Work.Queries.GetWorkByID(ctx, uint(workID)) if err != nil { if errors.Is(err, domain.ErrEntityNotFound) { return nil, nil } return nil, err } go func() { if err := r.App.Analytics.IncrementWorkViews(context.Background(), uint(workID)); err != nil { log.Error(err, "failed to increment work views") } }() content := r.resolveWorkContent(ctx, workDTO.ID, workDTO.Language) return &model.Work{ ID: id, Name: workDTO.Title, Language: workDTO.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) { 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 } 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) { translationID, err := strconv.ParseUint(id, 10, 32) if err != nil { return nil, fmt.Errorf("invalid translation ID: %v", err) } translationDTO, err := r.App.Translation.Queries.Translation(ctx, uint(translationID)) if err != nil { return nil, err } if translationDTO == nil { return nil, nil } go func() { if err := r.App.Analytics.IncrementTranslationViews(context.Background(), uint(translationID)); err != nil { log.Error(err, "failed to increment translation views") } }() return &model.Translation{ ID: id, Name: translationDTO.Title, Language: translationDTO.Language, Content: &translationDTO.Content, WorkID: fmt.Sprintf("%d", translationDTO.TranslatableID), }, nil } // 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) { wID, err := strconv.ParseUint(workID, 10, 32) if err != nil { return nil, fmt.Errorf("%w: invalid work ID", domain.ErrValidation) } page := 1 pageSize := 20 if limit != nil { pageSize = int(*limit) } if offset != nil { page = int(*offset)/pageSize + 1 } paginatedResult, err := r.App.Translation.Queries.ListTranslations(ctx, uint(wID), language, page, pageSize) if err != nil { return nil, err } var result []*model.Translation for _, t := range paginatedResult.Items { result = append(result, &model.Translation{ ID: fmt.Sprintf("%d", t.ID), Name: t.Title, Language: t.Language, Content: &t.Content, WorkID: workID, }) } return result, nil } // Book is the resolver for the book field. func (r *queryResolver) Book(ctx context.Context, id string) (*model.Book, error) { bookID, err := strconv.ParseUint(id, 10, 32) if err != nil { return nil, fmt.Errorf("%w: invalid book ID", domain.ErrValidation) } bookRecord, err := r.App.Book.Queries.Book(ctx, uint(bookID)) if err != nil { return nil, err } if bookRecord == nil { return nil, nil } return &model.Book{ ID: fmt.Sprintf("%d", bookRecord.ID), Name: bookRecord.Title, Language: bookRecord.Language, Description: &bookRecord.Description, Isbn: &bookRecord.ISBN, }, nil } // Books is the resolver for the books field. func (r *queryResolver) Books(ctx context.Context, limit *int32, offset *int32) ([]*model.Book, error) { books, err := r.App.Book.Queries.Books(ctx) if err != nil { return nil, err } var result []*model.Book for _, b := range books { result = append(result, &model.Book{ ID: fmt.Sprintf("%d", b.ID), Name: b.Title, Language: b.Language, Description: &b.Description, Isbn: &b.ISBN, }) } return result, nil } // Author is the resolver for the author field. func (r *queryResolver) Author(ctx context.Context, id string) (*model.Author, error) { authorID, err := strconv.ParseUint(id, 10, 32) if err != nil { return nil, fmt.Errorf("%w: invalid author ID", domain.ErrValidation) } authorRecord, err := r.App.Author.Queries.Author(ctx, uint(authorID)) if err != nil { return nil, err } if authorRecord == nil { return nil, nil } var bio *string authorWithTranslations, err := r.App.Author.Queries.AuthorWithTranslations(ctx, authorRecord.ID) if err == nil && authorWithTranslations != nil { 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. 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 } 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 { biography, err := r.App.Localization.Queries.GetAuthorBiography(ctx, a.ID, a.Language) if err == nil && biography != "" { bio = &biography } } 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) { userID, err := strconv.ParseUint(id, 10, 32) if err != nil { return nil, fmt.Errorf("%w: invalid user ID", domain.ErrValidation) } userRecord, err := r.App.User.Queries.User(ctx, uint(userID)) if err != nil { return nil, err } if userRecord == nil { return nil, nil } return &model.User{ ID: fmt.Sprintf("%d", userRecord.ID), Username: userRecord.Username, Email: userRecord.Email, FirstName: &userRecord.FirstName, LastName: &userRecord.LastName, DisplayName: &userRecord.DisplayName, Bio: &userRecord.Bio, AvatarURL: &userRecord.AvatarURL, Role: toModelUserRole(userRecord.Role), Verified: userRecord.Verified, Active: userRecord.Active, }, nil } // UserByEmail is the resolver for the userByEmail field. func (r *queryResolver) UserByEmail(ctx context.Context, email string) (*model.User, error) { userRecord, err := r.App.User.Queries.UserByEmail(ctx, email) if err != nil { return nil, err } if userRecord == nil { return nil, nil } return &model.User{ ID: fmt.Sprintf("%d", userRecord.ID), Username: userRecord.Username, Email: userRecord.Email, FirstName: &userRecord.FirstName, LastName: &userRecord.LastName, DisplayName: &userRecord.DisplayName, Bio: &userRecord.Bio, AvatarURL: &userRecord.AvatarURL, Role: toModelUserRole(userRecord.Role), Verified: userRecord.Verified, Active: userRecord.Active, }, nil } // UserByUsername is the resolver for the userByUsername field. func (r *queryResolver) UserByUsername(ctx context.Context, username string) (*model.User, error) { userRecord, err := r.App.User.Queries.UserByUsername(ctx, username) if err != nil { return nil, err } if userRecord == nil { return nil, nil } return &model.User{ ID: fmt.Sprintf("%d", userRecord.ID), Username: userRecord.Username, Email: userRecord.Email, FirstName: &userRecord.FirstName, LastName: &userRecord.LastName, DisplayName: &userRecord.DisplayName, Bio: &userRecord.Bio, AvatarURL: &userRecord.AvatarURL, Role: toModelUserRole(userRecord.Role), Verified: userRecord.Verified, Active: userRecord.Active, }, nil } // 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 { 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 } var result []*model.User for _, u := range users { 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) { userID, ok := platform_auth.GetUserIDFromContext(ctx) if !ok { return nil, domain.ErrUnauthorized } userRecord, err := r.App.User.Queries.User(ctx, userID) if err != nil { return nil, err } if userRecord == nil { return nil, domain.ErrUserNotFound } return &model.User{ ID: fmt.Sprintf("%d", userRecord.ID), Username: userRecord.Username, Email: userRecord.Email, FirstName: &userRecord.FirstName, LastName: &userRecord.LastName, DisplayName: &userRecord.DisplayName, Bio: &userRecord.Bio, AvatarURL: &userRecord.AvatarURL, Role: toModelUserRole(userRecord.Role), Verified: userRecord.Verified, Active: userRecord.Active, }, nil } // UserProfile is the resolver for the userProfile field. func (r *queryResolver) UserProfile(ctx context.Context, userID string) (*model.UserProfile, error) { uID, err := strconv.ParseUint(userID, 10, 32) if err != nil { return nil, fmt.Errorf("%w: invalid user ID", domain.ErrValidation) } profile, err := r.App.User.Queries.UserProfile(ctx, uint(uID)) if err != nil { return nil, err } if profile == nil { return nil, nil } user, err := r.App.User.Queries.User(ctx, uint(uID)) if err != nil { return nil, err } if user == nil { return nil, fmt.Errorf("user not found for profile %d", profile.ID) } return &model.UserProfile{ ID: fmt.Sprintf("%d", profile.ID), UserID: userID, User: &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: toModelUserRole(user.Role), Verified: user.Verified, Active: user.Active, }, PhoneNumber: &profile.PhoneNumber, Website: &profile.Website, Twitter: &profile.Twitter, Facebook: &profile.Facebook, LinkedIn: &profile.LinkedIn, Github: &profile.Github, }, nil } // Collection is the resolver for the collection field. func (r *queryResolver) Collection(ctx context.Context, id string) (*model.Collection, error) { collID, err := strconv.ParseUint(id, 10, 32) if err != nil { return nil, fmt.Errorf("%w: invalid collection ID", domain.ErrValidation) } collectionRecord, err := r.App.Collection.Queries.Collection(ctx, uint(collID)) if err != nil { return nil, err } if collectionRecord == nil { return nil, nil } workRecords, err := r.App.Work.Queries.ListByCollectionID(ctx, uint(collID)) if err != nil { return nil, err } var works []*model.Work for _, w := range workRecords { content := r.resolveWorkContent(ctx, w.ID, w.Language) works = append(works, &model.Work{ ID: fmt.Sprintf("%d", w.ID), Name: w.Title, Language: w.Language, Content: content, }) } return &model.Collection{ ID: fmt.Sprintf("%d", collectionRecord.ID), Name: collectionRecord.Name, Description: &collectionRecord.Description, Works: works, User: &model.User{ ID: fmt.Sprintf("%d", collectionRecord.UserID), }, }, nil } // Collections is the resolver for the collections field. func (r *queryResolver) Collections(ctx context.Context, userID *string, limit *int32, offset *int32) ([]*model.Collection, error) { var collectionRecords []domain.Collection var err error if userID != nil { uID, idErr := strconv.ParseUint(*userID, 10, 32) if idErr != nil { return nil, fmt.Errorf("%w: invalid user ID", domain.ErrValidation) } collectionRecords, err = r.App.Collection.Queries.CollectionsByUserID(ctx, uint(uID)) } else { collectionRecords, err = r.App.Collection.Queries.PublicCollections(ctx) } if err != nil { return nil, err } start := 0 if offset != nil { start = int(*offset) } end := len(collectionRecords) if limit != nil { end = start + int(*limit) if end > len(collectionRecords) { end = len(collectionRecords) } } if start > len(collectionRecords) { start = len(collectionRecords) } paginatedRecords := collectionRecords[start:end] var result []*model.Collection for _, c := range paginatedRecords { result = append(result, &model.Collection{ ID: fmt.Sprintf("%d", c.ID), Name: c.Name, Description: &c.Description, User: &model.User{ ID: fmt.Sprintf("%d", c.UserID), }, }) } return result, nil } // 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 } 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, fmt.Errorf("invalid category ID: %v", err) } category, err := r.App.Category.Queries.Category(ctx, uint(categoryID)) if err != nil { return nil, err } if category == nil { return nil, nil } 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 } 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) { cID, err := strconv.ParseUint(id, 10, 32) if err != nil { return nil, fmt.Errorf("%w: invalid comment ID", domain.ErrValidation) } commentRecord, err := r.App.Comment.Queries.Comment(ctx, uint(cID)) if err != nil { return nil, err } if commentRecord == nil { return nil, nil } return &model.Comment{ ID: fmt.Sprintf("%d", commentRecord.ID), Text: commentRecord.Text, User: &model.User{ ID: fmt.Sprintf("%d", commentRecord.UserID), }, }, nil } // 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) { var commentRecords []domain.Comment var err error if workID != nil { wID, idErr := strconv.ParseUint(*workID, 10, 32) if idErr != nil { return nil, fmt.Errorf("%w: invalid work ID", domain.ErrValidation) } commentRecords, err = r.App.Comment.Queries.CommentsByWorkID(ctx, uint(wID)) } else if translationID != nil { tID, idErr := strconv.ParseUint(*translationID, 10, 32) if idErr != nil { return nil, fmt.Errorf("%w: invalid translation ID", domain.ErrValidation) } commentRecords, err = r.App.Comment.Queries.CommentsByTranslationID(ctx, uint(tID)) } else if userID != nil { uID, idErr := strconv.ParseUint(*userID, 10, 32) if idErr != nil { return nil, fmt.Errorf("%w: invalid user ID", domain.ErrValidation) } commentRecords, err = r.App.Comment.Queries.CommentsByUserID(ctx, uint(uID)) } else { commentRecords, err = r.App.Comment.Queries.Comments(ctx) } if err != nil { return nil, err } start := 0 if offset != nil { start = int(*offset) } end := len(commentRecords) if limit != nil { end = start + int(*limit) if end > len(commentRecords) { end = len(commentRecords) } } if start > len(commentRecords) { start = len(commentRecords) } paginatedRecords := commentRecords[start:end] var result []*model.Comment for _, c := range paginatedRecords { result = append(result, &model.Comment{ ID: fmt.Sprintf("%d", c.ID), Text: c.Text, User: &model.User{ ID: fmt.Sprintf("%d", c.UserID), }, }) } return result, nil } // 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) { page := 1 pageSize := 20 if limit != nil { pageSize = int(*limit) } if offset != nil { page = int(*offset)/pageSize + 1 } var searchFilters domain.SearchFilters if filters != nil { searchFilters.Languages = filters.Languages searchFilters.Categories = filters.Categories searchFilters.Tags = filters.Tags searchFilters.Authors = filters.Authors if filters.DateFrom != nil { t, err := time.Parse(time.RFC3339, *filters.DateFrom) if err != nil { return nil, fmt.Errorf("invalid DateFrom format: %w", err) } searchFilters.DateFrom = &t } if filters.DateTo != nil { t, err := time.Parse(time.RFC3339, *filters.DateTo) if err != nil { return nil, fmt.Errorf("invalid DateTo format: %w", err) } searchFilters.DateTo = &t } } params := domainsearch.SearchParams{ Query: query, Filters: domainsearch.SearchFilters{ Languages: searchFilters.Languages, Tags: searchFilters.Tags, Categories: searchFilters.Categories, Authors: searchFilters.Authors, DateFrom: searchFilters.DateFrom, DateTo: searchFilters.DateTo, }, Limit: pageSize, Offset: (page - 1) * pageSize, } results, err := r.App.Search.Search(ctx, params) if err != nil { return nil, err } var works []*model.Work var translations []*model.Translation var authors []*model.Author for _, item := range results.Results { switch item.Type { case "Work": if work, ok := item.Entity.(domain.Work); ok { works = append(works, &model.Work{ ID: fmt.Sprintf("%d", work.ID), Name: work.Title, Language: work.Language, }) } case "Translation": if translation, ok := item.Entity.(domain.Translation); ok { translations = append(translations, &model.Translation{ ID: fmt.Sprintf("%d", translation.ID), Name: translation.Title, Language: translation.Language, Content: &translation.Content, WorkID: fmt.Sprintf("%d", translation.TranslatableID), }) } case "Author": if author, ok := item.Entity.(domain.Author); ok { authors = append(authors, &model.Author{ ID: fmt.Sprintf("%d", author.ID), Name: author.Name, Language: author.Language, }) } } } return &model.SearchResults{ Works: works, Translations: translations, Authors: authors, Total: int32(results.TotalResults), }, nil } // 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) } workRecords, err := r.App.Analytics.GetTrendingWorks(ctx, tp, l) if err != nil { return nil, err } var result []*model.Work for _, w := range workRecords { 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 }