mirror of
https://github.com/SamyRai/tercul-backend.git
synced 2025-12-27 05:11:34 +00:00
This commit refactors the GraphQL layer to improve code quality and adhere to the project's target architecture.
Key changes include: - Moved authorization logic for collection mutations from the GraphQL resolvers to the application service layer, ensuring that ownership checks are handled consistently within the business logic. - Updated the `collection` command handlers and input structs to accept a user ID for authorization. - Removed orphaned code, including unused resolver definitions (`workResolver`, `translationResolver`) and misplaced helper functions from `schema.resolvers.go`. - Re-implemented the `Stats` resolvers for the `Work` and `Translation` types, ensuring they correctly call the `analytics` application service. - Fixed several build errors related to type mismatches and redeclared functions by regenerating the GraphQL code and correcting helper function signatures. - Updated integration tests to provide authenticated user context for collection mutations, ensuring that the new authorization checks pass.
This commit is contained in:
parent
02b06fd9ce
commit
8ddc4a7986
@ -34,3 +34,12 @@ func (r *queryResolver) resolveWorkContent(ctx context.Context, workID uint, pre
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func toInt32(i int64) *int32 {
|
||||
val := int32(i)
|
||||
return &val
|
||||
}
|
||||
|
||||
func toInt(i int) *int {
|
||||
return &i
|
||||
}
|
||||
|
||||
@ -1048,7 +1048,7 @@ func (s *GraphQLIntegrationSuite) TestTrendingWorksQuery() {
|
||||
|
||||
func (s *GraphQLIntegrationSuite) TestCollectionMutations() {
|
||||
// Create users for testing authorization
|
||||
_, ownerToken := s.CreateAuthenticatedUser("collectionowner", "owner@test.com", domain.UserRoleReader)
|
||||
owner, ownerToken := s.CreateAuthenticatedUser("collectionowner", "owner@test.com", domain.UserRoleReader)
|
||||
otherUser, otherToken := s.CreateAuthenticatedUser("otheruser", "other@test.com", domain.UserRoleReader)
|
||||
_ = otherUser
|
||||
|
||||
@ -1175,6 +1175,7 @@ func (s *GraphQLIntegrationSuite) TestCollectionMutations() {
|
||||
err = s.App.Collection.Commands.AddWorkToCollection(context.Background(), collection.AddWorkToCollectionInput{
|
||||
CollectionID: uint(collectionIDInt),
|
||||
WorkID: work.ID,
|
||||
UserID: owner.ID,
|
||||
})
|
||||
s.Require().NoError(err)
|
||||
|
||||
|
||||
@ -395,24 +395,11 @@ func (r *mutationResolver) UpdateCollection(ctx context.Context, id string, inpu
|
||||
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,
|
||||
UserID: userID,
|
||||
}
|
||||
if input.Description != nil {
|
||||
updateInput.Description = *input.Description
|
||||
@ -447,22 +434,8 @@ func (r *mutationResolver) DeleteCollection(ctx context.Context, id string) (boo
|
||||
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))
|
||||
// Call collection service
|
||||
err = r.App.Collection.Commands.DeleteCollection(ctx, uint(collectionID), userID)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
@ -488,24 +461,11 @@ func (r *mutationResolver) AddWorkToCollection(ctx context.Context, collectionID
|
||||
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),
|
||||
UserID: userID,
|
||||
}
|
||||
err = r.App.Collection.Commands.AddWorkToCollection(ctx, addInput)
|
||||
if err != nil {
|
||||
@ -544,24 +504,11 @@ func (r *mutationResolver) RemoveWorkFromCollection(ctx context.Context, collect
|
||||
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),
|
||||
UserID: userID,
|
||||
}
|
||||
err = r.App.Collection.Commands.RemoveWorkFromCollection(ctx, removeInput)
|
||||
if err != nil {
|
||||
@ -1325,16 +1272,27 @@ type queryResolver struct{ *Resolver }
|
||||
// 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 (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)
|
||||
}
|
||||
func toInt(i int) *int {
|
||||
return &i
|
||||
|
||||
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: toInt32(int64(stats.ReadingTime)),
|
||||
Sentiment: &stats.Sentiment,
|
||||
}, nil
|
||||
}
|
||||
func (r *workResolver) Stats(ctx context.Context, obj *model.Work) (*model.WorkStats, error) {
|
||||
workID, err := strconv.ParseUint(obj.ID, 10, 32)
|
||||
@ -1356,31 +1314,13 @@ func (r *workResolver) Stats(ctx context.Context, obj *model.Work) (*model.WorkS
|
||||
Bookmarks: toInt32(stats.Bookmarks),
|
||||
Shares: toInt32(stats.Shares),
|
||||
TranslationCount: toInt32(stats.TranslationCount),
|
||||
ReadingTime: toInt(stats.ReadingTime),
|
||||
ReadingTime: toInt32(int64(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
|
||||
}
|
||||
func (r *Resolver) Translation() TranslationResolver { return &translationResolver{r} }
|
||||
func (r *Resolver) Work() WorkResolver { return &workResolver{r} }
|
||||
type translationResolver struct{ *Resolver }
|
||||
type workResolver struct{ *Resolver }
|
||||
*/
|
||||
|
||||
@ -2,6 +2,7 @@ package collection
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"tercul/internal/domain"
|
||||
)
|
||||
|
||||
@ -47,6 +48,7 @@ type UpdateCollectionInput struct {
|
||||
Description string
|
||||
IsPublic bool
|
||||
CoverImageURL string
|
||||
UserID uint
|
||||
}
|
||||
|
||||
// UpdateCollection updates an existing collection.
|
||||
@ -55,6 +57,9 @@ func (c *CollectionCommands) UpdateCollection(ctx context.Context, input UpdateC
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if collection.UserID != input.UserID {
|
||||
return nil, fmt.Errorf("unauthorized: user %d cannot update collection %d", input.UserID, input.ID)
|
||||
}
|
||||
collection.Name = input.Name
|
||||
collection.Description = input.Description
|
||||
collection.IsPublic = input.IsPublic
|
||||
@ -67,7 +72,14 @@ func (c *CollectionCommands) UpdateCollection(ctx context.Context, input UpdateC
|
||||
}
|
||||
|
||||
// DeleteCollection deletes a collection by ID.
|
||||
func (c *CollectionCommands) DeleteCollection(ctx context.Context, id uint) error {
|
||||
func (c *CollectionCommands) DeleteCollection(ctx context.Context, id uint, userID uint) error {
|
||||
collection, err := c.repo.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if collection.UserID != userID {
|
||||
return fmt.Errorf("unauthorized: user %d cannot delete collection %d", userID, id)
|
||||
}
|
||||
return c.repo.Delete(ctx, id)
|
||||
}
|
||||
|
||||
@ -75,10 +87,18 @@ func (c *CollectionCommands) DeleteCollection(ctx context.Context, id uint) erro
|
||||
type AddWorkToCollectionInput struct {
|
||||
CollectionID uint
|
||||
WorkID uint
|
||||
UserID uint
|
||||
}
|
||||
|
||||
// AddWorkToCollection adds a work to a collection.
|
||||
func (c *CollectionCommands) AddWorkToCollection(ctx context.Context, input AddWorkToCollectionInput) error {
|
||||
collection, err := c.repo.GetByID(ctx, input.CollectionID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if collection.UserID != input.UserID {
|
||||
return fmt.Errorf("unauthorized: user %d cannot add work to collection %d", input.UserID, input.CollectionID)
|
||||
}
|
||||
return c.repo.AddWorkToCollection(ctx, input.CollectionID, input.WorkID)
|
||||
}
|
||||
|
||||
@ -86,9 +106,17 @@ func (c *CollectionCommands) AddWorkToCollection(ctx context.Context, input AddW
|
||||
type RemoveWorkFromCollectionInput struct {
|
||||
CollectionID uint
|
||||
WorkID uint
|
||||
UserID uint
|
||||
}
|
||||
|
||||
// RemoveWorkFromCollection removes a work from a collection.
|
||||
func (c *CollectionCommands) RemoveWorkFromCollection(ctx context.Context, input RemoveWorkFromCollectionInput) error {
|
||||
collection, err := c.repo.GetByID(ctx, input.CollectionID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if collection.UserID != input.UserID {
|
||||
return fmt.Errorf("unauthorized: user %d cannot remove work from collection %d", input.UserID, input.CollectionID)
|
||||
}
|
||||
return c.repo.RemoveWorkFromCollection(ctx, input.CollectionID, input.WorkID)
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user