diff --git a/internal/adapters/graphql/integration_test.go b/internal/adapters/graphql/integration_test.go index fd8ee5d..01e20a6 100644 --- a/internal/adapters/graphql/integration_test.go +++ b/internal/adapters/graphql/integration_test.go @@ -527,7 +527,7 @@ func (s *GraphQLIntegrationSuite) TestUpdateTranslationValidation() { Language: "en", Content: "Test content", TranslatableID: work.ID, - TranslatableType: "Work", + TranslatableType: "works", }) s.Require().NoError(err) @@ -631,7 +631,7 @@ func (s *GraphQLIntegrationSuite) TestDeleteTranslation() { Language: "en", Content: "Test content", TranslatableID: work.ID, - TranslatableType: "Work", + TranslatableType: "works", }) s.Require().NoError(err) diff --git a/internal/adapters/graphql/schema.resolvers.go b/internal/adapters/graphql/schema.resolvers.go index 46d0712..268fb69 100644 --- a/internal/adapters/graphql/schema.resolvers.go +++ b/internal/adapters/graphql/schema.resolvers.go @@ -111,7 +111,7 @@ func (r *mutationResolver) CreateWork(ctx context.Context, input model.WorkInput Content: *input.Content, Language: input.Language, TranslatableID: createdWork.ID, - TranslatableType: "Work", + TranslatableType: "works", IsOriginalLanguage: true, } _, err := r.App.Translation.Commands.CreateTranslation(ctx, translationInput) @@ -1074,11 +1074,9 @@ func (r *queryResolver) Authors(ctx context.Context, limit *int32, offset *int32 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 - } + biography, err := r.App.Localization.Queries.GetAuthorBiography(ctx, a.ID, a.Language) + if err == nil && biography != "" { + bio = &biography } } @@ -1133,9 +1131,6 @@ func (r *queryResolver) Users(ctx context.Context, limit *int32, offset *int32, 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 { @@ -1234,13 +1229,16 @@ func (r *queryResolver) Tags(ctx context.Context, limit *int32, offset *int32) ( 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 + 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), diff --git a/internal/app/app.go b/internal/app/app.go index 6424610..623102d 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -34,7 +34,6 @@ type Application struct { Auth *auth.Service Work *work.Service Analytics analytics.Service - Repos *sql.Repositories } func NewApplication(repos *sql.Repositories, searchClient search.SearchClient, analyticsService analytics.Service) *Application { @@ -66,6 +65,5 @@ func NewApplication(repos *sql.Repositories, searchClient search.SearchClient, a Auth: authService, Work: workService, Analytics: analyticsService, - Repos: repos, } } \ No newline at end of file diff --git a/internal/app/localization/commands.go b/internal/app/localization/commands.go new file mode 100644 index 0000000..c23b14c --- /dev/null +++ b/internal/app/localization/commands.go @@ -0,0 +1,13 @@ +package localization + +import "tercul/internal/domain/localization" + +// LocalizationCommands contains the command handlers for the localization aggregate. +type LocalizationCommands struct { + repo localization.LocalizationRepository +} + +// NewLocalizationCommands creates a new LocalizationCommands handler. +func NewLocalizationCommands(repo localization.LocalizationRepository) *LocalizationCommands { + return &LocalizationCommands{repo: repo} +} \ No newline at end of file diff --git a/internal/app/localization/queries.go b/internal/app/localization/queries.go new file mode 100644 index 0000000..7e4988c --- /dev/null +++ b/internal/app/localization/queries.go @@ -0,0 +1,31 @@ +package localization + +import ( + "context" + "tercul/internal/domain/localization" +) + +// LocalizationQueries contains the query handlers for the localization aggregate. +type LocalizationQueries struct { + repo localization.LocalizationRepository +} + +// NewLocalizationQueries creates a new LocalizationQueries handler. +func NewLocalizationQueries(repo localization.LocalizationRepository) *LocalizationQueries { + return &LocalizationQueries{repo: repo} +} + +// GetTranslation returns a translation for a given key and language. +func (q *LocalizationQueries) GetTranslation(ctx context.Context, key string, language string) (string, error) { + return q.repo.GetTranslation(ctx, key, language) +} + +// GetTranslations returns a map of translations for a given set of keys and language. +func (q *LocalizationQueries) GetTranslations(ctx context.Context, keys []string, language string) (map[string]string, error) { + return q.repo.GetTranslations(ctx, keys, language) +} + +// GetAuthorBiography returns the biography of an author in a specific language. +func (q *LocalizationQueries) GetAuthorBiography(ctx context.Context, authorID uint, language string) (string, error) { + return q.repo.GetAuthorBiography(ctx, authorID, language) +} \ No newline at end of file diff --git a/internal/app/localization/service.go b/internal/app/localization/service.go index 04672dd..00e68d5 100644 --- a/internal/app/localization/service.go +++ b/internal/app/localization/service.go @@ -1,26 +1,17 @@ package localization -import ( - "context" - "tercul/internal/domain/localization" -) +import "tercul/internal/domain/localization" -// Service handles localization-related operations. +// Service is the application service for the localization aggregate. type Service struct { - repo localization.LocalizationRepository + Commands *LocalizationCommands + Queries *LocalizationQueries } -// NewService creates a new localization service. +// NewService creates a new localization Service. func NewService(repo localization.LocalizationRepository) *Service { - return &Service{repo: repo} -} - -// GetTranslation returns a translation for a given key and language. -func (s *Service) GetTranslation(ctx context.Context, key string, language string) (string, error) { - return s.repo.GetTranslation(ctx, key, language) -} - -// GetTranslations returns a map of translations for a given set of keys and language. -func (s *Service) GetTranslations(ctx context.Context, keys []string, language string) (map[string]string, error) { - return s.repo.GetTranslations(ctx, keys, language) -} + return &Service{ + Commands: NewLocalizationCommands(repo), + Queries: NewLocalizationQueries(repo), + } +} \ No newline at end of file diff --git a/internal/app/localization/service_test.go b/internal/app/localization/service_test.go index 1a1c3f0..e668f8b 100644 --- a/internal/app/localization/service_test.go +++ b/internal/app/localization/service_test.go @@ -25,6 +25,11 @@ func (m *mockLocalizationRepository) GetTranslations(ctx context.Context, keys [ return args.Get(0).(map[string]string), args.Error(1) } +func (m *mockLocalizationRepository) GetAuthorBiography(ctx context.Context, authorID uint, language string) (string, error) { + args := m.Called(ctx, authorID, language) + return args.String(0), args.Error(1) +} + func TestLocalizationService_GetTranslation(t *testing.T) { repo := new(mockLocalizationRepository) service := NewService(repo) @@ -36,7 +41,7 @@ func TestLocalizationService_GetTranslation(t *testing.T) { repo.On("GetTranslation", ctx, key, language).Return(expectedTranslation, nil) - translation, err := service.GetTranslation(ctx, key, language) + translation, err := service.Queries.GetTranslation(ctx, key, language) assert.NoError(t, err) assert.Equal(t, expectedTranslation, translation) @@ -57,9 +62,27 @@ func TestLocalizationService_GetTranslations(t *testing.T) { repo.On("GetTranslations", ctx, keys, language).Return(expectedTranslations, nil) - translations, err := service.GetTranslations(ctx, keys, language) + translations, err := service.Queries.GetTranslations(ctx, keys, language) assert.NoError(t, err) assert.Equal(t, expectedTranslations, translations) repo.AssertExpectations(t) } + +func TestLocalizationService_GetAuthorBiography(t *testing.T) { + repo := new(mockLocalizationRepository) + service := NewService(repo) + + ctx := context.Background() + authorID := uint(1) + language := "en" + expectedBiography := "This is a test biography." + + repo.On("GetAuthorBiography", ctx, authorID, language).Return(expectedBiography, nil) + + biography, err := service.Queries.GetAuthorBiography(ctx, authorID, language) + + assert.NoError(t, err) + assert.Equal(t, expectedBiography, biography) + repo.AssertExpectations(t) +} \ No newline at end of file diff --git a/internal/app/search/service_test.go b/internal/app/search/service_test.go index b293c72..77f94a9 100644 --- a/internal/app/search/service_test.go +++ b/internal/app/search/service_test.go @@ -27,6 +27,11 @@ func (m *mockLocalizationRepository) GetTranslations(ctx context.Context, keys [ return args.Get(0).(map[string]string), args.Error(1) } +func (m *mockLocalizationRepository) GetAuthorBiography(ctx context.Context, authorID uint, language string) (string, error) { + args := m.Called(ctx, authorID, language) + return args.String(0), args.Error(1) +} + type mockWeaviateWrapper struct { mock.Mock } diff --git a/internal/data/sql/localization_repository.go b/internal/data/sql/localization_repository.go index be33aef..3fac30e 100644 --- a/internal/data/sql/localization_repository.go +++ b/internal/data/sql/localization_repository.go @@ -2,6 +2,7 @@ package sql import ( "context" + "tercul/internal/domain" "tercul/internal/domain/localization" "gorm.io/gorm" @@ -36,3 +37,17 @@ func (r *localizationRepository) GetTranslations(ctx context.Context, keys []str } return result, nil } + +func (r *localizationRepository) GetAuthorBiography(ctx context.Context, authorID uint, language string) (string, error) { + var translation domain.Translation + err := r.db.WithContext(ctx). + Where("translatable_type = ? AND translatable_id = ? AND language = ?", "authors", authorID, language). + First(&translation).Error + if err != nil { + if err == gorm.ErrRecordNotFound { + return "", nil + } + return "", err + } + return translation.Content, nil +} diff --git a/internal/data/sql/translation_repository.go b/internal/data/sql/translation_repository.go index 28e332e..1d5da94 100644 --- a/internal/data/sql/translation_repository.go +++ b/internal/data/sql/translation_repository.go @@ -23,7 +23,7 @@ func NewTranslationRepository(db *gorm.DB) domain.TranslationRepository { // ListByWorkID finds translations by work ID func (r *translationRepository) ListByWorkID(ctx context.Context, workID uint) ([]domain.Translation, error) { var translations []domain.Translation - if err := r.db.WithContext(ctx).Where("translatable_id = ? AND translatable_type = ?", workID, "Work").Find(&translations).Error; err != nil { + if err := r.db.WithContext(ctx).Where("translatable_id = ? AND translatable_type = ?", workID, "works").Find(&translations).Error; err != nil { return nil, err } return translations, nil diff --git a/internal/domain/entities.go b/internal/domain/entities.go index ced4d4a..0a339f3 100644 --- a/internal/domain/entities.go +++ b/internal/domain/entities.go @@ -209,7 +209,7 @@ type Work struct { Type WorkType `gorm:"size:50;default:'other'"` Status WorkStatus `gorm:"size:50;default:'draft'"` PublishedAt *time.Time - Translations []Translation `gorm:"polymorphic:Translatable"` + Translations []*Translation `gorm:"polymorphic:Translatable"` Authors []*Author `gorm:"many2many:work_authors"` Tags []*Tag `gorm:"many2many:work_tags"` Categories []*Category `gorm:"many2many:work_categories"` @@ -239,7 +239,7 @@ type Author struct { Place *Place `gorm:"foreignKey:PlaceID"` AddressID *uint Address *Address `gorm:"foreignKey:AddressID"` - Translations []Translation `gorm:"polymorphic:Translatable"` + Translations []*Translation `gorm:"polymorphic:Translatable"` Copyrights []*Copyright `gorm:"many2many:author_copyrights;constraint:OnDelete:CASCADE"` Monetizations []*Monetization `gorm:"many2many:author_monetizations;constraint:OnDelete:CASCADE"` } @@ -271,7 +271,7 @@ type Book struct { Authors []*Author `gorm:"many2many:book_authors"` PublisherID *uint Publisher *Publisher `gorm:"foreignKey:PublisherID"` - Translations []Translation `gorm:"polymorphic:Translatable"` + Translations []*Translation `gorm:"polymorphic:Translatable"` Copyrights []*Copyright `gorm:"many2many:book_copyrights;constraint:OnDelete:CASCADE"` Monetizations []*Monetization `gorm:"many2many:book_monetizations;constraint:OnDelete:CASCADE"` } @@ -290,7 +290,7 @@ type Publisher struct { Books []*Book `gorm:"foreignKey:PublisherID"` CountryID *uint Country *Country `gorm:"foreignKey:CountryID"` - Translations []Translation `gorm:"polymorphic:Translatable"` + Translations []*Translation `gorm:"polymorphic:Translatable"` Copyrights []*Copyright `gorm:"many2many:publisher_copyrights;constraint:OnDelete:CASCADE"` Monetizations []*Monetization `gorm:"many2many:publisher_monetizations;constraint:OnDelete:CASCADE"` } @@ -308,7 +308,7 @@ type Source struct { URL string `gorm:"size:512"` Status SourceStatus `gorm:"size:50;default:'active'"` Works []*Work `gorm:"many2many:work_sources"` - Translations []Translation `gorm:"polymorphic:Translatable"` + Translations []*Translation `gorm:"polymorphic:Translatable"` Copyrights []*Copyright `gorm:"many2many:source_copyrights;constraint:OnDelete:CASCADE"` Monetizations []*Monetization `gorm:"many2many:source_monetizations;constraint:OnDelete:CASCADE"` } diff --git a/internal/domain/localization/repo.go b/internal/domain/localization/repo.go index fc51eee..636b4dd 100644 --- a/internal/domain/localization/repo.go +++ b/internal/domain/localization/repo.go @@ -8,4 +8,5 @@ import ( type LocalizationRepository interface { GetTranslation(ctx context.Context, key string, language string) (string, error) GetTranslations(ctx context.Context, keys []string, language string) (map[string]string, error) + GetAuthorBiography(ctx context.Context, authorID uint, language string) (string, error) } \ No newline at end of file diff --git a/internal/testutil/integration_test_utils.go b/internal/testutil/integration_test_utils.go index 2721d89..4be68b2 100644 --- a/internal/testutil/integration_test_utils.go +++ b/internal/testutil/integration_test_utils.go @@ -199,7 +199,7 @@ func (s *IntegrationTestSuite) CreateTestWork(title, language string, content st Content: content, Language: language, TranslatableID: createdWork.ID, - TranslatableType: "Work", + TranslatableType: "works", } _, err = s.App.Translation.Commands.CreateTranslation(context.Background(), translationInput) s.Require().NoError(err) @@ -214,7 +214,7 @@ func (s *IntegrationTestSuite) CreateTestTranslation(workID uint, language, cont Content: content, Language: language, TranslatableID: workID, - TranslatableType: "Work", + TranslatableType: "works", } createdTranslation, err := s.App.Translation.Commands.CreateTranslation(context.Background(), translationInput) s.Require().NoError(err) diff --git a/internal/testutil/simple_test_utils.go b/internal/testutil/simple_test_utils.go index c4ab885..84e469b 100644 --- a/internal/testutil/simple_test_utils.go +++ b/internal/testutil/simple_test_utils.go @@ -55,6 +55,11 @@ func (m *MockLocalizationRepository) GetTranslations(ctx context.Context, keys [ return results, nil } +// GetAuthorBiography is a mock implementation of the GetAuthorBiography method. +func (m *MockLocalizationRepository) GetAuthorBiography(ctx context.Context, authorID uint, language string) (string, error) { + return "This is a mock biography.", nil +} + // GetResolver returns a minimal GraphQL resolver for testing func (s *SimpleTestSuite) GetResolver() *graph.Resolver { var mockLocalizationRepo domain_localization.LocalizationRepository = &MockLocalizationRepository{}