From 06e6e2be85d0749e965e5015a88b567b2754ca15 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 3 Oct 2025 16:15:09 +0000 Subject: [PATCH] refactor(domain): Isolate Work aggregate This commit isolates the `Work` aggregate into its own package at `internal/domain/work`, following the first step of the refactoring plan in `refactor.md`. - The `Work` struct, related types, and the `WorkRepository` interface have been moved to the new package. - A circular dependency between `domain` and `work` was resolved by moving the `AnalyticsRepository` to the `app` layer. - All references to the moved types have been updated across the entire codebase to fix compilation errors. - Test files, including mocks and integration tests, have been updated to reflect the new structure. --- internal/adapters/graphql/integration_test.go | 5 +- internal/adapters/graphql/schema.resolvers.go | 28 ++-- internal/app/analytics/interfaces.go | 22 +++ internal/app/analytics/service.go | 23 ++-- internal/app/analytics/service_test.go | 7 +- internal/app/copyright/main_test.go | 8 +- internal/app/copyright/queries.go | 9 +- internal/app/copyright/queries_test.go | 7 +- internal/app/monetization/main_test.go | 8 +- internal/app/monetization/queries.go | 9 +- internal/app/monetization/queries_test.go | 7 +- internal/app/search/service.go | 6 +- internal/app/search/service_test.go | 5 +- internal/app/work/commands.go | 10 +- internal/app/work/commands_test.go | 23 ++-- internal/app/work/main_test.go | 47 +++---- internal/app/work/queries.go | 19 +-- internal/app/work/queries_test.go | 31 +++-- internal/app/work/service.go | 4 +- internal/data/sql/analytics_repository.go | 36 ++--- internal/data/sql/monetization_repository.go | 9 +- .../data/sql/monetization_repository_test.go | 5 +- internal/data/sql/repositories.go | 6 +- internal/data/sql/work_repository.go | 49 +++---- internal/data/sql/work_repository_test.go | 49 +++---- internal/domain/analytics.go | 18 --- internal/domain/entities.go | 130 +----------------- internal/domain/interfaces.go | 10 -- internal/domain/search/client.go | 4 +- internal/domain/work/entity.go | 116 ++++++++++++++++ internal/domain/work/repo.go | 17 +++ .../jobs/linguistics/analysis_repository.go | 27 ++-- internal/jobs/linguistics/sync_job.go | 3 +- internal/platform/search/weaviate_client.go | 4 +- internal/platform/search/weaviate_wrapper.go | 6 +- internal/testutil/integration_test_utils.go | 19 +-- internal/testutil/mock_analytics_service.go | 11 +- internal/testutil/mock_weaviate_wrapper.go | 6 +- internal/testutil/mock_work_repository.go | 41 +++--- internal/testutil/simple_test_utils.go | 7 +- 40 files changed, 440 insertions(+), 411 deletions(-) create mode 100644 internal/app/analytics/interfaces.go delete mode 100644 internal/domain/analytics.go create mode 100644 internal/domain/work/entity.go create mode 100644 internal/domain/work/repo.go diff --git a/internal/adapters/graphql/integration_test.go b/internal/adapters/graphql/integration_test.go index 62f9e73..cad482a 100644 --- a/internal/adapters/graphql/integration_test.go +++ b/internal/adapters/graphql/integration_test.go @@ -17,6 +17,7 @@ import ( "tercul/internal/app/like" "tercul/internal/app/translation" "tercul/internal/domain" + "tercul/internal/domain/work" platform_auth "tercul/internal/platform/auth" "tercul/internal/testutil" @@ -966,8 +967,8 @@ func (s *GraphQLIntegrationSuite) TestTrendingWorksQuery() { // Arrange work1 := s.CreateTestWork("Work 1", "en", "content") work2 := s.CreateTestWork("Work 2", "en", "content") - s.DB.Create(&domain.WorkStats{WorkID: work1.ID, Views: 100, Likes: 10, Comments: 1}) - s.DB.Create(&domain.WorkStats{WorkID: work2.ID, Views: 10, Likes: 100, Comments: 10}) + s.DB.Create(&work.WorkStats{WorkID: work1.ID, Views: 100, Likes: 10, Comments: 1}) + s.DB.Create(&work.WorkStats{WorkID: work2.ID, Views: 10, Likes: 100, Comments: 10}) s.Require().NoError(s.App.Analytics.UpdateTrending(context.Background())) // Act diff --git a/internal/adapters/graphql/schema.resolvers.go b/internal/adapters/graphql/schema.resolvers.go index 95f4630..f0810ce 100644 --- a/internal/adapters/graphql/schema.resolvers.go +++ b/internal/adapters/graphql/schema.resolvers.go @@ -17,6 +17,7 @@ import ( "tercul/internal/app/like" "tercul/internal/app/translation" "tercul/internal/domain" + "tercul/internal/domain/work" platform_auth "tercul/internal/platform/auth" ) @@ -91,7 +92,7 @@ func (r *mutationResolver) CreateWork(ctx context.Context, input model.WorkInput return nil, fmt.Errorf("%w: %v", ErrValidation, err) } // Create domain model - work := &domain.Work{ + workModel := &work.Work{ Title: input.Name, TranslatableModel: domain.TranslatableModel{Language: input.Language}, // Description: *input.Description, @@ -99,11 +100,10 @@ func (r *mutationResolver) CreateWork(ctx context.Context, input model.WorkInput } // Call work service - createdWork, err := r.App.Work.Commands.CreateWork(ctx, work) + createdWork, err := r.App.Work.Commands.CreateWork(ctx, workModel) if err != nil { return nil, err } - work = createdWork if input.Content != nil && *input.Content != "" { translationInput := translation.CreateTranslationInput{ @@ -140,7 +140,7 @@ func (r *mutationResolver) UpdateWork(ctx context.Context, id string, input mode } // Create domain model - work := &domain.Work{ + workModel := &work.Work{ TranslatableModel: domain.TranslatableModel{ BaseModel: domain.BaseModel{ID: uint(workID)}, Language: input.Language, @@ -149,7 +149,7 @@ func (r *mutationResolver) UpdateWork(ctx context.Context, id string, input mode } // Call work service - err = r.App.Work.Commands.UpdateWork(ctx, work) + err = r.App.Work.Commands.UpdateWork(ctx, workModel) if err != nil { return nil, err } @@ -157,8 +157,8 @@ func (r *mutationResolver) UpdateWork(ctx context.Context, id string, input mode // Convert to GraphQL model return &model.Work{ ID: id, - Name: work.Title, - Language: work.Language, + Name: workModel.Title, + Language: workModel.Language, Content: input.Content, }, nil } @@ -929,20 +929,20 @@ func (r *queryResolver) Work(ctx context.Context, id string) (*model.Work, error return nil, fmt.Errorf("invalid work ID: %v", err) } - work, err := r.App.Work.Queries.GetWorkByID(ctx, uint(workID)) + workRecord, err := r.App.Work.Queries.GetWorkByID(ctx, uint(workID)) if err != nil { return nil, err } - if work == nil { + if workRecord == nil { return nil, nil } - content := r.resolveWorkContent(ctx, work.ID, work.Language) + content := r.resolveWorkContent(ctx, workRecord.ID, workRecord.Language) return &model.Work{ ID: id, - Name: work.Title, - Language: work.Language, + Name: workRecord.Title, + Language: workRecord.Language, Content: content, }, nil } @@ -1239,13 +1239,13 @@ func (r *queryResolver) TrendingWorks(ctx context.Context, timePeriod *string, l l = int(*limit) } - works, err := r.App.Analytics.GetTrendingWorks(ctx, tp, l) + workRecords, err := r.App.Analytics.GetTrendingWorks(ctx, tp, l) if err != nil { return nil, err } var result []*model.Work - for _, w := range works { + for _, w := range workRecords { result = append(result, &model.Work{ ID: fmt.Sprintf("%d", w.ID), Name: w.Title, diff --git a/internal/app/analytics/interfaces.go b/internal/app/analytics/interfaces.go new file mode 100644 index 0000000..aec9748 --- /dev/null +++ b/internal/app/analytics/interfaces.go @@ -0,0 +1,22 @@ +package analytics + +import ( + "context" + "tercul/internal/domain" + "tercul/internal/domain/work" + "time" +) + +// AnalyticsRepository defines the data access layer for analytics. +type Repository interface { + IncrementWorkCounter(ctx context.Context, workID uint, field string, value int) error + IncrementTranslationCounter(ctx context.Context, translationID uint, field string, value int) error + UpdateWorkStats(ctx context.Context, workID uint, stats work.WorkStats) error + UpdateTranslationStats(ctx context.Context, translationID uint, stats domain.TranslationStats) error + GetOrCreateWorkStats(ctx context.Context, workID uint) (*work.WorkStats, error) + GetOrCreateTranslationStats(ctx context.Context, translationID uint) (*domain.TranslationStats, error) + GetOrCreateUserEngagement(ctx context.Context, userID uint, date time.Time) (*domain.UserEngagement, error) + UpdateUserEngagement(ctx context.Context, userEngagement *domain.UserEngagement) error + UpdateTrendingWorks(ctx context.Context, timePeriod string, trending []*domain.Trending) error + GetTrendingWorks(ctx context.Context, timePeriod string, limit int) ([]*work.Work, error) +} \ No newline at end of file diff --git a/internal/app/analytics/service.go b/internal/app/analytics/service.go index 87e1107..7565f4c 100644 --- a/internal/app/analytics/service.go +++ b/internal/app/analytics/service.go @@ -7,6 +7,7 @@ import ( "sort" "strings" "tercul/internal/domain" + "tercul/internal/domain/work" "tercul/internal/jobs/linguistics" "tercul/internal/platform/log" "time" @@ -23,7 +24,7 @@ type Service interface { IncrementTranslationLikes(ctx context.Context, translationID uint) error IncrementTranslationComments(ctx context.Context, translationID uint) error IncrementTranslationShares(ctx context.Context, translationID uint) error - GetOrCreateWorkStats(ctx context.Context, workID uint) (*domain.WorkStats, error) + GetOrCreateWorkStats(ctx context.Context, workID uint) (*work.WorkStats, error) GetOrCreateTranslationStats(ctx context.Context, translationID uint) (*domain.TranslationStats, error) UpdateWorkReadingTime(ctx context.Context, workID uint) error @@ -34,18 +35,18 @@ type Service interface { UpdateUserEngagement(ctx context.Context, userID uint, eventType string) error UpdateTrending(ctx context.Context) error - GetTrendingWorks(ctx context.Context, timePeriod string, limit int) ([]*domain.Work, error) + GetTrendingWorks(ctx context.Context, timePeriod string, limit int) ([]*work.Work, error) } type service struct { - repo domain.AnalyticsRepository + repo Repository analysisRepo linguistics.AnalysisRepository translationRepo domain.TranslationRepository - workRepo domain.WorkRepository + workRepo work.WorkRepository sentimentProvider linguistics.SentimentProvider } -func NewService(repo domain.AnalyticsRepository, analysisRepo linguistics.AnalysisRepository, translationRepo domain.TranslationRepository, workRepo domain.WorkRepository, sentimentProvider linguistics.SentimentProvider) Service { +func NewService(repo Repository, analysisRepo linguistics.AnalysisRepository, translationRepo domain.TranslationRepository, workRepo work.WorkRepository, sentimentProvider linguistics.SentimentProvider) Service { return &service{ repo: repo, analysisRepo: analysisRepo, @@ -95,7 +96,7 @@ func (s *service) IncrementTranslationShares(ctx context.Context, translationID return s.repo.IncrementTranslationCounter(ctx, translationID, "shares", 1) } -func (s *service) GetOrCreateWorkStats(ctx context.Context, workID uint) (*domain.WorkStats, error) { +func (s *service) GetOrCreateWorkStats(ctx context.Context, workID uint) (*work.WorkStats, error) { return s.repo.GetOrCreateWorkStats(ctx, workID) } @@ -251,7 +252,7 @@ func (s *service) UpdateUserEngagement(ctx context.Context, userID uint, eventTy return s.repo.UpdateUserEngagement(ctx, engagement) } -func (s *service) GetTrendingWorks(ctx context.Context, timePeriod string, limit int) ([]*domain.Work, error) { +func (s *service) GetTrendingWorks(ctx context.Context, timePeriod string, limit int) ([]*work.Work, error) { return s.repo.GetTrendingWorks(ctx, timePeriod, limit) } @@ -264,10 +265,10 @@ func (s *service) UpdateTrending(ctx context.Context) error { } trendingWorks := make([]*domain.Trending, 0, len(works)) - for _, work := range works { - stats, err := s.repo.GetOrCreateWorkStats(ctx, work.ID) + for _, aWork := range works { + stats, err := s.repo.GetOrCreateWorkStats(ctx, aWork.ID) if err != nil { - log.LogWarn("failed to get work stats", log.F("workID", work.ID), log.F("error", err)) + log.LogWarn("failed to get work stats", log.F("workID", aWork.ID), log.F("error", err)) continue } @@ -275,7 +276,7 @@ func (s *service) UpdateTrending(ctx context.Context) error { trendingWorks = append(trendingWorks, &domain.Trending{ EntityType: "Work", - EntityID: work.ID, + EntityID: aWork.ID, Score: score, TimePeriod: "daily", // Hardcoded for now Date: time.Now().UTC(), diff --git a/internal/app/analytics/service_test.go b/internal/app/analytics/service_test.go index 08f0963..e9bf1f2 100644 --- a/internal/app/analytics/service_test.go +++ b/internal/app/analytics/service_test.go @@ -7,6 +7,7 @@ import ( "tercul/internal/app/analytics" "tercul/internal/data/sql" "tercul/internal/domain" + "tercul/internal/domain/work" "tercul/internal/jobs/linguistics" "tercul/internal/testutil" @@ -239,8 +240,8 @@ func (s *AnalyticsServiceTestSuite) TestUpdateTrending() { // Arrange work1 := s.CreateTestWork("Work 1", "en", "content") work2 := s.CreateTestWork("Work 2", "en", "content") - s.DB.Create(&domain.WorkStats{WorkID: work1.ID, Views: 100, Likes: 10, Comments: 1}) - s.DB.Create(&domain.WorkStats{WorkID: work2.ID, Views: 10, Likes: 100, Comments: 10}) + s.DB.Create(&work.WorkStats{WorkID: work1.ID, Views: 100, Likes: 10, Comments: 1}) + s.DB.Create(&work.WorkStats{WorkID: work2.ID, Views: 10, Likes: 100, Comments: 10}) // Act err := s.service.UpdateTrending(context.Background()) @@ -257,4 +258,4 @@ func (s *AnalyticsServiceTestSuite) TestUpdateTrending() { func TestAnalyticsService(t *testing.T) { suite.Run(t, new(AnalyticsServiceTestSuite)) -} +} \ No newline at end of file diff --git a/internal/app/copyright/main_test.go b/internal/app/copyright/main_test.go index 79cef2a..20e98dc 100644 --- a/internal/app/copyright/main_test.go +++ b/internal/app/copyright/main_test.go @@ -4,6 +4,7 @@ import ( "context" "gorm.io/gorm" "tercul/internal/domain" + "tercul/internal/domain/work" ) type mockCopyrightRepository struct { @@ -172,10 +173,11 @@ func (m *mockCopyrightRepository) WithTx(ctx context.Context, fn func(tx *gorm.D } type mockWorkRepository struct { - domain.WorkRepository - getByIDWithOptionsFunc func(ctx context.Context, id uint, options *domain.QueryOptions) (*domain.Work, error) + work.WorkRepository + getByIDWithOptionsFunc func(ctx context.Context, id uint, options *domain.QueryOptions) (*work.Work, error) } -func (m *mockWorkRepository) GetByIDWithOptions(ctx context.Context, id uint, options *domain.QueryOptions) (*domain.Work, error) { + +func (m *mockWorkRepository) GetByIDWithOptions(ctx context.Context, id uint, options *domain.QueryOptions) (*work.Work, error) { if m.getByIDWithOptionsFunc != nil { return m.getByIDWithOptionsFunc(ctx, id, options) } diff --git a/internal/app/copyright/queries.go b/internal/app/copyright/queries.go index ac29cdb..1684411 100644 --- a/internal/app/copyright/queries.go +++ b/internal/app/copyright/queries.go @@ -4,13 +4,14 @@ import ( "context" "errors" "tercul/internal/domain" + "tercul/internal/domain/work" "tercul/internal/platform/log" ) // CopyrightQueries contains the query handlers for copyright. type CopyrightQueries struct { repo domain.CopyrightRepository - workRepo domain.WorkRepository + workRepo work.WorkRepository authorRepo domain.AuthorRepository bookRepo domain.BookRepository publisherRepo domain.PublisherRepository @@ -18,7 +19,7 @@ type CopyrightQueries struct { } // NewCopyrightQueries creates a new CopyrightQueries handler. -func NewCopyrightQueries(repo domain.CopyrightRepository, workRepo domain.WorkRepository, authorRepo domain.AuthorRepository, bookRepo domain.BookRepository, publisherRepo domain.PublisherRepository, sourceRepo domain.SourceRepository) *CopyrightQueries { +func NewCopyrightQueries(repo domain.CopyrightRepository, workRepo work.WorkRepository, authorRepo domain.AuthorRepository, bookRepo domain.BookRepository, publisherRepo domain.PublisherRepository, sourceRepo domain.SourceRepository) *CopyrightQueries { return &CopyrightQueries{repo: repo, workRepo: workRepo, authorRepo: authorRepo, bookRepo: bookRepo, publisherRepo: publisherRepo, sourceRepo: sourceRepo} } @@ -42,11 +43,11 @@ func (q *CopyrightQueries) ListCopyrights(ctx context.Context) ([]domain.Copyrig // GetCopyrightsForWork gets all copyrights for a specific work. func (q *CopyrightQueries) GetCopyrightsForWork(ctx context.Context, workID uint) ([]*domain.Copyright, error) { log.LogDebug("Getting copyrights for work", log.F("work_id", workID)) - work, err := q.workRepo.GetByIDWithOptions(ctx, workID, &domain.QueryOptions{Preloads: []string{"Copyrights"}}) + workRecord, err := q.workRepo.GetByIDWithOptions(ctx, workID, &domain.QueryOptions{Preloads: []string{"Copyrights"}}) if err != nil { return nil, err } - return work.Copyrights, nil + return workRecord.Copyrights, nil } // GetCopyrightsForAuthor gets all copyrights for a specific author. diff --git a/internal/app/copyright/queries_test.go b/internal/app/copyright/queries_test.go index bf52d31..ad1cd7e 100644 --- a/internal/app/copyright/queries_test.go +++ b/internal/app/copyright/queries_test.go @@ -6,6 +6,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" "tercul/internal/domain" + "tercul/internal/domain/work" "testing" ) @@ -99,8 +100,8 @@ func (s *CopyrightQueriesSuite) TestGetCopyrightsForSource_RepoError() { func (s *CopyrightQueriesSuite) TestGetCopyrightsForWork_Success() { copyrights := []*domain.Copyright{{Name: "Test Copyright"}} - s.workRepo.getByIDWithOptionsFunc = func(ctx context.Context, id uint, options *domain.QueryOptions) (*domain.Work, error) { - return &domain.Work{Copyrights: copyrights}, nil + s.workRepo.getByIDWithOptionsFunc = func(ctx context.Context, id uint, options *domain.QueryOptions) (*work.Work, error) { + return &work.Work{Copyrights: copyrights}, nil } c, err := s.queries.GetCopyrightsForWork(context.Background(), 1) assert.NoError(s.T(), err) @@ -108,7 +109,7 @@ func (s *CopyrightQueriesSuite) TestGetCopyrightsForWork_Success() { } func (s *CopyrightQueriesSuite) TestGetCopyrightsForWork_RepoError() { - s.workRepo.getByIDWithOptionsFunc = func(ctx context.Context, id uint, options *domain.QueryOptions) (*domain.Work, error) { + s.workRepo.getByIDWithOptionsFunc = func(ctx context.Context, id uint, options *domain.QueryOptions) (*work.Work, error) { return nil, errors.New("db error") } c, err := s.queries.GetCopyrightsForWork(context.Background(), 1) diff --git a/internal/app/monetization/main_test.go b/internal/app/monetization/main_test.go index 2ebb668..a38dab8 100644 --- a/internal/app/monetization/main_test.go +++ b/internal/app/monetization/main_test.go @@ -3,6 +3,7 @@ package monetization import ( "context" "tercul/internal/domain" + "tercul/internal/domain/work" ) type mockMonetizationRepository struct { @@ -97,10 +98,11 @@ func (m *mockMonetizationRepository) RemoveMonetizationFromSource(ctx context.Co } type mockWorkRepository struct { - domain.WorkRepository - getByIDWithOptionsFunc func(ctx context.Context, id uint, options *domain.QueryOptions) (*domain.Work, error) + work.WorkRepository + getByIDWithOptionsFunc func(ctx context.Context, id uint, options *domain.QueryOptions) (*work.Work, error) } -func (m *mockWorkRepository) GetByIDWithOptions(ctx context.Context, id uint, options *domain.QueryOptions) (*domain.Work, error) { + +func (m *mockWorkRepository) GetByIDWithOptions(ctx context.Context, id uint, options *domain.QueryOptions) (*work.Work, error) { if m.getByIDWithOptionsFunc != nil { return m.getByIDWithOptionsFunc(ctx, id, options) } diff --git a/internal/app/monetization/queries.go b/internal/app/monetization/queries.go index 4e1b410..f017777 100644 --- a/internal/app/monetization/queries.go +++ b/internal/app/monetization/queries.go @@ -4,13 +4,14 @@ import ( "context" "errors" "tercul/internal/domain" + "tercul/internal/domain/work" "tercul/internal/platform/log" ) // MonetizationQueries contains the query handlers for monetization. type MonetizationQueries struct { repo domain.MonetizationRepository - workRepo domain.WorkRepository + workRepo work.WorkRepository authorRepo domain.AuthorRepository bookRepo domain.BookRepository publisherRepo domain.PublisherRepository @@ -18,7 +19,7 @@ type MonetizationQueries struct { } // NewMonetizationQueries creates a new MonetizationQueries handler. -func NewMonetizationQueries(repo domain.MonetizationRepository, workRepo domain.WorkRepository, authorRepo domain.AuthorRepository, bookRepo domain.BookRepository, publisherRepo domain.PublisherRepository, sourceRepo domain.SourceRepository) *MonetizationQueries { +func NewMonetizationQueries(repo domain.MonetizationRepository, workRepo work.WorkRepository, authorRepo domain.AuthorRepository, bookRepo domain.BookRepository, publisherRepo domain.PublisherRepository, sourceRepo domain.SourceRepository) *MonetizationQueries { return &MonetizationQueries{repo: repo, workRepo: workRepo, authorRepo: authorRepo, bookRepo: bookRepo, publisherRepo: publisherRepo, sourceRepo: sourceRepo} } @@ -39,11 +40,11 @@ func (q *MonetizationQueries) ListMonetizations(ctx context.Context) ([]domain.M func (q *MonetizationQueries) GetMonetizationsForWork(ctx context.Context, workID uint) ([]*domain.Monetization, error) { log.LogDebug("Getting monetizations for work", log.F("work_id", workID)) - work, err := q.workRepo.GetByIDWithOptions(ctx, workID, &domain.QueryOptions{Preloads: []string{"Monetizations"}}) + workRecord, err := q.workRepo.GetByIDWithOptions(ctx, workID, &domain.QueryOptions{Preloads: []string{"Monetizations"}}) if err != nil { return nil, err } - return work.Monetizations, nil + return workRecord.Monetizations, nil } func (q *MonetizationQueries) GetMonetizationsForAuthor(ctx context.Context, authorID uint) ([]*domain.Monetization, error) { diff --git a/internal/app/monetization/queries_test.go b/internal/app/monetization/queries_test.go index 7ba483e..fae8ead 100644 --- a/internal/app/monetization/queries_test.go +++ b/internal/app/monetization/queries_test.go @@ -6,6 +6,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" "tercul/internal/domain" + "tercul/internal/domain/work" "testing" ) @@ -81,8 +82,8 @@ func (s *MonetizationQueriesSuite) TestListMonetizations_Success() { func (s *MonetizationQueriesSuite) TestGetMonetizationsForWork_Success() { monetizations := []*domain.Monetization{{Amount: 10.0}} - s.workRepo.getByIDWithOptionsFunc = func(ctx context.Context, id uint, options *domain.QueryOptions) (*domain.Work, error) { - return &domain.Work{Monetizations: monetizations}, nil + s.workRepo.getByIDWithOptionsFunc = func(ctx context.Context, id uint, options *domain.QueryOptions) (*work.Work, error) { + return &work.Work{Monetizations: monetizations}, nil } m, err := s.queries.GetMonetizationsForWork(context.Background(), 1) assert.NoError(s.T(), err) @@ -90,7 +91,7 @@ func (s *MonetizationQueriesSuite) TestGetMonetizationsForWork_Success() { } func (s *MonetizationQueriesSuite) TestGetMonetizationsForWork_RepoError() { - s.workRepo.getByIDWithOptionsFunc = func(ctx context.Context, id uint, options *domain.QueryOptions) (*domain.Work, error) { + s.workRepo.getByIDWithOptionsFunc = func(ctx context.Context, id uint, options *domain.QueryOptions) (*work.Work, error) { return nil, errors.New("db error") } m, err := s.queries.GetMonetizationsForWork(context.Background(), 1) diff --git a/internal/app/search/service.go b/internal/app/search/service.go index db86847..403e9b5 100644 --- a/internal/app/search/service.go +++ b/internal/app/search/service.go @@ -4,14 +4,14 @@ import ( "context" "fmt" "tercul/internal/app/localization" - "tercul/internal/domain" + "tercul/internal/domain/work" "tercul/internal/platform/log" "tercul/internal/platform/search" ) // IndexService pushes localized snapshots into Weaviate for search type IndexService interface { - IndexWork(ctx context.Context, work domain.Work) error + IndexWork(ctx context.Context, work work.Work) error } type indexService struct { @@ -23,7 +23,7 @@ func NewIndexService(localization *localization.Service, weaviate search.Weaviat return &indexService{localization: localization, weaviate: weaviate} } -func (s *indexService) IndexWork(ctx context.Context, work domain.Work) error { +func (s *indexService) IndexWork(ctx context.Context, work work.Work) error { log.LogDebug("Indexing work", log.F("work_id", work.ID)) // TODO: Get content from translation service content := "" diff --git a/internal/app/search/service_test.go b/internal/app/search/service_test.go index 77f94a9..ca5aa91 100644 --- a/internal/app/search/service_test.go +++ b/internal/app/search/service_test.go @@ -8,6 +8,7 @@ import ( "github.com/stretchr/testify/mock" "tercul/internal/app/localization" "tercul/internal/domain" + "tercul/internal/domain/work" ) type mockLocalizationRepository struct { @@ -36,7 +37,7 @@ type mockWeaviateWrapper struct { mock.Mock } -func (m *mockWeaviateWrapper) IndexWork(ctx context.Context, work *domain.Work, content string) error { +func (m *mockWeaviateWrapper) IndexWork(ctx context.Context, work *work.Work, content string) error { args := m.Called(ctx, work, content) return args.Error(0) } @@ -48,7 +49,7 @@ func TestIndexService_IndexWork(t *testing.T) { service := NewIndexService(localizationService, weaviateWrapper) ctx := context.Background() - work := domain.Work{ + work := work.Work{ TranslatableModel: domain.TranslatableModel{ BaseModel: domain.BaseModel{ID: 1}, Language: "en", diff --git a/internal/app/work/commands.go b/internal/app/work/commands.go index 7dc3889..e9f0616 100644 --- a/internal/app/work/commands.go +++ b/internal/app/work/commands.go @@ -3,18 +3,18 @@ package work import ( "context" "errors" - "tercul/internal/domain" "tercul/internal/domain/search" + "tercul/internal/domain/work" ) // WorkCommands contains the command handlers for the work aggregate. type WorkCommands struct { - repo domain.WorkRepository + repo work.WorkRepository searchClient search.SearchClient } // NewWorkCommands creates a new WorkCommands handler. -func NewWorkCommands(repo domain.WorkRepository, searchClient search.SearchClient) *WorkCommands { +func NewWorkCommands(repo work.WorkRepository, searchClient search.SearchClient) *WorkCommands { return &WorkCommands{ repo: repo, searchClient: searchClient, @@ -22,7 +22,7 @@ func NewWorkCommands(repo domain.WorkRepository, searchClient search.SearchClien } // CreateWork creates a new work. -func (c *WorkCommands) CreateWork(ctx context.Context, work *domain.Work) (*domain.Work, error) { +func (c *WorkCommands) CreateWork(ctx context.Context, work *work.Work) (*work.Work, error) { if work == nil { return nil, errors.New("work cannot be nil") } @@ -45,7 +45,7 @@ func (c *WorkCommands) CreateWork(ctx context.Context, work *domain.Work) (*doma } // UpdateWork updates an existing work. -func (c *WorkCommands) UpdateWork(ctx context.Context, work *domain.Work) error { +func (c *WorkCommands) UpdateWork(ctx context.Context, work *work.Work) error { if work == nil { return errors.New("work cannot be nil") } diff --git a/internal/app/work/commands_test.go b/internal/app/work/commands_test.go index b95aecd..6a0d0b6 100644 --- a/internal/app/work/commands_test.go +++ b/internal/app/work/commands_test.go @@ -6,6 +6,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" "tercul/internal/domain" + workdomain "tercul/internal/domain/work" "testing" ) @@ -27,7 +28,7 @@ func TestWorkCommandsSuite(t *testing.T) { } func (s *WorkCommandsSuite) TestCreateWork_Success() { - work := &domain.Work{Title: "Test Work", TranslatableModel: domain.TranslatableModel{Language: "en"}} + work := &workdomain.Work{Title: "Test Work", TranslatableModel: domain.TranslatableModel{Language: "en"}} _, err := s.commands.CreateWork(context.Background(), work) assert.NoError(s.T(), err) } @@ -38,20 +39,20 @@ func (s *WorkCommandsSuite) TestCreateWork_Nil() { } func (s *WorkCommandsSuite) TestCreateWork_EmptyTitle() { - work := &domain.Work{TranslatableModel: domain.TranslatableModel{Language: "en"}} + work := &workdomain.Work{TranslatableModel: domain.TranslatableModel{Language: "en"}} _, err := s.commands.CreateWork(context.Background(), work) assert.Error(s.T(), err) } func (s *WorkCommandsSuite) TestCreateWork_EmptyLanguage() { - work := &domain.Work{Title: "Test Work"} + work := &workdomain.Work{Title: "Test Work"} _, err := s.commands.CreateWork(context.Background(), work) assert.Error(s.T(), err) } func (s *WorkCommandsSuite) TestCreateWork_RepoError() { - work := &domain.Work{Title: "Test Work", TranslatableModel: domain.TranslatableModel{Language: "en"}} - s.repo.createFunc = func(ctx context.Context, w *domain.Work) error { + work := &workdomain.Work{Title: "Test Work", TranslatableModel: domain.TranslatableModel{Language: "en"}} + s.repo.createFunc = func(ctx context.Context, w *workdomain.Work) error { return errors.New("db error") } _, err := s.commands.CreateWork(context.Background(), work) @@ -59,7 +60,7 @@ func (s *WorkCommandsSuite) TestCreateWork_RepoError() { } func (s *WorkCommandsSuite) TestUpdateWork_Success() { - work := &domain.Work{Title: "Test Work", TranslatableModel: domain.TranslatableModel{Language: "en"}} + work := &workdomain.Work{Title: "Test Work", TranslatableModel: domain.TranslatableModel{Language: "en"}} work.ID = 1 err := s.commands.UpdateWork(context.Background(), work) assert.NoError(s.T(), err) @@ -71,29 +72,29 @@ func (s *WorkCommandsSuite) TestUpdateWork_Nil() { } func (s *WorkCommandsSuite) TestUpdateWork_ZeroID() { - work := &domain.Work{Title: "Test Work", TranslatableModel: domain.TranslatableModel{Language: "en"}} + work := &workdomain.Work{Title: "Test Work", TranslatableModel: domain.TranslatableModel{Language: "en"}} err := s.commands.UpdateWork(context.Background(), work) assert.Error(s.T(), err) } func (s *WorkCommandsSuite) TestUpdateWork_EmptyTitle() { - work := &domain.Work{TranslatableModel: domain.TranslatableModel{Language: "en"}} + work := &workdomain.Work{TranslatableModel: domain.TranslatableModel{Language: "en"}} work.ID = 1 err := s.commands.UpdateWork(context.Background(), work) assert.Error(s.T(), err) } func (s *WorkCommandsSuite) TestUpdateWork_EmptyLanguage() { - work := &domain.Work{Title: "Test Work"} + work := &workdomain.Work{Title: "Test Work"} work.ID = 1 err := s.commands.UpdateWork(context.Background(), work) assert.Error(s.T(), err) } func (s *WorkCommandsSuite) TestUpdateWork_RepoError() { - work := &domain.Work{Title: "Test Work", TranslatableModel: domain.TranslatableModel{Language: "en"}} + work := &workdomain.Work{Title: "Test Work", TranslatableModel: domain.TranslatableModel{Language: "en"}} work.ID = 1 - s.repo.updateFunc = func(ctx context.Context, w *domain.Work) error { + s.repo.updateFunc = func(ctx context.Context, w *workdomain.Work) error { return errors.New("db error") } err := s.commands.UpdateWork(context.Background(), work) diff --git a/internal/app/work/main_test.go b/internal/app/work/main_test.go index b29f229..a913041 100644 --- a/internal/app/work/main_test.go +++ b/internal/app/work/main_test.go @@ -3,29 +3,30 @@ package work import ( "context" "tercul/internal/domain" + "tercul/internal/domain/work" ) type mockWorkRepository struct { - domain.WorkRepository - createFunc func(ctx context.Context, work *domain.Work) error - updateFunc func(ctx context.Context, work *domain.Work) error - deleteFunc func(ctx context.Context, id uint) error - getByIDFunc func(ctx context.Context, id uint) (*domain.Work, error) - listFunc func(ctx context.Context, page, pageSize int) (*domain.PaginatedResult[domain.Work], error) - getWithTranslationsFunc func(ctx context.Context, id uint) (*domain.Work, error) - findByTitleFunc func(ctx context.Context, title string) ([]domain.Work, error) - findByAuthorFunc func(ctx context.Context, authorID uint) ([]domain.Work, error) - findByCategoryFunc func(ctx context.Context, categoryID uint) ([]domain.Work, error) - findByLanguageFunc func(ctx context.Context, language string, page, pageSize int) (*domain.PaginatedResult[domain.Work], error) + work.WorkRepository + createFunc func(ctx context.Context, work *work.Work) error + updateFunc func(ctx context.Context, work *work.Work) error + deleteFunc func(ctx context.Context, id uint) error + getByIDFunc func(ctx context.Context, id uint) (*work.Work, error) + listFunc func(ctx context.Context, page, pageSize int) (*domain.PaginatedResult[work.Work], error) + getWithTranslationsFunc func(ctx context.Context, id uint) (*work.Work, error) + findByTitleFunc func(ctx context.Context, title string) ([]work.Work, error) + findByAuthorFunc func(ctx context.Context, authorID uint) ([]work.Work, error) + findByCategoryFunc func(ctx context.Context, categoryID uint) ([]work.Work, error) + findByLanguageFunc func(ctx context.Context, language string, page, pageSize int) (*domain.PaginatedResult[work.Work], error) } -func (m *mockWorkRepository) Create(ctx context.Context, work *domain.Work) error { +func (m *mockWorkRepository) Create(ctx context.Context, work *work.Work) error { if m.createFunc != nil { return m.createFunc(ctx, work) } return nil } -func (m *mockWorkRepository) Update(ctx context.Context, work *domain.Work) error { +func (m *mockWorkRepository) Update(ctx context.Context, work *work.Work) error { if m.updateFunc != nil { return m.updateFunc(ctx, work) } @@ -37,43 +38,43 @@ func (m *mockWorkRepository) Delete(ctx context.Context, id uint) error { } return nil } -func (m *mockWorkRepository) GetByID(ctx context.Context, id uint) (*domain.Work, error) { +func (m *mockWorkRepository) GetByID(ctx context.Context, id uint) (*work.Work, error) { if m.getByIDFunc != nil { return m.getByIDFunc(ctx, id) } return nil, nil } -func (m *mockWorkRepository) List(ctx context.Context, page, pageSize int) (*domain.PaginatedResult[domain.Work], error) { +func (m *mockWorkRepository) List(ctx context.Context, page, pageSize int) (*domain.PaginatedResult[work.Work], error) { if m.listFunc != nil { return m.listFunc(ctx, page, pageSize) } return nil, nil } -func (m *mockWorkRepository) GetWithTranslations(ctx context.Context, id uint) (*domain.Work, error) { +func (m *mockWorkRepository) GetWithTranslations(ctx context.Context, id uint) (*work.Work, error) { if m.getWithTranslationsFunc != nil { return m.getWithTranslationsFunc(ctx, id) } return nil, nil } -func (m *mockWorkRepository) FindByTitle(ctx context.Context, title string) ([]domain.Work, error) { +func (m *mockWorkRepository) FindByTitle(ctx context.Context, title string) ([]work.Work, error) { if m.findByTitleFunc != nil { return m.findByTitleFunc(ctx, title) } return nil, nil } -func (m *mockWorkRepository) FindByAuthor(ctx context.Context, authorID uint) ([]domain.Work, error) { +func (m *mockWorkRepository) FindByAuthor(ctx context.Context, authorID uint) ([]work.Work, error) { if m.findByAuthorFunc != nil { return m.findByAuthorFunc(ctx, authorID) } return nil, nil } -func (m *mockWorkRepository) FindByCategory(ctx context.Context, categoryID uint) ([]domain.Work, error) { +func (m *mockWorkRepository) FindByCategory(ctx context.Context, categoryID uint) ([]work.Work, error) { if m.findByCategoryFunc != nil { return m.findByCategoryFunc(ctx, categoryID) } return nil, nil } -func (m *mockWorkRepository) FindByLanguage(ctx context.Context, language string, page, pageSize int) (*domain.PaginatedResult[domain.Work], error) { +func (m *mockWorkRepository) FindByLanguage(ctx context.Context, language string, page, pageSize int) (*domain.PaginatedResult[work.Work], error) { if m.findByLanguageFunc != nil { return m.findByLanguageFunc(ctx, language, page, pageSize) } @@ -81,12 +82,12 @@ func (m *mockWorkRepository) FindByLanguage(ctx context.Context, language string } type mockSearchClient struct { - indexWorkFunc func(ctx context.Context, work *domain.Work, pipeline string) error + indexWorkFunc func(ctx context.Context, work *work.Work, pipeline string) error } -func (m *mockSearchClient) IndexWork(ctx context.Context, work *domain.Work, pipeline string) error { +func (m *mockSearchClient) IndexWork(ctx context.Context, work *work.Work, pipeline string) error { if m.indexWorkFunc != nil { return m.indexWorkFunc(ctx, work, pipeline) } return nil -} +} \ No newline at end of file diff --git a/internal/app/work/queries.go b/internal/app/work/queries.go index b8f64ff..237de4f 100644 --- a/internal/app/work/queries.go +++ b/internal/app/work/queries.go @@ -4,6 +4,7 @@ import ( "context" "errors" "tercul/internal/domain" + "tercul/internal/domain/work" ) // WorkAnalytics contains analytics data for a work @@ -30,18 +31,18 @@ type TranslationAnalytics struct { // WorkQueries contains the query handlers for the work aggregate. type WorkQueries struct { - repo domain.WorkRepository + repo work.WorkRepository } // NewWorkQueries creates a new WorkQueries handler. -func NewWorkQueries(repo domain.WorkRepository) *WorkQueries { +func NewWorkQueries(repo work.WorkRepository) *WorkQueries { return &WorkQueries{ repo: repo, } } // GetWorkByID retrieves a work by ID. -func (q *WorkQueries) GetWorkByID(ctx context.Context, id uint) (*domain.Work, error) { +func (q *WorkQueries) GetWorkByID(ctx context.Context, id uint) (*work.Work, error) { if id == 0 { return nil, errors.New("invalid work ID") } @@ -49,12 +50,12 @@ func (q *WorkQueries) GetWorkByID(ctx context.Context, id uint) (*domain.Work, e } // ListWorks returns a paginated list of works. -func (q *WorkQueries) ListWorks(ctx context.Context, page, pageSize int) (*domain.PaginatedResult[domain.Work], error) { +func (q *WorkQueries) ListWorks(ctx context.Context, page, pageSize int) (*domain.PaginatedResult[work.Work], error) { return q.repo.List(ctx, page, pageSize) } // GetWorkWithTranslations retrieves a work with its translations. -func (q *WorkQueries) GetWorkWithTranslations(ctx context.Context, id uint) (*domain.Work, error) { +func (q *WorkQueries) GetWorkWithTranslations(ctx context.Context, id uint) (*work.Work, error) { if id == 0 { return nil, errors.New("invalid work ID") } @@ -62,7 +63,7 @@ func (q *WorkQueries) GetWorkWithTranslations(ctx context.Context, id uint) (*do } // FindWorksByTitle finds works by title. -func (q *WorkQueries) FindWorksByTitle(ctx context.Context, title string) ([]domain.Work, error) { +func (q *WorkQueries) FindWorksByTitle(ctx context.Context, title string) ([]work.Work, error) { if title == "" { return nil, errors.New("title cannot be empty") } @@ -70,7 +71,7 @@ func (q *WorkQueries) FindWorksByTitle(ctx context.Context, title string) ([]dom } // FindWorksByAuthor finds works by author ID. -func (q *WorkQueries) FindWorksByAuthor(ctx context.Context, authorID uint) ([]domain.Work, error) { +func (q *WorkQueries) FindWorksByAuthor(ctx context.Context, authorID uint) ([]work.Work, error) { if authorID == 0 { return nil, errors.New("invalid author ID") } @@ -78,7 +79,7 @@ func (q *WorkQueries) FindWorksByAuthor(ctx context.Context, authorID uint) ([]d } // FindWorksByCategory finds works by category ID. -func (q *WorkQueries) FindWorksByCategory(ctx context.Context, categoryID uint) ([]domain.Work, error) { +func (q *WorkQueries) FindWorksByCategory(ctx context.Context, categoryID uint) ([]work.Work, error) { if categoryID == 0 { return nil, errors.New("invalid category ID") } @@ -86,7 +87,7 @@ func (q *WorkQueries) FindWorksByCategory(ctx context.Context, categoryID uint) } // FindWorksByLanguage finds works by language. -func (q *WorkQueries) FindWorksByLanguage(ctx context.Context, language string, page, pageSize int) (*domain.PaginatedResult[domain.Work], error) { +func (q *WorkQueries) FindWorksByLanguage(ctx context.Context, language string, page, pageSize int) (*domain.PaginatedResult[work.Work], error) { if language == "" { return nil, errors.New("language cannot be empty") } diff --git a/internal/app/work/queries_test.go b/internal/app/work/queries_test.go index 3a4d585..f1acde4 100644 --- a/internal/app/work/queries_test.go +++ b/internal/app/work/queries_test.go @@ -5,6 +5,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" "tercul/internal/domain" + workdomain "tercul/internal/domain/work" "testing" ) @@ -24,9 +25,9 @@ func TestWorkQueriesSuite(t *testing.T) { } func (s *WorkQueriesSuite) TestGetWorkByID_Success() { - work := &domain.Work{Title: "Test Work"} + work := &workdomain.Work{Title: "Test Work"} work.ID = 1 - s.repo.getByIDFunc = func(ctx context.Context, id uint) (*domain.Work, error) { + s.repo.getByIDFunc = func(ctx context.Context, id uint) (*workdomain.Work, error) { return work, nil } w, err := s.queries.GetWorkByID(context.Background(), 1) @@ -41,8 +42,8 @@ func (s *WorkQueriesSuite) TestGetWorkByID_ZeroID() { } func (s *WorkQueriesSuite) TestListWorks_Success() { - works := &domain.PaginatedResult[domain.Work]{} - s.repo.listFunc = func(ctx context.Context, page, pageSize int) (*domain.PaginatedResult[domain.Work], error) { + works := &domain.PaginatedResult[workdomain.Work]{} + s.repo.listFunc = func(ctx context.Context, page, pageSize int) (*domain.PaginatedResult[workdomain.Work], error) { return works, nil } w, err := s.queries.ListWorks(context.Background(), 1, 10) @@ -51,9 +52,9 @@ func (s *WorkQueriesSuite) TestListWorks_Success() { } func (s *WorkQueriesSuite) TestGetWorkWithTranslations_Success() { - work := &domain.Work{Title: "Test Work"} + work := &workdomain.Work{Title: "Test Work"} work.ID = 1 - s.repo.getWithTranslationsFunc = func(ctx context.Context, id uint) (*domain.Work, error) { + s.repo.getWithTranslationsFunc = func(ctx context.Context, id uint) (*workdomain.Work, error) { return work, nil } w, err := s.queries.GetWorkWithTranslations(context.Background(), 1) @@ -68,8 +69,8 @@ func (s *WorkQueriesSuite) TestGetWorkWithTranslations_ZeroID() { } func (s *WorkQueriesSuite) TestFindWorksByTitle_Success() { - works := []domain.Work{{Title: "Test Work"}} - s.repo.findByTitleFunc = func(ctx context.Context, title string) ([]domain.Work, error) { + works := []workdomain.Work{{Title: "Test Work"}} + s.repo.findByTitleFunc = func(ctx context.Context, title string) ([]workdomain.Work, error) { return works, nil } w, err := s.queries.FindWorksByTitle(context.Background(), "Test") @@ -84,8 +85,8 @@ func (s *WorkQueriesSuite) TestFindWorksByTitle_Empty() { } func (s *WorkQueriesSuite) TestFindWorksByAuthor_Success() { - works := []domain.Work{{Title: "Test Work"}} - s.repo.findByAuthorFunc = func(ctx context.Context, authorID uint) ([]domain.Work, error) { + works := []workdomain.Work{{Title: "Test Work"}} + s.repo.findByAuthorFunc = func(ctx context.Context, authorID uint) ([]workdomain.Work, error) { return works, nil } w, err := s.queries.FindWorksByAuthor(context.Background(), 1) @@ -100,8 +101,8 @@ func (s *WorkQueriesSuite) TestFindWorksByAuthor_ZeroID() { } func (s *WorkQueriesSuite) TestFindWorksByCategory_Success() { - works := []domain.Work{{Title: "Test Work"}} - s.repo.findByCategoryFunc = func(ctx context.Context, categoryID uint) ([]domain.Work, error) { + works := []workdomain.Work{{Title: "Test Work"}} + s.repo.findByCategoryFunc = func(ctx context.Context, categoryID uint) ([]workdomain.Work, error) { return works, nil } w, err := s.queries.FindWorksByCategory(context.Background(), 1) @@ -116,8 +117,8 @@ func (s *WorkQueriesSuite) TestFindWorksByCategory_ZeroID() { } func (s *WorkQueriesSuite) TestFindWorksByLanguage_Success() { - works := &domain.PaginatedResult[domain.Work]{} - s.repo.findByLanguageFunc = func(ctx context.Context, language string, page, pageSize int) (*domain.PaginatedResult[domain.Work], error) { + works := &domain.PaginatedResult[workdomain.Work]{} + s.repo.findByLanguageFunc = func(ctx context.Context, language string, page, pageSize int) (*domain.PaginatedResult[workdomain.Work], error) { return works, nil } w, err := s.queries.FindWorksByLanguage(context.Background(), "en", 1, 10) @@ -129,4 +130,4 @@ func (s *WorkQueriesSuite) TestFindWorksByLanguage_Empty() { w, err := s.queries.FindWorksByLanguage(context.Background(), "", 1, 10) assert.Error(s.T(), err) assert.Nil(s.T(), w) -} +} \ No newline at end of file diff --git a/internal/app/work/service.go b/internal/app/work/service.go index 62ded51..0c8f8eb 100644 --- a/internal/app/work/service.go +++ b/internal/app/work/service.go @@ -1,8 +1,8 @@ package work import ( - "tercul/internal/domain" "tercul/internal/domain/search" + "tercul/internal/domain/work" ) // Service is the application service for the work aggregate. @@ -12,7 +12,7 @@ type Service struct { } // NewService creates a new work Service. -func NewService(repo domain.WorkRepository, searchClient search.SearchClient) *Service { +func NewService(repo work.WorkRepository, searchClient search.SearchClient) *Service { return &Service{ Commands: NewWorkCommands(repo, searchClient), Queries: NewWorkQueries(repo), diff --git a/internal/data/sql/analytics_repository.go b/internal/data/sql/analytics_repository.go index cd68058..a22717e 100644 --- a/internal/data/sql/analytics_repository.go +++ b/internal/data/sql/analytics_repository.go @@ -3,7 +3,9 @@ package sql import ( "context" "fmt" + "tercul/internal/app/analytics" "tercul/internal/domain" + "tercul/internal/domain/work" "time" "gorm.io/gorm" @@ -13,7 +15,7 @@ type analyticsRepository struct { db *gorm.DB } -func NewAnalyticsRepository(db *gorm.DB) domain.AnalyticsRepository { +func NewAnalyticsRepository(db *gorm.DB) analytics.Repository { return &analyticsRepository{db: db} } @@ -41,7 +43,7 @@ func (r *analyticsRepository) IncrementWorkCounter(ctx context.Context, workID u // Using a transaction to ensure atomicity return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { // First, try to update the existing record - result := tx.Model(&domain.WorkStats{}).Where("work_id = ?", workID).UpdateColumn(field, gorm.Expr(fmt.Sprintf("%s + ?", field), value)) + result := tx.Model(&work.WorkStats{}).Where("work_id = ?", workID).UpdateColumn(field, gorm.Expr(fmt.Sprintf("%s + ?", field), value)) if result.Error != nil { return result.Error } @@ -49,14 +51,14 @@ func (r *analyticsRepository) IncrementWorkCounter(ctx context.Context, workID u // If no rows were affected, the record does not exist, so create it if result.RowsAffected == 0 { initialData := map[string]interface{}{"work_id": workID, field: value} - return tx.Model(&domain.WorkStats{}).Create(initialData).Error + return tx.Model(&work.WorkStats{}).Create(initialData).Error } return nil }) } -func (r *analyticsRepository) GetTrendingWorks(ctx context.Context, timePeriod string, limit int) ([]*domain.Work, error) { +func (r *analyticsRepository) GetTrendingWorks(ctx context.Context, timePeriod string, limit int) ([]*work.Work, error) { var trendingWorks []*domain.Trending err := r.db.WithContext(ctx). Where("entity_type = ? AND time_period = ?", "Work", timePeriod). @@ -68,7 +70,7 @@ func (r *analyticsRepository) GetTrendingWorks(ctx context.Context, timePeriod s } if len(trendingWorks) == 0 { - return []*domain.Work{}, nil + return []*work.Work{}, nil } workIDs := make([]uint, len(trendingWorks)) @@ -76,22 +78,22 @@ func (r *analyticsRepository) GetTrendingWorks(ctx context.Context, timePeriod s workIDs[i] = tw.EntityID } - var works []*domain.Work + var works []*work.Work err = r.db.WithContext(ctx). Where("id IN ?", workIDs). Find(&works).Error // This part is tricky because the order from the IN clause is not guaranteed. // We need to re-order the works based on the trending rank. - workMap := make(map[uint]*domain.Work) - for _, work := range works { - workMap[work.ID] = work + workMap := make(map[uint]*work.Work) + for _, w := range works { + workMap[w.ID] = w } - orderedWorks := make([]*domain.Work, len(workIDs)) + orderedWorks := make([]*work.Work, len(workIDs)) for i, id := range workIDs { - if work, ok := workMap[id]; ok { - orderedWorks[i] = work + if w, ok := workMap[id]; ok { + orderedWorks[i] = w } } @@ -118,17 +120,17 @@ func (r *analyticsRepository) IncrementTranslationCounter(ctx context.Context, t }) } -func (r *analyticsRepository) UpdateWorkStats(ctx context.Context, workID uint, stats domain.WorkStats) error { - return r.db.WithContext(ctx).Model(&domain.WorkStats{}).Where("work_id = ?", workID).Updates(stats).Error +func (r *analyticsRepository) UpdateWorkStats(ctx context.Context, workID uint, stats work.WorkStats) error { + return r.db.WithContext(ctx).Model(&work.WorkStats{}).Where("work_id = ?", workID).Updates(stats).Error } func (r *analyticsRepository) UpdateTranslationStats(ctx context.Context, translationID uint, stats domain.TranslationStats) error { return r.db.WithContext(ctx).Model(&domain.TranslationStats{}).Where("translation_id = ?", translationID).Updates(stats).Error } -func (r *analyticsRepository) GetOrCreateWorkStats(ctx context.Context, workID uint) (*domain.WorkStats, error) { - var stats domain.WorkStats - err := r.db.WithContext(ctx).Where(domain.WorkStats{WorkID: workID}).FirstOrCreate(&stats).Error +func (r *analyticsRepository) GetOrCreateWorkStats(ctx context.Context, workID uint) (*work.WorkStats, error) { + var stats work.WorkStats + err := r.db.WithContext(ctx).Where(work.WorkStats{WorkID: workID}).FirstOrCreate(&stats).Error return &stats, err } diff --git a/internal/data/sql/monetization_repository.go b/internal/data/sql/monetization_repository.go index 2485ae5..ee8bbc9 100644 --- a/internal/data/sql/monetization_repository.go +++ b/internal/data/sql/monetization_repository.go @@ -3,6 +3,7 @@ package sql import ( "context" "tercul/internal/domain" + "tercul/internal/domain/work" "gorm.io/gorm" ) @@ -21,15 +22,15 @@ func NewMonetizationRepository(db *gorm.DB) domain.MonetizationRepository { } func (r *monetizationRepository) AddMonetizationToWork(ctx context.Context, workID uint, monetizationID uint) error { - work := &domain.Work{TranslatableModel: domain.TranslatableModel{BaseModel: domain.BaseModel{ID: workID}}} + workRecord := &work.Work{TranslatableModel: domain.TranslatableModel{BaseModel: domain.BaseModel{ID: workID}}} monetization := &domain.Monetization{BaseModel: domain.BaseModel{ID: monetizationID}} - return r.db.WithContext(ctx).Model(work).Association("Monetizations").Append(monetization) + return r.db.WithContext(ctx).Model(workRecord).Association("Monetizations").Append(monetization) } func (r *monetizationRepository) RemoveMonetizationFromWork(ctx context.Context, workID uint, monetizationID uint) error { - work := &domain.Work{TranslatableModel: domain.TranslatableModel{BaseModel: domain.BaseModel{ID: workID}}} + workRecord := &work.Work{TranslatableModel: domain.TranslatableModel{BaseModel: domain.BaseModel{ID: workID}}} monetization := &domain.Monetization{BaseModel: domain.BaseModel{ID: monetizationID}} - return r.db.WithContext(ctx).Model(work).Association("Monetizations").Delete(monetization) + return r.db.WithContext(ctx).Model(workRecord).Association("Monetizations").Delete(monetization) } func (r *monetizationRepository) AddMonetizationToAuthor(ctx context.Context, authorID uint, monetizationID uint) error { diff --git a/internal/data/sql/monetization_repository_test.go b/internal/data/sql/monetization_repository_test.go index 29965c4..3fc24dc 100644 --- a/internal/data/sql/monetization_repository_test.go +++ b/internal/data/sql/monetization_repository_test.go @@ -5,6 +5,7 @@ import ( "testing" "tercul/internal/data/sql" "tercul/internal/domain" + workdomain "tercul/internal/domain/work" "tercul/internal/testutil" "github.com/stretchr/testify/suite" @@ -40,7 +41,7 @@ func (s *MonetizationRepositoryTestSuite) TestAddMonetizationToWork() { s.Require().NoError(err) // Verify that the association was created in the database - var foundWork domain.Work + var foundWork workdomain.Work err = s.DB.Preload("Monetizations").First(&foundWork, work.ID).Error s.Require().NoError(err) s.Require().Len(foundWork.Monetizations, 1) @@ -50,4 +51,4 @@ func (s *MonetizationRepositoryTestSuite) TestAddMonetizationToWork() { func TestMonetizationRepository(t *testing.T) { suite.Run(t, new(MonetizationRepositoryTestSuite)) -} +} \ No newline at end of file diff --git a/internal/data/sql/repositories.go b/internal/data/sql/repositories.go index 9bec6bc..ceaf8c6 100644 --- a/internal/data/sql/repositories.go +++ b/internal/data/sql/repositories.go @@ -1,15 +1,17 @@ package sql import ( + "tercul/internal/app/analytics" "tercul/internal/domain" "tercul/internal/domain/auth" "tercul/internal/domain/localization" + "tercul/internal/domain/work" "gorm.io/gorm" ) type Repositories struct { - Work domain.WorkRepository + Work work.WorkRepository User domain.UserRepository Author domain.AuthorRepository Translation domain.TranslationRepository @@ -24,7 +26,7 @@ type Repositories struct { Source domain.SourceRepository Copyright domain.CopyrightRepository Monetization domain.MonetizationRepository - Analytics domain.AnalyticsRepository + Analytics analytics.Repository Auth auth.AuthRepository Localization localization.LocalizationRepository } diff --git a/internal/data/sql/work_repository.go b/internal/data/sql/work_repository.go index effd495..3797608 100644 --- a/internal/data/sql/work_repository.go +++ b/internal/data/sql/work_repository.go @@ -3,26 +3,27 @@ package sql import ( "context" "tercul/internal/domain" + "tercul/internal/domain/work" "gorm.io/gorm" ) type workRepository struct { - domain.BaseRepository[domain.Work] + domain.BaseRepository[work.Work] db *gorm.DB } // NewWorkRepository creates a new WorkRepository. -func NewWorkRepository(db *gorm.DB) domain.WorkRepository { +func NewWorkRepository(db *gorm.DB) work.WorkRepository { return &workRepository{ - BaseRepository: NewBaseRepositoryImpl[domain.Work](db), + BaseRepository: NewBaseRepositoryImpl[work.Work](db), db: db, } } // FindByTitle finds works by title (partial match) -func (r *workRepository) FindByTitle(ctx context.Context, title string) ([]domain.Work, error) { - var works []domain.Work +func (r *workRepository) FindByTitle(ctx context.Context, title string) ([]work.Work, error) { + var works []work.Work if err := r.db.WithContext(ctx).Where("title LIKE ?", "%"+title+"%").Find(&works).Error; err != nil { return nil, err } @@ -30,8 +31,8 @@ func (r *workRepository) FindByTitle(ctx context.Context, title string) ([]domai } // FindByAuthor finds works by author ID -func (r *workRepository) FindByAuthor(ctx context.Context, authorID uint) ([]domain.Work, error) { - var works []domain.Work +func (r *workRepository) FindByAuthor(ctx context.Context, authorID uint) ([]work.Work, error) { + var works []work.Work if err := r.db.WithContext(ctx).Joins("JOIN work_authors ON work_authors.work_id = works.id"). Where("work_authors.author_id = ?", authorID). Find(&works).Error; err != nil { @@ -41,8 +42,8 @@ func (r *workRepository) FindByAuthor(ctx context.Context, authorID uint) ([]dom } // FindByCategory finds works by category ID -func (r *workRepository) FindByCategory(ctx context.Context, categoryID uint) ([]domain.Work, error) { - var works []domain.Work +func (r *workRepository) FindByCategory(ctx context.Context, categoryID uint) ([]work.Work, error) { + var works []work.Work if err := r.db.WithContext(ctx).Joins("JOIN work_categories ON work_categories.work_id = works.id"). Where("work_categories.category_id = ?", categoryID). Find(&works).Error; err != nil { @@ -52,7 +53,7 @@ func (r *workRepository) FindByCategory(ctx context.Context, categoryID uint) ([ } // FindByLanguage finds works by language with pagination -func (r *workRepository) FindByLanguage(ctx context.Context, language string, page, pageSize int) (*domain.PaginatedResult[domain.Work], error) { +func (r *workRepository) FindByLanguage(ctx context.Context, language string, page, pageSize int) (*domain.PaginatedResult[work.Work], error) { if page < 1 { page = 1 } @@ -61,11 +62,11 @@ func (r *workRepository) FindByLanguage(ctx context.Context, language string, pa pageSize = 20 } - var works []domain.Work + var works []work.Work var totalCount int64 // Get total count - if err := r.db.WithContext(ctx).Model(&domain.Work{}).Where("language = ?", language).Count(&totalCount).Error; err != nil { + if err := r.db.WithContext(ctx).Model(&work.Work{}).Where("language = ?", language).Count(&totalCount).Error; err != nil { return nil, err } @@ -88,7 +89,7 @@ func (r *workRepository) FindByLanguage(ctx context.Context, language string, pa hasNext := page < totalPages hasPrev := page > 1 - return &domain.PaginatedResult[domain.Work]{ + return &domain.PaginatedResult[work.Work]{ Items: works, TotalCount: totalCount, Page: page, @@ -99,23 +100,15 @@ func (r *workRepository) FindByLanguage(ctx context.Context, language string, pa }, nil } - - - - - - - - // Delete removes a work and its associations func (r *workRepository) Delete(ctx context.Context, id uint) error { return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { // Manually delete associations - if err := tx.Select("Copyrights", "Monetizations", "Authors", "Tags", "Categories").Delete(&domain.Work{TranslatableModel: domain.TranslatableModel{BaseModel: domain.BaseModel{ID: id}}}).Error; err != nil { + if err := tx.Select("Copyrights", "Monetizations", "Authors", "Tags", "Categories").Delete(&work.Work{TranslatableModel: domain.TranslatableModel{BaseModel: domain.BaseModel{ID: id}}}).Error; err != nil { return err } // Also delete the work itself - if err := tx.Delete(&domain.Work{}, id).Error; err != nil { + if err := tx.Delete(&work.Work{}, id).Error; err != nil { return err } return nil @@ -123,12 +116,12 @@ func (r *workRepository) Delete(ctx context.Context, id uint) error { } // GetWithTranslations gets a work with its translations -func (r *workRepository) GetWithTranslations(ctx context.Context, id uint) (*domain.Work, error) { +func (r *workRepository) GetWithTranslations(ctx context.Context, id uint) (*work.Work, error) { return r.FindWithPreload(ctx, []string{"Translations"}, id) } // ListWithTranslations lists works with their translations -func (r *workRepository) ListWithTranslations(ctx context.Context, page, pageSize int) (*domain.PaginatedResult[domain.Work], error) { +func (r *workRepository) ListWithTranslations(ctx context.Context, page, pageSize int) (*domain.PaginatedResult[work.Work], error) { if page < 1 { page = 1 } @@ -137,11 +130,11 @@ func (r *workRepository) ListWithTranslations(ctx context.Context, page, pageSiz pageSize = 20 } - var works []domain.Work + var works []work.Work var totalCount int64 // Get total count - if err := r.db.WithContext(ctx).Model(&domain.Work{}).Count(&totalCount).Error; err != nil { + if err := r.db.WithContext(ctx).Model(&work.Work{}).Count(&totalCount).Error; err != nil { return nil, err } @@ -164,7 +157,7 @@ func (r *workRepository) ListWithTranslations(ctx context.Context, page, pageSiz hasNext := page < totalPages hasPrev := page > 1 - return &domain.PaginatedResult[domain.Work]{ + return &domain.PaginatedResult[work.Work]{ Items: works, TotalCount: totalCount, Page: page, diff --git a/internal/data/sql/work_repository_test.go b/internal/data/sql/work_repository_test.go index 95b4494..36800dd 100644 --- a/internal/data/sql/work_repository_test.go +++ b/internal/data/sql/work_repository_test.go @@ -5,6 +5,7 @@ import ( "testing" "tercul/internal/data/sql" "tercul/internal/domain" + "tercul/internal/domain/work" "tercul/internal/testutil" "github.com/stretchr/testify/suite" @@ -12,7 +13,7 @@ import ( type WorkRepositoryTestSuite struct { testutil.IntegrationTestSuite - WorkRepo domain.WorkRepository + WorkRepo work.WorkRepository } func (s *WorkRepositoryTestSuite) SetupSuite() { @@ -29,7 +30,7 @@ func (s *WorkRepositoryTestSuite) TestCreateWork() { } s.Require().NoError(s.DB.Create(copyright).Error) - work := &domain.Work{ + workModel := &work.Work{ Title: "New Test Work", TranslatableModel: domain.TranslatableModel{ Language: "en", @@ -38,15 +39,15 @@ func (s *WorkRepositoryTestSuite) TestCreateWork() { } // Act - err := s.WorkRepo.Create(context.Background(), work) + err := s.WorkRepo.Create(context.Background(), workModel) // Assert s.Require().NoError(err) - s.NotZero(work.ID) + s.NotZero(workModel.ID) // Verify that the work was actually created in the database - var foundWork domain.Work - err = s.DB.Preload("Copyrights").First(&foundWork, work.ID).Error + var foundWork work.Work + err = s.DB.Preload("Copyrights").First(&foundWork, workModel.ID).Error s.Require().NoError(err) s.Equal("New Test Work", foundWork.Title) s.Equal("en", foundWork.Language) @@ -64,16 +65,16 @@ func (s *WorkRepositoryTestSuite) TestGetWorkByID() { } s.Require().NoError(s.DB.Create(copyright).Error) - work := s.CreateTestWork("Test Work", "en", "Test content") - s.Require().NoError(s.DB.Model(work).Association("Copyrights").Append(copyright)) + workModel := s.CreateTestWork("Test Work", "en", "Test content") + s.Require().NoError(s.DB.Model(workModel).Association("Copyrights").Append(copyright)) // Act - foundWork, err := s.WorkRepo.GetByID(context.Background(), work.ID) + foundWork, err := s.WorkRepo.GetByID(context.Background(), workModel.ID) // Assert s.Require().NoError(err) s.Require().NotNil(foundWork) - s.Equal(work.ID, foundWork.ID) + s.Equal(workModel.ID, foundWork.ID) s.Equal("Test Work", foundWork.Title) }) @@ -95,21 +96,21 @@ func (s *WorkRepositoryTestSuite) TestUpdateWork() { s.Require().NoError(s.DB.Create(©right1).Error) s.Require().NoError(s.DB.Create(©right2).Error) - work := s.CreateTestWork("Original Title", "en", "Original content") - s.Require().NoError(s.DB.Model(work).Association("Copyrights").Append(copyright1)) + workModel := s.CreateTestWork("Original Title", "en", "Original content") + s.Require().NoError(s.DB.Model(workModel).Association("Copyrights").Append(copyright1)) - work.Title = "Updated Title" - s.Require().NoError(s.DB.Model(work).Association("Copyrights").Replace(copyright2)) + workModel.Title = "Updated Title" + s.Require().NoError(s.DB.Model(workModel).Association("Copyrights").Replace(copyright2)) // Act - err := s.WorkRepo.Update(context.Background(), work) + err := s.WorkRepo.Update(context.Background(), workModel) // Assert s.Require().NoError(err) // Verify that the work was actually updated in the database - var foundWork domain.Work - err = s.DB.Preload("Copyrights").First(&foundWork, work.ID).Error + var foundWork work.Work + err = s.DB.Preload("Copyrights").First(&foundWork, workModel.ID).Error s.Require().NoError(err) s.Equal("Updated Title", foundWork.Title) s.Require().Len(foundWork.Copyrights, 1) @@ -120,29 +121,29 @@ func (s *WorkRepositoryTestSuite) TestUpdateWork() { func (s *WorkRepositoryTestSuite) TestDeleteWork() { s.Run("should delete an existing work and its associations", func() { // Arrange - work := s.CreateTestWork("To Be Deleted", "en", "Content") + workModel := s.CreateTestWork("To Be Deleted", "en", "Content") copyright := &domain.Copyright{Name: "C1", Identificator: "C1"} s.Require().NoError(s.DB.Create(copyright).Error) - s.Require().NoError(s.DB.Model(work).Association("Copyrights").Append(copyright)) + s.Require().NoError(s.DB.Model(workModel).Association("Copyrights").Append(copyright)) // Act - err := s.WorkRepo.Delete(context.Background(), work.ID) + err := s.WorkRepo.Delete(context.Background(), workModel.ID) // Assert s.Require().NoError(err) // Verify that the work was actually deleted from the database - var foundWork domain.Work - err = s.DB.First(&foundWork, work.ID).Error + var foundWork work.Work + err = s.DB.First(&foundWork, workModel.ID).Error s.Require().Error(err) // Verify that the association in the join table is also deleted var count int64 - s.DB.Table("work_copyrights").Where("work_id = ?", work.ID).Count(&count) + s.DB.Table("work_copyrights").Where("work_id = ?", workModel.ID).Count(&count) s.Zero(count) }) } func TestWorkRepository(t *testing.T) { suite.Run(t, new(WorkRepositoryTestSuite)) -} +} \ No newline at end of file diff --git a/internal/domain/analytics.go b/internal/domain/analytics.go deleted file mode 100644 index 68ce2a9..0000000 --- a/internal/domain/analytics.go +++ /dev/null @@ -1,18 +0,0 @@ -package domain - -import "context" - -import "time" - -type AnalyticsRepository interface { - IncrementWorkCounter(ctx context.Context, workID uint, field string, value int) error - IncrementTranslationCounter(ctx context.Context, translationID uint, field string, value int) error - UpdateWorkStats(ctx context.Context, workID uint, stats WorkStats) error - UpdateTranslationStats(ctx context.Context, translationID uint, stats TranslationStats) error - GetOrCreateWorkStats(ctx context.Context, workID uint) (*WorkStats, error) - GetOrCreateTranslationStats(ctx context.Context, translationID uint) (*TranslationStats, error) - GetOrCreateUserEngagement(ctx context.Context, userID uint, date time.Time) (*UserEngagement, error) - UpdateUserEngagement(ctx context.Context, userEngagement *UserEngagement) error - UpdateTrendingWorks(ctx context.Context, timePeriod string, trending []*Trending) error - GetTrendingWorks(ctx context.Context, timePeriod string, limit int) ([]*Work, error) -} diff --git a/internal/domain/entities.go b/internal/domain/entities.go index 76d1a7c..65d72c0 100644 --- a/internal/domain/entities.go +++ b/internal/domain/entities.go @@ -182,40 +182,6 @@ func (u *User) CheckPassword(password string) bool { return err == nil } -type WorkStatus string -const ( - WorkStatusDraft WorkStatus = "draft" - WorkStatusPublished WorkStatus = "published" - WorkStatusArchived WorkStatus = "archived" - WorkStatusDeleted WorkStatus = "deleted" -) -type WorkType string -const ( - WorkTypePoetry WorkType = "poetry" - WorkTypeProse WorkType = "prose" - WorkTypeDrama WorkType = "drama" - WorkTypeEssay WorkType = "essay" - WorkTypeNovel WorkType = "novel" - WorkTypeShortStory WorkType = "short_story" - WorkTypeNovella WorkType = "novella" - WorkTypePlay WorkType = "play" - WorkTypeScript WorkType = "script" - WorkTypeOther WorkType = "other" -) -type Work struct { - TranslatableModel - Title string `gorm:"size:255;not null"` - Description string `gorm:"type:text"` - Type WorkType `gorm:"size:50;default:'other'"` - Status WorkStatus `gorm:"size:50;default:'draft'"` - PublishedAt *time.Time - Translations []*Translation `gorm:"polymorphic:Translatable"` - Authors []*Author `gorm:"many2many:work_authors"` - Tags []*Tag `gorm:"many2many:work_tags"` - Categories []*Category `gorm:"many2many:work_categories"` - Copyrights []*Copyright `gorm:"many2many:work_copyrights;constraint:OnDelete:CASCADE"` - Monetizations []*Monetization `gorm:"many2many:work_monetizations;constraint:OnDelete:CASCADE"` -} type AuthorStatus string const ( @@ -229,7 +195,6 @@ type Author struct { Status AuthorStatus `gorm:"size:50;default:'active'"` BirthDate *time.Time DeathDate *time.Time - Works []*Work `gorm:"many2many:work_authors"` Books []*Book `gorm:"many2many:book_authors"` CountryID *uint Country *Country `gorm:"foreignKey:CountryID"` @@ -267,7 +232,6 @@ type Book struct { Format BookFormat `gorm:"size:50;default:'paperback'"` Status BookStatus `gorm:"size:50;default:'draft'"` PublishedAt *time.Time - Works []*Work `gorm:"many2many:book_works"` Authors []*Author `gorm:"many2many:book_authors"` PublisherID *uint Publisher *Publisher `gorm:"foreignKey:PublisherID"` @@ -307,7 +271,6 @@ type Source struct { Description string `gorm:"type:text"` URL string `gorm:"size:512"` Status SourceStatus `gorm:"size:50;default:'active'"` - Works []*Work `gorm:"many2many:work_sources"` Translations []*Translation `gorm:"polymorphic:Translatable"` Copyrights []*Copyright `gorm:"many2many:source_copyrights;constraint:OnDelete:CASCADE"` Monetizations []*Monetization `gorm:"many2many:source_monetizations;constraint:OnDelete:CASCADE"` @@ -333,12 +296,6 @@ type Edition struct { Book *Book `gorm:"foreignKey:BookID"` } -func (w *Work) BeforeSave(tx *gorm.DB) error { - if w.Title == "" { - w.Title = "Untitled Work" - } - return nil -} func (a *Author) BeforeSave(tx *gorm.DB) error { if a.Name == "" { a.Name = "Unknown Author" @@ -364,7 +321,6 @@ type Comment struct { UserID uint User *User `gorm:"foreignKey:UserID"` WorkID *uint - Work *Work `gorm:"foreignKey:WorkID"` TranslationID *uint Translation *Translation `gorm:"foreignKey:TranslationID"` LineNumber *int `gorm:"index"` @@ -380,7 +336,6 @@ type Like struct { UserID uint `gorm:"index;uniqueIndex:uniq_like_user_target"` User *User `gorm:"foreignKey:UserID"` WorkID *uint `gorm:"index;uniqueIndex:uniq_like_user_target"` - Work *Work `gorm:"foreignKey:WorkID"` TranslationID *uint `gorm:"index;uniqueIndex:uniq_like_user_target"` Translation *Translation `gorm:"foreignKey:TranslationID"` CommentID *uint `gorm:"index;uniqueIndex:uniq_like_user_target"` @@ -392,7 +347,6 @@ type Bookmark struct { UserID uint `gorm:"index;uniqueIndex:uniq_bookmark_user_work"` User *User `gorm:"foreignKey:UserID"` WorkID uint `gorm:"index;uniqueIndex:uniq_bookmark_user_work"` - Work *Work `gorm:"foreignKey:WorkID"` Notes string `gorm:"type:text"` LastReadAt *time.Time Progress int `gorm:"default:0"` @@ -403,10 +357,14 @@ type Collection struct { Description string `gorm:"type:text"` UserID uint User *User `gorm:"foreignKey:UserID"` - Works []*Work `gorm:"many2many:collection_works"` IsPublic bool `gorm:"default:true"` CoverImageURL string `gorm:"size:255"` } + +type CollectionWork struct { + CollectionID uint `gorm:"primaryKey"` + WorkID uint `gorm:"primaryKey"` +} type Contribution struct { BaseModel Name string `gorm:"size:100;not null"` @@ -414,7 +372,6 @@ type Contribution struct { UserID uint User *User `gorm:"foreignKey:UserID"` WorkID *uint - Work *Work `gorm:"foreignKey:WorkID"` TranslationID *uint Translation *Translation `gorm:"foreignKey:TranslationID"` ReviewerID *uint @@ -477,7 +434,6 @@ type Tag struct { BaseModel Name string `gorm:"size:100;not null;uniqueIndex"` Description string `gorm:"type:text"` - Works []*Work `gorm:"many2many:work_tags"` Slug string `gorm:"size:255;index"` } type Category struct { @@ -487,7 +443,6 @@ type Category struct { ParentID *uint Parent *Category `gorm:"foreignKey:ParentID"` Children []*Category `gorm:"foreignKey:ParentID"` - Works []*Work `gorm:"many2many:work_categories"` Path string `gorm:"size:1024;index"` Slug string `gorm:"size:255;index"` } @@ -496,14 +451,6 @@ type Series struct { Name string `gorm:"size:255;not null;uniqueIndex"` Description string `gorm:"type:text"` } -type WorkSeries struct { - BaseModel - WorkID uint `gorm:"index;uniqueIndex:uniq_work_series"` - Work *Work `gorm:"foreignKey:WorkID"` - SeriesID uint `gorm:"index;uniqueIndex:uniq_work_series"` - Series *Series `gorm:"foreignKey:SeriesID"` - NumberInSeries int `gorm:"default:0"` -} type Translation struct { BaseModel @@ -554,9 +501,6 @@ func (t *Translation) BeforeSave(tx *gorm.DB) error { } return nil } -func (w *Work) GetID() uint { return w.ID } -func (w *Work) GetType() string { return "Work" } -func (w *Work) GetDefaultLanguage() string { return w.Language } func (a *Author) GetID() uint { return a.ID } func (a *Author) GetType() string { return "Author" } func (a *Author) GetDefaultLanguage() string { return a.Language } @@ -583,13 +527,6 @@ type Copyright struct { EndDate *time.Time Translations []CopyrightTranslation `gorm:"foreignKey:CopyrightID"` } -type WorkCopyright struct { - WorkID uint `gorm:"primaryKey;index"` - CopyrightID uint `gorm:"primaryKey;index"` - CreatedAt time.Time -} - -func (WorkCopyright) TableName() string { return "work_copyrights" } type AuthorCopyright struct { AuthorID uint `gorm:"primaryKey;index"` @@ -660,13 +597,6 @@ const ( MonetizationStatusInactive MonetizationStatus = "inactive" MonetizationStatusPending MonetizationStatus = "pending" ) -type WorkMonetization struct { - WorkID uint `gorm:"primaryKey;index"` - MonetizationID uint `gorm:"primaryKey;index"` - CreatedAt time.Time -} - -func (WorkMonetization) TableName() string { return "work_monetizations" } type AuthorMonetization struct { AuthorID uint `gorm:"primaryKey;index"` @@ -742,20 +672,6 @@ type AuditLog struct { // This is getting very long, but it's the correct approach. // I will just paste the rest of the structs here. -type WorkStats struct { - BaseModel - Views int64 `gorm:"default:0"` - Likes int64 `gorm:"default:0"` - Comments int64 `gorm:"default:0"` - Bookmarks int64 `gorm:"default:0"` - Shares int64 `gorm:"default:0"` - TranslationCount int64 `gorm:"default:0"` - ReadingTime int `gorm:"default:0"` - Complexity float64 `gorm:"type:decimal(5,2);default:0.0"` - Sentiment float64 `gorm:"type:decimal(5,2);default:0.0"` - WorkID uint `gorm:"uniqueIndex;index"` - Work *Work `gorm:"foreignKey:WorkID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"` -} type TranslationStats struct { BaseModel Views int64 `gorm:"default:0"` @@ -815,14 +731,6 @@ type MediaStats struct { Media interface{} `gorm:"-"` } -type BookWork struct { - BaseModel - BookID uint `gorm:"index;uniqueIndex:uniq_book_work"` - Book *Book `gorm:"foreignKey:BookID"` - WorkID uint `gorm:"index;uniqueIndex:uniq_book_work"` - Work *Work `gorm:"foreignKey:WorkID"` - Order int `gorm:"default:0"` -} type AuthorCountry struct { BaseModel AuthorID uint `gorm:"index;uniqueIndex:uniq_author_country"` @@ -830,15 +738,6 @@ type AuthorCountry struct { CountryID uint `gorm:"index;uniqueIndex:uniq_author_country"` Country *Country `gorm:"foreignKey:CountryID"` } -type WorkAuthor struct { - BaseModel - WorkID uint `gorm:"index;uniqueIndex:uniq_work_author_role"` - Work *Work `gorm:"foreignKey:WorkID"` - AuthorID uint `gorm:"index;uniqueIndex:uniq_work_author_role"` - Author *Author `gorm:"foreignKey:AuthorID"` - Role string `gorm:"size:50;default:'author';uniqueIndex:uniq_work_author_role"` - Ordinal int `gorm:"default:0"` -} type BookAuthor struct { BaseModel BookID uint `gorm:"index;uniqueIndex:uniq_book_author_role"` @@ -855,7 +754,6 @@ type ReadabilityScore struct { Language string `gorm:"size:50;not null"` Method string `gorm:"size:50"` WorkID uint - Work *Work `gorm:"foreignKey:WorkID"` } type WritingStyle struct { BaseModel @@ -863,7 +761,6 @@ type WritingStyle struct { Description string `gorm:"type:text"` Language string `gorm:"size:50;not null"` WorkID uint - Work *Work `gorm:"foreignKey:WorkID"` } type LinguisticLayer struct { BaseModel @@ -872,13 +769,11 @@ type LinguisticLayer struct { Language string `gorm:"size:50;not null"` Type string `gorm:"size:50"` WorkID uint - Work *Work `gorm:"foreignKey:WorkID"` Data JSONB `gorm:"type:jsonb;default:'{}'"` } type TextBlock struct { BaseModel WorkID *uint - Work *Work `gorm:"foreignKey:WorkID"` TranslationID *uint Translation *Translation `gorm:"foreignKey:TranslationID"` Index int `gorm:"index"` @@ -897,7 +792,6 @@ type TextMetadata struct { AverageWordLength float64 `gorm:"type:decimal(5,2)"` AverageSentenceLength float64 `gorm:"type:decimal(5,2)"` WorkID uint - Work *Work `gorm:"foreignKey:WorkID"` } type PoeticAnalysis struct { BaseModel @@ -908,7 +802,6 @@ type PoeticAnalysis struct { StanzaCount int `gorm:"default:0"` LineCount int `gorm:"default:0"` WorkID uint - Work *Work `gorm:"foreignKey:WorkID"` } type Word struct { BaseModel @@ -918,7 +811,6 @@ type Word struct { Lemma string `gorm:"size:100"` ConceptID *uint Concept *Concept `gorm:"foreignKey:ConceptID"` - Works []*Work `gorm:"many2many:work_words"` } type WordOccurrence struct { BaseModel @@ -936,14 +828,12 @@ type Concept struct { Name string `gorm:"size:100;not null"` Description string `gorm:"type:text"` Words []*Word `gorm:"foreignKey:ConceptID"` - Works []*Work `gorm:"many2many:work_concepts"` } type LanguageEntity struct { BaseModel Name string `gorm:"size:100;not null"` Type string `gorm:"size:50"` Language string `gorm:"size:50;not null"` - Works []*Work `gorm:"many2many:work_language_entities"` } type EntityOccurrence struct { BaseModel @@ -960,7 +850,6 @@ type LanguageAnalysis struct { Language string `gorm:"size:50;not null;uniqueIndex:uniq_work_language_analysis"` Analysis JSONB `gorm:"type:jsonb;default:'{}'"` WorkID uint `gorm:"index;uniqueIndex:uniq_work_language_analysis"` - Work *Work `gorm:"foreignKey:WorkID"` } type Gamification struct { BaseModel @@ -981,7 +870,6 @@ type Stats struct { UserID *uint User *User `gorm:"foreignKey:UserID"` WorkID *uint - Work *Work `gorm:"foreignKey:WorkID"` } type SearchDocument struct { BaseModel @@ -1002,7 +890,6 @@ type Emotion struct { UserID *uint User *User `gorm:"foreignKey:UserID"` WorkID *uint - Work *Work `gorm:"foreignKey:WorkID"` CollectionID *uint Collection *Collection `gorm:"foreignKey:CollectionID"` } @@ -1011,14 +898,12 @@ type Mood struct { Name string `gorm:"size:100;not null"` Description string `gorm:"type:text"` Language string `gorm:"size:50;not null"` - Works []*Work `gorm:"many2many:work_moods"` } type TopicCluster struct { BaseModel Name string `gorm:"size:100;not null"` Description string `gorm:"type:text"` Keywords string `gorm:"type:text"` - Works []*Work `gorm:"many2many:work_topic_clusters"` } type Edge struct { @@ -1039,7 +924,6 @@ type Embedding struct { Model string `gorm:"size:50;not null;uniqueIndex:uniq_embedding"` Dim int `gorm:"default:0"` WorkID *uint - Work *Work `gorm:"foreignKey:WorkID"` TranslationID *uint Translation *Translation `gorm:"foreignKey:TranslationID"` } @@ -1086,7 +970,6 @@ type EditorialWorkflow struct { Notes string `gorm:"type:text"` Language string `gorm:"size:50;not null"` WorkID *uint - Work *Work `gorm:"foreignKey:WorkID"` TranslationID *uint Translation *Translation `gorm:"foreignKey:TranslationID"` UserID uint @@ -1109,7 +992,6 @@ type Vote struct { UserID uint User *User `gorm:"foreignKey:UserID"` WorkID *uint - Work *Work `gorm:"foreignKey:WorkID"` TranslationID *uint Translation *Translation `gorm:"foreignKey:TranslationID"` CommentID *uint @@ -1122,7 +1004,6 @@ type Contributor struct { UserID *uint User *User `gorm:"foreignKey:UserID"` WorkID *uint - Work *Work `gorm:"foreignKey:WorkID"` TranslationID *uint Translation *Translation `gorm:"foreignKey:TranslationID"` } @@ -1140,7 +1021,6 @@ type HybridEntityWork struct { Name string `gorm:"size:100;not null"` Type string `gorm:"size:50"` WorkID *uint - Work *Work `gorm:"foreignKey:WorkID"` TranslationID *uint Translation *Translation `gorm:"foreignKey:TranslationID"` } diff --git a/internal/domain/interfaces.go b/internal/domain/interfaces.go index 17258ab..b9d04e4 100644 --- a/internal/domain/interfaces.go +++ b/internal/domain/interfaces.go @@ -234,16 +234,6 @@ type BaseRepository[T any] interface { WithTx(ctx context.Context, fn func(tx *gorm.DB) error) error } -// WorkRepository defines methods specific to Work. -type WorkRepository interface { - BaseRepository[Work] - FindByTitle(ctx context.Context, title string) ([]Work, error) - FindByAuthor(ctx context.Context, authorID uint) ([]Work, error) - FindByCategory(ctx context.Context, categoryID uint) ([]Work, error) - FindByLanguage(ctx context.Context, language string, page, pageSize int) (*PaginatedResult[Work], error) - GetWithTranslations(ctx context.Context, id uint) (*Work, error) - ListWithTranslations(ctx context.Context, page, pageSize int) (*PaginatedResult[Work], error) -} // AuthorRepository defines CRUD methods specific to Author. type AuthorRepository interface { diff --git a/internal/domain/search/client.go b/internal/domain/search/client.go index bd008d2..b05da9a 100644 --- a/internal/domain/search/client.go +++ b/internal/domain/search/client.go @@ -2,10 +2,10 @@ package search import ( "context" - "tercul/internal/domain" + "tercul/internal/domain/work" ) // SearchClient defines the interface for a search client. type SearchClient interface { - IndexWork(ctx context.Context, work *domain.Work, pipeline string) error + IndexWork(ctx context.Context, work *work.Work, pipeline string) error } \ No newline at end of file diff --git a/internal/domain/work/entity.go b/internal/domain/work/entity.go new file mode 100644 index 0000000..8fb5905 --- /dev/null +++ b/internal/domain/work/entity.go @@ -0,0 +1,116 @@ +package work + +import ( + "gorm.io/gorm" + "tercul/internal/domain" + "time" +) + +type WorkStatus string + +const ( + WorkStatusDraft WorkStatus = "draft" + WorkStatusPublished WorkStatus = "published" + WorkStatusArchived WorkStatus = "archived" + WorkStatusDeleted WorkStatus = "deleted" +) + +type WorkType string + +const ( + WorkTypePoetry WorkType = "poetry" + WorkTypeProse WorkType = "prose" + WorkTypeDrama WorkType = "drama" + WorkTypeEssay WorkType = "essay" + WorkTypeNovel WorkType = "novel" + WorkTypeShortStory WorkType = "short_story" + WorkTypeNovella WorkType = "novella" + WorkTypePlay WorkType = "play" + WorkTypeScript WorkType = "script" + WorkTypeOther WorkType = "other" +) + +type Work struct { + domain.TranslatableModel + Title string `gorm:"size:255;not null"` + Description string `gorm:"type:text"` + Type WorkType `gorm:"size:50;default:'other'"` + Status WorkStatus `gorm:"size:50;default:'draft'"` + PublishedAt *time.Time + Translations []*domain.Translation `gorm:"polymorphic:Translatable"` + Authors []*domain.Author `gorm:"many2many:work_authors"` + Tags []*domain.Tag `gorm:"many2many:work_tags"` + Categories []*domain.Category `gorm:"many2many:work_categories"` + Copyrights []*domain.Copyright `gorm:"many2many:work_copyrights;constraint:OnDelete:CASCADE"` + Monetizations []*domain.Monetization `gorm:"many2many:work_monetizations;constraint:OnDelete:CASCADE"` +} + +func (w *Work) BeforeSave(tx *gorm.DB) error { + if w.Title == "" { + w.Title = "Untitled Work" + } + return nil +} + +func (w *Work) GetID() uint { return w.ID } +func (w *Work) GetType() string { return "Work" } +func (w *Work) GetDefaultLanguage() string { return w.Language } + +type WorkStats struct { + domain.BaseModel + Views int64 `gorm:"default:0"` + Likes int64 `gorm:"default:0"` + Comments int64 `gorm:"default:0"` + Bookmarks int64 `gorm:"default:0"` + Shares int64 `gorm:"default:0"` + TranslationCount int64 `gorm:"default:0"` + ReadingTime int `gorm:"default:0"` + Complexity float64 `gorm:"type:decimal(5,2);default:0.0"` + Sentiment float64 `gorm:"type:decimal(5,2);default:0.0"` + WorkID uint `gorm:"uniqueIndex;index"` + Work *Work `gorm:"foreignKey:WorkID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"` +} + +type WorkSeries struct { + domain.BaseModel + WorkID uint `gorm:"index;uniqueIndex:uniq_work_series"` + Work *Work `gorm:"foreignKey:WorkID"` + SeriesID uint `gorm:"index;uniqueIndex:uniq_work_series"` + Series *domain.Series `gorm:"foreignKey:SeriesID"` + NumberInSeries int `gorm:"default:0"` +} + +type BookWork struct { + domain.BaseModel + BookID uint `gorm:"index;uniqueIndex:uniq_book_work"` + Book *domain.Book `gorm:"foreignKey:BookID"` + WorkID uint `gorm:"index;uniqueIndex:uniq_book_work"` + Work *Work `gorm:"foreignKey:WorkID"` + Order int `gorm:"default:0"` +} + +type WorkAuthor struct { + domain.BaseModel + WorkID uint `gorm:"index;uniqueIndex:uniq_work_author_role"` + Work *Work `gorm:"foreignKey:WorkID"` + AuthorID uint `gorm:"index;uniqueIndex:uniq_work_author_role"` + Author *domain.Author `gorm:"foreignKey:AuthorID"` + Role string `gorm:"size:50;default:'author';uniqueIndex:uniq_work_author_role"` + Ordinal int `gorm:"default:0"` +} + +type WorkCopyright struct { + WorkID uint `gorm:"primaryKey;index"` + CopyrightID uint `gorm:"primaryKey;index"` + CreatedAt time.Time +} + +func (WorkCopyright) TableName() string { return "work_copyrights" } + +type WorkMonetization struct { + WorkID uint `gorm:"primaryKey;index"` + MonetizationID uint `gorm:"primaryKey;index"` + CreatedAt time.Time +} + +func (WorkMonetization) TableName() string { return "work_monetizations" } \ No newline at end of file diff --git a/internal/domain/work/repo.go b/internal/domain/work/repo.go new file mode 100644 index 0000000..114e78e --- /dev/null +++ b/internal/domain/work/repo.go @@ -0,0 +1,17 @@ +package work + +import ( + "context" + "tercul/internal/domain" +) + +// WorkRepository defines methods specific to Work. +type WorkRepository interface { + domain.BaseRepository[Work] + FindByTitle(ctx context.Context, title string) ([]Work, error) + FindByAuthor(ctx context.Context, authorID uint) ([]Work, error) + FindByCategory(ctx context.Context, categoryID uint) ([]Work, error) + FindByLanguage(ctx context.Context, language string, page, pageSize int) (*domain.PaginatedResult[Work], error) + GetWithTranslations(ctx context.Context, id uint) (*Work, error) + ListWithTranslations(ctx context.Context, page, pageSize int) (*domain.PaginatedResult[Work], error) +} \ No newline at end of file diff --git a/internal/jobs/linguistics/analysis_repository.go b/internal/jobs/linguistics/analysis_repository.go index 0198768..de87268 100644 --- a/internal/jobs/linguistics/analysis_repository.go +++ b/internal/jobs/linguistics/analysis_repository.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "tercul/internal/domain" + "tercul/internal/domain/work" "gorm.io/gorm" "tercul/internal/platform/log" @@ -22,7 +23,7 @@ type AnalysisRepository interface { readabilityScore *domain.ReadabilityScore, languageAnalysis *domain.LanguageAnalysis) error // GetWorkByID fetches a work by ID - GetWorkByID(ctx context.Context, workID uint) (*domain.Work, error) + GetWorkByID(ctx context.Context, workID uint) (*work.Work, error) // GetAnalysisData fetches persisted analysis data for a work GetAnalysisData(ctx context.Context, workID uint) (*domain.TextMetadata, *domain.ReadabilityScore, *domain.LanguageAnalysis, error) @@ -45,8 +46,8 @@ func (r *GORMAnalysisRepository) StoreAnalysisResults(ctx context.Context, workI } // Determine language from the work record to avoid hardcoded defaults - var work domain.Work - if err := r.db.WithContext(ctx).First(&work, workID).Error; err != nil { + var workRecord work.Work + if err := r.db.WithContext(ctx).First(&workRecord, workID).Error; err != nil { log.LogError("Failed to fetch work for language", log.F("workID", workID), log.F("error", err)) @@ -56,7 +57,7 @@ func (r *GORMAnalysisRepository) StoreAnalysisResults(ctx context.Context, workI // Create text metadata textMetadata := &domain.TextMetadata{ WorkID: workID, - Language: work.Language, + Language: workRecord.Language, WordCount: result.WordCount, SentenceCount: result.SentenceCount, ParagraphCount: result.ParagraphCount, @@ -67,7 +68,7 @@ func (r *GORMAnalysisRepository) StoreAnalysisResults(ctx context.Context, workI // Create readability score readabilityScore := &domain.ReadabilityScore{ WorkID: workID, - Language: work.Language, + Language: workRecord.Language, Score: result.ReadabilityScore, Method: result.ReadabilityMethod, } @@ -75,7 +76,7 @@ func (r *GORMAnalysisRepository) StoreAnalysisResults(ctx context.Context, workI // Create language analysis languageAnalysis := &domain.LanguageAnalysis{ WorkID: workID, - Language: work.Language, + Language: workRecord.Language, Analysis: domain.JSONB{ "sentiment": result.Sentiment, "keywords": extractKeywordsAsJSON(result.Keywords), @@ -89,8 +90,8 @@ func (r *GORMAnalysisRepository) StoreAnalysisResults(ctx context.Context, workI // GetWorkContent retrieves content for a work from translations func (r *GORMAnalysisRepository) GetWorkContent(ctx context.Context, workID uint, language string) (string, error) { // First, get the work to determine its language - var work domain.Work - if err := r.db.First(&work, workID).Error; err != nil { + var workRecord work.Work + if err := r.db.First(&workRecord, workID).Error; err != nil { log.LogError("Failed to fetch work for content retrieval", log.F("workID", workID), log.F("error", err)) @@ -112,7 +113,7 @@ func (r *GORMAnalysisRepository) GetWorkContent(ctx context.Context, workID uint // Try work's language if err := r.db.Where("translatable_type = ? AND translatable_id = ? AND language = ?", - "Work", workID, work.Language).First(&translation).Error; err == nil { + "Work", workID, workRecord.Language).First(&translation).Error; err == nil { return translation.Content, nil } @@ -126,12 +127,12 @@ func (r *GORMAnalysisRepository) GetWorkContent(ctx context.Context, workID uint } // GetWorkByID fetches a work by ID -func (r *GORMAnalysisRepository) GetWorkByID(ctx context.Context, workID uint) (*domain.Work, error) { - var work domain.Work - if err := r.db.WithContext(ctx).First(&work, workID).Error; err != nil { +func (r *GORMAnalysisRepository) GetWorkByID(ctx context.Context, workID uint) (*work.Work, error) { + var workRecord work.Work + if err := r.db.WithContext(ctx).First(&workRecord, workID).Error; err != nil { return nil, fmt.Errorf("failed to fetch work: %w", err) } - return &work, nil + return &workRecord, nil } // GetAnalysisData fetches persisted analysis data for a work diff --git a/internal/jobs/linguistics/sync_job.go b/internal/jobs/linguistics/sync_job.go index 8b1c042..652e6cf 100644 --- a/internal/jobs/linguistics/sync_job.go +++ b/internal/jobs/linguistics/sync_job.go @@ -6,6 +6,7 @@ import ( "fmt" "log" "tercul/internal/domain" + "tercul/internal/domain/work" "time" "github.com/hibiken/asynq" @@ -60,7 +61,7 @@ func (j *LinguisticSyncJob) EnqueueAnalysisForAllWorks() error { log.Println("Enqueueing linguistic analysis jobs for all works...") var workIDs []uint - if err := j.DB.Model(&domain.Work{}).Pluck("id", &workIDs).Error; err != nil { + if err := j.DB.Model(&work.Work{}).Pluck("id", &workIDs).Error; err != nil { return fmt.Errorf("error fetching work IDs: %w", err) } diff --git a/internal/platform/search/weaviate_client.go b/internal/platform/search/weaviate_client.go index 44c1fc2..07f8b6a 100644 --- a/internal/platform/search/weaviate_client.go +++ b/internal/platform/search/weaviate_client.go @@ -4,7 +4,7 @@ import ( "context" "fmt" "log" - "tercul/internal/domain" + "tercul/internal/domain/work" "time" "github.com/weaviate/weaviate-go-client/v5/weaviate" @@ -13,7 +13,7 @@ import ( var Client *weaviate.Client // UpsertWork inserts or updates a Work object in Weaviate -func UpsertWork(client *weaviate.Client, work domain.Work) error { +func UpsertWork(client *weaviate.Client, work work.Work) error { // Create a properties map with the fields that exist in the Work model properties := map[string]interface{}{ "language": work.Language, diff --git a/internal/platform/search/weaviate_wrapper.go b/internal/platform/search/weaviate_wrapper.go index 20563ce..a5e834d 100644 --- a/internal/platform/search/weaviate_wrapper.go +++ b/internal/platform/search/weaviate_wrapper.go @@ -3,14 +3,14 @@ package search import ( "context" "fmt" - "tercul/internal/domain" + "tercul/internal/domain/work" "time" "github.com/weaviate/weaviate-go-client/v5/weaviate" ) type WeaviateWrapper interface { - IndexWork(ctx context.Context, work *domain.Work, content string) error + IndexWork(ctx context.Context, work *work.Work, content string) error } type weaviateWrapper struct { @@ -21,7 +21,7 @@ func NewWeaviateWrapper(client *weaviate.Client) WeaviateWrapper { return &weaviateWrapper{client: client} } -func (w *weaviateWrapper) IndexWork(ctx context.Context, work *domain.Work, content string) error { +func (w *weaviateWrapper) IndexWork(ctx context.Context, work *work.Work, content string) error { properties := map[string]interface{}{ "language": work.Language, "title": work.Title, diff --git a/internal/testutil/integration_test_utils.go b/internal/testutil/integration_test_utils.go index 4be68b2..34f5cbb 100644 --- a/internal/testutil/integration_test_utils.go +++ b/internal/testutil/integration_test_utils.go @@ -11,6 +11,7 @@ import ( "tercul/internal/data/sql" "tercul/internal/domain" "tercul/internal/domain/search" + "tercul/internal/domain/work" "tercul/internal/jobs/linguistics" "time" @@ -23,7 +24,7 @@ import ( // mockSearchClient is a mock implementation of the SearchClient interface. type mockSearchClient struct{} -func (m *mockSearchClient) IndexWork(ctx context.Context, work *domain.Work, pipeline string) error { +func (m *mockSearchClient) IndexWork(ctx context.Context, work *work.Work, pipeline string) error { return nil } @@ -54,8 +55,8 @@ func (m *mockAnalyticsService) IncrementTranslationComments(ctx context.Context, func (m *mockAnalyticsService) IncrementTranslationShares(ctx context.Context, translationID uint) error { return nil } -func (m *mockAnalyticsService) GetOrCreateWorkStats(ctx context.Context, workID uint) (*domain.WorkStats, error) { - return &domain.WorkStats{}, nil +func (m *mockAnalyticsService) GetOrCreateWorkStats(ctx context.Context, workID uint) (*work.WorkStats, error) { + return &work.WorkStats{}, nil } func (m *mockAnalyticsService) GetOrCreateTranslationStats(ctx context.Context, translationID uint) (*domain.TranslationStats, error) { return &domain.TranslationStats{}, nil @@ -73,7 +74,7 @@ func (m *mockAnalyticsService) UpdateUserEngagement(ctx context.Context, userID return nil } func (m *mockAnalyticsService) UpdateTrending(ctx context.Context) error { return nil } -func (m *mockAnalyticsService) GetTrendingWorks(ctx context.Context, timePeriod string, limit int) ([]*domain.Work, error) { +func (m *mockAnalyticsService) GetTrendingWorks(ctx context.Context, timePeriod string, limit int) ([]*work.Work, error) { return nil, nil } @@ -139,13 +140,13 @@ func (s *IntegrationTestSuite) SetupSuite(config *TestConfig) { s.DB = db db.AutoMigrate( - &domain.Work{}, &domain.User{}, &domain.Author{}, &domain.Translation{}, + &work.Work{}, &domain.User{}, &domain.Author{}, &domain.Translation{}, &domain.Comment{}, &domain.Like{}, &domain.Bookmark{}, &domain.Collection{}, &domain.Tag{}, &domain.Category{}, &domain.Book{}, &domain.Publisher{}, &domain.Source{}, &domain.Copyright{}, &domain.Monetization{}, - &domain.WorkStats{}, &domain.Trending{}, &domain.UserSession{}, &domain.Localization{}, + &work.WorkStats{}, &domain.Trending{}, &domain.UserSession{}, &domain.Localization{}, &domain.LanguageAnalysis{}, &domain.TextMetadata{}, &domain.ReadabilityScore{}, - &domain.TranslationStats{}, &TestEntity{}, + &domain.TranslationStats{}, &TestEntity{}, &domain.CollectionWork{}, ) repos := sql.NewRepositories(s.DB) @@ -184,8 +185,8 @@ func (s *IntegrationTestSuite) SetupTest() { } // CreateTestWork creates a test work with optional content -func (s *IntegrationTestSuite) CreateTestWork(title, language string, content string) *domain.Work { - work := &domain.Work{ +func (s *IntegrationTestSuite) CreateTestWork(title, language string, content string) *work.Work { + work := &work.Work{ Title: title, TranslatableModel: domain.TranslatableModel{ Language: language, diff --git a/internal/testutil/mock_analytics_service.go b/internal/testutil/mock_analytics_service.go index 356c940..75e48ba 100644 --- a/internal/testutil/mock_analytics_service.go +++ b/internal/testutil/mock_analytics_service.go @@ -3,6 +3,7 @@ package testutil import ( "context" "tercul/internal/domain" + "tercul/internal/domain/work" "time" "github.com/stretchr/testify/mock" @@ -13,12 +14,12 @@ type MockAnalyticsService struct { mock.Mock } -func (m *MockAnalyticsService) GetTrendingWorks(ctx context.Context, timePeriod string, limit int) ([]*domain.Work, error) { +func (m *MockAnalyticsService) GetTrendingWorks(ctx context.Context, timePeriod string, limit int) ([]*work.Work, error) { args := m.Called(ctx, timePeriod, limit) if args.Get(0) == nil { return nil, args.Error(1) } - return args.Get(0).([]*domain.Work), args.Error(1) + return args.Get(0).([]*work.Work), args.Error(1) } func (m *MockAnalyticsService) UpdateTrending(ctx context.Context) error { @@ -46,12 +47,12 @@ func (m *MockAnalyticsService) IncrementWorkBookmarks(ctx context.Context, workI m.Called(ctx, workID) } -func (m *MockAnalyticsService) GetOrCreateWorkStats(ctx context.Context, workID uint) (*domain.WorkStats, error) { +func (m *MockAnalyticsService) GetOrCreateWorkStats(ctx context.Context, workID uint) (*work.WorkStats, error) { args := m.Called(ctx, workID) if args.Get(0) == nil { return nil, args.Error(1) } - return args.Get(0).(*domain.WorkStats), args.Error(1) + return args.Get(0).(*work.WorkStats), args.Error(1) } func (m *MockAnalyticsService) GetOrCreateTranslationStats(ctx context.Context, translationID uint) (*domain.TranslationStats, error) { @@ -85,7 +86,7 @@ func (m *MockAnalyticsService) IncrementTranslationCounter(ctx context.Context, return args.Error(0) } -func (m *MockAnalyticsService) UpdateWorkStats(ctx context.Context, workID uint, stats domain.WorkStats) error { +func (m *MockAnalyticsService) UpdateWorkStats(ctx context.Context, workID uint, stats work.WorkStats) error { args := m.Called(ctx, workID, stats) return args.Error(0) } diff --git a/internal/testutil/mock_weaviate_wrapper.go b/internal/testutil/mock_weaviate_wrapper.go index 1542f41..78d19e9 100644 --- a/internal/testutil/mock_weaviate_wrapper.go +++ b/internal/testutil/mock_weaviate_wrapper.go @@ -2,14 +2,14 @@ package testutil import ( "context" - "tercul/internal/domain" + "tercul/internal/domain/work" ) type MockWeaviateWrapper struct { - IndexWorkFunc func(ctx context.Context, work *domain.Work, content string) error + IndexWorkFunc func(ctx context.Context, work *work.Work, content string) error } -func (m *MockWeaviateWrapper) IndexWork(ctx context.Context, work *domain.Work, content string) error { +func (m *MockWeaviateWrapper) IndexWork(ctx context.Context, work *work.Work, content string) error { if m.IndexWorkFunc != nil { return m.IndexWorkFunc(ctx, work, content) } diff --git a/internal/testutil/mock_work_repository.go b/internal/testutil/mock_work_repository.go index 33cbb1d..974a3a4 100644 --- a/internal/testutil/mock_work_repository.go +++ b/internal/testutil/mock_work_repository.go @@ -3,6 +3,7 @@ package testutil import ( "context" "tercul/internal/domain" + "tercul/internal/domain/work" "github.com/stretchr/testify/mock" "gorm.io/gorm" @@ -11,23 +12,23 @@ import ( // MockWorkRepository is a mock implementation of the WorkRepository interface. type MockWorkRepository struct { mock.Mock - Works []*domain.Work + Works []*work.Work } // NewMockWorkRepository creates a new MockWorkRepository. func NewMockWorkRepository() *MockWorkRepository { - return &MockWorkRepository{Works: []*domain.Work{}} + return &MockWorkRepository{Works: []*work.Work{}} } // Create adds a new work to the mock repository. -func (m *MockWorkRepository) Create(ctx context.Context, work *domain.Work) error { +func (m *MockWorkRepository) Create(ctx context.Context, work *work.Work) error { work.ID = uint(len(m.Works) + 1) m.Works = append(m.Works, work) return nil } // GetByID retrieves a work by its ID from the mock repository. -func (m *MockWorkRepository) GetByID(ctx context.Context, id uint) (*domain.Work, error) { +func (m *MockWorkRepository) GetByID(ctx context.Context, id uint) (*work.Work, error) { for _, w := range m.Works { if w.ID == id { return w, nil @@ -43,31 +44,31 @@ func (m *MockWorkRepository) Exists(ctx context.Context, id uint) (bool, error) } // The rest of the WorkRepository and BaseRepository methods can be stubbed out. -func (m *MockWorkRepository) FindByTitle(ctx context.Context, title string) ([]domain.Work, error) { +func (m *MockWorkRepository) FindByTitle(ctx context.Context, title string) ([]work.Work, error) { panic("not implemented") } -func (m *MockWorkRepository) FindByAuthor(ctx context.Context, authorID uint) ([]domain.Work, error) { +func (m *MockWorkRepository) FindByAuthor(ctx context.Context, authorID uint) ([]work.Work, error) { panic("not implemented") } -func (m *MockWorkRepository) FindByCategory(ctx context.Context, categoryID uint) ([]domain.Work, error) { +func (m *MockWorkRepository) FindByCategory(ctx context.Context, categoryID uint) ([]work.Work, error) { panic("not implemented") } -func (m *MockWorkRepository) FindByLanguage(ctx context.Context, language string, page, pageSize int) (*domain.PaginatedResult[domain.Work], error) { +func (m *MockWorkRepository) FindByLanguage(ctx context.Context, language string, page, pageSize int) (*domain.PaginatedResult[work.Work], error) { panic("not implemented") } -func (m *MockWorkRepository) GetWithTranslations(ctx context.Context, id uint) (*domain.Work, error) { +func (m *MockWorkRepository) GetWithTranslations(ctx context.Context, id uint) (*work.Work, error) { return m.GetByID(ctx, id) } -func (m *MockWorkRepository) ListWithTranslations(ctx context.Context, page, pageSize int) (*domain.PaginatedResult[domain.Work], error) { +func (m *MockWorkRepository) ListWithTranslations(ctx context.Context, page, pageSize int) (*domain.PaginatedResult[work.Work], error) { panic("not implemented") } -func (m *MockWorkRepository) CreateInTx(ctx context.Context, tx *gorm.DB, entity *domain.Work) error { +func (m *MockWorkRepository) CreateInTx(ctx context.Context, tx *gorm.DB, entity *work.Work) error { return m.Create(ctx, entity) } -func (m *MockWorkRepository) GetByIDWithOptions(ctx context.Context, id uint, options *domain.QueryOptions) (*domain.Work, error) { +func (m *MockWorkRepository) GetByIDWithOptions(ctx context.Context, id uint, options *domain.QueryOptions) (*work.Work, error) { return m.GetByID(ctx, id) } -func (m *MockWorkRepository) Update(ctx context.Context, entity *domain.Work) error { +func (m *MockWorkRepository) Update(ctx context.Context, entity *work.Work) error { for i, w := range m.Works { if w.ID == entity.ID { m.Works[i] = entity @@ -76,7 +77,7 @@ func (m *MockWorkRepository) Update(ctx context.Context, entity *domain.Work) er } return gorm.ErrRecordNotFound } -func (m *MockWorkRepository) UpdateInTx(ctx context.Context, tx *gorm.DB, entity *domain.Work) error { +func (m *MockWorkRepository) UpdateInTx(ctx context.Context, tx *gorm.DB, entity *work.Work) error { return m.Update(ctx, entity) } func (m *MockWorkRepository) Delete(ctx context.Context, id uint) error { @@ -91,14 +92,14 @@ func (m *MockWorkRepository) Delete(ctx context.Context, id uint) error { func (m *MockWorkRepository) DeleteInTx(ctx context.Context, tx *gorm.DB, id uint) error { return m.Delete(ctx, id) } -func (m *MockWorkRepository) List(ctx context.Context, page, pageSize int) (*domain.PaginatedResult[domain.Work], error) { +func (m *MockWorkRepository) List(ctx context.Context, page, pageSize int) (*domain.PaginatedResult[work.Work], error) { panic("not implemented") } -func (m *MockWorkRepository) ListWithOptions(ctx context.Context, options *domain.QueryOptions) ([]domain.Work, error) { +func (m *MockWorkRepository) ListWithOptions(ctx context.Context, options *domain.QueryOptions) ([]work.Work, error) { panic("not implemented") } -func (m *MockWorkRepository) ListAll(ctx context.Context) ([]domain.Work, error) { - var works []domain.Work +func (m *MockWorkRepository) ListAll(ctx context.Context) ([]work.Work, error) { + var works []work.Work for _, w := range m.Works { works = append(works, *w) } @@ -110,10 +111,10 @@ func (m *MockWorkRepository) Count(ctx context.Context) (int64, error) { func (m *MockWorkRepository) CountWithOptions(ctx context.Context, options *domain.QueryOptions) (int64, error) { panic("not implemented") } -func (m *MockWorkRepository) FindWithPreload(ctx context.Context, preloads []string, id uint) (*domain.Work, error) { +func (m *MockWorkRepository) FindWithPreload(ctx context.Context, preloads []string, id uint) (*work.Work, error) { return m.GetByID(ctx, id) } -func (m *MockWorkRepository) GetAllForSync(ctx context.Context, batchSize, offset int) ([]domain.Work, error) { +func (m *MockWorkRepository) GetAllForSync(ctx context.Context, batchSize, offset int) ([]work.Work, error) { panic("not implemented") } func (m *MockWorkRepository) BeginTx(ctx context.Context) (*gorm.DB, error) { diff --git a/internal/testutil/simple_test_utils.go b/internal/testutil/simple_test_utils.go index 627e8cc..861f2fd 100644 --- a/internal/testutil/simple_test_utils.go +++ b/internal/testutil/simple_test_utils.go @@ -8,6 +8,7 @@ import ( "tercul/internal/app/work" "tercul/internal/domain" domain_localization "tercul/internal/domain/localization" + domain_work "tercul/internal/domain/work" "github.com/stretchr/testify/suite" ) @@ -24,7 +25,7 @@ type SimpleTestSuite struct { type MockSearchClient struct{} // IndexWork is the mock implementation of the IndexWork method. -func (m *MockSearchClient) IndexWork(ctx context.Context, work *domain.Work, pipeline string) error { +func (m *MockSearchClient) IndexWork(ctx context.Context, work *domain_work.Work, pipeline string) error { return nil } @@ -74,8 +75,8 @@ func (s *SimpleTestSuite) GetResolver() *graph.Resolver { } // CreateTestWork creates a test work with optional content -func (s *SimpleTestSuite) CreateTestWork(title, language string, content string) *domain.Work { - work := &domain.Work{ +func (s *SimpleTestSuite) CreateTestWork(title, language string, content string) *domain_work.Work { + work := &domain_work.Work{ Title: title, TranslatableModel: domain.TranslatableModel{Language: language}, }