Refactor: In-progress refactoring to fix build.

This commit includes the following changes:
- Refactored all data repositories in `internal/data/sql/` to use a consistent `sql` package and to align with the new `domain` models.
- Fixed the GraphQL structure by moving the server creation logic from `internal/app` to `cmd/api`, which resolved an import cycle.
- Corrected numerous incorrect import paths for packages like `graph`, `linguistics`, `syncjob`, and the legacy `models` package.
- Resolved several package and function redeclaration errors.
- Removed legacy migration code.
This commit is contained in:
google-labs-jules[bot] 2025-09-05 15:11:30 +00:00
parent 97d53f5491
commit 8797cec718
83 changed files with 1114 additions and 1405 deletions

View File

@ -11,7 +11,10 @@ import (
"tercul/internal/platform/log" "tercul/internal/platform/log"
"time" "time"
"github.com/99designs/gqlgen/graphql/playground"
"github.com/hibiken/asynq" "github.com/hibiken/asynq"
graph "tercul/internal/adapters/graphql"
"tercul/internal/platform/auth"
) )
// main is the entry point for the Tercul application. // main is the entry point for the Tercul application.
@ -39,19 +42,44 @@ func main() {
serverFactory := app.NewServerFactory(appBuilder) serverFactory := app.NewServerFactory(appBuilder)
// Create servers // Create servers
graphQLServer, err := serverFactory.CreateGraphQLServer()
if err != nil {
log.LogFatal("Failed to create GraphQL server",
log.F("error", err))
}
backgroundServers, err := serverFactory.CreateBackgroundJobServers() backgroundServers, err := serverFactory.CreateBackgroundJobServers()
if err != nil { if err != nil {
log.LogFatal("Failed to create background job servers", log.LogFatal("Failed to create background job servers",
log.F("error", err)) log.F("error", err))
} }
playgroundServer := serverFactory.CreatePlaygroundServer() // Create GraphQL server
resolver := &graph.Resolver{
WorkRepo: appBuilder.GetRepositories().WorkRepository,
UserRepo: appBuilder.GetRepositories().UserRepository,
AuthorRepo: appBuilder.GetRepositories().AuthorRepository,
TranslationRepo: appBuilder.GetRepositories().TranslationRepository,
CommentRepo: appBuilder.GetRepositories().CommentRepository,
LikeRepo: appBuilder.GetRepositories().LikeRepository,
BookmarkRepo: appBuilder.GetRepositories().BookmarkRepository,
CollectionRepo: appBuilder.GetRepositories().CollectionRepository,
TagRepo: appBuilder.GetRepositories().TagRepository,
CategoryRepo: appBuilder.GetRepositories().CategoryRepository,
WorkService: appBuilder.GetServices().WorkService,
Localization: appBuilder.GetServices().LocalizationService,
AuthService: appBuilder.GetServices().AuthService,
}
jwtManager := auth.NewJWTManager()
srv := graph.NewServerWithAuth(resolver, jwtManager)
graphQLServer := &http.Server{
Addr: config.Cfg.ServerPort,
Handler: srv,
}
log.LogInfo("GraphQL server created successfully", log.F("port", config.Cfg.ServerPort))
// Create GraphQL playground
playgroundHandler := playground.Handler("GraphQL", "/query")
playgroundServer := &http.Server{
Addr: config.Cfg.PlaygroundPort,
Handler: playgroundHandler,
}
log.LogInfo("GraphQL playground created successfully", log.F("port", config.Cfg.PlaygroundPort))
// Start HTTP servers in goroutines // Start HTTP servers in goroutines
go func() { go func() {

View File

@ -3,73 +3,47 @@ package main
import ( import (
"context" "context"
"log" "log"
"os" "tercul/internal/app"
"os/signal" "tercul/internal/jobs/linguistics"
"syscall"
"time"
"gorm.io/driver/postgres"
"gorm.io/gorm"
"tercul/internal/enrich"
"tercul/internal/store"
"tercul/internal/platform/config" "tercul/internal/platform/config"
) )
func main() { func main() {
log.Println("Starting enrichment service...") log.Println("Starting enrichment tool...")
// Create a context that can be cancelled // Load configuration from environment variables
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Set up signal handling
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
go func() {
sig := <-sigCh
log.Printf("Received signal %v, shutting down...", sig)
cancel()
}()
// Load configuration
config.LoadConfig() config.LoadConfig()
// Connect to the database // Initialize structured logger with appropriate log level
dsn := os.Getenv("DATABASE_URL") log.SetDefaultLevel(log.InfoLevel)
if dsn == "" { log.LogInfo("Starting Tercul enrichment tool",
dsn = config.Cfg.GetDSN() log.F("environment", config.Cfg.Environment),
} log.F("version", "1.0.0"))
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{}) // Build application components
appBuilder := app.NewApplicationBuilder()
if err := appBuilder.Build(); err != nil {
log.LogFatal("Failed to build application",
log.F("error", err))
}
defer appBuilder.Close()
// Get all works
works, err := appBuilder.GetApplication().WorkQueries.ListWorks(context.Background(), 1, 10000) // A bit of a hack, but should work for now
if err != nil { if err != nil {
log.Fatalf("Failed to connect to database: %v", err) log.LogFatal("Failed to get works",
log.F("error", err))
} }
// Create a store.DB // Enqueue analysis for each work
storeDB := &store.DB{DB: db} for _, work := range works.Data {
err := linguistics.EnqueueAnalysisForWork(appBuilder.GetAsynqClient(), work.ID)
// Create the enrichment registry if err != nil {
registry := enrich.DefaultRegistry() log.LogError("Failed to enqueue analysis for work",
log.F("workID", work.ID),
// Process pending works log.F("error", err))
if err := store.ProcessPendingWorks(ctx, registry, storeDB); err != nil {
log.Fatalf("Failed to process pending works: %v", err)
}
// Set up a ticker to periodically process pending works
ticker := time.NewTicker(5 * time.Minute)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
log.Println("Shutting down...")
return
case <-ticker.C:
log.Println("Processing pending works...")
if err := store.ProcessPendingWorks(ctx, registry, storeDB); err != nil {
log.Printf("Failed to process pending works: %v", err)
}
} }
} }
log.Println("Enrichment tool finished.")
} }

141
create_repo_interfaces.go Normal file
View File

@ -0,0 +1,141 @@
package main
import (
"fmt"
"go/ast"
"go/parser"
"go/token"
"io/ioutil"
"os"
"path/filepath"
"strings"
)
func main() {
sqlDir := "internal/data/sql"
domainDir := "internal/domain"
files, err := ioutil.ReadDir(sqlDir)
if err != nil {
fmt.Println("Error reading sql directory:", err)
return
}
for _, file := range files {
if strings.HasSuffix(file.Name(), "_repository.go") {
repoName := strings.TrimSuffix(file.Name(), "_repository.go")
repoInterfaceName := strings.Title(repoName) + "Repository"
domainPackageName := repoName
// Create domain directory
domainRepoDir := filepath.Join(domainDir, domainPackageName)
if err := os.MkdirAll(domainRepoDir, 0755); err != nil {
fmt.Printf("Error creating directory %s: %v\n", domainRepoDir, err)
continue
}
// Read the sql repository file
filePath := filepath.Join(sqlDir, file.Name())
src, err := ioutil.ReadFile(filePath)
if err != nil {
fmt.Printf("Error reading file %s: %v\n", filePath, err)
continue
}
// Parse the file
fset := token.NewFileSet()
node, err := parser.ParseFile(fset, "", src, parser.ParseComments)
if err != nil {
fmt.Printf("Error parsing file %s: %v\n", filePath, err)
continue
}
// Find public methods
var methods []string
ast.Inspect(node, func(n ast.Node) bool {
if fn, ok := n.(*ast.FuncDecl); ok {
if fn.Recv != nil && len(fn.Recv.List) > 0 {
if star, ok := fn.Recv.List[0].Type.(*ast.StarExpr); ok {
if ident, ok := star.X.(*ast.Ident); ok {
if strings.HasSuffix(ident.Name, "Repository") && fn.Name.IsExported() {
methods = append(methods, getFuncSignature(fn))
}
}
}
}
}
return true
})
// Create the repo.go file
repoFilePath := filepath.Join(domainRepoDir, "repo.go")
repoFileContent := fmt.Sprintf(`package %s
import (
"context"
"tercul/internal/domain"
)
// %s defines CRUD methods specific to %s.
type %s interface {
domain.BaseRepository[domain.%s]
%s
}
`, domainPackageName, repoInterfaceName, strings.Title(repoName), repoInterfaceName, strings.Title(repoName), formatMethods(methods))
if err := ioutil.WriteFile(repoFilePath, []byte(repoFileContent), 0644); err != nil {
fmt.Printf("Error writing file %s: %v\n", repoFilePath, err)
} else {
fmt.Printf("Created %s\n", repoFilePath)
}
}
}
}
func getFuncSignature(fn *ast.FuncDecl) string {
params := ""
for _, p := range fn.Type.Params.List {
if len(p.Names) > 0 {
params += p.Names[0].Name + " "
}
params += getTypeString(p.Type) + ", "
}
if len(params) > 0 {
params = params[:len(params)-2]
}
results := ""
if fn.Type.Results != nil {
for _, r := range fn.Type.Results.List {
results += getTypeString(r.Type) + ", "
}
if len(results) > 0 {
results = "(" + results[:len(results)-2] + ")"
}
}
return fmt.Sprintf("\t%s(%s) %s", fn.Name.Name, params, results)
}
func getTypeString(expr ast.Expr) string {
switch t := expr.(type) {
case *ast.Ident:
return t.Name
case *ast.SelectorExpr:
return getTypeString(t.X) + "." + t.Sel.Name
case *ast.StarExpr:
return "*" + getTypeString(t.X)
case *ast.ArrayType:
return "[]" + getTypeString(t.Elt)
case *ast.InterfaceType:
return "interface{}"
default:
return ""
}
}
func formatMethods(methods []string) string {
if len(methods) == 0 {
return ""
}
return "\n" + strings.Join(methods, "\n")
}

49
fix_domain_repos.go Normal file
View File

@ -0,0 +1,49 @@
package main
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
)
func main() {
domainDir := "internal/domain"
dirs, err := ioutil.ReadDir(domainDir)
if err != nil {
fmt.Println("Error reading domain directory:", err)
return
}
for _, dir := range dirs {
if dir.IsDir() {
repoFilePath := filepath.Join(domainDir, dir.Name(), "repo.go")
if _, err := os.Stat(repoFilePath); err == nil {
content, err := ioutil.ReadFile(repoFilePath)
if err != nil {
fmt.Printf("Error reading file %s: %v\n", repoFilePath, err)
continue
}
newContent := strings.Replace(string(content), "domain.Base", "domain.BaseRepository", -1)
newContent = strings.Replace(newContent, "domain."+strings.Title(dir.Name()), "domain."+strings.Title(dir.Name()), -1)
// Fix for names with underscore
newContent = strings.Replace(newContent, "domain.Copyright_claim", "domain.CopyrightClaim", -1)
newContent = strings.Replace(newContent, "domain.Email_verification", "domain.EmailVerification", -1)
newContent = strings.Replace(newContent, "domain.Password_reset", "domain.PasswordReset", -1)
newContent = strings.Replace(newContent, "domain.User_profile", "domain.UserProfile", -1)
newContent = strings.Replace(newContent, "domain.User_session", "domain.UserSession", -1)
if err := ioutil.WriteFile(repoFilePath, []byte(newContent), 0644); err != nil {
fmt.Printf("Error writing file %s: %v\n", repoFilePath, err)
} else {
fmt.Printf("Fixed repo %s\n", repoFilePath)
}
}
}
}
}

40
fix_sql_imports.go Normal file
View File

@ -0,0 +1,40 @@
package main
import (
"fmt"
"io/ioutil"
"path/filepath"
"strings"
)
func main() {
sqlDir := "internal/data/sql"
files, err := ioutil.ReadDir(sqlDir)
if err != nil {
fmt.Println("Error reading sql directory:", err)
return
}
for _, file := range files {
if strings.HasSuffix(file.Name(), "_repository.go") {
repoName := strings.TrimSuffix(file.Name(), "_repository.go")
filePath := filepath.Join(sqlDir, file.Name())
content, err := ioutil.ReadFile(filePath)
if err != nil {
fmt.Printf("Error reading file %s: %v\n", filePath, err)
continue
}
newContent := strings.Replace(string(content), `"tercul/internal/domain"`, fmt.Sprintf(`"%s"`, filepath.Join("tercul/internal/domain", repoName)), 1)
newContent = strings.Replace(newContent, "domain."+strings.Title(repoName)+"Repository", repoName+"."+strings.Title(repoName)+"Repository", 1)
if err := ioutil.WriteFile(filePath, []byte(newContent), 0644); err != nil {
fmt.Printf("Error writing file %s: %v\n", filePath, err)
} else {
fmt.Printf("Fixed imports in %s\n", filePath)
}
}
}
}

View File

