mirror of
https://github.com/SamyRai/tercul-backend.git
synced 2025-12-27 05:11:34 +00:00
commit
75a291c3a9
@ -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,32 @@ 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{
|
||||||
|
App: appBuilder.GetApplication(),
|
||||||
|
}
|
||||||
|
|
||||||
|
jwtManager := auth.NewJWTManager()
|
||||||
|
srv := 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() {
|
||||||
|
|||||||
@ -2,15 +2,15 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"tercul/internal/adapters/graphql"
|
||||||
"tercul/internal/platform/auth"
|
"tercul/internal/platform/auth"
|
||||||
|
|
||||||
"github.com/99designs/gqlgen/graphql/handler"
|
"github.com/99designs/gqlgen/graphql/handler"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewServer creates a new GraphQL server with the given resolver
|
// NewServer creates a new GraphQL server with the given resolver
|
||||||
func NewServer(resolver *Resolver) http.Handler {
|
func NewServer(resolver *graphql.Resolver) http.Handler {
|
||||||
srv := handler.NewDefaultServer(NewExecutableSchema(Config{Resolvers: resolver}))
|
srv := handler.NewDefaultServer(graphql.NewExecutableSchema(graphql.Config{Resolvers: resolver}))
|
||||||
|
|
||||||
// Create a mux to handle GraphQL endpoint only (no playground here; served separately in production)
|
// Create a mux to handle GraphQL endpoint only (no playground here; served separately in production)
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
@ -20,8 +20,8 @@ func NewServer(resolver *Resolver) http.Handler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewServerWithAuth creates a new GraphQL server with authentication middleware
|
// NewServerWithAuth creates a new GraphQL server with authentication middleware
|
||||||
func NewServerWithAuth(resolver *Resolver, jwtManager *auth.JWTManager) http.Handler {
|
func NewServerWithAuth(resolver *graphql.Resolver, jwtManager *auth.JWTManager) http.Handler {
|
||||||
srv := handler.NewDefaultServer(NewExecutableSchema(Config{Resolvers: resolver}))
|
srv := handler.NewDefaultServer(graphql.NewExecutableSchema(graphql.Config{Resolvers: resolver}))
|
||||||
|
|
||||||
// Apply authentication middleware to GraphQL endpoint
|
// Apply authentication middleware to GraphQL endpoint
|
||||||
authHandler := auth.GraphQLAuthMiddleware(jwtManager)(srv)
|
authHandler := auth.GraphQLAuthMiddleware(jwtManager)(srv)
|
||||||
|
|||||||
@ -2,74 +2,48 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"log"
|
"tercul/internal/app"
|
||||||
"os"
|
"tercul/internal/jobs/linguistics"
|
||||||
"os/signal"
|
|
||||||
"syscall"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"gorm.io/driver/postgres"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
"tercul/internal/enrich"
|
|
||||||
"tercul/internal/store"
|
|
||||||
"tercul/internal/platform/config"
|
"tercul/internal/platform/config"
|
||||||
|
log "tercul/internal/platform/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
log.Println("Starting enrichment service...")
|
log.LogInfo("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.Items {
|
||||||
|
err := linguistics.EnqueueAnalysisForWork(appBuilder.GetAsynq(), 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.LogInfo("Enrichment tool finished.")
|
||||||
}
|
}
|
||||||
|
|||||||
143
create_repo_interfaces.go
Normal file
143
create_repo_interfaces.go
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
//go:build tools
|
||||||
|
|
||||||
|
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")
|
||||||
|
}
|
||||||
51
fix_domain_repos.go
Normal file
51
fix_domain_repos.go
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
//go:build tools
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
42
fix_sql_imports.go
Normal file
42
fix_sql_imports.go
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
//go:build tools
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -4,10 +4,10 @@ import "context"
|
|||||||
|
|
||||||
// resolveWorkContent uses Localization service to fetch preferred content
|
// resolveWorkContent uses Localization service to fetch preferred content
|
||||||
func (r *queryResolver) resolveWorkContent(ctx context.Context, workID uint, preferredLanguage string) *string {
|
func (r *queryResolver) resolveWorkContent(ctx context.Context, workID uint, preferredLanguage string) *string {
|
||||||
if r.Localization == nil {
|
if r.App.Localization == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
content, err := r.Localization.GetWorkContent(ctx, workID, preferredLanguage)
|
content, err := r.App.Localization.GetWorkContent(ctx, workID, preferredLanguage)
|
||||||
if err != nil || content == "" {
|
if err != nil || content == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import (
|
|||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"tercul/internal/adapters/graphql"
|
graph "tercul/internal/adapters/graphql"
|
||||||
"tercul/internal/testutil"
|
"tercul/internal/testutil"
|
||||||
|
|
||||||
"github.com/99designs/gqlgen/graphql/handler"
|
"github.com/99designs/gqlgen/graphql/handler"
|
||||||
@ -140,9 +140,9 @@ func (s *GraphQLIntegrationSuite) TestQueryWork() {
|
|||||||
// TestQueryWorks tests the works query
|
// TestQueryWorks tests the works query
|
||||||
func (s *GraphQLIntegrationSuite) TestQueryWorks() {
|
func (s *GraphQLIntegrationSuite) TestQueryWorks() {
|
||||||
// Create test works
|
// Create test works
|
||||||
work1 := s.CreateTestWork("Test Work 1", "en", "Test content for work 1")
|
s.CreateTestWork("Test Work 1", "en", "Test content for work 1")
|
||||||
work2 := s.CreateTestWork("Test Work 2", "en", "Test content for work 2")
|
s.CreateTestWork("Test Work 2", "en", "Test content for work 2")
|
||||||
work3 := s.CreateTestWork("Test Work 3", "fr", "Test content for work 3")
|
s.CreateTestWork("Test Work 3", "fr", "Test content for work 3")
|
||||||
|
|
||||||
// Define the query
|
// Define the query
|
||||||
query := `
|
query := `
|
||||||
|
|||||||
@ -7,6 +7,7 @@ package graphql
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"strconv"
|
"strconv"
|
||||||
"tercul/internal/adapters/graphql/model"
|
"tercul/internal/adapters/graphql/model"
|
||||||
"tercul/internal/app/auth"
|
"tercul/internal/app/auth"
|
||||||
@ -82,9 +83,9 @@ func (r *mutationResolver) Login(ctx context.Context, email string, password str
|
|||||||
func (r *mutationResolver) CreateWork(ctx context.Context, input model.WorkInput) (*model.Work, error) {
|
func (r *mutationResolver) CreateWork(ctx context.Context, input model.WorkInput) (*model.Work, error) {
|
||||||
// Create domain model
|
// Create domain model
|
||||||
work := &domain.Work{
|
work := &domain.Work{
|
||||||
Title: input.Name,
|
Title: input.Name,
|
||||||
Description: *input.Description,
|
TranslatableModel: domain.TranslatableModel{Language: input.Language},
|
||||||
Language: input.Language,
|
// Description: *input.Description,
|
||||||
// Other fields can be set here
|
// Other fields can be set here
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -377,7 +378,7 @@ func (r *queryResolver) Author(ctx context.Context, id string) (*model.Author, e
|
|||||||
|
|
||||||
// Authors is the resolver for the authors field.
|
// Authors is the resolver for the authors field.
|
||||||
func (r *queryResolver) Authors(ctx context.Context, limit *int32, offset *int32, search *string, countryID *string) ([]*model.Author, error) {
|
func (r *queryResolver) Authors(ctx context.Context, limit *int32, offset *int32, search *string, countryID *string) ([]*model.Author, error) {
|
||||||
var authors []models2.Author
|
var authors []domain.Author
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
if countryID != nil {
|
if countryID != nil {
|
||||||
@ -385,9 +386,9 @@ func (r *queryResolver) Authors(ctx context.Context, limit *int32, offset *int32
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
authors, err = r.AuthorRepo.ListByCountryID(ctx, uint(countryIDUint))
|
authors, err = r.App.AuthorRepo.ListByCountryID(ctx, uint(countryIDUint))
|
||||||
} else {
|
} else {
|
||||||
result, err := r.AuthorRepo.List(ctx, 1, 1000) // Use pagination
|
result, err := r.App.AuthorRepo.List(ctx, 1, 1000) // Use pagination
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -402,8 +403,8 @@ func (r *queryResolver) Authors(ctx context.Context, limit *int32, offset *int32
|
|||||||
var result []*model.Author
|
var result []*model.Author
|
||||||
for _, a := range authors {
|
for _, a := range authors {
|
||||||
var bio *string
|
var bio *string
|
||||||
if r.Localization != nil {
|
if r.App.Localization != nil {
|
||||||
if b, err := r.Localization.GetAuthorBiography(ctx, a.ID, a.Language); err == nil && b != "" {
|
if b, err := r.App.Localization.GetAuthorBiography(ctx, a.ID, a.Language); err == nil && b != "" {
|
||||||
bio = &b
|
bio = &b
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -435,29 +436,29 @@ func (r *queryResolver) UserByUsername(ctx context.Context, username string) (*m
|
|||||||
|
|
||||||
// Users is the resolver for the users field.
|
// Users is the resolver for the users field.
|
||||||
func (r *queryResolver) Users(ctx context.Context, limit *int32, offset *int32, role *model.UserRole) ([]*model.User, error) {
|
func (r *queryResolver) Users(ctx context.Context, limit *int32, offset *int32, role *model.UserRole) ([]*model.User, error) {
|
||||||
var users []models2.User
|
var users []domain.User
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
if role != nil {
|
if role != nil {
|
||||||
// Convert GraphQL role to model role
|
// Convert GraphQL role to model role
|
||||||
var modelRole models2.UserRole
|
var modelRole domain.UserRole
|
||||||
switch *role {
|
switch *role {
|
||||||
case model.UserRoleReader:
|
case model.UserRoleReader:
|
||||||
modelRole = models2.UserRoleReader
|
modelRole = domain.UserRoleReader
|
||||||
case model.UserRoleContributor:
|
case model.UserRoleContributor:
|
||||||
modelRole = models2.UserRoleContributor
|
modelRole = domain.UserRoleContributor
|
||||||
case model.UserRoleReviewer:
|
case model.UserRoleReviewer:
|
||||||
modelRole = models2.UserRoleReviewer
|
modelRole = domain.UserRoleReviewer
|
||||||
case model.UserRoleEditor:
|
case model.UserRoleEditor:
|
||||||
modelRole = models2.UserRoleEditor
|
modelRole = domain.UserRoleEditor
|
||||||
case model.UserRoleAdmin:
|
case model.UserRoleAdmin:
|
||||||
modelRole = models2.UserRoleAdmin
|
modelRole = domain.UserRoleAdmin
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("invalid user role: %s", *role)
|
return nil, fmt.Errorf("invalid user role: %s", *role)
|
||||||
}
|
}
|
||||||
users, err = r.UserRepo.ListByRole(ctx, modelRole)
|
users, err = r.App.UserRepo.ListByRole(ctx, modelRole)
|
||||||
} else {
|
} else {
|
||||||
result, err := r.UserRepo.List(ctx, 1, 1000) // Use pagination
|
result, err := r.App.UserRepo.List(ctx, 1, 1000) // Use pagination
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -474,15 +475,15 @@ func (r *queryResolver) Users(ctx context.Context, limit *int32, offset *int32,
|
|||||||
// Convert model role to GraphQL role
|
// Convert model role to GraphQL role
|
||||||
var graphqlRole model.UserRole
|
var graphqlRole model.UserRole
|
||||||
switch u.Role {
|
switch u.Role {
|
||||||
case models2.UserRoleReader:
|
case domain.UserRoleReader:
|
||||||
graphqlRole = model.UserRoleReader
|
graphqlRole = model.UserRoleReader
|
||||||
case models2.UserRoleContributor:
|
case domain.UserRoleContributor:
|
||||||
graphqlRole = model.UserRoleContributor
|
graphqlRole = model.UserRoleContributor
|
||||||
case models2.UserRoleReviewer:
|
case domain.UserRoleReviewer:
|
||||||
graphqlRole = model.UserRoleReviewer
|
graphqlRole = model.UserRoleReviewer
|
||||||
case models2.UserRoleEditor:
|
case domain.UserRoleEditor:
|
||||||
graphqlRole = model.UserRoleEditor
|
graphqlRole = model.UserRoleEditor
|
||||||
case models2.UserRoleAdmin:
|
case domain.UserRoleAdmin:
|
||||||
graphqlRole = model.UserRoleAdmin
|
graphqlRole = model.UserRoleAdmin
|
||||||
default:
|
default:
|
||||||
graphqlRole = model.UserRoleReader
|
graphqlRole = model.UserRoleReader
|
||||||
@ -526,7 +527,7 @@ func (r *queryResolver) Tag(ctx context.Context, id string) (*model.Tag, error)
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
tag, err := r.TagRepo.GetByID(ctx, uint(tagID))
|
tag, err := r.App.TagRepo.GetByID(ctx, uint(tagID))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -539,7 +540,7 @@ func (r *queryResolver) Tag(ctx context.Context, id string) (*model.Tag, error)
|
|||||||
|
|
||||||
// Tags is the resolver for the tags field.
|
// Tags is the resolver for the tags field.
|
||||||
func (r *queryResolver) Tags(ctx context.Context, limit *int32, offset *int32) ([]*model.Tag, error) {
|
func (r *queryResolver) Tags(ctx context.Context, limit *int32, offset *int32) ([]*model.Tag, error) {
|
||||||
paginatedResult, err := r.TagRepo.List(ctx, 1, 1000) // Use pagination
|
paginatedResult, err := r.App.TagRepo.List(ctx, 1, 1000) // Use pagination
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -563,7 +564,7 @@ func (r *queryResolver) Category(ctx context.Context, id string) (*model.Categor
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
category, err := r.CategoryRepo.GetByID(ctx, uint(categoryID))
|
category, err := r.App.CategoryRepo.GetByID(ctx, uint(categoryID))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -576,7 +577,7 @@ func (r *queryResolver) Category(ctx context.Context, id string) (*model.Categor
|
|||||||
|
|
||||||
// Categories is the resolver for the categories field.
|
// Categories is the resolver for the categories field.
|
||||||
func (r *queryResolver) Categories(ctx context.Context, limit *int32, offset *int32) ([]*model.Category, error) {
|
func (r *queryResolver) Categories(ctx context.Context, limit *int32, offset *int32) ([]*model.Category, error) {
|
||||||
paginatedResult, err := r.CategoryRepo.List(ctx, 1, 1000)
|
paginatedResult, err := r.App.CategoryRepo.List(ctx, 1, 1000)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import (
|
|||||||
"tercul/internal/app/localization"
|
"tercul/internal/app/localization"
|
||||||
"tercul/internal/app/search"
|
"tercul/internal/app/search"
|
||||||
"tercul/internal/app/work"
|
"tercul/internal/app/work"
|
||||||
|
"tercul/internal/domain"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Application is a container for all the application-layer services.
|
// Application is a container for all the application-layer services.
|
||||||
@ -19,4 +20,10 @@ type Application struct {
|
|||||||
Search search.IndexService
|
Search search.IndexService
|
||||||
WorkCommands *work.WorkCommands
|
WorkCommands *work.WorkCommands
|
||||||
WorkQueries *work.WorkQueries
|
WorkQueries *work.WorkQueries
|
||||||
|
|
||||||
|
// Repositories - to be refactored into app services
|
||||||
|
AuthorRepo domain.AuthorRepository
|
||||||
|
UserRepo domain.UserRepository
|
||||||
|
TagRepo domain.TagRepository
|
||||||
|
CategoryRepo domain.CategoryRepository
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,14 +7,12 @@ import (
|
|||||||
"tercul/internal/app/search"
|
"tercul/internal/app/search"
|
||||||
"tercul/internal/app/work"
|
"tercul/internal/app/work"
|
||||||
"tercul/internal/data/sql"
|
"tercul/internal/data/sql"
|
||||||
"tercul/internal/domain"
|
|
||||||
"tercul/internal/platform/cache"
|
"tercul/internal/platform/cache"
|
||||||
"tercul/internal/platform/config"
|
"tercul/internal/platform/config"
|
||||||
"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"
|
|
||||||
|
|
||||||
"github.com/hibiken/asynq"
|
"github.com/hibiken/asynq"
|
||||||
"github.com/weaviate/weaviate-go-client/v5/weaviate"
|
"github.com/weaviate/weaviate-go-client/v5/weaviate"
|
||||||
@ -110,6 +108,9 @@ func (b *ApplicationBuilder) BuildApplication() error {
|
|||||||
// I need to add all the other repos here. For now, I'll just add the ones I need for the services.
|
// I need to add all the other repos here. For now, I'll just add the ones I need for the services.
|
||||||
translationRepo := sql.NewTranslationRepository(b.dbConn)
|
translationRepo := sql.NewTranslationRepository(b.dbConn)
|
||||||
copyrightRepo := sql.NewCopyrightRepository(b.dbConn)
|
copyrightRepo := sql.NewCopyrightRepository(b.dbConn)
|
||||||
|
authorRepo := sql.NewAuthorRepository(b.dbConn)
|
||||||
|
tagRepo := sql.NewTagRepository(b.dbConn)
|
||||||
|
categoryRepo := sql.NewCategoryRepository(b.dbConn)
|
||||||
|
|
||||||
|
|
||||||
// Initialize application services
|
// Initialize application services
|
||||||
@ -136,6 +137,10 @@ func (b *ApplicationBuilder) BuildApplication() error {
|
|||||||
CopyrightQueries: copyrightQueries,
|
CopyrightQueries: copyrightQueries,
|
||||||
Localization: localizationService,
|
Localization: localizationService,
|
||||||
Search: searchService,
|
Search: searchService,
|
||||||
|
AuthorRepo: authorRepo,
|
||||||
|
UserRepo: userRepo,
|
||||||
|
TagRepo: tagRepo,
|
||||||
|
CategoryRepo: categoryRepo,
|
||||||
}
|
}
|
||||||
|
|
||||||
log.LogInfo("Application layer initialized successfully")
|
log.LogInfo("Application layer initialized successfully")
|
||||||
@ -159,6 +164,21 @@ func (b *ApplicationBuilder) GetApplication() *Application {
|
|||||||
return b.App
|
return b.App
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetDB returns the database connection
|
||||||
|
func (b *ApplicationBuilder) GetDB() *gorm.DB {
|
||||||
|
return b.dbConn
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAsynq returns the Asynq client
|
||||||
|
func (b *ApplicationBuilder) GetAsynq() *asynq.Client {
|
||||||
|
return b.asynqClient
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLinguisticsFactory returns the linguistics factory
|
||||||
|
func (b *ApplicationBuilder) GetLinguisticsFactory() *linguistics.LinguisticsFactory {
|
||||||
|
return b.linguistics
|
||||||
|
}
|
||||||
|
|
||||||
// Close closes all resources
|
// Close closes all resources
|
||||||
func (b *ApplicationBuilder) Close() error {
|
func (b *ApplicationBuilder) Close() error {
|
||||||
if b.asynqClient != nil {
|
if b.asynqClient != nil {
|
||||||
|
|||||||
@ -1,15 +1,11 @@
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"tercul/internal/jobs/linguistics"
|
||||||
"tercul/internal/platform/auth"
|
syncjob "tercul/internal/jobs/sync"
|
||||||
"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/hibiken/asynq"
|
"github.com/hibiken/asynq"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -25,44 +21,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) {
|
||||||
@ -84,8 +42,8 @@ func (f *ServerFactory) CreateBackgroundJobServers() ([]*asynq.Server, error) {
|
|||||||
|
|
||||||
// Create sync job instance
|
// Create sync job instance
|
||||||
syncJobInstance := syncjob.NewSyncJob(
|
syncJobInstance := syncjob.NewSyncJob(
|
||||||
f.appBuilder.GetDatabase(),
|
f.appBuilder.GetDB(),
|
||||||
f.appBuilder.GetAsynqClient(),
|
f.appBuilder.GetAsynq(),
|
||||||
)
|
)
|
||||||
|
|
||||||
// Register sync job handlers
|
// Register sync job handlers
|
||||||
@ -98,9 +56,9 @@ func (f *ServerFactory) CreateBackgroundJobServers() ([]*asynq.Server, error) {
|
|||||||
|
|
||||||
// Create linguistic sync job
|
// Create linguistic sync job
|
||||||
linguisticSyncJob := linguistics.NewLinguisticSyncJob(
|
linguisticSyncJob := linguistics.NewLinguisticSyncJob(
|
||||||
f.appBuilder.GetDatabase(),
|
f.appBuilder.GetDB(),
|
||||||
f.appBuilder.GetLinguistics().GetAnalyzer(),
|
f.appBuilder.GetLinguisticsFactory().GetAnalyzer(),
|
||||||
f.appBuilder.GetAsynqClient(),
|
f.appBuilder.GetAsynq(),
|
||||||
)
|
)
|
||||||
|
|
||||||
// Create linguistic server and register handlers
|
// Create linguistic server and register handlers
|
||||||
@ -120,19 +78,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
|
|
||||||
}
|
|
||||||
|
|||||||
@ -2,8 +2,10 @@ package sql
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"gorm.io/gorm"
|
|
||||||
"tercul/internal/domain"
|
"tercul/internal/domain"
|
||||||
|
"tercul/internal/domain/author"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
type authorRepository struct {
|
type authorRepository struct {
|
||||||
@ -12,7 +14,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,
|
||||||
|
|||||||
@ -3,8 +3,10 @@ package sql
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"gorm.io/gorm"
|
|
||||||
"tercul/internal/domain"
|
"tercul/internal/domain"
|
||||||
|
"tercul/internal/domain/book"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
type bookRepository struct {
|
type bookRepository struct {
|
||||||
@ -13,7 +15,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,
|
||||||
|
|||||||
@ -2,8 +2,10 @@ package sql
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"gorm.io/gorm"
|
|
||||||
"tercul/internal/domain"
|
"tercul/internal/domain"
|
||||||
|
"tercul/internal/domain/bookmark"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
type bookmarkRepository struct {
|
type bookmarkRepository struct {
|
||||||
@ -12,7 +14,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,
|
||||||
|
|||||||
@ -3,8 +3,10 @@ package sql
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"gorm.io/gorm"
|
|
||||||
"tercul/internal/domain"
|
"tercul/internal/domain"
|
||||||
|
"tercul/internal/domain/category"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
type categoryRepository struct {
|
type categoryRepository struct {
|
||||||
@ -13,7 +15,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,
|
||||||
|
|||||||
@ -1,33 +1,29 @@
|
|||||||
package repositories
|
package sql
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"tercul/internal/domain"
|
||||||
|
"tercul/internal/domain/city"
|
||||||
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
"tercul/internal/models"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// 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
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,8 +2,10 @@ package sql
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"gorm.io/gorm"
|
|
||||||
"tercul/internal/domain"
|
"tercul/internal/domain"
|
||||||
|
"tercul/internal/domain/collection"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
type collectionRepository struct {
|
type collectionRepository struct {
|
||||||
@ -12,7 +14,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,
|
||||||
|
|||||||
@ -2,8 +2,10 @@ package sql
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"gorm.io/gorm"
|
|
||||||
"tercul/internal/domain"
|
"tercul/internal/domain"
|
||||||
|
"tercul/internal/domain/comment"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
type commentRepository struct {
|
type commentRepository struct {
|
||||||
@ -12,7 +14,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,
|
||||||
|
|||||||
@ -1,37 +1,29 @@
|
|||||||
package repositories
|
package sql
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"tercul/internal/domain"
|
||||||
|
"tercul/internal/domain/contribution"
|
||||||
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
"tercul/internal/models"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// 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 +31,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 +40,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 +49,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 +58,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
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,34 +1,29 @@
|
|||||||
package repositories
|
package sql
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"tercul/internal/domain"
|
||||||
|
"tercul/internal/domain/copyright_claim"
|
||||||
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
"tercul/internal/models"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// 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) copyright_claim.Copyright_claimRepository {
|
||||||
return ©rightClaimRepository{
|
return ©rightClaimRepository{
|
||||||
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 +31,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
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,42 +1,30 @@
|
|||||||
package repositories
|
package sql
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"tercul/internal/domain"
|
||||||
|
"tercul/internal/domain/copyright"
|
||||||
|
|
||||||
"gorm.io/gorm"
|
"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 {
|
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 ©rightRepository{
|
return ©rightRepository{
|
||||||
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 +35,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 +49,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(©rightables).Error
|
err := r.db.WithContext(ctx).Where("copyright_id = ?", copyrightID).Find(©rightables).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) {
|
||||||
|
|||||||
@ -1,35 +1,30 @@
|
|||||||
package repositories
|
package sql
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"tercul/internal/domain"
|
||||||
|
"tercul/internal/domain/country"
|
||||||
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
"tercul/internal/models"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// 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 +35,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
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,33 +1,29 @@
|
|||||||
package repositories
|
package sql
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"tercul/internal/domain"
|
||||||
|
"tercul/internal/domain/edge"
|
||||||
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
"tercul/internal/models"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// 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
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,35 +1,30 @@
|
|||||||
package repositories
|
package sql
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"tercul/internal/domain"
|
||||||
|
"tercul/internal/domain/edition"
|
||||||
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
"tercul/internal/models"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// 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 +32,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
|
||||||
|
|||||||
@ -1,38 +1,31 @@
|
|||||||
package repositories
|
package sql
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"gorm.io/gorm"
|
"tercul/internal/domain"
|
||||||
"tercul/internal/models"
|
"tercul/internal/domain/email_verification"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 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) email_verification.Email_verificationRepository {
|
||||||
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 +36,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 +46,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 +54,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
|
||||||
|
|||||||
@ -2,8 +2,10 @@ package sql
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"gorm.io/gorm"
|
|
||||||
"tercul/internal/domain"
|
"tercul/internal/domain"
|
||||||
|
"tercul/internal/domain/like"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
type likeRepository struct {
|
type likeRepository struct {
|
||||||
@ -12,7 +14,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,
|
||||||
|
|||||||
@ -1,35 +1,29 @@
|
|||||||
package repositories
|
package sql
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"tercul/internal/domain"
|
||||||
|
"tercul/internal/domain/monetization"
|
||||||
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
"tercul/internal/models"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// 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 +31,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 +40,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
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,38 +1,31 @@
|
|||||||
package repositories
|
package sql
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"gorm.io/gorm"
|
"tercul/internal/domain"
|
||||||
"tercul/internal/models"
|
"tercul/internal/domain/password_reset"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 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) password_reset.Password_resetRepository {
|
||||||
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 +36,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 +46,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 +54,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
|
||||||
|
|||||||
@ -1,36 +1,30 @@
|
|||||||
package repositories
|
package sql
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"gorm.io/gorm"
|
|
||||||
"math"
|
"math"
|
||||||
"tercul/internal/models"
|
"tercul/internal/domain"
|
||||||
|
"tercul/internal/domain/place"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 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 +32,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 +41,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
|
||||||
|
|||||||
@ -1,33 +1,29 @@
|
|||||||
package repositories
|
package sql
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"tercul/internal/domain"
|
||||||
|
"tercul/internal/domain/publisher"
|
||||||
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
"tercul/internal/models"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// 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
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,35 +1,30 @@
|
|||||||
package repositories
|
package sql
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"tercul/internal/domain"
|
||||||
|
"tercul/internal/domain/source"
|
||||||
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
"tercul/internal/models"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// 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 +34,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
|
||||||
|
|||||||
@ -3,8 +3,10 @@ package sql
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"gorm.io/gorm"
|
|
||||||
"tercul/internal/domain"
|
"tercul/internal/domain"
|
||||||
|
"tercul/internal/domain/tag"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
type tagRepository struct {
|
type tagRepository struct {
|
||||||
@ -13,7 +15,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,
|
||||||
|
|||||||
@ -2,8 +2,10 @@ package sql
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"gorm.io/gorm"
|
|
||||||
"tercul/internal/domain"
|
"tercul/internal/domain"
|
||||||
|
"tercul/internal/domain/translation"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
type translationRepository struct {
|
type translationRepository struct {
|
||||||
@ -12,7 +14,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,
|
||||||
|
|||||||
@ -1,34 +1,30 @@
|
|||||||
package repositories
|
package sql
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"tercul/internal/domain"
|
||||||
|
"tercul/internal/domain/user_profile"
|
||||||
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
"tercul/internal/models"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// 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) user_profile.User_profileRepository {
|
||||||
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
|
||||||
|
|||||||
@ -3,8 +3,10 @@ package sql
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"gorm.io/gorm"
|
|
||||||
"tercul/internal/domain"
|
"tercul/internal/domain"
|
||||||
|
"tercul/internal/domain/user"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
type userRepository struct {
|
type userRepository struct {
|
||||||
@ -13,7 +15,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,
|
||||||
|
|||||||
@ -1,37 +1,31 @@
|
|||||||
package repositories
|
package sql
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"gorm.io/gorm"
|
"tercul/internal/domain"
|
||||||
"tercul/internal/models"
|
"tercul/internal/domain/user_session"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 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) user_session.User_sessionRepository {
|
||||||
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 +36,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 +46,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
|
||||||
|
|||||||
@ -2,8 +2,10 @@ package sql
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"gorm.io/gorm"
|
|
||||||
"tercul/internal/domain"
|
"tercul/internal/domain"
|
||||||
|
"tercul/internal/domain/work"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
type workRepository struct {
|
type workRepository struct {
|
||||||
@ -12,7 +14,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,
|
||||||
|
|||||||
15
internal/domain/author/repo.go
Normal file
15
internal/domain/author/repo.go
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
package author
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"tercul/internal/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AuthorRepository defines CRUD methods specific to Author.
|
||||||
|
type AuthorRepository interface {
|
||||||
|
domain.BaseRepository[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)
|
||||||
|
}
|
||||||
16
internal/domain/book/repo.go
Normal file
16
internal/domain/book/repo.go
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
package book
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"tercul/internal/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BookRepository defines CRUD methods specific to Book.
|
||||||
|
type BookRepository interface {
|
||||||
|
domain.BaseRepository[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)
|
||||||
|
}
|
||||||
14
internal/domain/bookmark/repo.go
Normal file
14
internal/domain/bookmark/repo.go
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
package bookmark
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"tercul/internal/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BookmarkRepository defines CRUD methods specific to Bookmark.
|
||||||
|
type BookmarkRepository interface {
|
||||||
|
domain.BaseRepository[domain.Bookmark]
|
||||||
|
|
||||||
|
ListByUserID(ctx context.Context, userID uint) ([]domain.Bookmark, error)
|
||||||
|
ListByWorkID(ctx context.Context, workID uint) ([]domain.Bookmark, error)
|
||||||
|
}
|
||||||
15
internal/domain/category/repo.go
Normal file
15
internal/domain/category/repo.go
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
package category
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"tercul/internal/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CategoryRepository defines CRUD methods specific to Category.
|
||||||
|
type CategoryRepository interface {
|
||||||
|
domain.BaseRepository[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)
|
||||||
|
}
|
||||||
13
internal/domain/city/repo.go
Normal file
13
internal/domain/city/repo.go
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package city
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"tercul/internal/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CityRepository defines CRUD methods specific to City.
|
||||||
|
type CityRepository interface {
|
||||||
|
domain.BaseRepository[domain.City]
|
||||||
|
|
||||||
|
ListByCountryID(ctx context.Context, countryID uint) ([]domain.City, error)
|
||||||
|
}
|
||||||
15
internal/domain/collection/repo.go
Normal file
15
internal/domain/collection/repo.go
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
package collection
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"tercul/internal/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CollectionRepository defines CRUD methods specific to Collection.
|
||||||
|
type CollectionRepository interface {
|
||||||
|
domain.BaseRepository[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)
|
||||||
|
}
|
||||||
16
internal/domain/comment/repo.go
Normal file
16
internal/domain/comment/repo.go
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
package comment
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"tercul/internal/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CommentRepository defines CRUD methods specific to Comment.
|
||||||
|
type CommentRepository interface {
|
||||||
|
domain.BaseRepository[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)
|
||||||
|
}
|
||||||
17
internal/domain/contribution/repo.go
Normal file
17
internal/domain/contribution/repo.go
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package contribution
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"tercul/internal/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ContributionRepository defines CRUD methods specific to Contribution.
|
||||||
|
type ContributionRepository interface {
|
||||||
|
domain.BaseRepository[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)
|
||||||
|
}
|
||||||
19
internal/domain/copyright/repo.go
Normal file
19
internal/domain/copyright/repo.go
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package copyright
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"tercul/internal/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CopyrightRepository defines CRUD methods specific to Copyright.
|
||||||
|
type CopyrightRepository interface {
|
||||||
|
domain.BaseRepository[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)
|
||||||
|
}
|
||||||
14
internal/domain/copyright_claim/repo.go
Normal file
14
internal/domain/copyright_claim/repo.go
Normal 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.BaseRepository[domain.CopyrightClaim]
|
||||||
|
|
||||||
|
ListByWorkID(ctx context.Context, workID uint) ([]domain.CopyrightClaim, error)
|
||||||
|
ListByUserID(ctx context.Context, userID uint) ([]domain.CopyrightClaim, error)
|
||||||
|
}
|
||||||
14
internal/domain/country/repo.go
Normal file
14
internal/domain/country/repo.go
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
package country
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"tercul/internal/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CountryRepository defines CRUD methods specific to Country.
|
||||||
|
type CountryRepository interface {
|
||||||
|
domain.BaseRepository[domain.Country]
|
||||||
|
|
||||||
|
GetByCode(ctx context.Context, code string) (*domain.Country, error)
|
||||||
|
ListByContinent(ctx context.Context, continent string) ([]domain.Country, error)
|
||||||
|
}
|
||||||
13
internal/domain/edge/repo.go
Normal file
13
internal/domain/edge/repo.go
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package edge
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"tercul/internal/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
// EdgeRepository defines CRUD methods specific to Edge.
|
||||||
|
type EdgeRepository interface {
|
||||||
|
domain.BaseRepository[domain.Edge]
|
||||||
|
|
||||||
|
ListBySource(ctx context.Context, sourceTable string, sourceID uint) ([]domain.Edge, error)
|
||||||
|
}
|
||||||
14
internal/domain/edition/repo.go
Normal file
14
internal/domain/edition/repo.go
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
package edition
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"tercul/internal/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
// EditionRepository defines CRUD methods specific to Edition.
|
||||||
|
type EditionRepository interface {
|
||||||
|
domain.BaseRepository[domain.Edition]
|
||||||
|
|
||||||
|
ListByBookID(ctx context.Context, bookID uint) ([]domain.Edition, error)
|
||||||
|
FindByISBN(ctx context.Context, isbn string) (*domain.Edition, error)
|
||||||
|
}
|
||||||
16
internal/domain/email_verification/repo.go
Normal file
16
internal/domain/email_verification/repo.go
Normal 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.BaseRepository[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)
|
||||||
|
}
|
||||||
16
internal/domain/like/repo.go
Normal file
16
internal/domain/like/repo.go
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
package like
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"tercul/internal/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
// LikeRepository defines CRUD methods specific to Like.
|
||||||
|
type LikeRepository interface {
|
||||||
|
domain.BaseRepository[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)
|
||||||
|
}
|
||||||
15
internal/domain/monetization/repo.go
Normal file
15
internal/domain/monetization/repo.go
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
package monetization
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"tercul/internal/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MonetizationRepository defines CRUD methods specific to Monetization.
|
||||||
|
type MonetizationRepository interface {
|
||||||
|
domain.BaseRepository[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)
|
||||||
|
}
|
||||||
16
internal/domain/password_reset/repo.go
Normal file
16
internal/domain/password_reset/repo.go
Normal 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.BaseRepository[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)
|
||||||
|
}
|
||||||
15
internal/domain/place/repo.go
Normal file
15
internal/domain/place/repo.go
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
package place
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"tercul/internal/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PlaceRepository defines CRUD methods specific to Place.
|
||||||
|
type PlaceRepository interface {
|
||||||
|
domain.BaseRepository[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, longitude float64, radiusKm float64) ([]domain.Place, error)
|
||||||
|
}
|
||||||
13
internal/domain/publisher/repo.go
Normal file
13
internal/domain/publisher/repo.go
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package publisher
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"tercul/internal/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PublisherRepository defines CRUD methods specific to Publisher.
|
||||||
|
type PublisherRepository interface {
|
||||||
|
domain.BaseRepository[domain.Publisher]
|
||||||
|
|
||||||
|
ListByCountryID(ctx context.Context, countryID uint) ([]domain.Publisher, error)
|
||||||
|
}
|
||||||
14
internal/domain/source/repo.go
Normal file
14
internal/domain/source/repo.go
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
package source
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"tercul/internal/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SourceRepository defines CRUD methods specific to Source.
|
||||||
|
type SourceRepository interface {
|
||||||
|
domain.BaseRepository[domain.Source]
|
||||||
|
|
||||||
|
ListByWorkID(ctx context.Context, workID uint) ([]domain.Source, error)
|
||||||
|
FindByURL(ctx context.Context, url string) (*domain.Source, error)
|
||||||
|
}
|
||||||
14
internal/domain/tag/repo.go
Normal file
14
internal/domain/tag/repo.go
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
package tag
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"tercul/internal/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TagRepository defines CRUD methods specific to Tag.
|
||||||
|
type TagRepository interface {
|
||||||
|
domain.BaseRepository[domain.Tag]
|
||||||
|
|
||||||
|
FindByName(ctx context.Context, name string) (*domain.Tag, error)
|
||||||
|
ListByWorkID(ctx context.Context, workID uint) ([]domain.Tag, error)
|
||||||
|
}
|
||||||
16
internal/domain/translation/repo.go
Normal file
16
internal/domain/translation/repo.go
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
package translation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"tercul/internal/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TranslationRepository defines CRUD methods specific to Translation.
|
||||||
|
type TranslationRepository interface {
|
||||||
|
domain.BaseRepository[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)
|
||||||
|
}
|
||||||
15
internal/domain/user/repo.go
Normal file
15
internal/domain/user/repo.go
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
package user
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"tercul/internal/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UserRepository defines CRUD methods specific to User.
|
||||||
|
type UserRepository interface {
|
||||||
|
domain.BaseRepository[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)
|
||||||
|
}
|
||||||
13
internal/domain/user_profile/repo.go
Normal file
13
internal/domain/user_profile/repo.go
Normal 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.BaseRepository[domain.UserProfile]
|
||||||
|
|
||||||
|
GetByUserID(ctx context.Context, userID uint) (*domain.UserProfile, error)
|
||||||
|
}
|
||||||
15
internal/domain/user_session/repo.go
Normal file
15
internal/domain/user_session/repo.go
Normal 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.BaseRepository[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)
|
||||||
|
}
|
||||||
18
internal/domain/work/repo.go
Normal file
18
internal/domain/work/repo.go
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package work
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"tercul/internal/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
// WorkRepository defines CRUD methods specific to Work.
|
||||||
|
type WorkRepository interface {
|
||||||
|
domain.BaseRepository[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, pageSize int) (*domain.PaginatedResult[domain.Work], error)
|
||||||
|
GetWithTranslations(ctx context.Context, id uint) (*domain.Work, error)
|
||||||
|
ListWithTranslations(ctx context.Context, page, pageSize int) (*domain.PaginatedResult[domain.Work], error)
|
||||||
|
}
|
||||||
@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import (
|
|||||||
|
|
||||||
func TestLinguaLanguageDetector_DetectLanguage(t *testing.T) {
|
func TestLinguaLanguageDetector_DetectLanguage(t *testing.T) {
|
||||||
d := NewLinguaLanguageDetector()
|
d := NewLinguaLanguageDetector()
|
||||||
code, ok := d.DetectLanguage("This is an English sentence.")
|
code, err := d.DetectLanguage("This is an English sentence.")
|
||||||
require.True(t, ok)
|
require.NoError(t, err)
|
||||||
require.NotEmpty(t, code)
|
require.NotEmpty(t, code)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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]
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
package enrich
|
package linguistics
|
||||||
|
|
||||||
import "testing"
|
import "testing"
|
||||||
|
|
||||||
|
|||||||
@ -4,22 +4,22 @@ 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
|
||||||
|
|
||||||
// For demonstration purposes, we'll use a simple heuristic based on common words
|
// For demonstration purposes, we'll use a simple heuristic based on common words
|
||||||
content := strings.ToLower(text.Body)
|
content := strings.ToLower(text)
|
||||||
|
|
||||||
// Check for English
|
// Check for English
|
||||||
englishWords := []string{"the", "and", "is", "in", "to", "of", "that", "for"}
|
englishWords := []string{"the", "and", "is", "in", "to", "of", "that", "for"}
|
||||||
@ -35,15 +35,15 @@ func (d *LanguageDetector) Detect(text Text) (string, float64, error) {
|
|||||||
|
|
||||||
// Determine the most likely language
|
// Determine the most likely language
|
||||||
if englishCount > spanishCount && englishCount > frenchCount {
|
if englishCount > spanishCount && englishCount > frenchCount {
|
||||||
return "en", 0.7, nil
|
return "en", nil
|
||||||
} else if spanishCount > englishCount && spanishCount > frenchCount {
|
} else if spanishCount > englishCount && spanishCount > frenchCount {
|
||||||
return "es", 0.7, nil
|
return "es", nil
|
||||||
} else if frenchCount > englishCount && frenchCount > spanishCount {
|
} else if frenchCount > englishCount && frenchCount > spanishCount {
|
||||||
return "fr", 0.7, nil
|
return "fr", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default to English if we can't determine the language
|
// Default to English if we can't determine the language
|
||||||
return "en", 0.5, nil
|
return "en", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// countWords counts the occurrences of words in a text
|
// countWords counts the occurrences of words in a text
|
||||||
|
|||||||
@ -4,21 +4,21 @@ import "testing"
|
|||||||
|
|
||||||
func TestLanguageDetector_Detect_EN(t *testing.T) {
|
func TestLanguageDetector_Detect_EN(t *testing.T) {
|
||||||
d := NewLanguageDetector()
|
d := NewLanguageDetector()
|
||||||
lang, conf, err := d.Detect(Text{Body: " the and is in to of that for the "})
|
lang, err := d.DetectLanguage(" the and is in to of that for the ")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Detect returned error: %v", err)
|
t.Fatalf("DetectLanguage returned error: %v", err)
|
||||||
}
|
}
|
||||||
if lang != "en" {
|
if lang != "en" {
|
||||||
t.Fatalf("expected language 'en', got %q", lang)
|
t.Fatalf("expected language 'en', got %q", lang)
|
||||||
}
|
}
|
||||||
if conf <= 0 {
|
|
||||||
t.Errorf("expected positive confidence, got %f", conf)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLanguageDetector_Detect_ES(t *testing.T) {
|
func TestLanguageDetector_Detect_ES(t *testing.T) {
|
||||||
d := NewLanguageDetector()
|
d := NewLanguageDetector()
|
||||||
lang, _, _ := d.Detect(Text{Body: " el la es en de que por para el "})
|
lang, err := d.DetectLanguage(" el la es en de que por para el ")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("DetectLanguage returned error: %v", err)
|
||||||
|
}
|
||||||
if lang != "es" {
|
if lang != "es" {
|
||||||
t.Fatalf("expected language 'es', got %q", lang)
|
t.Fatalf("expected language 'es', got %q", lang)
|
||||||
}
|
}
|
||||||
@ -26,7 +26,10 @@ func TestLanguageDetector_Detect_ES(t *testing.T) {
|
|||||||
|
|
||||||
func TestLanguageDetector_Detect_FR(t *testing.T) {
|
func TestLanguageDetector_Detect_FR(t *testing.T) {
|
||||||
d := NewLanguageDetector()
|
d := NewLanguageDetector()
|
||||||
lang, _, _ := d.Detect(Text{Body: " le la est en de que pour dans le "})
|
lang, err := d.DetectLanguage(" le la est en de que pour dans le ")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("DetectLanguage returned error: %v", err)
|
||||||
|
}
|
||||||
if lang != "fr" {
|
if lang != "fr" {
|
||||||
t.Fatalf("expected language 'fr', got %q", lang)
|
t.Fatalf("expected language 'fr', got %q", lang)
|
||||||
}
|
}
|
||||||
@ -35,14 +38,11 @@ func TestLanguageDetector_Detect_FR(t *testing.T) {
|
|||||||
func TestLanguageDetector_Detect_DefaultEnglish(t *testing.T) {
|
func TestLanguageDetector_Detect_DefaultEnglish(t *testing.T) {
|
||||||
d := NewLanguageDetector()
|
d := NewLanguageDetector()
|
||||||
// Balanced/unknown should default to English per implementation
|
// Balanced/unknown should default to English per implementation
|
||||||
lang, conf, err := d.Detect(Text{Body: " lorem ipsum dolor sit amet "})
|
lang, err := d.DetectLanguage(" lorem ipsum dolor sit amet ")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Detect returned error: %v", err)
|
t.Fatalf("DetectLanguage returned error: %v", err)
|
||||||
}
|
}
|
||||||
if lang != "en" {
|
if lang != "en" {
|
||||||
t.Fatalf("expected default language 'en', got %q", lang)
|
t.Fatalf("expected default language 'en', got %q", lang)
|
||||||
}
|
}
|
||||||
if conf != 0.5 {
|
|
||||||
t.Errorf("expected default confidence 0.5, got %f", conf)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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]
|
||||||
|
|||||||
@ -2,7 +2,7 @@ package linguistics
|
|||||||
|
|
||||||
// Registry holds all the text analysis services
|
// Registry holds all the text analysis services
|
||||||
type Registry struct {
|
type Registry struct {
|
||||||
Lang *LanguageDetector
|
Lang LanguageDetector
|
||||||
Tok *Tokenizer
|
Tok *Tokenizer
|
||||||
Pos *POSTagger
|
Pos *POSTagger
|
||||||
Lem *Lemmatizer
|
Lem *Lemmatizer
|
||||||
@ -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
|
||||||
|
|||||||
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -52,7 +52,7 @@ func (a *BasicTextAnalyzer) AnalyzeText(ctx context.Context, text string, langua
|
|||||||
|
|
||||||
// Auto-detect language if not provided and a detector exists
|
// Auto-detect language if not provided and a detector exists
|
||||||
if language == "" && a.langDetector != nil {
|
if language == "" && a.langDetector != nil {
|
||||||
if detected, ok := a.langDetector.DetectLanguage(text); ok {
|
if detected, err := a.langDetector.DetectLanguage(text); err == nil {
|
||||||
language = detected
|
language = detected
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -114,7 +114,7 @@ func (a *BasicTextAnalyzer) AnalyzeTextConcurrently(ctx context.Context, text st
|
|||||||
|
|
||||||
// Auto-detect language if not provided and a detector exists
|
// Auto-detect language if not provided and a detector exists
|
||||||
if language == "" && a.langDetector != nil {
|
if language == "" && a.langDetector != nil {
|
||||||
if detected, ok := a.langDetector.DetectLanguage(text); ok {
|
if detected, err := a.langDetector.DetectLanguage(text); err == nil {
|
||||||
language = detected
|
language = detected
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,8 +11,8 @@ import (
|
|||||||
|
|
||||||
// Mocks for provider interfaces
|
// Mocks for provider interfaces
|
||||||
|
|
||||||
type mockLangDetector struct{ lang string; ok bool }
|
type mockLangDetector struct{ lang string; err error }
|
||||||
func (m mockLangDetector) DetectLanguage(text string) (string, bool) { return m.lang, m.ok }
|
func (m mockLangDetector) DetectLanguage(text string) (string, error) { return m.lang, m.err }
|
||||||
|
|
||||||
type mockSentimentProvider struct{ score float64; err error }
|
type mockSentimentProvider struct{ score float64; err error }
|
||||||
func (m mockSentimentProvider) Score(text string, language string) (float64, error) { return m.score, m.err }
|
func (m mockSentimentProvider) Score(text string, language string) (float64, error) { return m.score, m.err }
|
||||||
@ -34,7 +34,7 @@ func TestAnalyzeText_Empty(t *testing.T) {
|
|||||||
func TestAnalyzeText_ProvidersAndLangDetection(t *testing.T) {
|
func TestAnalyzeText_ProvidersAndLangDetection(t *testing.T) {
|
||||||
// Arrange
|
// Arrange
|
||||||
a := NewBasicTextAnalyzer().
|
a := NewBasicTextAnalyzer().
|
||||||
WithLanguageDetector(mockLangDetector{lang: "en", ok: true}).
|
WithLanguageDetector(mockLangDetector{lang: "en", err: nil}).
|
||||||
WithSentimentProvider(mockSentimentProvider{score: 0.75}).
|
WithSentimentProvider(mockSentimentProvider{score: 0.75}).
|
||||||
WithKeywordProvider(mockKeywordProvider{kws: []Keyword{{Text: "golang", Relevance: 0.42}}})
|
WithKeywordProvider(mockKeywordProvider{kws: []Keyword{{Text: "golang", Relevance: 0.42}}})
|
||||||
|
|
||||||
@ -84,7 +84,7 @@ func TestAnalyzeTextConcurrently_AggregatesWithProviders(t *testing.T) {
|
|||||||
// Providers return consistent values regardless of input
|
// Providers return consistent values regardless of input
|
||||||
kw := []Keyword{{Text: "constant", Relevance: 0.3}}
|
kw := []Keyword{{Text: "constant", Relevance: 0.3}}
|
||||||
a := NewBasicTextAnalyzer().
|
a := NewBasicTextAnalyzer().
|
||||||
WithLanguageDetector(mockLangDetector{lang: "en", ok: true}).
|
WithLanguageDetector(mockLangDetector{lang: "en", err: nil}).
|
||||||
WithSentimentProvider(mockSentimentProvider{score: 0.5}).
|
WithSentimentProvider(mockSentimentProvider{score: 0.5}).
|
||||||
WithKeywordProvider(mockKeywordProvider{kws: kw})
|
WithKeywordProvider(mockKeywordProvider{kws: kw})
|
||||||
|
|
||||||
@ -119,7 +119,7 @@ func TestAnalyzeTextConcurrently_AggregatesWithProviders(t *testing.T) {
|
|||||||
|
|
||||||
func TestAnalyzeTextConcurrently_ContextCanceled(t *testing.T) {
|
func TestAnalyzeTextConcurrently_ContextCanceled(t *testing.T) {
|
||||||
a := NewBasicTextAnalyzer().
|
a := NewBasicTextAnalyzer().
|
||||||
WithLanguageDetector(mockLangDetector{lang: "en", ok: true}).
|
WithLanguageDetector(mockLangDetector{lang: "en", err: nil}).
|
||||||
WithSentimentProvider(mockSentimentProvider{score: 0.9}).
|
WithSentimentProvider(mockSentimentProvider{score: 0.9}).
|
||||||
WithKeywordProvider(mockKeywordProvider{kws: []Keyword{{Text: "x", Relevance: 0.1}}})
|
WithKeywordProvider(mockKeywordProvider{kws: []Keyword{{Text: "x", Relevance: 0.1}}})
|
||||||
|
|
||||||
|
|||||||
@ -16,7 +16,7 @@ var (
|
|||||||
"do": {}, "does": {}, "did": {}, "will": {}, "would": {}, "could": {},
|
"do": {}, "does": {}, "did": {}, "will": {}, "would": {}, "could": {},
|
||||||
"should": {}, "may": {}, "might": {}, "can": {}, "this": {}, "that": {},
|
"should": {}, "may": {}, "might": {}, "can": {}, "this": {}, "that": {},
|
||||||
"these": {}, "those": {}, "i": {}, "you": {}, "he": {}, "she": {},
|
"these": {}, "those": {}, "i": {}, "you": {}, "he": {}, "she": {},
|
||||||
"it": {}, "we": {}, "they": {}, "me": {}, "him": {}, "hers": {},
|
"it": {}, "we": {}, "they": {}, "me": {}, "him": {}, "hers": {}, "over": {},
|
||||||
"us": {}, "them": {}, "my": {}, "your": {}, "his": {}, "its": {},
|
"us": {}, "them": {}, "my": {}, "your": {}, "his": {}, "its": {},
|
||||||
"our": {}, "their": {},
|
"our": {}, "their": {},
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
|
|
||||||
}
|
|
||||||
@ -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,
|
||||||
|
|||||||
@ -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 ©rightRepository{
|
|
||||||
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(©rightable).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(©rights).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(©rightables).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
|
|
||||||
}
|
|
||||||
@ -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
|
|
||||||
}
|
|
||||||
@ -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
|
|
||||||
}
|
|
||||||
@ -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
|
|
||||||
}
|
|
||||||
@ -2,48 +2,52 @@ package testutil
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
"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"
|
auth_platform "tercul/internal/platform/auth"
|
||||||
|
"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
|
||||||
|
AuthCommands *auth.AuthCommands
|
||||||
|
AuthQueries *auth.AuthQueries
|
||||||
|
|
||||||
// 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,19 @@ 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)
|
||||||
|
jwtManager := auth_platform.NewJWTManager()
|
||||||
|
s.AuthCommands = auth.NewAuthCommands(s.UserRepo, jwtManager)
|
||||||
|
s.AuthQueries = auth.NewAuthQueries(s.UserRepo, jwtManager)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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,9 +209,9 @@ 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", TranslatableModel: domain.TranslatableModel{Language: "en"}},
|
||||||
{Name: "Test Author 2", Language: "fr"},
|
{Name: "Test Author 2", TranslatableModel: domain.TranslatableModel{Language: "fr"}},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, author := range s.TestAuthors {
|
for _, author := range s.TestAuthors {
|
||||||
@ -214,10 +221,10 @@ 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", TranslatableModel: domain.TranslatableModel{Language: "en"}},
|
||||||
{Title: "Test Work 2", Language: "en"},
|
{Title: "Test Work 2", TranslatableModel: domain.TranslatableModel{Language: "en"}},
|
||||||
{Title: "Test Work 3", Language: "fr"},
|
{Title: "Test Work 3", TranslatableModel: domain.TranslatableModel{Language: "fr"}},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, work := range s.TestWorks {
|
for _, work := range s.TestWorks {
|
||||||
@ -227,7 +234,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 +299,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,
|
||||||
|
TranslatableModel: domain.TranslatableModel{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,
|
||||||
|
|||||||
@ -2,9 +2,8 @@ package testutil
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"tercul/internal/repositories"
|
"tercul/internal/domain"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -40,17 +39,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")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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, 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 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 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,
|
||||||
|
|||||||
@ -3,52 +3,48 @@ 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 == "" {
|
|
||||||
work.Language = "en" // default for tests, can be set by caller
|
|
||||||
}
|
|
||||||
m.Works = append(m.Works, work)
|
m.Works = append(m.Works, 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, 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 ErrEntityNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *UnifiedMockWorkRepository) Delete(ctx context.Context, id uint) error {
|
func (m *UnifiedMockWorkRepository) Delete(ctx context.Context, id uint) error {
|
||||||
@ -58,11 +54,11 @@ func (m *UnifiedMockWorkRepository) Delete(ctx context.Context, id uint) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return repositories.ErrEntityNotFound
|
return 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 +68,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 +90,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, 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 +114,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 +130,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 +138,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 +156,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 +166,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 +177,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 +195,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 +205,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, 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 +225,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
|
||||||
|
|||||||
@ -1,9 +1,11 @@
|
|||||||
package testutil
|
package testutil
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"tercul/graph"
|
"context"
|
||||||
"tercul/internal/models"
|
graph "tercul/internal/adapters/graphql"
|
||||||
"tercul/services"
|
"tercul/internal/app"
|
||||||
|
"tercul/internal/app/work"
|
||||||
|
"tercul/internal/domain"
|
||||||
|
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
)
|
)
|
||||||
@ -11,14 +13,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
|
||||||
@ -29,18 +43,30 @@ 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 {
|
||||||
return &graph.Resolver{
|
return &graph.Resolver{
|
||||||
WorkRepo: s.WorkRepo,
|
App: &app.Application{
|
||||||
WorkService: s.WorkService,
|
WorkCommands: s.WorkCommands,
|
||||||
// Other fields will be nil, but that's okay for basic tests
|
WorkQueries: s.WorkQueries,
|
||||||
|
Localization: &MockLocalization{},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type MockLocalization struct{}
|
||||||
|
|
||||||
|
func (m *MockLocalization) GetWorkContent(ctx context.Context, workID uint, preferredLanguage string) (string, error) {
|
||||||
|
return "Test content for work", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockLocalization) GetAuthorBiography(ctx context.Context, authorID uint, preferredLanguage string) (string, error) {
|
||||||
|
return "Test biography", nil
|
||||||
|
}
|
||||||
|
|
||||||
// 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,
|
||||||
|
TranslatableModel: domain.TranslatableModel{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)
|
||||||
|
|||||||
@ -2,6 +2,7 @@ package testutil
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
@ -15,6 +16,8 @@ import (
|
|||||||
"tercul/internal/platform/config"
|
"tercul/internal/platform/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var ErrEntityNotFound = errors.New("entity not found")
|
||||||
|
|
||||||
// TestDB holds the test database connection
|
// TestDB holds the test database connection
|
||||||
var TestDB *gorm.DB
|
var TestDB *gorm.DB
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user