mirror of
https://github.com/SamyRai/tercul-backend.git
synced 2025-12-27 02:51:34 +00:00
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.
This commit is contained in:
parent
c26d86ae80
commit
06e6e2be85
@ -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
|
||||
|
||||
@ -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,
|
||||
|
||||
22
internal/app/analytics/interfaces.go
Normal file
22
internal/app/analytics/interfaces.go
Normal file
@ -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)
|
||||
}
|
||||
@ -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(),
|
||||
|
||||
@ -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))
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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 := ""
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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")
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -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")
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
@ -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),
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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))
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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))
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
@ -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"`
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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
|
||||
}
|
||||
116
internal/domain/work/entity.go
Normal file
116
internal/domain/work/entity.go
Normal file
@ -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" }
|
||||
17
internal/domain/work/repo.go
Normal file
17
internal/domain/work/repo.go
Normal file
@ -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)
|
||||
}
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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},
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user