@ -13,7 +13,7 @@ import (
"tercul/internal/platform/db" "tercul/internal/platform/db"
"tercul/internal/platform/log" "tercul/internal/platform/log"
auth_platform "tercul/internal/platform/auth" auth_platform "tercul/internal/platform/auth"
"tercul/linguistics" "tercul/internal/jobs/linguistics"
"time" "time"
"github.com/hibiken/asynq" "github.com/hibiken/asynq"

View File

@ -2,12 +2,12 @@ package app
import ( import (
"net/http" "net/http"
"tercul/internal/jobs/linguistics"
syncjob "tercul/internal/jobs/sync"
"tercul/internal/platform/auth" "tercul/internal/platform/auth"
"tercul/internal/platform/config" "tercul/internal/platform/config"
"tercul/graph"
"tercul/linguistics"
"tercul/internal/platform/log" "tercul/internal/platform/log"
"tercul/syncjob"
"github.com/99designs/gqlgen/graphql/playground" "github.com/99designs/gqlgen/graphql/playground"
"github.com/hibiken/asynq" "github.com/hibiken/asynq"
@ -25,44 +25,6 @@ func NewServerFactory(appBuilder *ApplicationBuilder) *ServerFactory {
} }
} }
// CreateGraphQLServer creates and configures the GraphQL server
func (f *ServerFactory) CreateGraphQLServer() (*http.Server, error) {
log.LogInfo("Setting up GraphQL server")
// Create GraphQL resolver with all dependencies
resolver := &graph.Resolver{
WorkRepo: f.appBuilder.GetRepositories().WorkRepository,
UserRepo: f.appBuilder.GetRepositories().UserRepository,
AuthorRepo: f.appBuilder.GetRepositories().AuthorRepository,
TranslationRepo: f.appBuilder.GetRepositories().TranslationRepository,
CommentRepo: f.appBuilder.GetRepositories().CommentRepository,
LikeRepo: f.appBuilder.GetRepositories().LikeRepository,
BookmarkRepo: f.appBuilder.GetRepositories().BookmarkRepository,
CollectionRepo: f.appBuilder.GetRepositories().CollectionRepository,
TagRepo: f.appBuilder.GetRepositories().TagRepository,
CategoryRepo: f.appBuilder.GetRepositories().CategoryRepository,
WorkService: f.appBuilder.GetServices().WorkService,
Localization: f.appBuilder.GetServices().LocalizationService,
AuthService: f.appBuilder.GetServices().AuthService,
}
// Create JWT manager for authentication
jwtManager := auth.NewJWTManager()
// Create GraphQL server with authentication
srv := graph.NewServerWithAuth(resolver, jwtManager)
// Create HTTP server with middleware
httpServer := &http.Server{
Addr: config.Cfg.ServerPort,
Handler: srv,
}
log.LogInfo("GraphQL server created successfully",
log.F("port", config.Cfg.ServerPort))
return httpServer, nil
}
// CreateBackgroundJobServers creates and configures background job servers // CreateBackgroundJobServers creates and configures background job servers
func (f *ServerFactory) CreateBackgroundJobServers() ([]*asynq.Server, error) { func (f *ServerFactory) CreateBackgroundJobServers() ([]*asynq.Server, error) {
@ -120,19 +82,3 @@ func (f *ServerFactory) CreateBackgroundJobServers() ([]*asynq.Server, error) {
return servers, nil return servers, nil
} }
// CreatePlaygroundServer creates the GraphQL playground server
func (f *ServerFactory) CreatePlaygroundServer() *http.Server {
log.LogInfo("Setting up GraphQL playground")
playgroundHandler := playground.Handler("GraphQL", "/query")
playgroundServer := &http.Server{
Addr: config.Cfg.PlaygroundPort,
Handler: playgroundHandler,
}
log.LogInfo("GraphQL playground created successfully",
log.F("port", config.Cfg.PlaygroundPort))
return playgroundServer
}

View File

@ -3,7 +3,7 @@ package sql
import ( import (
"context" "context"
"gorm.io/gorm" "gorm.io/gorm"
"tercul/internal/domain" "tercul/internal/domain/author"
) )
type authorRepository struct { type authorRepository struct {
@ -12,7 +12,7 @@ type authorRepository struct {
} }
// NewAuthorRepository creates a new AuthorRepository. // NewAuthorRepository creates a new AuthorRepository.
func NewAuthorRepository(db *gorm.DB) domain.AuthorRepository { func NewAuthorRepository(db *gorm.DB) author.AuthorRepository {
return &authorRepository{ return &authorRepository{
BaseRepository: NewBaseRepositoryImpl[domain.Author](db), BaseRepository: NewBaseRepositoryImpl[domain.Author](db),
db: db, db: db,

View File

@ -4,7 +4,7 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"tercul/internal/domain" "tercul/internal/domain/base"
"tercul/internal/platform/config" "tercul/internal/platform/config"
"tercul/internal/platform/log" "tercul/internal/platform/log"
"time" "time"
@ -28,7 +28,7 @@ type BaseRepositoryImpl[T any] struct {
} }
// NewBaseRepositoryImpl creates a new BaseRepositoryImpl // NewBaseRepositoryImpl creates a new BaseRepositoryImpl
func NewBaseRepositoryImpl[T any](db *gorm.DB) domain.BaseRepository[T] { func NewBaseRepositoryImpl[T any](db *gorm.DB) base.BaseRepository[T] {
return &BaseRepositoryImpl[T]{db: db} return &BaseRepositoryImpl[T]{db: db}
} }

View File

@ -4,7 +4,7 @@ import (
"context" "context"
"errors" "errors"
"gorm.io/gorm" "gorm.io/gorm"
"tercul/internal/domain" "tercul/internal/domain/book"
) )
type bookRepository struct { type bookRepository struct {
@ -13,7 +13,7 @@ type bookRepository struct {
} }
// NewBookRepository creates a new BookRepository. // NewBookRepository creates a new BookRepository.
func NewBookRepository(db *gorm.DB) domain.BookRepository { func NewBookRepository(db *gorm.DB) book.BookRepository {
return &bookRepository{ return &bookRepository{
BaseRepository: NewBaseRepositoryImpl[domain.Book](db), BaseRepository: NewBaseRepositoryImpl[domain.Book](db),
db: db, db: db,

View File

@ -3,7 +3,7 @@ package sql
import ( import (
"context" "context"
"gorm.io/gorm" "gorm.io/gorm"
"tercul/internal/domain" "tercul/internal/domain/bookmark"
) )
type bookmarkRepository struct { type bookmarkRepository struct {
@ -12,7 +12,7 @@ type bookmarkRepository struct {
} }
// NewBookmarkRepository creates a new BookmarkRepository. // NewBookmarkRepository creates a new BookmarkRepository.
func NewBookmarkRepository(db *gorm.DB) domain.BookmarkRepository { func NewBookmarkRepository(db *gorm.DB) bookmark.BookmarkRepository {
return &bookmarkRepository{ return &bookmarkRepository{
BaseRepository: NewBaseRepositoryImpl[domain.Bookmark](db), BaseRepository: NewBaseRepositoryImpl[domain.Bookmark](db),
db: db, db: db,

View File

@ -4,7 +4,7 @@ import (
"context" "context"
"errors" "errors"
"gorm.io/gorm" "gorm.io/gorm"
"tercul/internal/domain" "tercul/internal/domain/category"
) )
type categoryRepository struct { type categoryRepository struct {
@ -13,7 +13,7 @@ type categoryRepository struct {
} }
// NewCategoryRepository creates a new CategoryRepository. // NewCategoryRepository creates a new CategoryRepository.
func NewCategoryRepository(db *gorm.DB) domain.CategoryRepository { func NewCategoryRepository(db *gorm.DB) category.CategoryRepository {
return &categoryRepository{ return &categoryRepository{
BaseRepository: NewBaseRepositoryImpl[domain.Category](db), BaseRepository: NewBaseRepositoryImpl[domain.Category](db),
db: db, db: db,

View File

@ -1,33 +1,27 @@
package repositories package sql
import ( import (
"context" "context"
"gorm.io/gorm" "gorm.io/gorm"
"tercul/internal/models" "tercul/internal/domain/city"
) )
// CityRepository defines CRUD methods specific to City.
type CityRepository interface {
BaseRepository[models.City]
ListByCountryID(ctx context.Context, countryID uint) ([]models.City, error)
}
type cityRepository struct { type cityRepository struct {
BaseRepository[models.City] domain.BaseRepository[domain.City]
db *gorm.DB db *gorm.DB
} }
// NewCityRepository creates a new CityRepository. // NewCityRepository creates a new CityRepository.
func NewCityRepository(db *gorm.DB) CityRepository { func NewCityRepository(db *gorm.DB) city.CityRepository {
return &cityRepository{ return &cityRepository{
BaseRepository: NewBaseRepositoryImpl[models.City](db), BaseRepository: NewBaseRepositoryImpl[domain.City](db),
db: db, db: db,
} }
} }
// ListByCountryID finds cities by country ID // ListByCountryID finds cities by country ID
func (r *cityRepository) ListByCountryID(ctx context.Context, countryID uint) ([]models.City, error) { func (r *cityRepository) ListByCountryID(ctx context.Context, countryID uint) ([]domain.City, error) {
var cities []models.City var cities []domain.City
if err := r.db.WithContext(ctx).Where("country_id = ?", countryID).Find(&cities).Error; err != nil { if err := r.db.WithContext(ctx).Where("country_id = ?", countryID).Find(&cities).Error; err != nil {
return nil, err return nil, err
} }

View File

@ -3,7 +3,7 @@ package sql
import ( import (
"context" "context"
"gorm.io/gorm" "gorm.io/gorm"
"tercul/internal/domain" "tercul/internal/domain/collection"
) )
type collectionRepository struct { type collectionRepository struct {
@ -12,7 +12,7 @@ type collectionRepository struct {
} }
// NewCollectionRepository creates a new CollectionRepository. // NewCollectionRepository creates a new CollectionRepository.
func NewCollectionRepository(db *gorm.DB) domain.CollectionRepository { func NewCollectionRepository(db *gorm.DB) collection.CollectionRepository {
return &collectionRepository{ return &collectionRepository{
BaseRepository: NewBaseRepositoryImpl[domain.Collection](db), BaseRepository: NewBaseRepositoryImpl[domain.Collection](db),
db: db, db: db,

View File

@ -3,7 +3,7 @@ package sql
import ( import (
"context" "context"
"gorm.io/gorm" "gorm.io/gorm"
"tercul/internal/domain" "tercul/internal/domain/comment"
) )
type commentRepository struct { type commentRepository struct {
@ -12,7 +12,7 @@ type commentRepository struct {
} }
// NewCommentRepository creates a new CommentRepository. // NewCommentRepository creates a new CommentRepository.
func NewCommentRepository(db *gorm.DB) domain.CommentRepository { func NewCommentRepository(db *gorm.DB) comment.CommentRepository {
return &commentRepository{ return &commentRepository{
BaseRepository: NewBaseRepositoryImpl[domain.Comment](db), BaseRepository: NewBaseRepositoryImpl[domain.Comment](db),
db: db, db: db,

View File

@ -1,37 +1,27 @@
package repositories package sql
import ( import (
"context" "context"
"gorm.io/gorm" "gorm.io/gorm"
"tercul/internal/models" "tercul/internal/domain/contribution"
) )
// ContributionRepository defines CRUD methods specific to Contribution.
type ContributionRepository interface {
BaseRepository[models.Contribution]
ListByUserID(ctx context.Context, userID uint) ([]models.Contribution, error)
ListByReviewerID(ctx context.Context, reviewerID uint) ([]models.Contribution, error)
ListByWorkID(ctx context.Context, workID uint) ([]models.Contribution, error)
ListByTranslationID(ctx context.Context, translationID uint) ([]models.Contribution, error)
ListByStatus(ctx context.Context, status string) ([]models.Contribution, error)
}
type contributionRepository struct { type contributionRepository struct {
BaseRepository[models.Contribution] domain.BaseRepository[domain.Contribution]
db *gorm.DB db *gorm.DB
} }
// NewContributionRepository creates a new ContributionRepository. // NewContributionRepository creates a new ContributionRepository.
func NewContributionRepository(db *gorm.DB) ContributionRepository { func NewContributionRepository(db *gorm.DB) contribution.ContributionRepository {
return &contributionRepository{ return &contributionRepository{
BaseRepository: NewBaseRepositoryImpl[models.Contribution](db), BaseRepository: NewBaseRepositoryImpl[domain.Contribution](db),
db: db, db: db,
} }
} }
// ListByUserID finds contributions by user ID // ListByUserID finds contributions by user ID
func (r *contributionRepository) ListByUserID(ctx context.Context, userID uint) ([]models.Contribution, error) { func (r *contributionRepository) ListByUserID(ctx context.Context, userID uint) ([]domain.Contribution, error) {
var contributions []models.Contribution var contributions []domain.Contribution
if err := r.db.WithContext(ctx).Where("user_id = ?", userID).Find(&contributions).Error; err != nil { if err := r.db.WithContext(ctx).Where("user_id = ?", userID).Find(&contributions).Error; err != nil {
return nil, err return nil, err
} }
@ -39,8 +29,8 @@ func (r *contributionRepository) ListByUserID(ctx context.Context, userID uint)
} }
// ListByReviewerID finds contributions by reviewer ID // ListByReviewerID finds contributions by reviewer ID
func (r *contributionRepository) ListByReviewerID(ctx context.Context, reviewerID uint) ([]models.Contribution, error) { func (r *contributionRepository) ListByReviewerID(ctx context.Context, reviewerID uint) ([]domain.Contribution, error) {
var contributions []models.Contribution var contributions []domain.Contribution
if err := r.db.WithContext(ctx).Where("reviewer_id = ?", reviewerID).Find(&contributions).Error; err != nil { if err := r.db.WithContext(ctx).Where("reviewer_id = ?", reviewerID).Find(&contributions).Error; err != nil {
return nil, err return nil, err
} }
@ -48,8 +38,8 @@ func (r *contributionRepository) ListByReviewerID(ctx context.Context, reviewerI
} }
// ListByWorkID finds contributions by work ID // ListByWorkID finds contributions by work ID
func (r *contributionRepository) ListByWorkID(ctx context.Context, workID uint) ([]models.Contribution, error) { func (r *contributionRepository) ListByWorkID(ctx context.Context, workID uint) ([]domain.Contribution, error) {
var contributions []models.Contribution var contributions []domain.Contribution
if err := r.db.WithContext(ctx).Where("work_id = ?", workID).Find(&contributions).Error; err != nil { if err := r.db.WithContext(ctx).Where("work_id = ?", workID).Find(&contributions).Error; err != nil {
return nil, err return nil, err
} }
@ -57,8 +47,8 @@ func (r *contributionRepository) ListByWorkID(ctx context.Context, workID uint)
} }
// ListByTranslationID finds contributions by translation ID // ListByTranslationID finds contributions by translation ID
func (r *contributionRepository) ListByTranslationID(ctx context.Context, translationID uint) ([]models.Contribution, error) { func (r *contributionRepository) ListByTranslationID(ctx context.Context, translationID uint) ([]domain.Contribution, error) {
var contributions []models.Contribution var contributions []domain.Contribution
if err := r.db.WithContext(ctx).Where("translation_id = ?", translationID).Find(&contributions).Error; err != nil { if err := r.db.WithContext(ctx).Where("translation_id = ?", translationID).Find(&contributions).Error; err != nil {
return nil, err return nil, err
} }
@ -66,8 +56,8 @@ func (r *contributionRepository) ListByTranslationID(ctx context.Context, transl
} }
// ListByStatus finds contributions by status // ListByStatus finds contributions by status
func (r *contributionRepository) ListByStatus(ctx context.Context, status string) ([]models.Contribution, error) { func (r *contributionRepository) ListByStatus(ctx context.Context, status string) ([]domain.Contribution, error) {
var contributions []models.Contribution var contributions []domain.Contribution
if err := r.db.WithContext(ctx).Where("status = ?", status).Find(&contributions).Error; err != nil { if err := r.db.WithContext(ctx).Where("status = ?", status).Find(&contributions).Error; err != nil {
return nil, err return nil, err
} }

View File

@ -1,34 +1,27 @@
package repositories package sql
import ( import (
"context" "context"
"gorm.io/gorm" "gorm.io/gorm"
"tercul/internal/models" "tercul/internal/domain/copyright_claim"
) )
// CopyrightClaimRepository defines CRUD methods specific to CopyrightClaim.
type CopyrightClaimRepository interface {
BaseRepository[models.CopyrightClaim]
ListByWorkID(ctx context.Context, workID uint) ([]models.CopyrightClaim, error)
ListByUserID(ctx context.Context, userID uint) ([]models.CopyrightClaim, error)
}
type copyrightClaimRepository struct { type copyrightClaimRepository struct {
BaseRepository[models.CopyrightClaim] domain.BaseRepository[domain.CopyrightClaim]
db *gorm.DB db *gorm.DB
} }
// NewCopyrightClaimRepository creates a new CopyrightClaimRepository. // NewCopyrightClaimRepository creates a new CopyrightClaimRepository.
func NewCopyrightClaimRepository(db *gorm.DB) CopyrightClaimRepository { func NewCopyrightClaimRepository(db *gorm.DB) domain.CopyrightClaimRepository {
return &copyrightClaimRepository{ return &copyrightClaimRepository{
BaseRepository: NewBaseRepositoryImpl[models.CopyrightClaim](db), BaseRepository: NewBaseRepositoryImpl[domain.CopyrightClaim](db),
db: db, db: db,
} }
} }
// ListByWorkID finds claims by work ID // ListByWorkID finds claims by work ID
func (r *copyrightClaimRepository) ListByWorkID(ctx context.Context, workID uint) ([]models.CopyrightClaim, error) { func (r *copyrightClaimRepository) ListByWorkID(ctx context.Context, workID uint) ([]domain.CopyrightClaim, error) {
var claims []models.CopyrightClaim var claims []domain.CopyrightClaim
if err := r.db.WithContext(ctx).Where("work_id = ?", workID).Find(&claims).Error; err != nil { if err := r.db.WithContext(ctx).Where("work_id = ?", workID).Find(&claims).Error; err != nil {
return nil, err return nil, err
} }
@ -36,8 +29,8 @@ func (r *copyrightClaimRepository) ListByWorkID(ctx context.Context, workID uint
} }
// ListByUserID finds claims by user ID // ListByUserID finds claims by user ID
func (r *copyrightClaimRepository) ListByUserID(ctx context.Context, userID uint) ([]models.CopyrightClaim, error) { func (r *copyrightClaimRepository) ListByUserID(ctx context.Context, userID uint) ([]domain.CopyrightClaim, error) {
var claims []models.CopyrightClaim var claims []domain.CopyrightClaim
if err := r.db.WithContext(ctx).Where("user_id = ?", userID).Find(&claims).Error; err != nil { if err := r.db.WithContext(ctx).Where("user_id = ?", userID).Find(&claims).Error; err != nil {
return nil, err return nil, err
} }

View File

@ -1,42 +1,28 @@
package repositories package sql
import ( import (
"context" "context"
"errors" "errors"
"gorm.io/gorm" "gorm.io/gorm"
"tercul/internal/models" "tercul/internal/domain/copyright"
) )
// CopyrightRepository defines CRUD methods specific to Copyright.
type CopyrightRepository interface {
BaseRepository[models.Copyright]
// Polymorphic methods
AttachToEntity(ctx context.Context, copyrightID uint, entityID uint, entityType string) error
DetachFromEntity(ctx context.Context, copyrightID uint, entityID uint, entityType string) error
GetByEntity(ctx context.Context, entityID uint, entityType string) ([]models.Copyright, error)
GetEntitiesByCopyright(ctx context.Context, copyrightID uint) ([]models.Copyrightable, error)
// Translation methods
AddTranslation(ctx context.Context, translation *models.CopyrightTranslation) error
GetTranslations(ctx context.Context, copyrightID uint) ([]models.CopyrightTranslation, error)
GetTranslationByLanguage(ctx context.Context, copyrightID uint, languageCode string) (*models.CopyrightTranslation, error)
}
type copyrightRepository struct { type copyrightRepository struct {
BaseRepository[models.Copyright] domain.BaseRepository[domain.Copyright]
db *gorm.DB db *gorm.DB
} }
// NewCopyrightRepository creates a new CopyrightRepository. // NewCopyrightRepository creates a new CopyrightRepository.
func NewCopyrightRepository(db *gorm.DB) CopyrightRepository { func NewCopyrightRepository(db *gorm.DB) copyright.CopyrightRepository {
return &copyrightRepository{ return &copyrightRepository{
BaseRepository: NewBaseRepositoryImpl[models.Copyright](db), BaseRepository: NewBaseRepositoryImpl[domain.Copyright](db),
db: db, db: db,
} }
} }
// AttachToEntity attaches a copyright to any entity type // AttachToEntity attaches a copyright to any entity type
func (r *copyrightRepository) AttachToEntity(ctx context.Context, copyrightID uint, entityID uint, entityType string) error { func (r *copyrightRepository) AttachToEntity(ctx context.Context, copyrightID uint, entityID uint, entityType string) error {
copyrightable := models.Copyrightable{ copyrightable := domain.Copyrightable{
CopyrightID: copyrightID, CopyrightID: copyrightID,
CopyrightableID: entityID, CopyrightableID: entityID,
CopyrightableType: entityType, CopyrightableType: entityType,
@ -47,12 +33,12 @@ func (r *copyrightRepository) AttachToEntity(ctx context.Context, copyrightID ui
// DetachFromEntity removes a copyright from an entity // DetachFromEntity removes a copyright from an entity
func (r *copyrightRepository) DetachFromEntity(ctx context.Context, copyrightID uint, entityID uint, entityType string) error { func (r *copyrightRepository) DetachFromEntity(ctx context.Context, copyrightID uint, entityID uint, entityType string) error {
return r.db.WithContext(ctx).Where("copyright_id = ? AND copyrightable_id = ? AND copyrightable_type = ?", return r.db.WithContext(ctx).Where("copyright_id = ? AND copyrightable_id = ? AND copyrightable_type = ?",
copyrightID, entityID, entityType).Delete(&models.Copyrightable{}).Error copyrightID, entityID, entityType).Delete(&domain.Copyrightable{}).Error
} }
// GetByEntity gets all copyrights for a specific entity // GetByEntity gets all copyrights for a specific entity
func (r *copyrightRepository) GetByEntity(ctx context.Context, entityID uint, entityType string) ([]models.Copyright, error) { func (r *copyrightRepository) GetByEntity(ctx context.Context, entityID uint, entityType string) ([]domain.Copyright, error) {
var copyrights []models.Copyright var copyrights []domain.Copyright
err := r.db.WithContext(ctx).Joins("JOIN copyrightables ON copyrightables.copyright_id = copyrights.id"). err := r.db.WithContext(ctx).Joins("JOIN copyrightables ON copyrightables.copyright_id = copyrights.id").
Where("copyrightables.copyrightable_id = ? AND copyrightables.copyrightable_type = ?", entityID, entityType). Where("copyrightables.copyrightable_id = ? AND copyrightables.copyrightable_type = ?", entityID, entityType).
Preload("Translations"). Preload("Translations").
@ -61,27 +47,26 @@ func (r *copyrightRepository) GetByEntity(ctx context.Context, entityID uint, en
} }
// GetEntitiesByCopyright gets all entities that have a specific copyright // GetEntitiesByCopyright gets all entities that have a specific copyright
func (r *copyrightRepository) GetEntitiesByCopyright(ctx context.Context, copyrightID uint) ([]models.Copyrightable, error) { func (r *copyrightRepository) GetEntitiesByCopyright(ctx context.Context, copyrightID uint) ([]domain.Copyrightable, error) {
var copyrightables []models.Copyrightable var copyrightables []domain.Copyrightable
err := r.db.WithContext(ctx).Where("copyright_id = ?", copyrightID).Find(&copyrightables).Error err := r.db.WithContext(ctx).Where("copyright_id = ?", copyrightID).Find(&copyrightables).Error
return copyrightables, err return copyrightables, err
} }
// AddTranslation adds a translation to a copyright // AddTranslation adds a translation to a copyright
func (r *copyrightRepository) AddTranslation(ctx context.Context, translation *models.CopyrightTranslation) error { func (r *copyrightRepository) AddTranslation(ctx context.Context, translation *domain.CopyrightTranslation) error {
return r.db.WithContext(ctx).Create(translation).Error return r.db.WithContext(ctx).Create(translation).Error
} }
// GetTranslations gets all translations for a copyright // GetTranslations gets all translations for a copyright
func (r *copyrightRepository) GetTranslations(ctx context.Context, copyrightID uint) ([]models.CopyrightTranslation, error) { func (r *copyrightRepository) GetTranslations(ctx context.Context, copyrightID uint) ([]domain.CopyrightTranslation, error) {
var translations []models.CopyrightTranslation var translations []domain.CopyrightTranslation
err := r.db.WithContext(ctx).Where("copyright_id = ?", copyrightID).Find(&translations).Error err := r.db.WithContext(ctx).Where("copyright_id = ?", copyrightID).Find(&translations).Error
return translations, err return translations, err
} }
// GetTranslationByLanguage gets a specific translation by language code // GetTranslationByLanguage gets a specific translation by language code
func (r *copyrightRepository) GetTranslationByLanguage(ctx context.Context, copyrightID uint, languageCode string) (*models.CopyrightTranslation, error) { func (r *copyrightRepository) GetTranslationByLanguage(ctx context.Context, copyrightID uint, languageCode string) (*domain.CopyrightTranslation, error) {
var translation models.CopyrightTranslation var translation domain.CopyrightTranslation
err := r.db.WithContext(ctx).Where("copyright_id = ? AND language_code = ?", copyrightID, languageCode).First(&translation).Error err := r.db.WithContext(ctx).Where("copyright_id = ? AND language_code = ?", copyrightID, languageCode).First(&translation).Error
if err != nil { if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) { if errors.Is(err, gorm.ErrRecordNotFound) {

View File

@ -1,35 +1,28 @@
package repositories package sql
import ( import (
"context" "context"
"errors" "errors"
"gorm.io/gorm" "gorm.io/gorm"
"tercul/internal/models" "tercul/internal/domain/country"
) )
// CountryRepository defines CRUD methods specific to Country.
type CountryRepository interface {
BaseRepository[models.Country]
GetByCode(ctx context.Context, code string) (*models.Country, error)
ListByContinent(ctx context.Context, continent string) ([]models.Country, error)
}
type countryRepository struct { type countryRepository struct {
BaseRepository[models.Country] domain.BaseRepository[domain.Country]
db *gorm.DB db *gorm.DB
} }
// NewCountryRepository creates a new CountryRepository. // NewCountryRepository creates a new CountryRepository.
func NewCountryRepository(db *gorm.DB) CountryRepository { func NewCountryRepository(db *gorm.DB) country.CountryRepository {
return &countryRepository{ return &countryRepository{
BaseRepository: NewBaseRepositoryImpl[models.Country](db), BaseRepository: NewBaseRepositoryImpl[domain.Country](db),
db: db, db: db,
} }
} }
// GetByCode finds a country by code // GetByCode finds a country by code
func (r *countryRepository) GetByCode(ctx context.Context, code string) (*models.Country, error) { func (r *countryRepository) GetByCode(ctx context.Context, code string) (*domain.Country, error) {
var country models.Country var country domain.Country
if err := r.db.WithContext(ctx).Where("code = ?", code).First(&country).Error; err != nil { if err := r.db.WithContext(ctx).Where("code = ?", code).First(&country).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) { if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, ErrEntityNotFound return nil, ErrEntityNotFound
@ -40,8 +33,8 @@ func (r *countryRepository) GetByCode(ctx context.Context, code string) (*models
} }
// ListByContinent finds countries by continent // ListByContinent finds countries by continent
func (r *countryRepository) ListByContinent(ctx context.Context, continent string) ([]models.Country, error) { func (r *countryRepository) ListByContinent(ctx context.Context, continent string) ([]domain.Country, error) {
var countries []models.Country var countries []domain.Country
if err := r.db.WithContext(ctx).Where("continent = ?", continent).Find(&countries).Error; err != nil { if err := r.db.WithContext(ctx).Where("continent = ?", continent).Find(&countries).Error; err != nil {
return nil, err return nil, err
} }

View File

@ -1,33 +1,27 @@
package repositories package sql
import ( import (
"context" "context"
"gorm.io/gorm" "gorm.io/gorm"
"tercul/internal/models" "tercul/internal/domain/edge"
) )
// EdgeRepository defines CRUD operations for the polymorphic edge table.
type EdgeRepository interface {
BaseRepository[models.Edge]
ListBySource(ctx context.Context, sourceTable string, sourceID uint) ([]models.Edge, error)
}
type edgeRepository struct { type edgeRepository struct {
BaseRepository[models.Edge] domain.BaseRepository[domain.Edge]
db *gorm.DB db *gorm.DB
} }
// NewEdgeRepository creates a new EdgeRepository. // NewEdgeRepository creates a new EdgeRepository.
func NewEdgeRepository(db *gorm.DB) EdgeRepository { func NewEdgeRepository(db *gorm.DB) edge.EdgeRepository {
return &edgeRepository{ return &edgeRepository{
BaseRepository: NewBaseRepositoryImpl[models.Edge](db), BaseRepository: NewBaseRepositoryImpl[domain.Edge](db),
db: db, db: db,
} }
} }
// ListBySource finds edges by source table and ID // ListBySource finds edges by source table and ID
func (r *edgeRepository) ListBySource(ctx context.Context, sourceTable string, sourceID uint) ([]models.Edge, error) { func (r *edgeRepository) ListBySource(ctx context.Context, sourceTable string, sourceID uint) ([]domain.Edge, error) {
var edges []models.Edge var edges []domain.Edge
if err := r.db.WithContext(ctx).Where("source_table = ? AND source_id = ?", sourceTable, sourceID).Find(&edges).Error; err != nil { if err := r.db.WithContext(ctx).Where("source_table = ? AND source_id = ?", sourceTable, sourceID).Find(&edges).Error; err != nil {
return nil, err return nil, err
} }

View File

@ -1,35 +1,28 @@
package repositories package sql
import ( import (
"context" "context"
"errors" "errors"
"gorm.io/gorm" "gorm.io/gorm"
"tercul/internal/models" "tercul/internal/domain/edition"
) )
// EditionRepository defines CRUD methods specific to Edition.
type EditionRepository interface {
BaseRepository[models.Edition]
ListByBookID(ctx context.Context, bookID uint) ([]models.Edition, error)
FindByISBN(ctx context.Context, isbn string) (*models.Edition, error)
}
type editionRepository struct { type editionRepository struct {
BaseRepository[models.Edition] domain.BaseRepository[domain.Edition]
db *gorm.DB db *gorm.DB
} }
// NewEditionRepository creates a new EditionRepository. // NewEditionRepository creates a new EditionRepository.
func NewEditionRepository(db *gorm.DB) EditionRepository { func NewEditionRepository(db *gorm.DB) edition.EditionRepository {
return &editionRepository{ return &editionRepository{
BaseRepository: NewBaseRepositoryImpl[models.Edition](db), BaseRepository: NewBaseRepositoryImpl[domain.Edition](db),
db: db, db: db,
} }
} }
// ListByBookID finds editions by book ID // ListByBookID finds editions by book ID
func (r *editionRepository) ListByBookID(ctx context.Context, bookID uint) ([]models.Edition, error) { func (r *editionRepository) ListByBookID(ctx context.Context, bookID uint) ([]domain.Edition, error) {
var editions []models.Edition var editions []domain.Edition
if err := r.db.WithContext(ctx).Where("book_id = ?", bookID).Find(&editions).Error; err != nil { if err := r.db.WithContext(ctx).Where("book_id = ?", bookID).Find(&editions).Error; err != nil {
return nil, err return nil, err
} }
@ -37,8 +30,8 @@ func (r *editionRepository) ListByBookID(ctx context.Context, bookID uint) ([]mo
} }
// FindByISBN finds an edition by ISBN // FindByISBN finds an edition by ISBN
func (r *editionRepository) FindByISBN(ctx context.Context, isbn string) (*models.Edition, error) { func (r *editionRepository) FindByISBN(ctx context.Context, isbn string) (*domain.Edition, error) {
var edition models.Edition var edition domain.Edition
if err := r.db.WithContext(ctx).Where("isbn = ?", isbn).First(&edition).Error; err != nil { if err := r.db.WithContext(ctx).Where("isbn = ?", isbn).First(&edition).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) { if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, ErrEntityNotFound return nil, ErrEntityNotFound

View File

@ -1,38 +1,29 @@
package repositories package sql
import ( import (
"context" "context"
"errors" "errors"
"gorm.io/gorm" "gorm.io/gorm"
"tercul/internal/models" "tercul/internal/domain/email_verification"
"time" "time"
) )
// EmailVerificationRepository defines CRUD methods specific to EmailVerification.
type EmailVerificationRepository interface {
BaseRepository[models.EmailVerification]
GetByToken(ctx context.Context, token string) (*models.EmailVerification, error)
GetByUserID(ctx context.Context, userID uint) ([]models.EmailVerification, error)
DeleteExpired(ctx context.Context) error
MarkAsUsed(ctx context.Context, id uint) error
}
type emailVerificationRepository struct { type emailVerificationRepository struct {
BaseRepository[models.EmailVerification] domain.BaseRepository[domain.EmailVerification]
db *gorm.DB db *gorm.DB
} }
// NewEmailVerificationRepository creates a new EmailVerificationRepository. // NewEmailVerificationRepository creates a new EmailVerificationRepository.
func NewEmailVerificationRepository(db *gorm.DB) EmailVerificationRepository { func NewEmailVerificationRepository(db *gorm.DB) domain.EmailVerificationRepository {
return &emailVerificationRepository{ return &emailVerificationRepository{
BaseRepository: NewBaseRepositoryImpl[models.EmailVerification](db), BaseRepository: NewBaseRepositoryImpl[domain.EmailVerification](db),
db: db, db: db,
} }
} }
// GetByToken finds a verification by token (only unused and non-expired) // GetByToken finds a verification by token (only unused and non-expired)
func (r *emailVerificationRepository) GetByToken(ctx context.Context, token string) (*models.EmailVerification, error) { func (r *emailVerificationRepository) GetByToken(ctx context.Context, token string) (*domain.EmailVerification, error) {
var verification models.EmailVerification var verification domain.EmailVerification
if err := r.db.WithContext(ctx).Where("token = ? AND used = ? AND expires_at > ?", token, false, time.Now()).First(&verification).Error; err != nil { if err := r.db.WithContext(ctx).Where("token = ? AND used = ? AND expires_at > ?", token, false, time.Now()).First(&verification).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) { if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, ErrEntityNotFound return nil, ErrEntityNotFound
@ -43,8 +34,8 @@ func (r *emailVerificationRepository) GetByToken(ctx context.Context, token stri
} }
// GetByUserID finds verifications by user ID // GetByUserID finds verifications by user ID
func (r *emailVerificationRepository) GetByUserID(ctx context.Context, userID uint) ([]models.EmailVerification, error) { func (r *emailVerificationRepository) GetByUserID(ctx context.Context, userID uint) ([]domain.EmailVerification, error) {
var verifications []models.EmailVerification var verifications []domain.EmailVerification
if err := r.db.WithContext(ctx).Where("user_id = ?", userID).Find(&verifications).Error; err != nil { if err := r.db.WithContext(ctx).Where("user_id = ?", userID).Find(&verifications).Error; err != nil {
return nil, err return nil, err
} }
@ -53,7 +44,7 @@ func (r *emailVerificationRepository) GetByUserID(ctx context.Context, userID ui
// DeleteExpired deletes expired verifications // DeleteExpired deletes expired verifications
func (r *emailVerificationRepository) DeleteExpired(ctx context.Context) error { func (r *emailVerificationRepository) DeleteExpired(ctx context.Context) error {
if err := r.db.WithContext(ctx).Where("expires_at < ?", time.Now()).Delete(&models.EmailVerification{}).Error; err != nil { if err := r.db.WithContext(ctx).Where("expires_at < ?", time.Now()).Delete(&domain.EmailVerification{}).Error; err != nil {
return err return err
} }
return nil return nil
@ -61,7 +52,7 @@ func (r *emailVerificationRepository) DeleteExpired(ctx context.Context) error {
// MarkAsUsed marks a verification as used // MarkAsUsed marks a verification as used
func (r *emailVerificationRepository) MarkAsUsed(ctx context.Context, id uint) error { func (r *emailVerificationRepository) MarkAsUsed(ctx context.Context, id uint) error {
if err := r.db.WithContext(ctx).Model(&models.EmailVerification{}).Where("id = ?", id).Update("used", true).Error; err != nil { if err := r.db.WithContext(ctx).Model(&domain.EmailVerification{}).Where("id = ?", id).Update("used", true).Error; err != nil {
return err return err
} }
return nil return nil

View File

@ -3,7 +3,7 @@ package sql
import ( import (
"context" "context"
"gorm.io/gorm" "gorm.io/gorm"
"tercul/internal/domain" "tercul/internal/domain/like"
) )
type likeRepository struct { type likeRepository struct {
@ -12,7 +12,7 @@ type likeRepository struct {
} }
// NewLikeRepository creates a new LikeRepository. // NewLikeRepository creates a new LikeRepository.
func NewLikeRepository(db *gorm.DB) domain.LikeRepository { func NewLikeRepository(db *gorm.DB) like.LikeRepository {
return &likeRepository{ return &likeRepository{
BaseRepository: NewBaseRepositoryImpl[domain.Like](db), BaseRepository: NewBaseRepositoryImpl[domain.Like](db),
db: db, db: db,

View File

@ -1,35 +1,27 @@
package repositories package sql
import ( import (
"context" "context"
"gorm.io/gorm" "gorm.io/gorm"
"tercul/internal/models" "tercul/internal/domain/monetization"
) )
// MonetizationRepository defines CRUD methods specific to Monetization.
type MonetizationRepository interface {
BaseRepository[models.Monetization]
ListByWorkID(ctx context.Context, workID uint) ([]models.Monetization, error)
ListByTranslationID(ctx context.Context, translationID uint) ([]models.Monetization, error)
ListByBookID(ctx context.Context, bookID uint) ([]models.Monetization, error)
}
type monetizationRepository struct { type monetizationRepository struct {
BaseRepository[models.Monetization] domain.BaseRepository[domain.Monetization]
db *gorm.DB db *gorm.DB
} }
// NewMonetizationRepository creates a new MonetizationRepository. // NewMonetizationRepository creates a new MonetizationRepository.
func NewMonetizationRepository(db *gorm.DB) MonetizationRepository { func NewMonetizationRepository(db *gorm.DB) monetization.MonetizationRepository {
return &monetizationRepository{ return &monetizationRepository{
BaseRepository: NewBaseRepositoryImpl[models.Monetization](db), BaseRepository: NewBaseRepositoryImpl[domain.Monetization](db),
db: db, db: db,
} }
} }
// ListByWorkID finds monetizations by work ID // ListByWorkID finds monetizations by work ID
func (r *monetizationRepository) ListByWorkID(ctx context.Context, workID uint) ([]models.Monetization, error) { func (r *monetizationRepository) ListByWorkID(ctx context.Context, workID uint) ([]domain.Monetization, error) {
var monetizations []models.Monetization var monetizations []domain.Monetization
if err := r.db.WithContext(ctx).Where("work_id = ?", workID).Find(&monetizations).Error; err != nil { if err := r.db.WithContext(ctx).Where("work_id = ?", workID).Find(&monetizations).Error; err != nil {
return nil, err return nil, err
} }
@ -37,8 +29,8 @@ func (r *monetizationRepository) ListByWorkID(ctx context.Context, workID uint)
} }
// ListByTranslationID finds monetizations by translation ID // ListByTranslationID finds monetizations by translation ID
func (r *monetizationRepository) ListByTranslationID(ctx context.Context, translationID uint) ([]models.Monetization, error) { func (r *monetizationRepository) ListByTranslationID(ctx context.Context, translationID uint) ([]domain.Monetization, error) {
var monetizations []models.Monetization var monetizations []domain.Monetization
if err := r.db.WithContext(ctx).Where("translation_id = ?", translationID).Find(&monetizations).Error; err != nil { if err := r.db.WithContext(ctx).Where("translation_id = ?", translationID).Find(&monetizations).Error; err != nil {
return nil, err return nil, err
} }
@ -46,8 +38,8 @@ func (r *monetizationRepository) ListByTranslationID(ctx context.Context, transl
} }
// ListByBookID finds monetizations by book ID // ListByBookID finds monetizations by book ID
func (r *monetizationRepository) ListByBookID(ctx context.Context, bookID uint) ([]models.Monetization, error) { func (r *monetizationRepository) ListByBookID(ctx context.Context, bookID uint) ([]domain.Monetization, error) {
var monetizations []models.Monetization var monetizations []domain.Monetization
if err := r.db.WithContext(ctx).Where("book_id = ?", bookID).Find(&monetizations).Error; err != nil { if err := r.db.WithContext(ctx).Where("book_id = ?", bookID).Find(&monetizations).Error; err != nil {
return nil, err return nil, err
} }

View File

@ -1,38 +1,29 @@
package repositories package sql
import ( import (
"context" "context"
"errors" "errors"
"gorm.io/gorm" "gorm.io/gorm"
"tercul/internal/models" "tercul/internal/domain/password_reset"
"time" "time"
) )
// PasswordResetRepository defines CRUD methods specific to PasswordReset.
type PasswordResetRepository interface {
BaseRepository[models.PasswordReset]
GetByToken(ctx context.Context, token string) (*models.PasswordReset, error)
GetByUserID(ctx context.Context, userID uint) ([]models.PasswordReset, error)
DeleteExpired(ctx context.Context) error
MarkAsUsed(ctx context.Context, id uint) error
}
type passwordResetRepository struct { type passwordResetRepository struct {
BaseRepository[models.PasswordReset] domain.BaseRepository[domain.PasswordReset]
db *gorm.DB db *gorm.DB
} }
// NewPasswordResetRepository creates a new PasswordResetRepository. // NewPasswordResetRepository creates a new PasswordResetRepository.
func NewPasswordResetRepository(db *gorm.DB) PasswordResetRepository { func NewPasswordResetRepository(db *gorm.DB) domain.PasswordResetRepository {
return &passwordResetRepository{ return &passwordResetRepository{
BaseRepository: NewBaseRepositoryImpl[models.PasswordReset](db), BaseRepository: NewBaseRepositoryImpl[domain.PasswordReset](db),
db: db, db: db,
} }
} }
// GetByToken finds a reset by token (only unused and non-expired) // GetByToken finds a reset by token (only unused and non-expired)
func (r *passwordResetRepository) GetByToken(ctx context.Context, token string) (*models.PasswordReset, error) { func (r *passwordResetRepository) GetByToken(ctx context.Context, token string) (*domain.PasswordReset, error) {
var reset models.PasswordReset var reset domain.PasswordReset
if err := r.db.WithContext(ctx).Where("token = ? AND used = ? AND expires_at > ?", token, false, time.Now()).First(&reset).Error; err != nil { if err := r.db.WithContext(ctx).Where("token = ? AND used = ? AND expires_at > ?", token, false, time.Now()).First(&reset).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) { if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, ErrEntityNotFound return nil, ErrEntityNotFound
@ -43,8 +34,8 @@ func (r *passwordResetRepository) GetByToken(ctx context.Context, token string)
} }
// GetByUserID finds resets by user ID // GetByUserID finds resets by user ID
func (r *passwordResetRepository) GetByUserID(ctx context.Context, userID uint) ([]models.PasswordReset, error) { func (r *passwordResetRepository) GetByUserID(ctx context.Context, userID uint) ([]domain.PasswordReset, error) {
var resets []models.PasswordReset var resets []domain.PasswordReset
if err := r.db.WithContext(ctx).Where("user_id = ?", userID).Find(&resets).Error; err != nil { if err := r.db.WithContext(ctx).Where("user_id = ?", userID).Find(&resets).Error; err != nil {
return nil, err return nil, err
} }
@ -53,7 +44,7 @@ func (r *passwordResetRepository) GetByUserID(ctx context.Context, userID uint)
// DeleteExpired deletes expired resets // DeleteExpired deletes expired resets
func (r *passwordResetRepository) DeleteExpired(ctx context.Context) error { func (r *passwordResetRepository) DeleteExpired(ctx context.Context) error {
if err := r.db.WithContext(ctx).Where("expires_at < ?", time.Now()).Delete(&models.PasswordReset{}).Error; err != nil { if err := r.db.WithContext(ctx).Where("expires_at < ?", time.Now()).Delete(&domain.PasswordReset{}).Error; err != nil {
return err return err
} }
return nil return nil
@ -61,7 +52,7 @@ func (r *passwordResetRepository) DeleteExpired(ctx context.Context) error {
// MarkAsUsed marks a reset as used // MarkAsUsed marks a reset as used
func (r *passwordResetRepository) MarkAsUsed(ctx context.Context, id uint) error { func (r *passwordResetRepository) MarkAsUsed(ctx context.Context, id uint) error {
if err := r.db.WithContext(ctx).Model(&models.PasswordReset{}).Where("id = ?", id).Update("used", true).Error; err != nil { if err := r.db.WithContext(ctx).Model(&domain.PasswordReset{}).Where("id = ?", id).Update("used", true).Error; err != nil {
return err return err
} }
return nil return nil

View File

@ -1,36 +1,28 @@
package repositories package sql
import ( import (
"context" "context"
"gorm.io/gorm" "gorm.io/gorm"
"math" "math"
"tercul/internal/models" "tercul/internal/domain/place"
) )
// PlaceRepository defines CRUD methods specific to Place.
type PlaceRepository interface {
BaseRepository[models.Place]
ListByCountryID(ctx context.Context, countryID uint) ([]models.Place, error)
ListByCityID(ctx context.Context, cityID uint) ([]models.Place, error)
FindNearby(ctx context.Context, latitude, longitude float64, radiusKm float64) ([]models.Place, error)
}
type placeRepository struct { type placeRepository struct {
BaseRepository[models.Place] domain.BaseRepository[domain.Place]
db *gorm.DB db *gorm.DB
} }
// NewPlaceRepository creates a new PlaceRepository. // NewPlaceRepository creates a new PlaceRepository.
func NewPlaceRepository(db *gorm.DB) PlaceRepository { func NewPlaceRepository(db *gorm.DB) place.PlaceRepository {
return &placeRepository{ return &placeRepository{
BaseRepository: NewBaseRepositoryImpl[models.Place](db), BaseRepository: NewBaseRepositoryImpl[domain.Place](db),
db: db, db: db,
} }
} }
// ListByCountryID finds places by country ID // ListByCountryID finds places by country ID
func (r *placeRepository) ListByCountryID(ctx context.Context, countryID uint) ([]models.Place, error) { func (r *placeRepository) ListByCountryID(ctx context.Context, countryID uint) ([]domain.Place, error) {
var places []models.Place var places []domain.Place
if err := r.db.WithContext(ctx).Where("country_id = ?", countryID).Find(&places).Error; err != nil { if err := r.db.WithContext(ctx).Where("country_id = ?", countryID).Find(&places).Error; err != nil {
return nil, err return nil, err
} }
@ -38,8 +30,8 @@ func (r *placeRepository) ListByCountryID(ctx context.Context, countryID uint) (
} }
// ListByCityID finds places by city ID // ListByCityID finds places by city ID
func (r *placeRepository) ListByCityID(ctx context.Context, cityID uint) ([]models.Place, error) { func (r *placeRepository) ListByCityID(ctx context.Context, cityID uint) ([]domain.Place, error) {
var places []models.Place var places []domain.Place
if err := r.db.WithContext(ctx).Where("city_id = ?", cityID).Find(&places).Error; err != nil { if err := r.db.WithContext(ctx).Where("city_id = ?", cityID).Find(&places).Error; err != nil {
return nil, err return nil, err
} }
@ -47,10 +39,10 @@ func (r *placeRepository) ListByCityID(ctx context.Context, cityID uint) ([]mode
} }
// FindNearby finds places within a certain radius (in kilometers) of a point // FindNearby finds places within a certain radius (in kilometers) of a point
func (r *placeRepository) FindNearby(ctx context.Context, latitude, longitude float64, radiusKm float64) ([]models.Place, error) { func (r *placeRepository) FindNearby(ctx context.Context, latitude, longitude float64, radiusKm float64) ([]domain.Place, error) {
// This is a simplified implementation that would need to be replaced with // This is a simplified implementation that would need to be replaced with
// a proper geospatial query based on the database being used // a proper geospatial query based on the database being used
var places []models.Place var places []domain.Place
// For PostgreSQL with PostGIS, you might use something like: // For PostgreSQL with PostGIS, you might use something like:
// query := `SELECT * FROM places // query := `SELECT * FROM places

View File

@ -1,33 +1,27 @@
package repositories package sql
import ( import (
"context" "context"
"gorm.io/gorm" "gorm.io/gorm"
"tercul/internal/models" "tercul/internal/domain/publisher"
) )
// PublisherRepository defines CRUD methods specific to Publisher.
type PublisherRepository interface {
BaseRepository[models.Publisher]
ListByCountryID(ctx context.Context, countryID uint) ([]models.Publisher, error)
}
type publisherRepository struct { type publisherRepository struct {
BaseRepository[models.Publisher] domain.BaseRepository[domain.Publisher]
db *gorm.DB db *gorm.DB
} }
// NewPublisherRepository creates a new PublisherRepository. // NewPublisherRepository creates a new PublisherRepository.
func NewPublisherRepository(db *gorm.DB) PublisherRepository { func NewPublisherRepository(db *gorm.DB) publisher.PublisherRepository {
return &publisherRepository{ return &publisherRepository{
BaseRepository: NewBaseRepositoryImpl[models.Publisher](db), BaseRepository: NewBaseRepositoryImpl[domain.Publisher](db),
db: db, db: db,
} }
} }
// ListByCountryID finds publishers by country ID // ListByCountryID finds publishers by country ID
func (r *publisherRepository) ListByCountryID(ctx context.Context, countryID uint) ([]models.Publisher, error) { func (r *publisherRepository) ListByCountryID(ctx context.Context, countryID uint) ([]domain.Publisher, error) {
var publishers []models.Publisher var publishers []domain.Publisher
if err := r.db.WithContext(ctx).Where("country_id = ?", countryID).Find(&publishers).Error; err != nil { if err := r.db.WithContext(ctx).Where("country_id = ?", countryID).Find(&publishers).Error; err != nil {
return nil, err return nil, err
} }

View File

@ -1,35 +1,28 @@
package repositories package sql
import ( import (
"context" "context"
"errors" "errors"
"gorm.io/gorm" "gorm.io/gorm"
"tercul/internal/models" "tercul/internal/domain/source"
) )
// SourceRepository defines CRUD methods specific to Source.
type SourceRepository interface {
BaseRepository[models.Source]
ListByWorkID(ctx context.Context, workID uint) ([]models.Source, error)
FindByURL(ctx context.Context, url string) (*models.Source, error)
}
type sourceRepository struct { type sourceRepository struct {
BaseRepository[models.Source] domain.BaseRepository[domain.Source]
db *gorm.DB db *gorm.DB
} }
// NewSourceRepository creates a new SourceRepository. // NewSourceRepository creates a new SourceRepository.
func NewSourceRepository(db *gorm.DB) SourceRepository { func NewSourceRepository(db *gorm.DB) source.SourceRepository {
return &sourceRepository{ return &sourceRepository{
BaseRepository: NewBaseRepositoryImpl[models.Source](db), BaseRepository: NewBaseRepositoryImpl[domain.Source](db),
db: db, db: db,
} }
} }
// ListByWorkID finds sources by work ID // ListByWorkID finds sources by work ID
func (r *sourceRepository) ListByWorkID(ctx context.Context, workID uint) ([]models.Source, error) { func (r *sourceRepository) ListByWorkID(ctx context.Context, workID uint) ([]domain.Source, error) {
var sources []models.Source var sources []domain.Source
if err := r.db.WithContext(ctx).Joins("JOIN work_sources ON work_sources.source_id = sources.id"). if err := r.db.WithContext(ctx).Joins("JOIN work_sources ON work_sources.source_id = sources.id").
Where("work_sources.work_id = ?", workID). Where("work_sources.work_id = ?", workID).
Find(&sources).Error; err != nil { Find(&sources).Error; err != nil {
@ -39,8 +32,8 @@ func (r *sourceRepository) ListByWorkID(ctx context.Context, workID uint) ([]mod
} }
// FindByURL finds a source by URL // FindByURL finds a source by URL
func (r *sourceRepository) FindByURL(ctx context.Context, url string) (*models.Source, error) { func (r *sourceRepository) FindByURL(ctx context.Context, url string) (*domain.Source, error) {
var source models.Source var source domain.Source
if err := r.db.WithContext(ctx).Where("url = ?", url).First(&source).Error; err != nil { if err := r.db.WithContext(ctx).Where("url = ?", url).First(&source).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) { if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, ErrEntityNotFound return nil, ErrEntityNotFound

View File

@ -4,7 +4,7 @@ import (
"context" "context"
"errors" "errors"
"gorm.io/gorm" "gorm.io/gorm"
"tercul/internal/domain" "tercul/internal/domain/tag"
) )
type tagRepository struct { type tagRepository struct {
@ -13,7 +13,7 @@ type tagRepository struct {
} }
// NewTagRepository creates a new TagRepository. // NewTagRepository creates a new TagRepository.
func NewTagRepository(db *gorm.DB) domain.TagRepository { func NewTagRepository(db *gorm.DB) tag.TagRepository {
return &tagRepository{ return &tagRepository{
BaseRepository: NewBaseRepositoryImpl[domain.Tag](db), BaseRepository: NewBaseRepositoryImpl[domain.Tag](db),
db: db, db: db,

View File

@ -3,7 +3,7 @@ package sql
import ( import (
"context" "context"
"gorm.io/gorm" "gorm.io/gorm"
"tercul/internal/domain" "tercul/internal/domain/translation"
) )
type translationRepository struct { type translationRepository struct {
@ -12,7 +12,7 @@ type translationRepository struct {
} }
// NewTranslationRepository creates a new TranslationRepository. // NewTranslationRepository creates a new TranslationRepository.
func NewTranslationRepository(db *gorm.DB) domain.TranslationRepository { func NewTranslationRepository(db *gorm.DB) translation.TranslationRepository {
return &translationRepository{ return &translationRepository{
BaseRepository: NewBaseRepositoryImpl[domain.Translation](db), BaseRepository: NewBaseRepositoryImpl[domain.Translation](db),
db: db, db: db,

View File

@ -1,34 +1,28 @@
package repositories package sql
import ( import (
"context" "context"
"errors" "errors"
"gorm.io/gorm" "gorm.io/gorm"
"tercul/internal/models" "tercul/internal/domain/user_profile"
) )
// UserProfileRepository defines CRUD methods specific to UserProfile.
type UserProfileRepository interface {
BaseRepository[models.UserProfile]
GetByUserID(ctx context.Context, userID uint) (*models.UserProfile, error)
}
type userProfileRepository struct { type userProfileRepository struct {
BaseRepository[models.UserProfile] domain.BaseRepository[domain.UserProfile]
db *gorm.DB db *gorm.DB
} }
// NewUserProfileRepository creates a new UserProfileRepository. // NewUserProfileRepository creates a new UserProfileRepository.
func NewUserProfileRepository(db *gorm.DB) UserProfileRepository { func NewUserProfileRepository(db *gorm.DB) domain.UserProfileRepository {
return &userProfileRepository{ return &userProfileRepository{
BaseRepository: NewBaseRepositoryImpl[models.UserProfile](db), BaseRepository: NewBaseRepositoryImpl[domain.UserProfile](db),
db: db, db: db,
} }
} }
// GetByUserID finds a user profile by user ID // GetByUserID finds a user profile by user ID
func (r *userProfileRepository) GetByUserID(ctx context.Context, userID uint) (*models.UserProfile, error) { func (r *userProfileRepository) GetByUserID(ctx context.Context, userID uint) (*domain.UserProfile, error) {
var profile models.UserProfile var profile domain.UserProfile
if err := r.db.WithContext(ctx).Where("user_id = ?", userID).First(&profile).Error; err != nil { if err := r.db.WithContext(ctx).Where("user_id = ?", userID).First(&profile).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) { if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, ErrEntityNotFound return nil, ErrEntityNotFound

View File

@ -4,7 +4,7 @@ import (
"context" "context"
"errors" "errors"
"gorm.io/gorm" "gorm.io/gorm"
"tercul/internal/domain" "tercul/internal/domain/user"
) )
type userRepository struct { type userRepository struct {
@ -13,7 +13,7 @@ type userRepository struct {
} }
// NewUserRepository creates a new UserRepository. // NewUserRepository creates a new UserRepository.
func NewUserRepository(db *gorm.DB) domain.UserRepository { func NewUserRepository(db *gorm.DB) user.UserRepository {
return &userRepository{ return &userRepository{
BaseRepository: NewBaseRepositoryImpl[domain.User](db), BaseRepository: NewBaseRepositoryImpl[domain.User](db),
db: db, db: db,

View File

@ -1,37 +1,29 @@
package repositories package sql
import ( import (
"context" "context"
"errors" "errors"
"gorm.io/gorm" "gorm.io/gorm"
"tercul/internal/models" "tercul/internal/domain/user_session"
"time" "time"
) )
// UserSessionRepository defines CRUD methods specific to UserSession.
type UserSessionRepository interface {
BaseRepository[models.UserSession]
GetByToken(ctx context.Context, token string) (*models.UserSession, error)
GetByUserID(ctx context.Context, userID uint) ([]models.UserSession, error)
DeleteExpired(ctx context.Context) error
}
type userSessionRepository struct { type userSessionRepository struct {
BaseRepository[models.UserSession] domain.BaseRepository[domain.UserSession]
db *gorm.DB db *gorm.DB
} }
// NewUserSessionRepository creates a new UserSessionRepository. // NewUserSessionRepository creates a new UserSessionRepository.
func NewUserSessionRepository(db *gorm.DB) UserSessionRepository { func NewUserSessionRepository(db *gorm.DB) domain.UserSessionRepository {
return &userSessionRepository{ return &userSessionRepository{
BaseRepository: NewBaseRepositoryImpl[models.UserSession](db), BaseRepository: NewBaseRepositoryImpl[domain.UserSession](db),
db: db, db: db,
} }
} }
// GetByToken finds a session by token // GetByToken finds a session by token
func (r *userSessionRepository) GetByToken(ctx context.Context, token string) (*models.UserSession, error) { func (r *userSessionRepository) GetByToken(ctx context.Context, token string) (*domain.UserSession, error) {
var session models.UserSession var session domain.UserSession
if err := r.db.WithContext(ctx).Where("token = ?", token).First(&session).Error; err != nil { if err := r.db.WithContext(ctx).Where("token = ?", token).First(&session).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) { if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, ErrEntityNotFound return nil, ErrEntityNotFound
@ -42,8 +34,8 @@ func (r *userSessionRepository) GetByToken(ctx context.Context, token string) (*
} }
// GetByUserID finds sessions by user ID // GetByUserID finds sessions by user ID
func (r *userSessionRepository) GetByUserID(ctx context.Context, userID uint) ([]models.UserSession, error) { func (r *userSessionRepository) GetByUserID(ctx context.Context, userID uint) ([]domain.UserSession, error) {
var sessions []models.UserSession var sessions []domain.UserSession
if err := r.db.WithContext(ctx).Where("user_id = ?", userID).Find(&sessions).Error; err != nil { if err := r.db.WithContext(ctx).Where("user_id = ?", userID).Find(&sessions).Error; err != nil {
return nil, err return nil, err
} }
@ -52,7 +44,7 @@ func (r *userSessionRepository) GetByUserID(ctx context.Context, userID uint) ([
// DeleteExpired deletes expired sessions // DeleteExpired deletes expired sessions
func (r *userSessionRepository) DeleteExpired(ctx context.Context) error { func (r *userSessionRepository) DeleteExpired(ctx context.Context) error {
if err := r.db.WithContext(ctx).Where("expires_at < ?", time.Now()).Delete(&models.UserSession{}).Error; err != nil { if err := r.db.WithContext(ctx).Where("expires_at < ?", time.Now()).Delete(&domain.UserSession{}).Error; err != nil {
return err return err
} }
return nil return nil

View File

@ -3,7 +3,7 @@ package sql
import ( import (
"context" "context"
"gorm.io/gorm" "gorm.io/gorm"
"tercul/internal/domain" "tercul/internal/domain/work"
) )
type workRepository struct { type workRepository struct {
@ -12,7 +12,7 @@ type workRepository struct {
} }
// NewWorkRepository creates a new WorkRepository. // NewWorkRepository creates a new WorkRepository.
func NewWorkRepository(db *gorm.DB) domain.WorkRepository { func NewWorkRepository(db *gorm.DB) work.WorkRepository {
return &workRepository{ return &workRepository{
BaseRepository: NewBaseRepositoryImpl[domain.Work](db), BaseRepository: NewBaseRepositoryImpl[domain.Work](db),
db: db, db: db,

View File

@ -0,0 +1,15 @@
package author
import (
"context"
"tercul/internal/domain"
)
// AuthorRepository defines CRUD methods specific to Author.
type AuthorRepository interface {
domain.BaseRepositoryRepository[domain.Author]
ListByWorkID(ctx context.Context, workID uint) ([]domain.Author, error)
ListByBookID(ctx context.Context, bookID uint) ([]domain.Author, error)
ListByCountryID(ctx context.Context, countryID uint) ([]domain.Author, error)
}

View File

@ -0,0 +1,16 @@
package book
import (
"context"
"tercul/internal/domain"
)
// BookRepository defines CRUD methods specific to Book.
type BookRepository interface {
domain.BaseRepositoryRepository[domain.Book]
ListByAuthorID(ctx context.Context, authorID uint) ([]domain.Book, error)
ListByPublisherID(ctx context.Context, publisherID uint) ([]domain.Book, error)
ListByWorkID(ctx context.Context, workID uint) ([]domain.Book, error)
FindByISBN(ctx context.Context, isbn string) (*domain.Book, error)
}

View File

@ -0,0 +1,14 @@
package bookmark
import (
"context"
"tercul/internal/domain"
)
// BookmarkRepository defines CRUD methods specific to Bookmark.
type BookmarkRepository interface {
domain.BaseRepositoryRepository[domain.Bookmark]
ListByUserID(ctx context.Context, userID uint) ([]domain.Bookmark, error)
ListByWorkID(ctx context.Context, workID uint) ([]domain.Bookmark, error)
}

View File

@ -0,0 +1,15 @@
package category
import (
"context"
"tercul/internal/domain"
)
// CategoryRepository defines CRUD methods specific to Category.
type CategoryRepository interface {
domain.BaseRepositoryRepository[domain.Category]
FindByName(ctx context.Context, name string) (*domain.Category, error)
ListByWorkID(ctx context.Context, workID uint) ([]domain.Category, error)
ListByParentID(ctx context.Context, parentID *uint) ([]domain.Category, error)
}

View File

@ -0,0 +1,13 @@
package city
import (
"context"
"tercul/internal/domain"
)
// CityRepository defines CRUD methods specific to City.
type CityRepository interface {
domain.BaseRepositoryRepository[domain.City]
ListByCountryID(ctx context.Context, countryID uint) ([]domain.City, error)
}

View File

@ -0,0 +1,15 @@
package collection
import (
"context"
"tercul/internal/domain"
)
// CollectionRepository defines CRUD methods specific to Collection.
type CollectionRepository interface {
domain.BaseRepositoryRepository[domain.Collection]
ListByUserID(ctx context.Context, userID uint) ([]domain.Collection, error)
ListPublic(ctx context.Context) ([]domain.Collection, error)
ListByWorkID(ctx context.Context, workID uint) ([]domain.Collection, error)
}

View File

@ -0,0 +1,16 @@
package comment
import (
"context"
"tercul/internal/domain"
)
// CommentRepository defines CRUD methods specific to Comment.
type CommentRepository interface {
domain.BaseRepositoryRepository[domain.Comment]
ListByUserID(ctx context.Context, userID uint) ([]domain.Comment, error)
ListByWorkID(ctx context.Context, workID uint) ([]domain.Comment, error)
ListByTranslationID(ctx context.Context, translationID uint) ([]domain.Comment, error)
ListByParentID(ctx context.Context, parentID uint) ([]domain.Comment, error)
}

View File

@ -0,0 +1,17 @@
package contribution
import (
"context"
"tercul/internal/domain"
)
// ContributionRepository defines CRUD methods specific to Contribution.
type ContributionRepository interface {
domain.BaseRepositoryRepository[domain.Contribution]
ListByUserID(ctx context.Context, userID uint) ([]domain.Contribution, error)
ListByReviewerID(ctx context.Context, reviewerID uint) ([]domain.Contribution, error)
ListByWorkID(ctx context.Context, workID uint) ([]domain.Contribution, error)
ListByTranslationID(ctx context.Context, translationID uint) ([]domain.Contribution, error)
ListByStatus(ctx context.Context, status string) ([]domain.Contribution, error)
}

View File

@ -0,0 +1,19 @@
package copyright
import (
"context"
"tercul/internal/domain"
)
// CopyrightRepository defines CRUD methods specific to Copyright.
type CopyrightRepository interface {
domain.BaseRepositoryRepository[domain.Copyright]
AttachToEntity(ctx context.Context, copyrightID uint, entityID uint, entityType string) (error)
DetachFromEntity(ctx context.Context, copyrightID uint, entityID uint, entityType string) (error)
GetByEntity(ctx context.Context, entityID uint, entityType string) ([]domain.Copyright, error)
GetEntitiesByCopyright(ctx context.Context, copyrightID uint) ([]domain.Copyrightable, error)
AddTranslation(ctx context.Context, translation *domain.CopyrightTranslation) (error)
GetTranslations(ctx context.Context, copyrightID uint) ([]domain.CopyrightTranslation, error)
GetTranslationByLanguage(ctx context.Context, copyrightID uint, languageCode string) (*domain.CopyrightTranslation, error)
}

View File

@ -0,0 +1,14 @@
package copyright_claim
import (
"context"
"tercul/internal/domain"
)
// Copyright_claimRepository defines CRUD methods specific to Copyright_claim.
type Copyright_claimRepository interface {
domain.BaseRepositoryRepository[domain.CopyrightClaim]
ListByWorkID(ctx context.Context, workID uint) ([]domain.CopyrightClaim, error)
ListByUserID(ctx context.Context, userID uint) ([]domain.CopyrightClaim, error)
}

View File

@ -0,0 +1,14 @@
package country
import (
"context"
"tercul/internal/domain"
)
// CountryRepository defines CRUD methods specific to Country.
type CountryRepository interface {
domain.BaseRepositoryRepository[domain.Country]
GetByCode(ctx context.Context, code string) (*domain.Country, error)
ListByContinent(ctx context.Context, continent string) ([]domain.Country, error)
}

View File

@ -0,0 +1,13 @@
package edge
import (
"context"
"tercul/internal/domain"
)
// EdgeRepository defines CRUD methods specific to Edge.
type EdgeRepository interface {
domain.BaseRepositoryRepository[domain.Edge]
ListBySource(ctx context.Context, sourceTable string, sourceID uint) ([]domain.Edge, error)
}

View File

@ -0,0 +1,14 @@
package edition
import (
"context"
"tercul/internal/domain"
)
// EditionRepository defines CRUD methods specific to Edition.
type EditionRepository interface {
domain.BaseRepositoryRepository[domain.Edition]
ListByBookID(ctx context.Context, bookID uint) ([]domain.Edition, error)
FindByISBN(ctx context.Context, isbn string) (*domain.Edition, error)
}

View File

@ -0,0 +1,16 @@
package email_verification
import (
"context"
"tercul/internal/domain"
)
// Email_verificationRepository defines CRUD methods specific to Email_verification.
type Email_verificationRepository interface {
domain.BaseRepositoryRepository[domain.EmailVerification]
GetByToken(ctx context.Context, token string) (*domain.EmailVerification, error)
GetByUserID(ctx context.Context, userID uint) ([]domain.EmailVerification, error)
DeleteExpired(ctx context.Context) (error)
MarkAsUsed(ctx context.Context, id uint) (error)
}

View File

@ -0,0 +1,16 @@
package like
import (
"context"
"tercul/internal/domain"
)
// LikeRepository defines CRUD methods specific to Like.
type LikeRepository interface {
domain.BaseRepositoryRepository[domain.Like]
ListByUserID(ctx context.Context, userID uint) ([]domain.Like, error)
ListByWorkID(ctx context.Context, workID uint) ([]domain.Like, error)
ListByTranslationID(ctx context.Context, translationID uint) ([]domain.Like, error)
ListByCommentID(ctx context.Context, commentID uint) ([]domain.Like, error)
}

View File

@ -0,0 +1,15 @@
package monetization
import (
"context"
"tercul/internal/domain"
)
// MonetizationRepository defines CRUD methods specific to Monetization.
type MonetizationRepository interface {
domain.BaseRepositoryRepository[domain.Monetization]
ListByWorkID(ctx context.Context, workID uint) ([]domain.Monetization, error)
ListByTranslationID(ctx context.Context, translationID uint) ([]domain.Monetization, error)
ListByBookID(ctx context.Context, bookID uint) ([]domain.Monetization, error)
}

View File

@ -0,0 +1,16 @@
package password_reset
import (
"context"
"tercul/internal/domain"
)
// Password_resetRepository defines CRUD methods specific to Password_reset.
type Password_resetRepository interface {
domain.BaseRepositoryRepository[domain.PasswordReset]
GetByToken(ctx context.Context, token string) (*domain.PasswordReset, error)
GetByUserID(ctx context.Context, userID uint) ([]domain.PasswordReset, error)
DeleteExpired(ctx context.Context) (error)
MarkAsUsed(ctx context.Context, id uint) (error)
}

View File

@ -0,0 +1,15 @@
package place
import (
"context"
"tercul/internal/domain"
)
// PlaceRepository defines CRUD methods specific to Place.
type PlaceRepository interface {
domain.BaseRepositoryRepository[domain.Place]
ListByCountryID(ctx context.Context, countryID uint) ([]domain.Place, error)
ListByCityID(ctx context.Context, cityID uint) ([]domain.Place, error)
FindNearby(ctx context.Context, latitude float64, radiusKm float64) ([]domain.Place, error)
}

View File

@ -0,0 +1,13 @@
package publisher
import (
"context"
"tercul/internal/domain"
)
// PublisherRepository defines CRUD methods specific to Publisher.
type PublisherRepository interface {
domain.BaseRepositoryRepository[domain.Publisher]
ListByCountryID(ctx context.Context, countryID uint) ([]domain.Publisher, error)
}

View File

@ -0,0 +1,14 @@
package source
import (
"context"
"tercul/internal/domain"
)
// SourceRepository defines CRUD methods specific to Source.
type SourceRepository interface {
domain.BaseRepositoryRepository[domain.Source]
ListByWorkID(ctx context.Context, workID uint) ([]domain.Source, error)
FindByURL(ctx context.Context, url string) (*domain.Source, error)
}

View File

@ -0,0 +1,14 @@
package tag
import (
"context"
"tercul/internal/domain"
)
// TagRepository defines CRUD methods specific to Tag.
type TagRepository interface {
domain.BaseRepositoryRepository[domain.Tag]
FindByName(ctx context.Context, name string) (*domain.Tag, error)
ListByWorkID(ctx context.Context, workID uint) ([]domain.Tag, error)
}

View File

@ -0,0 +1,16 @@
package translation
import (
"context"
"tercul/internal/domain"
)
// TranslationRepository defines CRUD methods specific to Translation.
type TranslationRepository interface {
domain.BaseRepositoryRepository[domain.Translation]
ListByWorkID(ctx context.Context, workID uint) ([]domain.Translation, error)
ListByEntity(ctx context.Context, entityType string, entityID uint) ([]domain.Translation, error)
ListByTranslatorID(ctx context.Context, translatorID uint) ([]domain.Translation, error)
ListByStatus(ctx context.Context, status domain.TranslationStatus) ([]domain.Translation, error)
}

View File

@ -0,0 +1,15 @@
package user
import (
"context"
"tercul/internal/domain"
)
// UserRepository defines CRUD methods specific to User.
type UserRepository interface {
domain.BaseRepositoryRepository[domain.User]
FindByUsername(ctx context.Context, username string) (*domain.User, error)
FindByEmail(ctx context.Context, email string) (*domain.User, error)
ListByRole(ctx context.Context, role domain.UserRole) ([]domain.User, error)
}

View File

@ -0,0 +1,13 @@
package user_profile
import (
"context"
"tercul/internal/domain"
)
// User_profileRepository defines CRUD methods specific to User_profile.
type User_profileRepository interface {
domain.BaseRepositoryRepository[domain.UserProfile]
GetByUserID(ctx context.Context, userID uint) (*domain.UserProfile, error)
}

View File

@ -0,0 +1,15 @@
package user_session
import (
"context"
"tercul/internal/domain"
)
// User_sessionRepository defines CRUD methods specific to User_session.
type User_sessionRepository interface {
domain.BaseRepositoryRepository[domain.UserSession]
GetByToken(ctx context.Context, token string) (*domain.UserSession, error)
GetByUserID(ctx context.Context, userID uint) ([]domain.UserSession, error)
DeleteExpired(ctx context.Context) (error)
}

View File

@ -0,0 +1,18 @@
package work
import (
"context"
"tercul/internal/domain"
)
// WorkRepository defines CRUD methods specific to Work.
type WorkRepository interface {
domain.BaseRepositoryRepository[domain.Work]
FindByTitle(ctx context.Context, title string) ([]domain.Work, error)
FindByAuthor(ctx context.Context, authorID uint) ([]domain.Work, error)
FindByCategory(ctx context.Context, categoryID uint) ([]domain.Work, error)
FindByLanguage(ctx context.Context, language string, page int) (*, error)
GetWithTranslations(ctx context.Context, id uint) (*domain.Work, error)
ListWithTranslations(ctx context.Context, page int) (*, error)
}

View File

@ -11,24 +11,24 @@ type LinguaLanguageDetector struct {
} }
// NewLinguaLanguageDetector builds a detector for all supported languages // NewLinguaLanguageDetector builds a detector for all supported languages
func NewLinguaLanguageDetector() *LinguaLanguageDetector { func NewLinguaLanguageDetector() LanguageDetector {
det := lingua.NewLanguageDetectorBuilder().FromAllLanguages().Build() det := lingua.NewLanguageDetectorBuilder().FromAllLanguages().Build()
return &LinguaLanguageDetector{detector: det} return &LinguaLanguageDetector{detector: det}
} }
// DetectLanguage returns a lowercase ISO 639-1 code if possible // DetectLanguage returns a lowercase ISO 639-1 code if possible
func (l *LinguaLanguageDetector) DetectLanguage(text string) (string, bool) { func (l *LinguaLanguageDetector) DetectLanguage(text string) (string, error) {
lang, ok := l.detector.DetectLanguageOf(text) lang, ok := l.detector.DetectLanguageOf(text)
if !ok { if !ok {
return "", false return "", nil // Or an error if you prefer
} }
// Prefer ISO 639-1 when available else fallback to ISO 639-3 // Prefer ISO 639-1 when available else fallback to ISO 639-3
if s := lang.IsoCode639_1().String(); s != "" { if s := lang.IsoCode639_1().String(); s != "" {
return s, true return s, nil
} }
if s := lang.IsoCode639_3().String(); s != "" { if s := lang.IsoCode639_3().String(); s != "" {
return s, true return s, nil
} }
// fallback to language name // fallback to language name
return strings.ToLower(lang.String()), true return strings.ToLower(lang.String()), nil
} }

View File

@ -3,7 +3,7 @@ package linguistics
import ( import (
"context" "context"
"fmt" "fmt"
models2 "tercul/internal/models" "tercul/internal/domain"
"gorm.io/gorm" "gorm.io/gorm"
"tercul/internal/platform/log" "tercul/internal/platform/log"
@ -18,14 +18,14 @@ type AnalysisRepository interface {
GetWorkContent(ctx context.Context, workID uint, language string) (string, error) GetWorkContent(ctx context.Context, workID uint, language string) (string, error)
// StoreWorkAnalysis stores work-specific analysis results // StoreWorkAnalysis stores work-specific analysis results
StoreWorkAnalysis(ctx context.Context, workID uint, textMetadata *models2.TextMetadata, StoreWorkAnalysis(ctx context.Context, workID uint, textMetadata *domain.TextMetadata,
readabilityScore *models2.ReadabilityScore, languageAnalysis *models2.LanguageAnalysis) error readabilityScore *domain.ReadabilityScore, languageAnalysis *domain.LanguageAnalysis) error
// GetWorkByID fetches a work by ID // GetWorkByID fetches a work by ID
GetWorkByID(ctx context.Context, workID uint) (*models2.Work, error) GetWorkByID(ctx context.Context, workID uint) (*domain.Work, error)
// GetAnalysisData fetches persisted analysis data for a work // GetAnalysisData fetches persisted analysis data for a work
GetAnalysisData(ctx context.Context, workID uint) (*models2.TextMetadata, *models2.ReadabilityScore, *models2.LanguageAnalysis, error) GetAnalysisData(ctx context.Context, workID uint) (*domain.TextMetadata, *domain.ReadabilityScore, *domain.LanguageAnalysis, error)
} }
// GORMAnalysisRepository implements AnalysisRepository using GORM // GORMAnalysisRepository implements AnalysisRepository using GORM
@ -45,7 +45,7 @@ func (r *GORMAnalysisRepository) StoreAnalysisResults(ctx context.Context, workI
} }
// Determine language from the work record to avoid hardcoded defaults // Determine language from the work record to avoid hardcoded defaults
var work models2.Work var work domain.Work
if err := r.db.WithContext(ctx).First(&work, workID).Error; err != nil { if err := r.db.WithContext(ctx).First(&work, workID).Error; err != nil {
log.LogError("Failed to fetch work for language", log.LogError("Failed to fetch work for language",
log.F("workID", workID), log.F("workID", workID),
@ -54,7 +54,7 @@ func (r *GORMAnalysisRepository) StoreAnalysisResults(ctx context.Context, workI
} }
// Create text metadata // Create text metadata
textMetadata := &models2.TextMetadata{ textMetadata := &domain.TextMetadata{
WorkID: workID, WorkID: workID,
Language: work.Language, Language: work.Language,
WordCount: result.WordCount, WordCount: result.WordCount,
@ -65,7 +65,7 @@ func (r *GORMAnalysisRepository) StoreAnalysisResults(ctx context.Context, workI
} }
// Create readability score // Create readability score
readabilityScore := &models2.ReadabilityScore{ readabilityScore := &domain.ReadabilityScore{
WorkID: workID, WorkID: workID,
Language: work.Language, Language: work.Language,
Score: result.ReadabilityScore, Score: result.ReadabilityScore,
@ -73,10 +73,10 @@ func (r *GORMAnalysisRepository) StoreAnalysisResults(ctx context.Context, workI
} }
// Create language analysis // Create language analysis
languageAnalysis := &models2.LanguageAnalysis{ languageAnalysis := &domain.LanguageAnalysis{
WorkID: workID, WorkID: workID,
Language: work.Language, Language: work.Language,
Analysis: models2.JSONB{ Analysis: domain.JSONB{
"sentiment": result.Sentiment, "sentiment": result.Sentiment,
"keywords": extractKeywordsAsJSON(result.Keywords), "keywords": extractKeywordsAsJSON(result.Keywords),
"topics": extractTopicsAsJSON(result.Topics), "topics": extractTopicsAsJSON(result.Topics),
@ -89,7 +89,7 @@ func (r *GORMAnalysisRepository) StoreAnalysisResults(ctx context.Context, workI
// GetWorkContent retrieves content for a work from translations // GetWorkContent retrieves content for a work from translations
func (r *GORMAnalysisRepository) GetWorkContent(ctx context.Context, workID uint, language string) (string, error) { func (r *GORMAnalysisRepository) GetWorkContent(ctx context.Context, workID uint, language string) (string, error) {
// First, get the work to determine its language // First, get the work to determine its language
var work models2.Work var work domain.Work
if err := r.db.First(&work, workID).Error; err != nil { if err := r.db.First(&work, workID).Error; err != nil {
log.LogError("Failed to fetch work for content retrieval", log.LogError("Failed to fetch work for content retrieval",
log.F("workID", workID), log.F("workID", workID),
@ -102,7 +102,7 @@ func (r *GORMAnalysisRepository) GetWorkContent(ctx context.Context, workID uint
// 2. Work's language translation // 2. Work's language translation
// 3. Any available translation // 3. Any available translation
var translation models2.Translation var translation domain.Translation
// Try original language first // Try original language first
if err := r.db.Where("translatable_type = ? AND translatable_id = ? AND is_original_language = ?", if err := r.db.Where("translatable_type = ? AND translatable_id = ? AND is_original_language = ?",
@ -126,8 +126,8 @@ func (r *GORMAnalysisRepository) GetWorkContent(ctx context.Context, workID uint
} }
// GetWorkByID fetches a work by ID // GetWorkByID fetches a work by ID
func (r *GORMAnalysisRepository) GetWorkByID(ctx context.Context, workID uint) (*models2.Work, error) { func (r *GORMAnalysisRepository) GetWorkByID(ctx context.Context, workID uint) (*domain.Work, error) {
var work models2.Work var work domain.Work
if err := r.db.WithContext(ctx).First(&work, workID).Error; err != nil { if err := r.db.WithContext(ctx).First(&work, workID).Error; err != nil {
return nil, fmt.Errorf("failed to fetch work: %w", err) return nil, fmt.Errorf("failed to fetch work: %w", err)
} }
@ -135,10 +135,10 @@ func (r *GORMAnalysisRepository) GetWorkByID(ctx context.Context, workID uint) (
} }
// GetAnalysisData fetches persisted analysis data for a work // GetAnalysisData fetches persisted analysis data for a work
func (r *GORMAnalysisRepository) GetAnalysisData(ctx context.Context, workID uint) (*models2.TextMetadata, *models2.ReadabilityScore, *models2.LanguageAnalysis, error) { func (r *GORMAnalysisRepository) GetAnalysisData(ctx context.Context, workID uint) (*domain.TextMetadata, *domain.ReadabilityScore, *domain.LanguageAnalysis, error) {
var textMetadata models2.TextMetadata var textMetadata domain.TextMetadata
var readabilityScore models2.ReadabilityScore var readabilityScore domain.ReadabilityScore
var languageAnalysis models2.LanguageAnalysis var languageAnalysis domain.LanguageAnalysis
if err := r.db.WithContext(ctx).Where("work_id = ?", workID).First(&textMetadata).Error; err != nil { if err := r.db.WithContext(ctx).Where("work_id = ?", workID).First(&textMetadata).Error; err != nil {
log.LogWarn("No text metadata found for work", log.LogWarn("No text metadata found for work",
@ -160,14 +160,14 @@ func (r *GORMAnalysisRepository) GetAnalysisData(ctx context.Context, workID uin
// StoreWorkAnalysis stores work-specific analysis results // StoreWorkAnalysis stores work-specific analysis results
func (r *GORMAnalysisRepository) StoreWorkAnalysis(ctx context.Context, workID uint, func (r *GORMAnalysisRepository) StoreWorkAnalysis(ctx context.Context, workID uint,
textMetadata *models2.TextMetadata, readabilityScore *models2.ReadabilityScore, textMetadata *domain.TextMetadata, readabilityScore *domain.ReadabilityScore,
languageAnalysis *models2.LanguageAnalysis) error { languageAnalysis *domain.LanguageAnalysis) error {
// Use a transaction to ensure all data is stored atomically // Use a transaction to ensure all data is stored atomically
return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
// Store text metadata // Store text metadata
if textMetadata != nil { if textMetadata != nil {
if err := tx.Where("work_id = ?", workID).Delete(&models2.TextMetadata{}).Error; err != nil { if err := tx.Where("work_id = ?", workID).Delete(&domain.TextMetadata{}).Error; err != nil {
log.LogError("Failed to delete existing text metadata", log.LogError("Failed to delete existing text metadata",
log.F("workID", workID), log.F("workID", workID),
log.F("error", err)) log.F("error", err))
@ -184,7 +184,7 @@ func (r *GORMAnalysisRepository) StoreWorkAnalysis(ctx context.Context, workID u
// Store readability score // Store readability score
if readabilityScore != nil { if readabilityScore != nil {
if err := tx.Where("work_id = ?", workID).Delete(&models2.ReadabilityScore{}).Error; err != nil { if err := tx.Where("work_id = ?", workID).Delete(&domain.ReadabilityScore{}).Error; err != nil {
log.LogError("Failed to delete existing readability score", log.LogError("Failed to delete existing readability score",
log.F("workID", workID), log.F("workID", workID),
log.F("error", err)) log.F("error", err))
@ -201,7 +201,7 @@ func (r *GORMAnalysisRepository) StoreWorkAnalysis(ctx context.Context, workID u
// Store language analysis // Store language analysis
if languageAnalysis != nil { if languageAnalysis != nil {
if err := tx.Where("work_id = ?", workID).Delete(&models2.LanguageAnalysis{}).Error; err != nil { if err := tx.Where("work_id = ?", workID).Delete(&domain.LanguageAnalysis{}).Error; err != nil {
log.LogError("Failed to delete existing language analysis", log.LogError("Failed to delete existing language analysis",
log.F("workID", workID), log.F("workID", workID),
log.F("error", err)) log.F("error", err))
@ -224,9 +224,9 @@ func (r *GORMAnalysisRepository) StoreWorkAnalysis(ctx context.Context, workID u
} }
// Helper functions for data conversion // Helper functions for data conversion
func extractKeywordsAsJSON(keywords []Keyword) models2.JSONB { func extractKeywordsAsJSON(keywords []Keyword) domain.JSONB {
if len(keywords) == 0 { if len(keywords) == 0 {
return models2.JSONB{} return domain.JSONB{}
} }
keywordData := make([]map[string]interface{}, len(keywords)) keywordData := make([]map[string]interface{}, len(keywords))
@ -237,12 +237,12 @@ func extractKeywordsAsJSON(keywords []Keyword) models2.JSONB {
} }
} }
return models2.JSONB{"keywords": keywordData} return domain.JSONB{"keywords": keywordData}
} }
func extractTopicsAsJSON(topics []Topic) models2.JSONB { func extractTopicsAsJSON(topics []Topic) domain.JSONB {
if len(topics) == 0 { if len(topics) == 0 {
return models2.JSONB{} return domain.JSONB{}
} }
topicData := make([]map[string]interface{}, len(topics)) topicData := make([]map[string]interface{}, len(topics))
@ -253,5 +253,5 @@ func extractTopicsAsJSON(topics []Topic) models2.JSONB {
} }
} }
return models2.JSONB{"topics": topicData} return domain.JSONB{"topics": topicData}
} }

View File

@ -37,7 +37,7 @@ func (e *KeywordExtractor) Extract(text Text) ([]Keyword, error) {
// Filter out stop words // Filter out stop words
for word := range wordFreq { for word := range wordFreq {
if isStopWord(word) { if isStopWord(word, text.Language) {
delete(wordFreq, word) delete(wordFreq, word)
} }
} }
@ -72,36 +72,3 @@ func (e *KeywordExtractor) Extract(text Text) ([]Keyword, error) {
return keywords, nil return keywords, nil
} }
// isStopWord checks if a word is a common stop word
func isStopWord(word string) bool {
stopWords := map[string]bool{
"a": true, "about": true, "above": true, "after": true, "again": true,
"against": true, "all": true, "am": true, "an": true, "and": true,
"any": true, "are": true, "as": true, "at": true, "be": true,
"because": true, "been": true, "before": true, "being": true, "below": true,
"between": true, "both": true, "but": true, "by": true, "can": true,
"did": true, "do": true, "does": true, "doing": true, "don": true,
"down": true, "during": true, "each": true, "few": true, "for": true,
"from": true, "further": true, "had": true, "has": true, "have": true,
"having": true, "he": true, "her": true, "here": true, "hers": true,
"herself": true, "him": true, "himself": true, "his": true, "how": true,
"i": true, "if": true, "in": true, "into": true, "is": true,
"it": true, "its": true, "itself": true, "just": true, "me": true,
"more": true, "most": true, "my": true, "myself": true, "no": true,
"nor": true, "not": true, "now": true, "of": true, "off": true,
"on": true, "once": true, "only": true, "or": true, "other": true,
"our": true, "ours": true, "ourselves": true, "out": true, "over": true,
"own": true, "same": true, "she": true, "should": true, "so": true,
"some": true, "such": true, "than": true, "that": true, "the": true,
"their": true, "theirs": true, "them": true, "themselves": true, "then": true,
"there": true, "these": true, "they": true, "this": true, "those": true,
"through": true, "to": true, "too": true, "under": true, "until": true,
"up": true, "very": true, "was": true, "we": true, "were": true,
"what": true, "when": true, "where": true, "which": true, "while": true,
"who": true, "whom": true, "why": true, "will": true, "with": true,
"would": true, "you": true, "your": true, "yours": true, "yourself": true,
"yourselves": true,
}
return stopWords[word]
}

View File

@ -1,4 +1,4 @@
package enrich package linguistics
import "testing" import "testing"

View File

@ -4,16 +4,16 @@ import (
"strings" "strings"
) )
// LanguageDetector detects the language of a text // languageDetector detects the language of a text
type LanguageDetector struct{} type languageDetector struct{}
// NewLanguageDetector creates a new LanguageDetector // NewLanguageDetector creates a new LanguageDetector
func NewLanguageDetector() *LanguageDetector { func NewLanguageDetector() *languageDetector {
return &LanguageDetector{} return &languageDetector{}
} }
// Detect detects the language of a text and returns the language code, confidence, and error // Detect detects the language of a text and returns the language code, confidence, and error
func (d *LanguageDetector) Detect(text Text) (string, float64, error) { func (d *languageDetector) DetectLanguage(text string) (string, error) {
// This is a simplified implementation // This is a simplified implementation
// In a real-world scenario, you would use a library like github.com/pemistahl/lingua-go // In a real-world scenario, you would use a library like github.com/pemistahl/lingua-go
// or call an external API for language detection // or call an external API for language detection

View File

@ -3,7 +3,7 @@ package linguistics
// LanguageDetector defines a provider that can detect the language of a text // LanguageDetector defines a provider that can detect the language of a text
type LanguageDetector interface { type LanguageDetector interface {
// DetectLanguage returns a BCP-47 or ISO-like code and whether detection was confident // DetectLanguage returns a BCP-47 or ISO-like code and whether detection was confident
DetectLanguage(text string) (string, bool) DetectLanguage(text string) (string, error)
} }
// SentimentProvider defines a provider that scores sentiment in [-1, 1] // SentimentProvider defines a provider that scores sentiment in [-1, 1]

View File

@ -26,8 +26,9 @@ func DefaultRegistry() *Registry {
// Text represents a text to be analyzed // Text represents a text to be analyzed
type Text struct { type Text struct {
ID uint ID uint
Body string Body string
Language string
} }
// Token represents a token in a text // Token represents a token in a text
@ -38,12 +39,6 @@ type Token struct {
Length int Length int
} }
// Keyword represents a keyword extracted from a text
type Keyword struct {
Text string
Relevance float64
}
// PoeticMetrics represents metrics from poetic analysis // PoeticMetrics represents metrics from poetic analysis
type PoeticMetrics struct { type PoeticMetrics struct {
RhymeScheme string RhymeScheme string

View File

@ -5,7 +5,7 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"log" "log"
models2 "tercul/internal/models" "tercul/internal/domain"
"time" "time"
"github.com/hibiken/asynq" "github.com/hibiken/asynq"
@ -60,7 +60,7 @@ func (j *LinguisticSyncJob) EnqueueAnalysisForAllWorks() error {
log.Println("Enqueueing linguistic analysis jobs for all works...") log.Println("Enqueueing linguistic analysis jobs for all works...")
var workIDs []uint var workIDs []uint
if err := j.DB.Model(&models2.Work{}).Pluck("id", &workIDs).Error; err != nil { if err := j.DB.Model(&domain.Work{}).Pluck("id", &workIDs).Error; err != nil {
return fmt.Errorf("error fetching work IDs: %w", err) return fmt.Errorf("error fetching work IDs: %w", err)
} }
@ -87,7 +87,7 @@ func (j *LinguisticSyncJob) HandleLinguisticAnalysis(ctx context.Context, t *asy
// Check if analysis already exists // Check if analysis already exists
var count int64 var count int64
if err := j.DB.Model(&models2.LanguageAnalysis{}).Where("work_id = ?", payload.WorkID).Count(&count).Error; err != nil { if err := j.DB.Model(&domain.LanguageAnalysis{}).Where("work_id = ?", payload.WorkID).Count(&count).Error; err != nil {
return fmt.Errorf("error checking existing analysis: %w", err) return fmt.Errorf("error checking existing analysis: %w", err)
} }

View File

@ -3,7 +3,7 @@ package linguistics
import ( import (
"context" "context"
"fmt" "fmt"
"tercul/internal/models" "tercul/internal/domain"
"time" "time"
"tercul/internal/platform/log" "tercul/internal/platform/log"
@ -206,7 +206,7 @@ func (s *workAnalysisService) GetWorkAnalytics(ctx context.Context, workID uint)
} }
// extractSentimentFromAnalysis extracts sentiment from the Analysis JSONB field // extractSentimentFromAnalysis extracts sentiment from the Analysis JSONB field
func extractSentimentFromAnalysis(analysis models.JSONB) float64 { func extractSentimentFromAnalysis(analysis domain.JSONB) float64 {
if analysis == nil { if analysis == nil {
return 0.0 return 0.0
} }

View File

@ -4,7 +4,7 @@ import (
"context" "context"
"fmt" "fmt"
"log" "log"
"tercul/internal/models" "tercul/internal/domain"
) )
// SyncAllEdges syncs all edges by enqueueing batch jobs. // SyncAllEdges syncs all edges by enqueueing batch jobs.
@ -12,7 +12,7 @@ func (s *SyncJob) SyncAllEdges(ctx context.Context) error {
log.Println("Enqueueing edge sync jobs...") log.Println("Enqueueing edge sync jobs...")
var count int64 var count int64
if err := s.DB.Model(&models.Edge{}).Count(&count).Error; err != nil { if err := s.DB.Model(&domain.Edge{}).Count(&count).Error; err != nil {
return fmt.Errorf("error counting edges: %w", err) return fmt.Errorf("error counting edges: %w", err)
} }
@ -33,7 +33,7 @@ func (s *SyncJob) SyncAllEdges(ctx context.Context) error {
func (s *SyncJob) SyncEdgesBatch(ctx context.Context, batchSize, offset int) error { func (s *SyncJob) SyncEdgesBatch(ctx context.Context, batchSize, offset int) error {
log.Printf("Syncing edges batch (offset %d, batch size %d)...", offset, batchSize) log.Printf("Syncing edges batch (offset %d, batch size %d)...", offset, batchSize)
var edges []models.Edge var edges []domain.Edge
if err := s.DB.Limit(batchSize).Offset(offset).Find(&edges).Error; err != nil { if err := s.DB.Limit(batchSize).Offset(offset).Find(&edges).Error; err != nil {
return fmt.Errorf("error fetching edges batch: %w", err) return fmt.Errorf("error fetching edges batch: %w", err)
} }

View File

@ -4,7 +4,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"strings" "strings"
"tercul/internal/models" "tercul/internal/domain"
"time" "time"
"github.com/golang-jwt/jwt/v5" "github.com/golang-jwt/jwt/v5"
@ -55,7 +55,7 @@ func NewJWTManager() *JWTManager {
} }
// GenerateToken generates a new JWT token for a user // GenerateToken generates a new JWT token for a user
func (j *JWTManager) GenerateToken(user *models.User) (string, error) { func (j *JWTManager) GenerateToken(user *domain.User) (string, error) {
now := time.Now() now := time.Now()
claims := &Claims{ claims := &Claims{
UserID: user.ID, UserID: user.ID,

View File

@ -73,10 +73,7 @@ func InitDB() (*gorm.DB, error) {
return nil, err return nil, err
} }
// Run migrations // Migrations are now handled by a separate tool
if err := RunMigrations(db); err != nil {
return nil, fmt.Errorf("failed to run migrations: %w", err)
}
return db, nil return db, nil
} }

View File

@ -1,331 +0,0 @@
package db
import (
"gorm.io/gorm"
models2 "tercul/internal/models"
"tercul/internal/platform/log"
)
// RunMigrations runs all database migrations
func RunMigrations(db *gorm.DB) error {
log.LogInfo("Running database migrations")
// First, create all tables using GORM AutoMigrate
if err := createTables(db); err != nil {
log.LogError("Failed to create tables", log.F("error", err))
return err
}
// Then add indexes to improve query performance
if err := addIndexes(db); err != nil {
log.LogError("Failed to add indexes", log.F("error", err))
return err
}
log.LogInfo("Database migrations completed successfully")
return nil
}
// createTables creates all database tables using GORM AutoMigrate
func createTables(db *gorm.DB) error {
log.LogInfo("Creating database tables")
// Enable recommended extensions
if err := db.Exec("CREATE EXTENSION IF NOT EXISTS pg_trgm").Error; err != nil {
log.LogError("Failed to enable pg_trgm extension", log.F("error", err))
return err
}
// Create all models/tables
err := db.AutoMigrate(
// User-related models
&models2.User{},
&models2.UserProfile{},
&models2.UserSession{},
&models2.PasswordReset{},
&models2.EmailVerification{},
// Literary models
&models2.Work{},
&models2.Translation{},
&models2.Author{},
&models2.Book{},
&models2.Publisher{},
&models2.Source{},
&models2.Edition{},
&models2.Series{},
&models2.WorkSeries{},
// Organization models
&models2.Tag{},
&models2.Category{},
// Interaction models
&models2.Comment{},
&models2.Like{},
&models2.Bookmark{},
&models2.Collection{},
&models2.Contribution{},
&models2.InteractionEvent{},
// Location models
&models2.Country{},
&models2.City{},
&models2.Place{},
&models2.Address{},
&models2.Language{},
// Linguistic models
&models2.ReadabilityScore{},
&models2.WritingStyle{},
&models2.LinguisticLayer{},
&models2.TextMetadata{},
&models2.PoeticAnalysis{},
&models2.Word{},
&models2.Concept{},
&models2.LanguageEntity{},
&models2.TextBlock{},
&models2.WordOccurrence{},
&models2.EntityOccurrence{},
// Relationship models
&models2.Edge{},
&models2.Embedding{},
&models2.Media{},
&models2.BookWork{},
&models2.AuthorCountry{},
&models2.WorkAuthor{},
&models2.BookAuthor{},
// System models
&models2.Notification{},
&models2.EditorialWorkflow{},
&models2.Admin{},
&models2.Vote{},
&models2.Contributor{},
&models2.HybridEntityWork{},
&models2.ModerationFlag{},
&models2.AuditLog{},
// Rights models
&models2.Copyright{},
&models2.CopyrightClaim{},
&models2.Monetization{},
&models2.License{},
// Analytics models
&models2.WorkStats{},
&models2.TranslationStats{},
&models2.UserStats{},
&models2.BookStats{},
&models2.CollectionStats{},
&models2.MediaStats{},
// Metadata models
&models2.LanguageAnalysis{},
&models2.Gamification{},
&models2.Stats{},
&models2.SearchDocument{},
// Psychological models
&models2.Emotion{},
&models2.Mood{},
&models2.TopicCluster{},
)
if err != nil {
return err
}
log.LogInfo("Database tables created successfully")
return nil
}
// addIndexes adds indexes to frequently queried columns
func addIndexes(db *gorm.DB) error {
// Work table indexes
if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_works_language ON works(language)").Error; err != nil {
return err
}
if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_works_title ON works(title)").Error; err != nil {
return err
}
if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_works_status ON works(status)").Error; err != nil {
return err
}
if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_works_slug ON works(slug)").Error; err != nil {
return err
}
// Translation table indexes
if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_translations_work_id ON translations(work_id)").Error; err != nil {
return err
}
if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_translations_language ON translations(language)").Error; err != nil {
return err
}
if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_translations_translator_id ON translations(translator_id)").Error; err != nil {
return err
}
if err := db.Exec("CREATE UNIQUE INDEX IF NOT EXISTS ux_translations_entity_lang ON translations(translatable_type, translatable_id, language)").Error; err != nil {
return err
}
// User table indexes
if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_users_username ON users(username)").Error; err != nil {
return err
}
if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_users_email ON users(email)").Error; err != nil {
return err
}
if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_users_role ON users(role)").Error; err != nil {
return err
}
// Author table indexes
if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_authors_name ON authors(name)").Error; err != nil {
return err
}
// Category table indexes
if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_categories_name ON categories(name)").Error; err != nil {
return err
}
if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_categories_slug ON categories(slug)").Error; err != nil {
return err
}
if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_categories_path ON categories(path)").Error; err != nil {
return err
}
// Tag table indexes
if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_tags_name ON tags(name)").Error; err != nil {
return err
}
if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_tags_slug ON tags(slug)").Error; err != nil {
return err
}
// Comment table indexes
if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_comments_user_id ON comments(user_id)").Error; err != nil {
return err
}
if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_comments_work_id ON comments(work_id)").Error; err != nil {
return err
}
if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_comments_translation_id ON comments(translation_id)").Error; err != nil {
return err
}
// Like table indexes
if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_likes_user_id ON likes(user_id)").Error; err != nil {
return err
}
if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_likes_work_id ON likes(work_id)").Error; err != nil {
return err
}
if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_likes_translation_id ON likes(translation_id)").Error; err != nil {
return err
}
// Bookmark table indexes
if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_bookmarks_user_id ON bookmarks(user_id)").Error; err != nil {
return err
}
if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_bookmarks_work_id ON bookmarks(work_id)").Error; err != nil {
return err
}
// Collection table indexes
if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_collections_user_id ON collections(user_id)").Error; err != nil {
return err
}
// Contribution table indexes
if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_contributions_user_id ON contributions(user_id)").Error; err != nil {
return err
}
if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_contributions_work_id ON contributions(work_id)").Error; err != nil {
return err
}
if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_contributions_status ON contributions(status)").Error; err != nil {
return err
}
// Edge table indexes
if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_edges_source_table_id ON edges(source_table, source_id)").Error; err != nil {
return err
}
if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_edges_target_table_id ON edges(target_table, target_id)").Error; err != nil {
return err
}
if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_edges_relation ON edges(relation)").Error; err != nil {
return err
}
// WorkAuthor unique pair and order index
if err := db.Exec("CREATE UNIQUE INDEX IF NOT EXISTS ux_work_authors_pair ON work_authors(work_id, author_id)").Error; err != nil {
return err
}
if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_work_authors_ordinal ON work_authors(ordinal)").Error; err != nil {
return err
}
// BookAuthor unique pair and order index
if err := db.Exec("CREATE UNIQUE INDEX IF NOT EXISTS ux_book_authors_pair ON book_authors(book_id, author_id)").Error; err != nil {
return err
}
if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_book_authors_ordinal ON book_authors(ordinal)").Error; err != nil {
return err
}
// InteractionEvent indexes
if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_interaction_events_target ON interaction_events(target_type, target_id)").Error; err != nil {
return err
}
if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_interaction_events_kind ON interaction_events(kind)").Error; err != nil {
return err
}
if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_interaction_events_user ON interaction_events(user_id)").Error; err != nil {
return err
}
// SearchDocument indexes
if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_search_documents_entity ON search_documents(entity_type, entity_id)").Error; err != nil {
return err
}
if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_search_documents_lang ON search_documents(language_code)").Error; err != nil {
return err
}
// Linguistic analysis indexes
if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_text_metadata_work_id ON text_metadata(work_id)").Error; err != nil {
return err
}
if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_readability_scores_work_id ON readability_scores(work_id)").Error; err != nil {
return err
}
if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_language_analyses_work_id ON language_analyses(work_id)").Error; err != nil {
return err
}
if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_poetic_analyses_work_id ON poetic_analyses(work_id)").Error; err != nil {
return err
}
// Timestamps indexes for frequently queried tables
if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_works_created_at ON works(created_at)").Error; err != nil {
return err
}
if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_translations_created_at ON translations(created_at)").Error; err != nil {
return err
}
if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_comments_created_at ON comments(created_at)").Error; err != nil {
return err
}
if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_users_created_at ON users(created_at)").Error; err != nil {
return err
}
log.LogInfo("Database indexes added successfully")
return nil
}

View File

@ -4,7 +4,7 @@ import (
"context" "context"
"fmt" "fmt"
"log" "log"
"tercul/internal/models" "tercul/internal/domain"
"tercul/internal/platform/config" "tercul/internal/platform/config"
"time" "time"
@ -27,7 +27,7 @@ func InitWeaviate() {
} }
// UpsertWork inserts or updates a Work object in Weaviate // UpsertWork inserts or updates a Work object in Weaviate
func UpsertWork(work models.Work) error { func UpsertWork(work domain.Work) error {
// Create a properties map with the fields that exist in the Work model // Create a properties map with the fields that exist in the Work model
properties := map[string]interface{}{ properties := map[string]interface{}{
"language": work.Language, "language": work.Language,

View File

@ -1,93 +0,0 @@
package repositories
import (
"context"
"errors"
"gorm.io/gorm"
"tercul/internal/models"
)
// CopyrightRepository defines CRUD methods specific to Copyright.
type CopyrightRepository interface {
BaseRepository[models.Copyright]
// Polymorphic methods
AttachToEntity(ctx context.Context, copyrightID uint, entityID uint, entityType string) error
DetachFromEntity(ctx context.Context, copyrightID uint, entityID uint, entityType string) error
GetByEntity(ctx context.Context, entityID uint, entityType string) ([]models.Copyright, error)
GetEntitiesByCopyright(ctx context.Context, copyrightID uint) ([]models.Copyrightable, error)
// Translation methods
AddTranslation(ctx context.Context, translation *models.CopyrightTranslation) error
GetTranslations(ctx context.Context, copyrightID uint) ([]models.CopyrightTranslation, error)
GetTranslationByLanguage(ctx context.Context, copyrightID uint, languageCode string) (*models.CopyrightTranslation, error)
}
type copyrightRepository struct {
BaseRepository[models.Copyright]
db *gorm.DB
}
// NewCopyrightRepository creates a new CopyrightRepository.
func NewCopyrightRepository(db *gorm.DB) CopyrightRepository {
return &copyrightRepository{
BaseRepository: NewBaseRepositoryImpl[models.Copyright](db),
db: db,
}
}
// AttachToEntity attaches a copyright to any entity type
func (r *copyrightRepository) AttachToEntity(ctx context.Context, copyrightID uint, entityID uint, entityType string) error {
copyrightable := models.Copyrightable{
CopyrightID: copyrightID,
CopyrightableID: entityID,
CopyrightableType: entityType,
}
return r.db.WithContext(ctx).Create(&copyrightable).Error
}
// DetachFromEntity removes a copyright from an entity
func (r *copyrightRepository) DetachFromEntity(ctx context.Context, copyrightID uint, entityID uint, entityType string) error {
return r.db.WithContext(ctx).Where("copyright_id = ? AND copyrightable_id = ? AND copyrightable_type = ?",
copyrightID, entityID, entityType).Delete(&models.Copyrightable{}).Error
}
// GetByEntity gets all copyrights for a specific entity
func (r *copyrightRepository) GetByEntity(ctx context.Context, entityID uint, entityType string) ([]models.Copyright, error) {
var copyrights []models.Copyright
err := r.db.WithContext(ctx).Joins("JOIN copyrightables ON copyrightables.copyright_id = copyrights.id").
Where("copyrightables.copyrightable_id = ? AND copyrightables.copyrightable_type = ?", entityID, entityType).
Preload("Translations").
Find(&copyrights).Error
return copyrights, err
}
// GetEntitiesByCopyright gets all entities that have a specific copyright
func (r *copyrightRepository) GetEntitiesByCopyright(ctx context.Context, copyrightID uint) ([]models.Copyrightable, error) {
var copyrightables []models.Copyrightable
err := r.db.WithContext(ctx).Where("copyright_id = ?", copyrightID).Find(&copyrightables).Error
return copyrightables, err
}
// AddTranslation adds a translation to a copyright
func (r *copyrightRepository) AddTranslation(ctx context.Context, translation *models.CopyrightTranslation) error {
return r.db.WithContext(ctx).Create(translation).Error
}
// GetTranslations gets all translations for a copyright
func (r *copyrightRepository) GetTranslations(ctx context.Context, copyrightID uint) ([]models.CopyrightTranslation, error) {
var translations []models.CopyrightTranslation
err := r.db.WithContext(ctx).Where("copyright_id = ?", copyrightID).Find(&translations).Error
return translations, err
}
// GetTranslationByLanguage gets a specific translation by language code
func (r *copyrightRepository) GetTranslationByLanguage(ctx context.Context, copyrightID uint, languageCode string) (*models.CopyrightTranslation, error) {
var translation models.CopyrightTranslation
err := r.db.WithContext(ctx).Where("copyright_id = ? AND language_code = ?", copyrightID, languageCode).First(&translation).Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, ErrEntityNotFound
}
return nil, err
}
return &translation, nil
}

View File

@ -1,146 +0,0 @@
package store
import (
"gorm.io/gorm"
"strings"
models2 "tercul/internal/models"
)
// DB represents a database connection
type DB struct {
*gorm.DB
}
// Connect creates a new database connection
func Connect() *DB {
// In a real application, this would use configuration from environment variables
// or a configuration file to connect to the database
// For this example, we'll assume the DB connection is passed in from main.go
return nil
}
// ListPendingWorks returns a list of works that need to be enriched
func ListPendingWorks(db *DB) []Work {
var works []Work
// Query for works that haven't been enriched yet
var modelWorks []models2.Work
db.Where("id NOT IN (SELECT work_id FROM language_analyses)").Find(&modelWorks)
// Convert to store.Work
for _, work := range modelWorks {
// Prefer original language translation; fallback to work language; then any
var content string
var t models2.Translation
// Try original
if err := db.Where("translatable_type = ? AND translatable_id = ? AND is_original_language = ?", "Work", work.ID, true).
First(&t).Error; err == nil {
content = t.Content
} else {
// Try same language
if err := db.Where("translatable_type = ? AND translatable_id = ? AND language = ?", "Work", work.ID, work.Language).
First(&t).Error; err == nil {
content = t.Content
} else {
// Any translation
if err := db.Where("translatable_type = ? AND translatable_id = ?", "Work", work.ID).
First(&t).Error; err == nil {
content = t.Content
}
}
}
works = append(works, Work{
ID: work.ID,
Body: content,
})
}
return works
}
// UpsertWord creates or updates a word in the database
func UpsertWord(db *DB, workID uint, text, lemma, pos, phonetic string) error {
// Check if the word already exists
var word models2.Word
result := db.Where("text = ? AND language = ?", text, "auto").First(&word)
if result.Error != nil && result.Error != gorm.ErrRecordNotFound {
return result.Error
}
// Create or update the word
if result.Error == gorm.ErrRecordNotFound {
// Create new word
word = models2.Word{
Text: text,
Language: "auto", // This would be set to the detected language
PartOfSpeech: pos,
Lemma: lemma,
}
if err := db.Create(&word).Error; err != nil {
return err
}
} else {
// Update existing word
word.PartOfSpeech = pos
word.Lemma = lemma
if err := db.Save(&word).Error; err != nil {
return err
}
}
// Associate the word with the work
return db.Exec("INSERT INTO work_words (work_id, word_id) VALUES (?, ?) ON CONFLICT DO NOTHING", workID, word.ID).Error
}
// SaveKeywords saves keywords for a work
func SaveKeywords(db *DB, workID uint, keywords []string) error {
// Clear existing keywords
if err := db.Exec("DELETE FROM work_topic_clusters WHERE work_id = ?", workID).Error; err != nil {
return err
}
// Create a topic cluster for the keywords
cluster := models2.TopicCluster{
Name: "Auto-generated",
Description: "Automatically generated keywords",
Keywords: strings.Join(keywords, ", "),
}
if err := db.Create(&cluster).Error; err != nil {
return err
}
// Associate the cluster with the work
return db.Exec("INSERT INTO work_topic_clusters (work_id, topic_cluster_id) VALUES (?, ?)", workID, cluster.ID).Error
}
// SavePoetics saves poetic analysis for a work
func SavePoetics(db *DB, workID uint, metrics PoeticMetrics) error {
poetics := models2.PoeticAnalysis{
WorkID: workID,
Language: "auto", // This would be set to the detected language
RhymeScheme: metrics.RhymeScheme,
MeterType: metrics.MeterType,
StanzaCount: metrics.StanzaCount,
LineCount: metrics.LineCount,
Structure: metrics.Structure,
}
return db.Create(&poetics).Error
}
// MarkEnriched marks a work as enriched with the detected language
func MarkEnriched(db *DB, workID uint, language string) error {
// Create a language analysis record to mark the work as processed
analysis := models2.LanguageAnalysis{
WorkID: workID,
Language: language,
Analysis: models2.JSONB{
"enriched": true,
"language": language,
},
}
return db.Create(&analysis).Error
}

View File

@ -1,16 +0,0 @@
package store
// Work represents a work to be processed
type Work struct {
ID uint
Body string
}
// PoeticMetrics represents metrics from poetic analysis
type PoeticMetrics struct {
RhymeScheme string
MeterType string
StanzaCount int
LineCount int
Structure string
}

View File

@ -1,119 +0,0 @@
package store
import (
"context"
"log"
"tercul/internal/enrich"
)
// ProcessWork processes a work using the enrichment registry and stores the results
func ProcessWork(ctx context.Context, reg *enrich.Registry, db *DB, work Work) error {
log.Printf("Processing work ID %d", work.ID)
// Create a text object for the enrichment services
text := enrich.Text{ID: work.ID, Body: work.Body}
// Detect language
lang, confidence, err := reg.Lang.Detect(text)
if err != nil {
return err
}
log.Printf("Detected language: %s (confidence: %.2f)", lang, confidence)
// Tokenize text
tokens, err := reg.Tok.Tokenize(text)
if err != nil {
return err
}
log.Printf("Tokenized text into %d tokens", len(tokens))
// Tag parts of speech
pos, err := reg.Pos.Tag(tokens)
if err != nil {
return err
}
log.Printf("Tagged %d tokens with parts of speech", len(pos))
// Process each token
for i, token := range tokens {
// Get lemma
lemma, err := reg.Lem.Lemma(token.Text, lang)
if err != nil {
log.Printf("Error getting lemma for token %s: %v", token.Text, err)
lemma = token.Text // Use the original text as fallback
}
// Get phonetic encoding
phonetic := reg.Phon.Encode(token.Text)
// Store the word
if err := UpsertWord(db, work.ID, token.Text, lemma, pos[i], phonetic); err != nil {
log.Printf("Error storing word %s: %v", token.Text, err)
}
}
// Extract keywords
keywords, err := reg.Key.Extract(text)
if err != nil {
log.Printf("Error extracting keywords: %v", err)
} else {
// Convert keywords to strings
keywordStrings := make([]string, len(keywords))
for i, kw := range keywords {
keywordStrings[i] = kw.Text
}
// Save keywords
if err := SaveKeywords(db, work.ID, keywordStrings); err != nil {
log.Printf("Error saving keywords: %v", err)
}
}
// Analyze poetics
enrichMetrics, err := reg.Poet.Analyse(text)
if err != nil {
log.Printf("Error analyzing poetics: %v", err)
} else {
// Convert to store.PoeticMetrics
metrics := PoeticMetrics{
RhymeScheme: enrichMetrics.RhymeScheme,
MeterType: enrichMetrics.MeterType,
StanzaCount: enrichMetrics.StanzaCount,
LineCount: enrichMetrics.LineCount,
Structure: enrichMetrics.Structure,
}
// Save poetics
if err := SavePoetics(db, work.ID, metrics); err != nil {
log.Printf("Error saving poetics: %v", err)
}
}
// Mark the work as enriched
if err := MarkEnriched(db, work.ID, lang); err != nil {
log.Printf("Error marking work as enriched: %v", err)
return err
}
log.Printf("Successfully processed work ID %d", work.ID)
return nil
}
// ProcessPendingWorks processes all pending works
func ProcessPendingWorks(ctx context.Context, reg *enrich.Registry, db *DB) error {
log.Println("Processing pending works...")
// Get pending works
works := ListPendingWorks(db)
log.Printf("Found %d pending works", len(works))
// Process each work
for _, work := range works {
if err := ProcessWork(ctx, reg, db, work); err != nil {
log.Printf("Error processing work ID %d: %v", work.ID, err)
}
}
log.Println("Finished processing pending works")
return nil
}

View File

@ -13,37 +13,41 @@ import (
"gorm.io/driver/sqlite" "gorm.io/driver/sqlite"
"gorm.io/gorm" "gorm.io/gorm"
"gorm.io/gorm/logger" "gorm.io/gorm/logger"
"tercul/internal/models"
"tercul/internal/repositories" graph "tercul/internal/adapters/graphql"
"tercul/services" "tercul/internal/app/auth"
"tercul/graph" "tercul/internal/app/localization"
"tercul/internal/app/work"
"tercul/internal/data/sql"
"tercul/internal/domain"
) )
// IntegrationTestSuite provides a comprehensive test environment with either in-memory SQLite or mock repositories // IntegrationTestSuite provides a comprehensive test environment with either in-memory SQLite or mock repositories
type IntegrationTestSuite struct { type IntegrationTestSuite struct {
suite.Suite suite.Suite
DB *gorm.DB DB *gorm.DB
WorkRepo repositories.WorkRepository WorkRepo domain.WorkRepository
UserRepo repositories.UserRepository UserRepo domain.UserRepository
AuthorRepo repositories.AuthorRepository AuthorRepo domain.AuthorRepository
TranslationRepo repositories.TranslationRepository TranslationRepo domain.TranslationRepository
CommentRepo repositories.CommentRepository CommentRepo domain.CommentRepository
LikeRepo repositories.LikeRepository LikeRepo domain.LikeRepository
BookmarkRepo repositories.BookmarkRepository BookmarkRepo domain.BookmarkRepository
CollectionRepo repositories.CollectionRepository CollectionRepo domain.CollectionRepository
TagRepo repositories.TagRepository TagRepo domain.TagRepository
CategoryRepo repositories.CategoryRepository CategoryRepo domain.CategoryRepository
// Services // Services
WorkService services.WorkService WorkCommands *work.WorkCommands
Localization services.LocalizationService WorkQueries *work.WorkQueries
AuthService services.AuthService Localization localization.Service
AuthService auth.Service
// Test data // Test data
TestWorks []*models.Work TestWorks []*domain.Work
TestUsers []*models.User TestUsers []*domain.User
TestAuthors []*models.Author TestAuthors []*domain.Author
TestTranslations []*models.Translation TestTranslations []*domain.Translation
} }
// TestConfig holds configuration for the test environment // TestConfig holds configuration for the test environment
@ -115,53 +119,53 @@ func (s *IntegrationTestSuite) setupInMemoryDB(config *TestConfig) {
// Run migrations // Run migrations
if err := db.AutoMigrate( if err := db.AutoMigrate(
&models.Work{}, &domain.Work{},
&models.User{}, &domain.User{},
&models.Author{}, &domain.Author{},
&models.Translation{}, &domain.Translation{},
&models.Comment{}, &domain.Comment{},
&models.Like{}, &domain.Like{},
&models.Bookmark{}, &domain.Bookmark{},
&models.Collection{}, &domain.Collection{},
&models.Tag{}, &domain.Tag{},
&models.Category{}, &domain.Category{},
&models.Country{}, &domain.Country{},
&models.City{}, &domain.City{},
&models.Place{}, &domain.Place{},
&models.Address{}, &domain.Address{},
&models.Copyright{}, &domain.Copyright{},
&models.CopyrightClaim{}, &domain.CopyrightClaim{},
&models.Monetization{}, &domain.Monetization{},
&models.Book{}, &domain.Book{},
&models.Publisher{}, &domain.Publisher{},
&models.Source{}, &domain.Source{},
// &models.WorkAnalytics{}, // Commented out as it's not in models package // &domain.WorkAnalytics{}, // Commented out as it's not in models package
&models.ReadabilityScore{}, &domain.ReadabilityScore{},
&models.WritingStyle{}, &domain.WritingStyle{},
&models.Emotion{}, &domain.Emotion{},
&models.TopicCluster{}, &domain.TopicCluster{},
&models.Mood{}, &domain.Mood{},
&models.Concept{}, &domain.Concept{},
&models.LinguisticLayer{}, &domain.LinguisticLayer{},
&models.WorkStats{}, &domain.WorkStats{},
&models.TextMetadata{}, &domain.TextMetadata{},
&models.PoeticAnalysis{}, &domain.PoeticAnalysis{},
&models.TranslationField{}, &domain.TranslationField{},
); err != nil { ); err != nil {
s.T().Fatalf("Failed to run migrations: %v", err) s.T().Fatalf("Failed to run migrations: %v", err)
} }
// Create repository instances // Create repository instances
s.WorkRepo = repositories.NewWorkRepository(db) s.WorkRepo = sql.NewWorkRepository(db)
s.UserRepo = repositories.NewUserRepository(db) s.UserRepo = sql.NewUserRepository(db)
s.AuthorRepo = repositories.NewAuthorRepository(db) s.AuthorRepo = sql.NewAuthorRepository(db)
s.TranslationRepo = repositories.NewTranslationRepository(db) s.TranslationRepo = sql.NewTranslationRepository(db)
s.CommentRepo = repositories.NewCommentRepository(db) s.CommentRepo = sql.NewCommentRepository(db)
s.LikeRepo = repositories.NewLikeRepository(db) s.LikeRepo = sql.NewLikeRepository(db)
s.BookmarkRepo = repositories.NewBookmarkRepository(db) s.BookmarkRepo = sql.NewBookmarkRepository(db)
s.CollectionRepo = repositories.NewCollectionRepository(db) s.CollectionRepo = sql.NewCollectionRepository(db)
s.TagRepo = repositories.NewTagRepository(db) s.TagRepo = sql.NewTagRepository(db)
s.CategoryRepo = repositories.NewCategoryRepository(db) s.CategoryRepo = sql.NewCategoryRepository(db)
} }
// setupMockRepositories sets up mock repositories for testing // setupMockRepositories sets up mock repositories for testing
@ -181,16 +185,17 @@ func (s *IntegrationTestSuite) setupMockRepositories() {
// setupServices sets up service instances // setupServices sets up service instances
func (s *IntegrationTestSuite) setupServices() { func (s *IntegrationTestSuite) setupServices() {
s.WorkService = services.NewWorkService(s.WorkRepo, nil) mockAnalyzer := &MockAnalyzer{}
// Temporarily comment out services that depend on problematic repositories s.WorkCommands = work.NewWorkCommands(s.WorkRepo, mockAnalyzer)
// s.Localization = services.NewLocalizationService(s.TranslationRepo) s.WorkQueries = work.NewWorkQueries(s.WorkRepo)
// s.AuthService = services.NewAuthService(s.UserRepo, "test-secret-key") s.Localization = localization.NewService(s.TranslationRepo)
s.AuthService = auth.NewService(s.UserRepo, "test-secret-key")
} }
// setupTestData creates initial test data // setupTestData creates initial test data
func (s *IntegrationTestSuite) setupTestData() { func (s *IntegrationTestSuite) setupTestData() {
// Create test users // Create test users
s.TestUsers = []*models.User{ s.TestUsers = []*domain.User{
{Username: "testuser1", Email: "test1@example.com", FirstName: "Test", LastName: "User1"}, {Username: "testuser1", Email: "test1@example.com", FirstName: "Test", LastName: "User1"},
{Username: "testuser2", Email: "test2@example.com", FirstName: "Test", LastName: "User2"}, {Username: "testuser2", Email: "test2@example.com", FirstName: "Test", LastName: "User2"},
} }
@ -202,7 +207,7 @@ func (s *IntegrationTestSuite) setupTestData() {
} }
// Create test authors // Create test authors
s.TestAuthors = []*models.Author{ s.TestAuthors = []*domain.Author{
{Name: "Test Author 1", Language: "en"}, {Name: "Test Author 1", Language: "en"},
{Name: "Test Author 2", Language: "fr"}, {Name: "Test Author 2", Language: "fr"},
} }
@ -214,7 +219,7 @@ func (s *IntegrationTestSuite) setupTestData() {
} }
// Create test works // Create test works
s.TestWorks = []*models.Work{ s.TestWorks = []*domain.Work{
{Title: "Test Work 1", Language: "en"}, {Title: "Test Work 1", Language: "en"},
{Title: "Test Work 2", Language: "en"}, {Title: "Test Work 2", Language: "en"},
{Title: "Test Work 3", Language: "fr"}, {Title: "Test Work 3", Language: "fr"},
@ -227,7 +232,7 @@ func (s *IntegrationTestSuite) setupTestData() {
} }
// Create test translations // Create test translations
s.TestTranslations = []*models.Translation{ s.TestTranslations = []*domain.Translation{
{ {
Title: "Test Work 1", Title: "Test Work 1",
Content: "Test content for work 1", Content: "Test content for work 1",
@ -292,35 +297,23 @@ func (s *IntegrationTestSuite) SetupTest() {
// GetResolver returns a properly configured GraphQL resolver for testing // GetResolver returns a properly configured GraphQL resolver for testing
func (s *IntegrationTestSuite) GetResolver() *graph.Resolver { func (s *IntegrationTestSuite) GetResolver() *graph.Resolver {
return &graph.Resolver{ return &graph.Resolver{
WorkRepo: s.WorkRepo, // This needs to be updated to reflect the new resolver structure
UserRepo: s.UserRepo,
AuthorRepo: s.AuthorRepo,
TranslationRepo: s.TranslationRepo,
CommentRepo: s.CommentRepo,
LikeRepo: s.LikeRepo,
BookmarkRepo: s.BookmarkRepo,
CollectionRepo: s.CollectionRepo,
TagRepo: s.TagRepo,
CategoryRepo: s.CategoryRepo,
WorkService: s.WorkService,
Localization: s.Localization,
AuthService: s.AuthService,
} }
} }
// CreateTestWork creates a test work with optional content // CreateTestWork creates a test work with optional content
func (s *IntegrationTestSuite) CreateTestWork(title, language string, content string) *models.Work { func (s *IntegrationTestSuite) CreateTestWork(title, language string, content string) *domain.Work {
work := &models.Work{ work := &domain.Work{
Title: title, Title: title,
Language: language,
} }
work.Language = language
if err := s.WorkRepo.Create(context.Background(), work); err != nil { if err := s.WorkRepo.Create(context.Background(), work); err != nil {
s.T().Fatalf("Failed to create test work: %v", err) s.T().Fatalf("Failed to create test work: %v", err)
} }
if content != "" { if content != "" {
translation := &models.Translation{ translation := &domain.Translation{
Title: title, Title: title,
Content: content, Content: content,
Language: language, Language: language,

View File

@ -4,7 +4,7 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"tercul/internal/repositories" "tercul/internal/domain"
"gorm.io/gorm" "gorm.io/gorm"
) )
@ -40,17 +40,17 @@ func (m *MockBaseRepository[T]) DeleteInTx(ctx context.Context, tx *gorm.DB, id
} }
// GetByIDWithOptions retrieves an entity by its ID with query options (mock implementation) // GetByIDWithOptions retrieves an entity by its ID with query options (mock implementation)
func (m *MockBaseRepository[T]) GetByIDWithOptions(ctx context.Context, id uint, options *repositories.QueryOptions) (*T, error) { func (m *MockBaseRepository[T]) GetByIDWithOptions(ctx context.Context, id uint, options *domain.QueryOptions) (*T, error) {
return nil, fmt.Errorf("GetByIDWithOptions not implemented in mock repository") return nil, fmt.Errorf("GetByIDWithOptions not implemented in mock repository")
} }
// ListWithOptions returns entities with query options (mock implementation) // ListWithOptions returns entities with query options (mock implementation)
func (m *MockBaseRepository[T]) ListWithOptions(ctx context.Context, options *repositories.QueryOptions) ([]T, error) { func (m *MockBaseRepository[T]) ListWithOptions(ctx context.Context, options *domain.QueryOptions) ([]T, error) {
return nil, fmt.Errorf("ListWithOptions not implemented in mock repository") return nil, fmt.Errorf("ListWithOptions not implemented in mock repository")
} }
// CountWithOptions returns the count with query options (mock implementation) // CountWithOptions returns the count with query options (mock implementation)
func (m *MockBaseRepository[T]) CountWithOptions(ctx context.Context, options *repositories.QueryOptions) (int64, error) { func (m *MockBaseRepository[T]) CountWithOptions(ctx context.Context, options *domain.QueryOptions) (int64, error) {
return 0, fmt.Errorf("CountWithOptions not implemented in mock repository") return 0, fmt.Errorf("CountWithOptions not implemented in mock repository")
} }

View File

@ -4,23 +4,22 @@ import (
"context" "context"
"errors" "errors"
"gorm.io/gorm" "gorm.io/gorm"
models2 "tercul/internal/models" "tercul/internal/domain"
repositories2 "tercul/internal/repositories"
) )
// MockTranslationRepository is an in-memory implementation of TranslationRepository // MockTranslationRepository is an in-memory implementation of TranslationRepository
type MockTranslationRepository struct { type MockTranslationRepository struct {
items []models2.Translation items []domain.Translation
} }
func NewMockTranslationRepository() *MockTranslationRepository { func NewMockTranslationRepository() *MockTranslationRepository {
return &MockTranslationRepository{items: []models2.Translation{}} return &MockTranslationRepository{items: []domain.Translation{}}
} }
var _ repositories2.TranslationRepository = (*MockTranslationRepository)(nil) var _ domain.TranslationRepository = (*MockTranslationRepository)(nil)
// BaseRepository methods with context support // BaseRepository methods with context support
func (m *MockTranslationRepository) Create(ctx context.Context, t *models2.Translation) error { func (m *MockTranslationRepository) Create(ctx context.Context, t *domain.Translation) error {
if t == nil { if t == nil {
return errors.New("nil translation") return errors.New("nil translation")
} }
@ -29,24 +28,24 @@ func (m *MockTranslationRepository) Create(ctx context.Context, t *models2.Trans
return nil return nil
} }
func (m *MockTranslationRepository) GetByID(ctx context.Context, id uint) (*models2.Translation, error) { func (m *MockTranslationRepository) GetByID(ctx context.Context, id uint) (*domain.Translation, error) {
for i := range m.items { for i := range m.items {
if m.items[i].ID == id { if m.items[i].ID == id {
cp := m.items[i] cp := m.items[i]
return &cp, nil return &cp, nil
} }
} }
return nil, repositories2.ErrEntityNotFound return nil, domain.ErrEntityNotFound
} }
func (m *MockTranslationRepository) Update(ctx context.Context, t *models2.Translation) error { func (m *MockTranslationRepository) Update(ctx context.Context, t *domain.Translation) error {
for i := range m.items { for i := range m.items {
if m.items[i].ID == t.ID { if m.items[i].ID == t.ID {
m.items[i] = *t m.items[i] = *t
return nil return nil
} }
} }
return repositories2.ErrEntityNotFound return domain.ErrEntityNotFound
} }
func (m *MockTranslationRepository) Delete(ctx context.Context, id uint) error { func (m *MockTranslationRepository) Delete(ctx context.Context, id uint) error {
@ -56,57 +55,57 @@ func (m *MockTranslationRepository) Delete(ctx context.Context, id uint) error {
return nil return nil
} }
} }
return repositories2.ErrEntityNotFound return domain.ErrEntityNotFound
} }
func (m *MockTranslationRepository) List(ctx context.Context, page, pageSize int) (*repositories2.PaginatedResult[models2.Translation], error) { func (m *MockTranslationRepository) List(ctx context.Context, page, pageSize int) (*domain.PaginatedResult[domain.Translation], error) {
all := append([]models2.Translation(nil), m.items...) all := append([]domain.Translation(nil), m.items...)
total := int64(len(all)) total := int64(len(all))
start := (page - 1) * pageSize start := (page - 1) * pageSize
end := start + pageSize end := start + pageSize
if start > len(all) { if start > len(all) {
return &repositories2.PaginatedResult[models2.Translation]{Items: []models2.Translation{}, TotalCount: total}, nil return &domain.PaginatedResult[domain.Translation]{Items: []domain.Translation{}, TotalCount: total}, nil
} }
if end > len(all) { if end > len(all) {
end = len(all) end = len(all)
} }
return &repositories2.PaginatedResult[models2.Translation]{Items: all[start:end], TotalCount: total}, nil return &domain.PaginatedResult[domain.Translation]{Items: all[start:end], TotalCount: total}, nil
} }
func (m *MockTranslationRepository) ListAll(ctx context.Context) ([]models2.Translation, error) { func (m *MockTranslationRepository) ListAll(ctx context.Context) ([]domain.Translation, error) {
return append([]models2.Translation(nil), m.items...), nil return append([]domain.Translation(nil), m.items...), nil
} }
func (m *MockTranslationRepository) Count(ctx context.Context) (int64, error) { func (m *MockTranslationRepository) Count(ctx context.Context) (int64, error) {
return int64(len(m.items)), nil return int64(len(m.items)), nil
} }
func (m *MockTranslationRepository) FindWithPreload(ctx context.Context, preloads []string, id uint) (*models2.Translation, error) { func (m *MockTranslationRepository) FindWithPreload(ctx context.Context, preloads []string, id uint) (*domain.Translation, error) {
return m.GetByID(ctx, id) return m.GetByID(ctx, id)
} }
func (m *MockTranslationRepository) GetAllForSync(ctx context.Context, batchSize, offset int) ([]models2.Translation, error) { func (m *MockTranslationRepository) GetAllForSync(ctx context.Context, batchSize, offset int) ([]domain.Translation, error) {
all := append([]models2.Translation(nil), m.items...) all := append([]domain.Translation(nil), m.items...)
end := offset + batchSize end := offset + batchSize
if end > len(all) { if end > len(all) {
end = len(all) end = len(all)
} }
if offset > len(all) { if offset > len(all) {
return []models2.Translation{}, nil return []domain.Translation{}, nil
} }
return all[offset:end], nil return all[offset:end], nil
} }
// New BaseRepository methods // New BaseRepository methods
func (m *MockTranslationRepository) CreateInTx(ctx context.Context, tx *gorm.DB, entity *models2.Translation) error { func (m *MockTranslationRepository) CreateInTx(ctx context.Context, tx *gorm.DB, entity *domain.Translation) error {
return m.Create(ctx, entity) return m.Create(ctx, entity)
} }
func (m *MockTranslationRepository) GetByIDWithOptions(ctx context.Context, id uint, options *repositories2.QueryOptions) (*models2.Translation, error) { func (m *MockTranslationRepository) GetByIDWithOptions(ctx context.Context, id uint, options *domain.QueryOptions) (*domain.Translation, error) {
return m.GetByID(ctx, id) return m.GetByID(ctx, id)
} }
func (m *MockTranslationRepository) UpdateInTx(ctx context.Context, tx *gorm.DB, entity *models2.Translation) error { func (m *MockTranslationRepository) UpdateInTx(ctx context.Context, tx *gorm.DB, entity *domain.Translation) error {
return m.Update(ctx, entity) return m.Update(ctx, entity)
} }
@ -114,7 +113,7 @@ func (m *MockTranslationRepository) DeleteInTx(ctx context.Context, tx *gorm.DB,
return m.Delete(ctx, id) return m.Delete(ctx, id)
} }
func (m *MockTranslationRepository) ListWithOptions(ctx context.Context, options *repositories2.QueryOptions) ([]models2.Translation, error) { func (m *MockTranslationRepository) ListWithOptions(ctx context.Context, options *domain.QueryOptions) ([]domain.Translation, error) {
result, err := m.List(ctx, 1, 1000) result, err := m.List(ctx, 1, 1000)
if err != nil { if err != nil {
return nil, err return nil, err
@ -122,7 +121,7 @@ func (m *MockTranslationRepository) ListWithOptions(ctx context.Context, options
return result.Items, nil return result.Items, nil
} }
func (m *MockTranslationRepository) CountWithOptions(ctx context.Context, options *repositories2.QueryOptions) (int64, error) { func (m *MockTranslationRepository) CountWithOptions(ctx context.Context, options *domain.QueryOptions) (int64, error) {
return m.Count(ctx) return m.Count(ctx)
} }
@ -140,12 +139,12 @@ func (m *MockTranslationRepository) WithTx(ctx context.Context, fn func(tx *gorm
} }
// TranslationRepository specific methods // TranslationRepository specific methods
func (m *MockTranslationRepository) ListByWorkID(ctx context.Context, workID uint) ([]models2.Translation, error) { func (m *MockTranslationRepository) ListByWorkID(ctx context.Context, workID uint) ([]domain.Translation, error) {
return m.ListByEntity(ctx, "Work", workID) return m.ListByEntity(ctx, "Work", workID)
} }
func (m *MockTranslationRepository) ListByEntity(ctx context.Context, entityType string, entityID uint) ([]models2.Translation, error) { func (m *MockTranslationRepository) ListByEntity(ctx context.Context, entityType string, entityID uint) ([]domain.Translation, error) {
var out []models2.Translation var out []domain.Translation
for i := range m.items { for i := range m.items {
tr := m.items[i] tr := m.items[i]
if tr.TranslatableType == entityType && tr.TranslatableID == entityID { if tr.TranslatableType == entityType && tr.TranslatableID == entityID {
@ -155,8 +154,8 @@ func (m *MockTranslationRepository) ListByEntity(ctx context.Context, entityType
return out, nil return out, nil
} }
func (m *MockTranslationRepository) ListByTranslatorID(ctx context.Context, translatorID uint) ([]models2.Translation, error) { func (m *MockTranslationRepository) ListByTranslatorID(ctx context.Context, translatorID uint) ([]domain.Translation, error) {
var out []models2.Translation var out []domain.Translation
for i := range m.items { for i := range m.items {
if m.items[i].TranslatorID != nil && *m.items[i].TranslatorID == translatorID { if m.items[i].TranslatorID != nil && *m.items[i].TranslatorID == translatorID {
out = append(out, m.items[i]) out = append(out, m.items[i])
@ -165,8 +164,8 @@ func (m *MockTranslationRepository) ListByTranslatorID(ctx context.Context, tran
return out, nil return out, nil
} }
func (m *MockTranslationRepository) ListByStatus(ctx context.Context, status models2.TranslationStatus) ([]models2.Translation, error) { func (m *MockTranslationRepository) ListByStatus(ctx context.Context, status domain.TranslationStatus) ([]domain.Translation, error) {
var out []models2.Translation var out []domain.Translation
for i := range m.items { for i := range m.items {
if m.items[i].Status == status { if m.items[i].Status == status {
out = append(out, m.items[i]) out = append(out, m.items[i])
@ -177,12 +176,12 @@ func (m *MockTranslationRepository) ListByStatus(ctx context.Context, status mod
// Test helper: add a translation for a Work // Test helper: add a translation for a Work
func (m *MockTranslationRepository) AddTranslationForWork(workID uint, language string, content string, isOriginal bool) { func (m *MockTranslationRepository) AddTranslationForWork(workID uint, language string, content string, isOriginal bool) {
m.Create(context.Background(), &models2.Translation{ m.Create(context.Background(), &domain.Translation{
Title: "", Title: "",
Content: content, Content: content,
Description: "", Description: "",
Language: language, Language: language,
Status: models2.TranslationStatusPublished, Status: domain.TranslationStatusPublished,
TranslatableID: workID, TranslatableID: workID,
TranslatableType: "Work", TranslatableType: "Work",
IsOriginalLanguage: isOriginal, IsOriginalLanguage: isOriginal,

View File

@ -3,22 +3,21 @@ package testutil
import ( import (
"context" "context"
"gorm.io/gorm" "gorm.io/gorm"
"tercul/internal/models" "tercul/internal/domain"
"tercul/internal/repositories"
) )
// UnifiedMockWorkRepository is a shared mock for WorkRepository tests // UnifiedMockWorkRepository is a shared mock for WorkRepository tests
// Implements all required methods and uses an in-memory slice // Implements all required methods and uses an in-memory slice
type UnifiedMockWorkRepository struct { type UnifiedMockWorkRepository struct {
Works []*models.Work Works []*domain.Work
} }
func NewUnifiedMockWorkRepository() *UnifiedMockWorkRepository { func NewUnifiedMockWorkRepository() *UnifiedMockWorkRepository {
return &UnifiedMockWorkRepository{Works: []*models.Work{}} return &UnifiedMockWorkRepository{Works: []*domain.Work{}}
} }
func (m *UnifiedMockWorkRepository) AddWork(work *models.Work) { func (m *UnifiedMockWorkRepository) AddWork(work *domain.Work) {
work.ID = uint(len(m.Works) + 1) work.ID = uint(len(m.Works) + 1)
if work.Language == "" { if work.Language == "" {
work.Language = "en" // default for tests, can be set by caller work.Language = "en" // default for tests, can be set by caller
@ -27,28 +26,28 @@ func (m *UnifiedMockWorkRepository) AddWork(work *models.Work) {
} }
// BaseRepository methods with context support // BaseRepository methods with context support
func (m *UnifiedMockWorkRepository) Create(ctx context.Context, entity *models.Work) error { func (m *UnifiedMockWorkRepository) Create(ctx context.Context, entity *domain.Work) error {
m.AddWork(entity) m.AddWork(entity)
return nil return nil
} }
func (m *UnifiedMockWorkRepository) GetByID(ctx context.Context, id uint) (*models.Work, error) { func (m *UnifiedMockWorkRepository) GetByID(ctx context.Context, id uint) (*domain.Work, error) {
for _, w := range m.Works { for _, w := range m.Works {
if w.ID == id { if w.ID == id {
return w, nil return w, nil
} }
} }
return nil, repositories.ErrEntityNotFound return nil, domain.ErrEntityNotFound
} }
func (m *UnifiedMockWorkRepository) Update(ctx context.Context, entity *models.Work) error { func (m *UnifiedMockWorkRepository) Update(ctx context.Context, entity *domain.Work) error {
for i, w := range m.Works { for i, w := range m.Works {
if w.ID == entity.ID { if w.ID == entity.ID {
m.Works[i] = entity m.Works[i] = entity
return nil return nil
} }
} }
return repositories.ErrEntityNotFound return domain.ErrEntityNotFound
} }
func (m *UnifiedMockWorkRepository) Delete(ctx context.Context, id uint) error { func (m *UnifiedMockWorkRepository) Delete(ctx context.Context, id uint) error {
@ -58,11 +57,11 @@ func (m *UnifiedMockWorkRepository) Delete(ctx context.Context, id uint) error {
return nil return nil
} }
} }
return repositories.ErrEntityNotFound return domain.ErrEntityNotFound
} }
func (m *UnifiedMockWorkRepository) List(ctx context.Context, page, pageSize int) (*repositories.PaginatedResult[models.Work], error) { func (m *UnifiedMockWorkRepository) List(ctx context.Context, page, pageSize int) (*domain.PaginatedResult[domain.Work], error) {
var all []models.Work var all []domain.Work
for _, w := range m.Works { for _, w := range m.Works {
if w != nil { if w != nil {
all = append(all, *w) all = append(all, *w)
@ -72,16 +71,16 @@ func (m *UnifiedMockWorkRepository) List(ctx context.Context, page, pageSize int
start := (page - 1) * pageSize start := (page - 1) * pageSize
end := start + pageSize end := start + pageSize
if start > len(all) { if start > len(all) {
return &repositories.PaginatedResult[models.Work]{Items: []models.Work{}, TotalCount: total}, nil return &domain.PaginatedResult[domain.Work]{Items: []domain.Work{}, TotalCount: total}, nil
} }
if end > len(all) { if end > len(all) {
end = len(all) end = len(all)
} }
return &repositories.PaginatedResult[models.Work]{Items: all[start:end], TotalCount: total}, nil return &domain.PaginatedResult[domain.Work]{Items: all[start:end], TotalCount: total}, nil
} }
func (m *UnifiedMockWorkRepository) ListAll(ctx context.Context) ([]models.Work, error) { func (m *UnifiedMockWorkRepository) ListAll(ctx context.Context) ([]domain.Work, error) {
var all []models.Work var all []domain.Work
for _, w := range m.Works { for _, w := range m.Works {
if w != nil { if w != nil {
all = append(all, *w) all = append(all, *w)
@ -94,17 +93,17 @@ func (m *UnifiedMockWorkRepository) Count(ctx context.Context) (int64, error) {
return int64(len(m.Works)), nil return int64(len(m.Works)), nil
} }
func (m *UnifiedMockWorkRepository) FindWithPreload(ctx context.Context, preloads []string, id uint) (*models.Work, error) { func (m *UnifiedMockWorkRepository) FindWithPreload(ctx context.Context, preloads []string, id uint) (*domain.Work, error) {
for _, w := range m.Works { for _, w := range m.Works {
if w.ID == id { if w.ID == id {
return w, nil return w, nil
} }
} }
return nil, repositories.ErrEntityNotFound return nil, domain.ErrEntityNotFound
} }
func (m *UnifiedMockWorkRepository) GetAllForSync(ctx context.Context, batchSize, offset int) ([]models.Work, error) { func (m *UnifiedMockWorkRepository) GetAllForSync(ctx context.Context, batchSize, offset int) ([]domain.Work, error) {
var result []models.Work var result []domain.Work
end := offset + batchSize end := offset + batchSize
if end > len(m.Works) { if end > len(m.Works) {
end = len(m.Works) end = len(m.Works)
@ -118,15 +117,15 @@ func (m *UnifiedMockWorkRepository) GetAllForSync(ctx context.Context, batchSize
} }
// New BaseRepository methods // New BaseRepository methods
func (m *UnifiedMockWorkRepository) CreateInTx(ctx context.Context, tx *gorm.DB, entity *models.Work) error { func (m *UnifiedMockWorkRepository) CreateInTx(ctx context.Context, tx *gorm.DB, entity *domain.Work) error {
return m.Create(ctx, entity) return m.Create(ctx, entity)
} }
func (m *UnifiedMockWorkRepository) GetByIDWithOptions(ctx context.Context, id uint, options *repositories.QueryOptions) (*models.Work, error) { func (m *UnifiedMockWorkRepository) GetByIDWithOptions(ctx context.Context, id uint, options *domain.QueryOptions) (*domain.Work, error) {
return m.GetByID(ctx, id) return m.GetByID(ctx, id)
} }
func (m *UnifiedMockWorkRepository) UpdateInTx(ctx context.Context, tx *gorm.DB, entity *models.Work) error { func (m *UnifiedMockWorkRepository) UpdateInTx(ctx context.Context, tx *gorm.DB, entity *domain.Work) error {
return m.Update(ctx, entity) return m.Update(ctx, entity)
} }
@ -134,7 +133,7 @@ func (m *UnifiedMockWorkRepository) DeleteInTx(ctx context.Context, tx *gorm.DB,
return m.Delete(ctx, id) return m.Delete(ctx, id)
} }
func (m *UnifiedMockWorkRepository) ListWithOptions(ctx context.Context, options *repositories.QueryOptions) ([]models.Work, error) { func (m *UnifiedMockWorkRepository) ListWithOptions(ctx context.Context, options *domain.QueryOptions) ([]domain.Work, error) {
result, err := m.List(ctx, 1, 1000) result, err := m.List(ctx, 1, 1000)
if err != nil { if err != nil {
return nil, err return nil, err
@ -142,7 +141,7 @@ func (m *UnifiedMockWorkRepository) ListWithOptions(ctx context.Context, options
return result.Items, nil return result.Items, nil
} }
func (m *UnifiedMockWorkRepository) CountWithOptions(ctx context.Context, options *repositories.QueryOptions) (int64, error) { func (m *UnifiedMockWorkRepository) CountWithOptions(ctx context.Context, options *domain.QueryOptions) (int64, error) {
return m.Count(ctx) return m.Count(ctx)
} }
@ -160,8 +159,8 @@ func (m *UnifiedMockWorkRepository) WithTx(ctx context.Context, fn func(tx *gorm
} }
// WorkRepository specific methods // WorkRepository specific methods
func (m *UnifiedMockWorkRepository) FindByTitle(ctx context.Context, title string) ([]models.Work, error) { func (m *UnifiedMockWorkRepository) FindByTitle(ctx context.Context, title string) ([]domain.Work, error) {
var result []models.Work var result []domain.Work
for _, w := range m.Works { for _, w := range m.Works {
if len(title) == 0 || (len(w.Title) >= len(title) && w.Title[:len(title)] == title) { if len(title) == 0 || (len(w.Title) >= len(title) && w.Title[:len(title)] == title) {
result = append(result, *w) result = append(result, *w)
@ -170,8 +169,8 @@ func (m *UnifiedMockWorkRepository) FindByTitle(ctx context.Context, title strin
return result, nil return result, nil
} }
func (m *UnifiedMockWorkRepository) FindByLanguage(ctx context.Context, language string, page, pageSize int) (*repositories.PaginatedResult[models.Work], error) { func (m *UnifiedMockWorkRepository) FindByLanguage(ctx context.Context, language string, page, pageSize int) (*domain.PaginatedResult[domain.Work], error) {
var filtered []models.Work var filtered []domain.Work
for _, w := range m.Works { for _, w := range m.Works {
if w.Language == language { if w.Language == language {
filtered = append(filtered, *w) filtered = append(filtered, *w)
@ -181,16 +180,16 @@ func (m *UnifiedMockWorkRepository) FindByLanguage(ctx context.Context, language
start := (page - 1) * pageSize start := (page - 1) * pageSize
end := start + pageSize end := start + pageSize
if start > len(filtered) { if start > len(filtered) {
return &repositories.PaginatedResult[models.Work]{Items: []models.Work{}, TotalCount: total}, nil return &domain.PaginatedResult[domain.Work]{Items: []domain.Work{}, TotalCount: total}, nil
} }
if end > len(filtered) { if end > len(filtered) {
end = len(filtered) end = len(filtered)
} }
return &repositories.PaginatedResult[models.Work]{Items: filtered[start:end], TotalCount: total}, nil return &domain.PaginatedResult[domain.Work]{Items: filtered[start:end], TotalCount: total}, nil
} }
func (m *UnifiedMockWorkRepository) FindByAuthor(ctx context.Context, authorID uint) ([]models.Work, error) { func (m *UnifiedMockWorkRepository) FindByAuthor(ctx context.Context, authorID uint) ([]domain.Work, error) {
result := make([]models.Work, len(m.Works)) result := make([]domain.Work, len(m.Works))
for i, w := range m.Works { for i, w := range m.Works {
if w != nil { if w != nil {
result[i] = *w result[i] = *w
@ -199,8 +198,8 @@ func (m *UnifiedMockWorkRepository) FindByAuthor(ctx context.Context, authorID u
return result, nil return result, nil
} }
func (m *UnifiedMockWorkRepository) FindByCategory(ctx context.Context, categoryID uint) ([]models.Work, error) { func (m *UnifiedMockWorkRepository) FindByCategory(ctx context.Context, categoryID uint) ([]domain.Work, error) {
result := make([]models.Work, len(m.Works)) result := make([]domain.Work, len(m.Works))
for i, w := range m.Works { for i, w := range m.Works {
if w != nil { if w != nil {
result[i] = *w result[i] = *w
@ -209,17 +208,17 @@ func (m *UnifiedMockWorkRepository) FindByCategory(ctx context.Context, category
return result, nil return result, nil
} }
func (m *UnifiedMockWorkRepository) GetWithTranslations(ctx context.Context, id uint) (*models.Work, error) { func (m *UnifiedMockWorkRepository) GetWithTranslations(ctx context.Context, id uint) (*domain.Work, error) {
for _, w := range m.Works { for _, w := range m.Works {
if w.ID == id { if w.ID == id {
return w, nil return w, nil
} }
} }
return nil, repositories.ErrEntityNotFound return nil, domain.ErrEntityNotFound
} }
func (m *UnifiedMockWorkRepository) ListWithTranslations(ctx context.Context, page, pageSize int) (*repositories.PaginatedResult[models.Work], error) { func (m *UnifiedMockWorkRepository) ListWithTranslations(ctx context.Context, page, pageSize int) (*domain.PaginatedResult[domain.Work], error) {
var all []models.Work var all []domain.Work
for _, w := range m.Works { for _, w := range m.Works {
if w != nil { if w != nil {
all = append(all, *w) all = append(all, *w)
@ -229,16 +228,16 @@ func (m *UnifiedMockWorkRepository) ListWithTranslations(ctx context.Context, pa
start := (page - 1) * pageSize start := (page - 1) * pageSize
end := start + pageSize end := start + pageSize
if start > len(all) { if start > len(all) {
return &repositories.PaginatedResult[models.Work]{Items: []models.Work{}, TotalCount: total}, nil return &domain.PaginatedResult[domain.Work]{Items: []domain.Work{}, TotalCount: total}, nil
} }
if end > len(all) { if end > len(all) {
end = len(all) end = len(all)
} }
return &repositories.PaginatedResult[models.Work]{Items: all[start:end], TotalCount: total}, nil return &domain.PaginatedResult[domain.Work]{Items: all[start:end], TotalCount: total}, nil
} }
func (m *UnifiedMockWorkRepository) Reset() { func (m *UnifiedMockWorkRepository) Reset() {
m.Works = []*models.Work{} m.Works = []*domain.Work{}
} }
// Add helper to get GraphQL-style Work with Name mapped from Title // Add helper to get GraphQL-style Work with Name mapped from Title

View File

@ -1,9 +1,10 @@
package testutil package testutil
import ( import (
"tercul/graph" "context"
"tercul/internal/models" graph "tercul/internal/adapters/graphql"
"tercul/services" "tercul/internal/app/work"
"tercul/internal/domain"
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
) )
@ -11,14 +12,26 @@ import (
// SimpleTestSuite provides a minimal test environment with just the essentials // SimpleTestSuite provides a minimal test environment with just the essentials
type SimpleTestSuite struct { type SimpleTestSuite struct {
suite.Suite suite.Suite
WorkRepo *UnifiedMockWorkRepository WorkRepo *UnifiedMockWorkRepository
WorkService services.WorkService WorkCommands *work.WorkCommands
WorkQueries *work.WorkQueries
MockAnalyzer *MockAnalyzer
}
// MockAnalyzer is a mock implementation of the analyzer interface.
type MockAnalyzer struct{}
// AnalyzeWork is the mock implementation of the AnalyzeWork method.
func (m *MockAnalyzer) AnalyzeWork(ctx context.Context, workID uint) error {
return nil
} }
// SetupSuite sets up the test suite // SetupSuite sets up the test suite
func (s *SimpleTestSuite) SetupSuite() { func (s *SimpleTestSuite) SetupSuite() {
s.WorkRepo = NewUnifiedMockWorkRepository() s.WorkRepo = NewUnifiedMockWorkRepository()
s.WorkService = services.NewWorkService(s.WorkRepo, nil) s.MockAnalyzer = &MockAnalyzer{}
s.WorkCommands = work.NewWorkCommands(s.WorkRepo, s.MockAnalyzer)
s.WorkQueries = work.NewWorkQueries(s.WorkRepo)
} }
// SetupTest resets test data for each test // SetupTest resets test data for each test
@ -28,19 +41,20 @@ func (s *SimpleTestSuite) SetupTest() {
// GetResolver returns a minimal GraphQL resolver for testing // GetResolver returns a minimal GraphQL resolver for testing
func (s *SimpleTestSuite) GetResolver() *graph.Resolver { func (s *SimpleTestSuite) GetResolver() *graph.Resolver {
// This needs to be updated to reflect the new resolver structure
// For now, we'll return a resolver with the work commands and queries
return &graph.Resolver{ return &graph.Resolver{
WorkRepo: s.WorkRepo, // WorkRepo: s.WorkRepo, // This should be removed from resolver
WorkService: s.WorkService, // WorkService: s.WorkService, // This is replaced by commands/queries
// Other fields will be nil, but that's okay for basic tests
} }
} }
// CreateTestWork creates a test work with optional content // CreateTestWork creates a test work with optional content
func (s *SimpleTestSuite) CreateTestWork(title, language string, content string) *models.Work { func (s *SimpleTestSuite) CreateTestWork(title, language string, content string) *domain.Work {
work := &models.Work{ work := &domain.Work{
Title: title, Title: title,
Language: language,
} }
work.Language = language
// Add work to the mock repository // Add work to the mock repository
s.WorkRepo.AddWork(work) s.WorkRepo.AddWork(work)