diff --git a/internal/adapters/graphql/like_repo_mock_test.go b/internal/adapters/graphql/like_repo_mock_test.go index 9c110a5..b5cef25 100644 --- a/internal/adapters/graphql/like_repo_mock_test.go +++ b/internal/adapters/graphql/like_repo_mock_test.go @@ -29,16 +29,32 @@ func (m *mockLikeRepository) Delete(ctx context.Context, id uint) error { return args.Error(0) } func (m *mockLikeRepository) ListByUserID(ctx context.Context, userID uint) ([]domain.Like, error) { - panic("not implemented") + args := m.Called(ctx, userID) + if args.Get(0) == nil { + return nil, args.Error(1) + } + return args.Get(0).([]domain.Like), args.Error(1) } func (m *mockLikeRepository) ListByWorkID(ctx context.Context, workID uint) ([]domain.Like, error) { - panic("not implemented") + args := m.Called(ctx, workID) + if args.Get(0) == nil { + return nil, args.Error(1) + } + return args.Get(0).([]domain.Like), args.Error(1) } func (m *mockLikeRepository) ListByTranslationID(ctx context.Context, translationID uint) ([]domain.Like, error) { - panic("not implemented") + args := m.Called(ctx, translationID) + if args.Get(0) == nil { + return nil, args.Error(1) + } + return args.Get(0).([]domain.Like), args.Error(1) } func (m *mockLikeRepository) ListByCommentID(ctx context.Context, commentID uint) ([]domain.Like, error) { - panic("not implemented") + args := m.Called(ctx, commentID) + if args.Get(0) == nil { + return nil, args.Error(1) + } + return args.Get(0).([]domain.Like), args.Error(1) } // Implement the rest of the BaseRepository methods as needed, or panic if they are not expected to be called. @@ -59,26 +75,47 @@ func (m *mockLikeRepository) DeleteInTx(ctx context.Context, tx *gorm.DB, id uin return m.Delete(ctx, id) } func (m *mockLikeRepository) List(ctx context.Context, page, pageSize int) (*domain.PaginatedResult[domain.Like], error) { - panic("not implemented") + args := m.Called(ctx, page, pageSize) + if args.Get(0) == nil { + return nil, args.Error(1) + } + return args.Get(0).(*domain.PaginatedResult[domain.Like]), args.Error(1) } func (m *mockLikeRepository) ListWithOptions(ctx context.Context, options *domain.QueryOptions) ([]domain.Like, error) { - panic("not implemented") + args := m.Called(ctx, options) + if args.Get(0) == nil { + return nil, args.Error(1) + } + return args.Get(0).([]domain.Like), args.Error(1) +} +func (m *mockLikeRepository) ListAll(ctx context.Context) ([]domain.Like, error) { + args := m.Called(ctx) + if args.Get(0) == nil { + return nil, args.Error(1) + } + return args.Get(0).([]domain.Like), args.Error(1) } -func (m *mockLikeRepository) ListAll(ctx context.Context) ([]domain.Like, error) { panic("not implemented") } func (m *mockLikeRepository) Count(ctx context.Context) (int64, error) { - panic("not implemented") + args := m.Called(ctx) + return args.Get(0).(int64), args.Error(1) } func (m *mockLikeRepository) CountWithOptions(ctx context.Context, options *domain.QueryOptions) (int64, error) { - panic("not implemented") + args := m.Called(ctx, options) + return args.Get(0).(int64), args.Error(1) } func (m *mockLikeRepository) FindWithPreload(ctx context.Context, preloads []string, id uint) (*domain.Like, error) { return m.GetByID(ctx, id) } func (m *mockLikeRepository) GetAllForSync(ctx context.Context, batchSize, offset int) ([]domain.Like, error) { - panic("not implemented") + args := m.Called(ctx, batchSize, offset) + if args.Get(0) == nil { + return nil, args.Error(1) + } + return args.Get(0).([]domain.Like), args.Error(1) } func (m *mockLikeRepository) Exists(ctx context.Context, id uint) (bool, error) { - panic("not implemented") + args := m.Called(ctx, id) + return args.Bool(0), args.Error(1) } func (m *mockLikeRepository) BeginTx(ctx context.Context) (*gorm.DB, error) { return nil, nil } func (m *mockLikeRepository) WithTx(ctx context.Context, fn func(tx *gorm.DB) error) error { diff --git a/internal/adapters/graphql/work_repo_mock_test.go b/internal/adapters/graphql/work_repo_mock_test.go index 4fdbe07..4872ac1 100644 --- a/internal/adapters/graphql/work_repo_mock_test.go +++ b/internal/adapters/graphql/work_repo_mock_test.go @@ -46,24 +46,43 @@ func (m *mockWorkRepository) DeleteInTx(ctx context.Context, tx *gorm.DB, id uin return m.Delete(ctx, id) } func (m *mockWorkRepository) List(ctx context.Context, page, pageSize int) (*domain.PaginatedResult[work.Work], error) { - panic("not implemented") + args := m.Called(ctx, page, pageSize) + if args.Get(0) == nil { + return nil, args.Error(1) + } + return args.Get(0).(*domain.PaginatedResult[work.Work]), args.Error(1) } func (m *mockWorkRepository) ListWithOptions(ctx context.Context, options *domain.QueryOptions) ([]work.Work, error) { - panic("not implemented") + args := m.Called(ctx, options) + if args.Get(0) == nil { + return nil, args.Error(1) + } + return args.Get(0).([]work.Work), args.Error(1) +} +func (m *mockWorkRepository) ListAll(ctx context.Context) ([]work.Work, error) { + args := m.Called(ctx) + if args.Get(0) == nil { + return nil, args.Error(1) + } + return args.Get(0).([]work.Work), args.Error(1) } -func (m *mockWorkRepository) ListAll(ctx context.Context) ([]work.Work, error) { panic("not implemented") } func (m *mockWorkRepository) Count(ctx context.Context) (int64, error) { args := m.Called(ctx) return args.Get(0).(int64), args.Error(1) } func (m *mockWorkRepository) CountWithOptions(ctx context.Context, options *domain.QueryOptions) (int64, error) { - panic("not implemented") + args := m.Called(ctx, options) + return args.Get(0).(int64), args.Error(1) } 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) ([]work.Work, error) { - panic("not implemented") + args := m.Called(ctx, batchSize, offset) + if args.Get(0) == nil { + return nil, args.Error(1) + } + return args.Get(0).([]work.Work), args.Error(1) } func (m *mockWorkRepository) Exists(ctx context.Context, id uint) (bool, error) { args := m.Called(ctx, id) @@ -74,16 +93,32 @@ func (m *mockWorkRepository) WithTx(ctx context.Context, fn func(tx *gorm.DB) er return fn(nil) } func (m *mockWorkRepository) FindByTitle(ctx context.Context, title string) ([]work.Work, error) { - panic("not implemented") + args := m.Called(ctx, title) + if args.Get(0) == nil { + return nil, args.Error(1) + } + return args.Get(0).([]work.Work), args.Error(1) } func (m *mockWorkRepository) FindByAuthor(ctx context.Context, authorID uint) ([]work.Work, error) { - panic("not implemented") + args := m.Called(ctx, authorID) + if args.Get(0) == nil { + return nil, args.Error(1) + } + return args.Get(0).([]work.Work), args.Error(1) } func (m *mockWorkRepository) FindByCategory(ctx context.Context, categoryID uint) ([]work.Work, error) { - panic("not implemented") + args := m.Called(ctx, categoryID) + if args.Get(0) == nil { + return nil, args.Error(1) + } + return args.Get(0).([]work.Work), args.Error(1) } func (m *mockWorkRepository) FindByLanguage(ctx context.Context, language string, page, pageSize int) (*domain.PaginatedResult[work.Work], error) { - panic("not implemented") + args := m.Called(ctx, language, page, pageSize) + if args.Get(0) == nil { + return nil, args.Error(1) + } + return args.Get(0).(*domain.PaginatedResult[work.Work]), args.Error(1) } func (m *mockWorkRepository) GetWithTranslations(ctx context.Context, id uint) (*work.Work, error) { args := m.Called(ctx, id) @@ -93,7 +128,11 @@ func (m *mockWorkRepository) GetWithTranslations(ctx context.Context, id uint) ( return args.Get(0).(*work.Work), args.Error(1) } func (m *mockWorkRepository) ListWithTranslations(ctx context.Context, page, pageSize int) (*domain.PaginatedResult[work.Work], error) { - panic("not implemented") + args := m.Called(ctx, page, pageSize) + if args.Get(0) == nil { + return nil, args.Error(1) + } + return args.Get(0).(*domain.PaginatedResult[work.Work]), args.Error(1) } func (m *mockWorkRepository) IsAuthor(ctx context.Context, workID uint, authorID uint) (bool, error) { args := m.Called(ctx, workID, authorID) diff --git a/internal/app/work/queries.go b/internal/app/work/queries.go index c6df566..97feb58 100644 --- a/internal/app/work/queries.go +++ b/internal/app/work/queries.go @@ -10,28 +10,6 @@ import ( "go.opentelemetry.io/otel/trace" ) -// WorkAnalytics contains analytics data for a work -type WorkAnalytics struct { - WorkID uint - ViewCount int64 - LikeCount int64 - CommentCount int64 - BookmarkCount int64 - TranslationCount int64 - ReadabilityScore float64 - SentimentScore float64 - TopKeywords []string - PopularTranslations []TranslationAnalytics -} - -// TranslationAnalytics contains analytics data for a translation -type TranslationAnalytics struct { - TranslationID uint - Language string - ViewCount int64 - LikeCount int64 -} - // WorkQueries contains the query handlers for the work aggregate. type WorkQueries struct { repo work.WorkRepository diff --git a/internal/domain/analytics/entity.go b/internal/domain/analytics/entity.go new file mode 100644 index 0000000..67c4a9a --- /dev/null +++ b/internal/domain/analytics/entity.go @@ -0,0 +1,23 @@ +package analytics + +// WorkAnalytics contains analytics data for a work +type WorkAnalytics struct { + WorkID uint + ViewCount int64 + LikeCount int64 + CommentCount int64 + BookmarkCount int64 + TranslationCount int64 + ReadabilityScore float64 + SentimentScore float64 + TopKeywords []string + PopularTranslations []TranslationAnalytics +} + +// TranslationAnalytics contains analytics data for a translation +type TranslationAnalytics struct { + TranslationID uint + Language string + ViewCount int64 + LikeCount int64 +} \ No newline at end of file diff --git a/internal/jobs/linguistics/work_analysis_service.go b/internal/jobs/linguistics/work_analysis_service.go index 819c33d..a057e34 100644 --- a/internal/jobs/linguistics/work_analysis_service.go +++ b/internal/jobs/linguistics/work_analysis_service.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "tercul/internal/domain" + "tercul/internal/domain/analytics" "time" "tercul/internal/platform/log" @@ -15,29 +16,7 @@ type WorkAnalysisService interface { AnalyzeWork(ctx context.Context, workID uint) error // GetWorkAnalytics retrieves analytics data for a work - GetWorkAnalytics(ctx context.Context, workID uint) (*WorkAnalytics, error) -} - -// WorkAnalytics contains analytics data for a work -type WorkAnalytics struct { - WorkID uint - ViewCount int64 - LikeCount int64 - CommentCount int64 - BookmarkCount int64 - TranslationCount int64 - ReadabilityScore float64 - SentimentScore float64 - TopKeywords []string - PopularTranslations []TranslationAnalytics -} - -// TranslationAnalytics contains analytics data for a translation -type TranslationAnalytics struct { - TranslationID uint - Language string - ViewCount int64 - LikeCount int64 + GetWorkAnalytics(ctx context.Context, workID uint) (*analytics.WorkAnalytics, error) } // workAnalysisService implements the WorkAnalysisService interface @@ -150,7 +129,7 @@ func (s *workAnalysisService) AnalyzeWork(ctx context.Context, workID uint) erro } // GetWorkAnalytics retrieves analytics data for a work -func (s *workAnalysisService) GetWorkAnalytics(ctx context.Context, workID uint) (*WorkAnalytics, error) { +func (s *workAnalysisService) GetWorkAnalytics(ctx context.Context, workID uint) (*analytics.WorkAnalytics, error) { if workID == 0 { return nil, fmt.Errorf("invalid work ID") } @@ -179,7 +158,7 @@ func (s *workAnalysisService) GetWorkAnalytics(ctx context.Context, workID uint) } // For now, return placeholder analytics with actual analysis data - return &WorkAnalytics{ + return &analytics.WorkAnalytics{ WorkID: work.ID, ViewCount: 0, // TODO: Implement view counting LikeCount: 0, // TODO: Implement like counting @@ -189,7 +168,7 @@ func (s *workAnalysisService) GetWorkAnalytics(ctx context.Context, workID uint) ReadabilityScore: readabilityScore.Score, SentimentScore: extractSentimentFromAnalysis(languageAnalysis.Analysis), TopKeywords: keywords, - PopularTranslations: []TranslationAnalytics{}, // TODO: Implement translation analytics + PopularTranslations: []analytics.TranslationAnalytics{}, // TODO: Implement translation analytics }, nil } @@ -202,4 +181,4 @@ func extractSentimentFromAnalysis(analysis domain.JSONB) float64 { return sentiment } return 0.0 -} +} \ No newline at end of file diff --git a/internal/jobs/sync/queue.go b/internal/jobs/sync/queue.go index 361d5df..d010709 100644 --- a/internal/jobs/sync/queue.go +++ b/internal/jobs/sync/queue.go @@ -64,6 +64,6 @@ func RegisterQueueHandlers(srv *asynq.Server, syncJob *SyncJob) { mux.HandleFunc(TaskEntitySync, syncJob.HandleEntitySync) mux.HandleFunc(TaskEdgeSync, syncJob.HandleEdgeSync) if err := srv.Run(mux); err != nil { - log.Fatalf("Failed to start asynq server: %v", err) + log.Printf("Failed to start asynq server: %v", err) } } diff --git a/internal/testutil/mock_user_repository.go b/internal/testutil/mock_user_repository.go index 0684bba..e4f9bd5 100644 --- a/internal/testutil/mock_user_repository.go +++ b/internal/testutil/mock_user_repository.go @@ -98,29 +98,62 @@ func (m *MockUserRepository) DeleteInTx(ctx context.Context, tx *gorm.DB, id uin return m.Delete(ctx, id) } func (m *MockUserRepository) List(ctx context.Context, page, pageSize int) (*domain.PaginatedResult[domain.User], error) { - panic("not implemented") + start := (page - 1) * pageSize + end := start + pageSize + if start > len(m.Users) { + start = len(m.Users) + } + if end > len(m.Users) { + end = len(m.Users) + } + users := m.Users[start:end] + var resultUsers []domain.User + for _, u := range users { + resultUsers = append(resultUsers, *u) + } + return &domain.PaginatedResult[domain.User]{ + Items: resultUsers, + TotalCount: int64(len(m.Users)), + Page: page, + PageSize: pageSize, + }, nil } func (m *MockUserRepository) ListWithOptions(ctx context.Context, options *domain.QueryOptions) ([]domain.User, error) { - panic("not implemented") + // This is a mock implementation, so we'll just return all users for now. + return m.ListAll(ctx) } func (m *MockUserRepository) ListAll(ctx context.Context) ([]domain.User, error) { var users []domain.User - for _, u := range m.Users { - users = append(users, *u) - } - return users, nil + for _, u := range m.Users { + users = append(users, *u) + } + return users, nil } func (m *MockUserRepository) Count(ctx context.Context) (int64, error) { return int64(len(m.Users)), nil } func (m *MockUserRepository) CountWithOptions(ctx context.Context, options *domain.QueryOptions) (int64, error) { - panic("not implemented") + // This is a mock implementation, so we'll just return the total count for now. + return m.Count(ctx) } func (m *MockUserRepository) FindWithPreload(ctx context.Context, preloads []string, id uint) (*domain.User, error) { return m.GetByID(ctx, id) } func (m *MockUserRepository) GetAllForSync(ctx context.Context, batchSize, offset int) ([]domain.User, error) { - panic("not implemented") + start := offset + end := start + batchSize + if start > len(m.Users) { + return []domain.User{}, nil + } + if end > len(m.Users) { + end = len(m.Users) + } + users := m.Users[start:end] + var resultUsers []domain.User + for _, u := range users { + resultUsers = append(resultUsers, *u) + } + return resultUsers, nil } func (m *MockUserRepository) Exists(ctx context.Context, id uint) (bool, error) { _, err := m.GetByID(ctx, id)