mirror of
https://github.com/SamyRai/tercul-backend.git
synced 2025-12-27 05:11:34 +00:00
Merge pull request #17 from SamyRai/feature/finish-top-prio-tasks
feat: Implement critical features and fix build
This commit is contained in:
commit
ca4ce84344
124
cmd/api/main.go
124
cmd/api/main.go
@ -3,11 +3,10 @@ package main
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"path/filepath"
|
|
||||||
"runtime"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
"tercul/internal/app"
|
"tercul/internal/app"
|
||||||
"tercul/internal/app/analytics"
|
"tercul/internal/app/analytics"
|
||||||
@ -18,7 +17,7 @@ import (
|
|||||||
"tercul/internal/platform/auth"
|
"tercul/internal/platform/auth"
|
||||||
"tercul/internal/platform/config"
|
"tercul/internal/platform/config"
|
||||||
"tercul/internal/platform/db"
|
"tercul/internal/platform/db"
|
||||||
"tercul/internal/platform/log"
|
app_log "tercul/internal/platform/log"
|
||||||
"tercul/internal/platform/search"
|
"tercul/internal/platform/search"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -30,7 +29,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// runMigrations applies database migrations using goose.
|
// runMigrations applies database migrations using goose.
|
||||||
func runMigrations(gormDB *gorm.DB) error {
|
func runMigrations(gormDB *gorm.DB, migrationPath string) error {
|
||||||
sqlDB, err := gormDB.DB()
|
sqlDB, err := gormDB.DB()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -40,35 +39,34 @@ func runMigrations(gormDB *gorm.DB) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is brittle. A better approach might be to use an env var or config.
|
app_log.Info(fmt.Sprintf("Applying database migrations from %s", migrationPath))
|
||||||
_, b, _, _ := runtime.Caller(0)
|
if err := goose.Up(sqlDB, migrationPath); err != nil {
|
||||||
migrationsDir := filepath.Join(filepath.Dir(b), "../../internal/data/migrations")
|
|
||||||
|
|
||||||
log.Info(fmt.Sprintf("Applying database migrations from %s", migrationsDir))
|
|
||||||
if err := goose.Up(sqlDB, migrationsDir); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
log.Info("Database migrations applied successfully")
|
app_log.Info("Database migrations applied successfully")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// main is the entry point for the Tercul application.
|
// main is the entry point for the Tercul application.
|
||||||
func main() {
|
func main() {
|
||||||
// Load configuration from environment variables
|
// Load configuration from environment variables
|
||||||
config.LoadConfig()
|
cfg, err := config.LoadConfig()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("cannot load config: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize logger
|
// Initialize logger
|
||||||
log.Init("tercul-api", config.Cfg.Environment)
|
app_log.Init("tercul-api", cfg.Environment)
|
||||||
obsLogger := observability.NewLogger("tercul-api", config.Cfg.Environment)
|
obsLogger := observability.NewLogger("tercul-api", cfg.Environment)
|
||||||
|
|
||||||
// Initialize OpenTelemetry Tracer Provider
|
// Initialize OpenTelemetry Tracer Provider
|
||||||
tp, err := observability.TracerProvider("tercul-api", config.Cfg.Environment)
|
tp, err := observability.TracerProvider("tercul-api", cfg.Environment)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err, "Failed to initialize OpenTelemetry tracer")
|
app_log.Fatal(err, "Failed to initialize OpenTelemetry tracer")
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := tp.Shutdown(context.Background()); err != nil {
|
if err := tp.Shutdown(context.Background()); err != nil {
|
||||||
log.Error(err, "Error shutting down tracer provider")
|
app_log.Error(err, "Error shutting down tracer provider")
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@ -76,71 +74,72 @@ func main() {
|
|||||||
reg := prometheus.NewRegistry()
|
reg := prometheus.NewRegistry()
|
||||||
metrics := observability.NewMetrics(reg) // Metrics are registered automatically
|
metrics := observability.NewMetrics(reg) // Metrics are registered automatically
|
||||||
|
|
||||||
log.Info(fmt.Sprintf("Starting Tercul application in %s environment, version 1.0.0", config.Cfg.Environment))
|
app_log.Info(fmt.Sprintf("Starting Tercul application in %s environment, version 1.0.0", cfg.Environment))
|
||||||
|
|
||||||
// Initialize database connection
|
// Initialize database connection
|
||||||
database, err := db.InitDB(metrics)
|
database, err := db.InitDB(cfg, metrics)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err, "Failed to initialize database")
|
app_log.Fatal(err, "Failed to initialize database")
|
||||||
}
|
}
|
||||||
defer db.Close()
|
defer db.Close(database)
|
||||||
|
|
||||||
if err := runMigrations(database); err != nil {
|
if err := runMigrations(database, cfg.MigrationPath); err != nil {
|
||||||
log.Fatal(err, "Failed to apply database migrations")
|
app_log.Fatal(err, "Failed to apply database migrations")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize Weaviate client
|
// Initialize Weaviate client
|
||||||
weaviateCfg := weaviate.Config{
|
weaviateCfg := weaviate.Config{
|
||||||
Host: config.Cfg.WeaviateHost,
|
Host: cfg.WeaviateHost,
|
||||||
Scheme: config.Cfg.WeaviateScheme,
|
Scheme: cfg.WeaviateScheme,
|
||||||
}
|
}
|
||||||
weaviateClient, err := weaviate.NewClient(weaviateCfg)
|
weaviateClient, err := weaviate.NewClient(weaviateCfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err, "Failed to create weaviate client")
|
app_log.Fatal(err, "Failed to create weaviate client")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create search client
|
// Create search client
|
||||||
searchClient := search.NewWeaviateWrapper(weaviateClient)
|
searchClient := search.NewWeaviateWrapper(weaviateClient)
|
||||||
|
|
||||||
// Create repositories
|
// Create repositories
|
||||||
repos := dbsql.NewRepositories(database)
|
repos := dbsql.NewRepositories(database, cfg)
|
||||||
|
|
||||||
// Create linguistics dependencies
|
// Create linguistics dependencies
|
||||||
analysisRepo := linguistics.NewGORMAnalysisRepository(database)
|
analysisRepo := linguistics.NewGORMAnalysisRepository(database)
|
||||||
sentimentProvider, err := linguistics.NewGoVADERSentimentProvider()
|
sentimentProvider, err := linguistics.NewGoVADERSentimentProvider()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err, "Failed to create sentiment provider")
|
app_log.Fatal(err, "Failed to create sentiment provider")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create platform components
|
// Create platform components
|
||||||
jwtManager := auth.NewJWTManager()
|
jwtManager := auth.NewJWTManager(cfg)
|
||||||
|
|
||||||
// Create application services
|
// Create application services
|
||||||
analyticsService := analytics.NewService(repos.Analytics, analysisRepo, repos.Translation, repos.Work, sentimentProvider)
|
analyticsService := analytics.NewService(repos.Analytics, analysisRepo, repos.Translation, repos.Work, sentimentProvider)
|
||||||
|
|
||||||
// Create application dependencies
|
// Create application dependencies
|
||||||
deps := app.Dependencies{
|
deps := app.Dependencies{
|
||||||
WorkRepo: repos.Work,
|
WorkRepo: repos.Work,
|
||||||
UserRepo: repos.User,
|
UserRepo: repos.User,
|
||||||
AuthorRepo: repos.Author,
|
AuthorRepo: repos.Author,
|
||||||
TranslationRepo: repos.Translation,
|
TranslationRepo: repos.Translation,
|
||||||
CommentRepo: repos.Comment,
|
CommentRepo: repos.Comment,
|
||||||
LikeRepo: repos.Like,
|
LikeRepo: repos.Like,
|
||||||
BookmarkRepo: repos.Bookmark,
|
BookmarkRepo: repos.Bookmark,
|
||||||
CollectionRepo: repos.Collection,
|
CollectionRepo: repos.Collection,
|
||||||
TagRepo: repos.Tag,
|
TagRepo: repos.Tag,
|
||||||
CategoryRepo: repos.Category,
|
CategoryRepo: repos.Category,
|
||||||
BookRepo: repos.Book,
|
BookRepo: repos.Book,
|
||||||
PublisherRepo: repos.Publisher,
|
PublisherRepo: repos.Publisher,
|
||||||
SourceRepo: repos.Source,
|
SourceRepo: repos.Source,
|
||||||
CopyrightRepo: repos.Copyright,
|
CopyrightRepo: repos.Copyright,
|
||||||
MonetizationRepo: repos.Monetization,
|
MonetizationRepo: repos.Monetization,
|
||||||
AnalyticsRepo: repos.Analytics,
|
ContributionRepo: repos.Contribution,
|
||||||
AuthRepo: repos.Auth,
|
AnalyticsRepo: repos.Analytics,
|
||||||
LocalizationRepo: repos.Localization,
|
AuthRepo: repos.Auth,
|
||||||
SearchClient: searchClient,
|
LocalizationRepo: repos.Localization,
|
||||||
AnalyticsService: analyticsService,
|
SearchClient: searchClient,
|
||||||
JWTManager: jwtManager,
|
AnalyticsService: analyticsService,
|
||||||
|
JWTManager: jwtManager,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create application
|
// Create application
|
||||||
@ -151,28 +150,27 @@ func main() {
|
|||||||
App: application,
|
App: application,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create handlers
|
// Create the main API handler with all middleware.
|
||||||
|
// NewServerWithAuth now returns the handler chain directly.
|
||||||
apiHandler := NewServerWithAuth(resolver, jwtManager, metrics, obsLogger)
|
apiHandler := NewServerWithAuth(resolver, jwtManager, metrics, obsLogger)
|
||||||
playgroundHandler := playground.Handler("GraphQL Playground", "/query")
|
|
||||||
metricsHandler := observability.PrometheusHandler(reg)
|
|
||||||
|
|
||||||
// Consolidate handlers into a single mux
|
// Create the main ServeMux and register all handlers.
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
mux.Handle("/query", apiHandler)
|
mux.Handle("/query", apiHandler)
|
||||||
mux.Handle("/playground", playgroundHandler)
|
mux.Handle("/playground", playground.Handler("GraphQL Playground", "/query"))
|
||||||
mux.Handle("/metrics", metricsHandler)
|
mux.Handle("/metrics", observability.PrometheusHandler(reg))
|
||||||
|
|
||||||
// Create a single HTTP server
|
// Create a single HTTP server with the main mux.
|
||||||
mainServer := &http.Server{
|
mainServer := &http.Server{
|
||||||
Addr: config.Cfg.ServerPort,
|
Addr: cfg.ServerPort,
|
||||||
Handler: mux,
|
Handler: mux,
|
||||||
}
|
}
|
||||||
log.Info(fmt.Sprintf("API server listening on port %s", config.Cfg.ServerPort))
|
app_log.Info(fmt.Sprintf("API server listening on port %s", cfg.ServerPort))
|
||||||
|
|
||||||
// Start the main server in a goroutine
|
// Start the main server in a goroutine
|
||||||
go func() {
|
go func() {
|
||||||
if err := mainServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
if err := mainServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||||
log.Fatal(err, "Failed to start server")
|
app_log.Fatal(err, "Failed to start server")
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@ -180,15 +178,15 @@ func main() {
|
|||||||
quit := make(chan os.Signal, 1)
|
quit := make(chan os.Signal, 1)
|
||||||
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
|
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
|
||||||
<-quit
|
<-quit
|
||||||
log.Info("Shutting down server...")
|
app_log.Info("Shutting down server...")
|
||||||
|
|
||||||
// Graceful shutdown
|
// Graceful shutdown
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
if err := mainServer.Shutdown(ctx); err != nil {
|
if err := mainServer.Shutdown(ctx); err != nil {
|
||||||
log.Error(err, "Server forced to shutdown")
|
app_log.Error(err, "Server forced to shutdown")
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Info("Server shut down successfully")
|
app_log.Info("Server shut down successfully")
|
||||||
}
|
}
|
||||||
@ -9,19 +9,6 @@ import (
|
|||||||
"github.com/99designs/gqlgen/graphql/handler"
|
"github.com/99designs/gqlgen/graphql/handler"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewServer creates a new GraphQL server with the given resolver
|
|
||||||
func NewServer(resolver *graphql.Resolver) http.Handler {
|
|
||||||
c := graphql.Config{Resolvers: resolver}
|
|
||||||
c.Directives.Binding = graphql.Binding
|
|
||||||
srv := handler.NewDefaultServer(graphql.NewExecutableSchema(c))
|
|
||||||
|
|
||||||
// Create a mux to handle GraphQL endpoint only (no playground here; served separately in production)
|
|
||||||
mux := http.NewServeMux()
|
|
||||||
mux.Handle("/query", srv)
|
|
||||||
|
|
||||||
return mux
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewServerWithAuth creates a new GraphQL server with authentication and observability middleware
|
// NewServerWithAuth creates a new GraphQL server with authentication and observability middleware
|
||||||
func NewServerWithAuth(resolver *graphql.Resolver, jwtManager *auth.JWTManager, metrics *observability.Metrics, logger *observability.Logger) http.Handler {
|
func NewServerWithAuth(resolver *graphql.Resolver, jwtManager *auth.JWTManager, metrics *observability.Metrics, logger *observability.Logger) http.Handler {
|
||||||
c := graphql.Config{Resolvers: resolver}
|
c := graphql.Config{Resolvers: resolver}
|
||||||
@ -42,9 +29,6 @@ func NewServerWithAuth(resolver *graphql.Resolver, jwtManager *auth.JWTManager,
|
|||||||
chain = observability.TracingMiddleware(chain)
|
chain = observability.TracingMiddleware(chain)
|
||||||
chain = observability.RequestIDMiddleware(chain)
|
chain = observability.RequestIDMiddleware(chain)
|
||||||
|
|
||||||
// Create a mux to handle GraphQL endpoint
|
// Return the handler chain directly. The caller is responsible for routing.
|
||||||
mux := http.NewServeMux()
|
return chain
|
||||||
mux.Handle("/query", chain)
|
|
||||||
|
|
||||||
return mux
|
|
||||||
}
|
}
|
||||||
@ -31,15 +31,18 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 2. Initialize dependencies
|
// 2. Initialize dependencies
|
||||||
config.LoadConfig()
|
cfg, err := config.LoadConfig()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err, "Failed to load config")
|
||||||
|
}
|
||||||
log.Init("enrich-tool", "development")
|
log.Init("enrich-tool", "development")
|
||||||
database, err := db.InitDB(nil) // No metrics needed for this tool
|
database, err := db.InitDB(cfg, nil) // No metrics needed for this tool
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err, "Failed to initialize database")
|
log.Fatal(err, "Failed to initialize database")
|
||||||
}
|
}
|
||||||
defer db.Close()
|
defer db.Close(database)
|
||||||
|
|
||||||
repos := sql.NewRepositories(database)
|
repos := sql.NewRepositories(database, cfg)
|
||||||
enrichmentSvc := enrichment.NewService()
|
enrichmentSvc := enrichment.NewService()
|
||||||
|
|
||||||
// 3. Fetch, enrich, and save the entity
|
// 3. Fetch, enrich, and save the entity
|
||||||
|
|||||||
@ -20,6 +20,7 @@ import (
|
|||||||
"tercul/internal/domain/work"
|
"tercul/internal/domain/work"
|
||||||
"tercul/internal/observability"
|
"tercul/internal/observability"
|
||||||
platform_auth "tercul/internal/platform/auth"
|
platform_auth "tercul/internal/platform/auth"
|
||||||
|
platform_config "tercul/internal/platform/config"
|
||||||
"tercul/internal/testutil"
|
"tercul/internal/testutil"
|
||||||
|
|
||||||
"github.com/99designs/gqlgen/graphql/handler"
|
"github.com/99designs/gqlgen/graphql/handler"
|
||||||
@ -58,7 +59,9 @@ func (s *GraphQLIntegrationSuite) CreateAuthenticatedUser(username, email string
|
|||||||
user.Role = role
|
user.Role = role
|
||||||
|
|
||||||
// Re-generate token with the new role
|
// Re-generate token with the new role
|
||||||
jwtManager := platform_auth.NewJWTManager()
|
cfg, err := platform_config.LoadConfig()
|
||||||
|
s.Require().NoError(err)
|
||||||
|
jwtManager := platform_auth.NewJWTManager(cfg)
|
||||||
newToken, err := jwtManager.GenerateToken(user)
|
newToken, err := jwtManager.GenerateToken(user)
|
||||||
s.Require().NoError(err)
|
s.Require().NoError(err)
|
||||||
token = newToken
|
token = newToken
|
||||||
@ -81,7 +84,9 @@ func (s *GraphQLIntegrationSuite) SetupSuite() {
|
|||||||
srv.SetErrorPresenter(graph.NewErrorPresenter())
|
srv.SetErrorPresenter(graph.NewErrorPresenter())
|
||||||
|
|
||||||
// Create JWT manager and middleware
|
// Create JWT manager and middleware
|
||||||
jwtManager := platform_auth.NewJWTManager()
|
cfg, err := platform_config.LoadConfig()
|
||||||
|
s.Require().NoError(err)
|
||||||
|
jwtManager := platform_auth.NewJWTManager(cfg)
|
||||||
reg := prometheus.NewRegistry()
|
reg := prometheus.NewRegistry()
|
||||||
metrics := observability.NewMetrics(reg)
|
metrics := observability.NewMetrics(reg)
|
||||||
|
|
||||||
|
|||||||
@ -15,6 +15,7 @@ import (
|
|||||||
"tercul/internal/app/bookmark"
|
"tercul/internal/app/bookmark"
|
||||||
"tercul/internal/app/collection"
|
"tercul/internal/app/collection"
|
||||||
"tercul/internal/app/comment"
|
"tercul/internal/app/comment"
|
||||||
|
"tercul/internal/app/contribution"
|
||||||
"tercul/internal/app/like"
|
"tercul/internal/app/like"
|
||||||
"tercul/internal/app/translation"
|
"tercul/internal/app/translation"
|
||||||
"tercul/internal/app/user"
|
"tercul/internal/app/user"
|
||||||
@ -502,7 +503,17 @@ func (r *mutationResolver) UpdateUser(ctx context.Context, id string, input mode
|
|||||||
|
|
||||||
// DeleteUser is the resolver for the deleteUser field.
|
// DeleteUser is the resolver for the deleteUser field.
|
||||||
func (r *mutationResolver) DeleteUser(ctx context.Context, id string) (bool, error) {
|
func (r *mutationResolver) DeleteUser(ctx context.Context, id string) (bool, error) {
|
||||||
panic(fmt.Errorf("not implemented: DeleteUser - deleteUser"))
|
userID, err := strconv.ParseUint(id, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("%w: invalid user ID", domain.ErrValidation)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = r.App.User.Commands.DeleteUser(ctx, uint(userID))
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateCollection is the resolver for the createCollection field.
|
// CreateCollection is the resolver for the createCollection field.
|
||||||
@ -1020,7 +1031,56 @@ func (r *mutationResolver) DeleteBookmark(ctx context.Context, id string) (bool,
|
|||||||
|
|
||||||
// CreateContribution is the resolver for the createContribution field.
|
// CreateContribution is the resolver for the createContribution field.
|
||||||
func (r *mutationResolver) CreateContribution(ctx context.Context, input model.ContributionInput) (*model.Contribution, error) {
|
func (r *mutationResolver) CreateContribution(ctx context.Context, input model.ContributionInput) (*model.Contribution, error) {
|
||||||
panic(fmt.Errorf("not implemented: CreateContribution - createContribution"))
|
// Get user ID from context
|
||||||
|
userID, ok := platform_auth.GetUserIDFromContext(ctx)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("unauthorized")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert GraphQL input to service input
|
||||||
|
createInput := contribution.CreateContributionInput{
|
||||||
|
Name: input.Name,
|
||||||
|
}
|
||||||
|
|
||||||
|
if input.WorkID != nil {
|
||||||
|
workID, err := strconv.ParseUint(*input.WorkID, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid work ID: %v", err)
|
||||||
|
}
|
||||||
|
wID := uint(workID)
|
||||||
|
createInput.WorkID = &wID
|
||||||
|
}
|
||||||
|
|
||||||
|
if input.TranslationID != nil {
|
||||||
|
translationID, err := strconv.ParseUint(*input.TranslationID, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid translation ID: %v", err)
|
||||||
|
}
|
||||||
|
tID := uint(translationID)
|
||||||
|
createInput.TranslationID = &tID
|
||||||
|
}
|
||||||
|
|
||||||
|
if input.Status != nil {
|
||||||
|
createInput.Status = input.Status.String()
|
||||||
|
} else {
|
||||||
|
createInput.Status = "DRAFT" // Default status
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call contribution service
|
||||||
|
createdContribution, err := r.App.Contribution.Commands.CreateContribution(ctx, createInput)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert to GraphQL model
|
||||||
|
return &model.Contribution{
|
||||||
|
ID: fmt.Sprintf("%d", createdContribution.ID),
|
||||||
|
Name: createdContribution.Name,
|
||||||
|
Status: model.ContributionStatus(createdContribution.Status),
|
||||||
|
User: &model.User{
|
||||||
|
ID: fmt.Sprintf("%d", userID),
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateContribution is the resolver for the updateContribution field.
|
// UpdateContribution is the resolver for the updateContribution field.
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import (
|
|||||||
"tercul/internal/domain"
|
"tercul/internal/domain"
|
||||||
"tercul/internal/domain/work"
|
"tercul/internal/domain/work"
|
||||||
"tercul/internal/jobs/linguistics"
|
"tercul/internal/jobs/linguistics"
|
||||||
|
"tercul/internal/platform/config"
|
||||||
"tercul/internal/testutil"
|
"tercul/internal/testutil"
|
||||||
|
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
@ -21,10 +22,12 @@ type AnalyticsServiceTestSuite struct {
|
|||||||
|
|
||||||
func (s *AnalyticsServiceTestSuite) SetupSuite() {
|
func (s *AnalyticsServiceTestSuite) SetupSuite() {
|
||||||
s.IntegrationTestSuite.SetupSuite(testutil.DefaultTestConfig())
|
s.IntegrationTestSuite.SetupSuite(testutil.DefaultTestConfig())
|
||||||
analyticsRepo := sql.NewAnalyticsRepository(s.DB)
|
cfg, err := config.LoadConfig()
|
||||||
|
s.Require().NoError(err)
|
||||||
|
analyticsRepo := sql.NewAnalyticsRepository(s.DB, cfg)
|
||||||
analysisRepo := linguistics.NewGORMAnalysisRepository(s.DB)
|
analysisRepo := linguistics.NewGORMAnalysisRepository(s.DB)
|
||||||
translationRepo := sql.NewTranslationRepository(s.DB)
|
translationRepo := sql.NewTranslationRepository(s.DB, cfg)
|
||||||
workRepo := sql.NewWorkRepository(s.DB)
|
workRepo := sql.NewWorkRepository(s.DB, cfg)
|
||||||
sentimentProvider, _ := linguistics.NewGoVADERSentimentProvider()
|
sentimentProvider, _ := linguistics.NewGoVADERSentimentProvider()
|
||||||
s.service = analytics.NewService(analyticsRepo, analysisRepo, translationRepo, workRepo, sentimentProvider)
|
s.service = analytics.NewService(analyticsRepo, analysisRepo, translationRepo, workRepo, sentimentProvider)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,18 +2,20 @@ package app
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"tercul/internal/app/analytics"
|
"tercul/internal/app/analytics"
|
||||||
|
"tercul/internal/app/auth"
|
||||||
"tercul/internal/app/author"
|
"tercul/internal/app/author"
|
||||||
|
"tercul/internal/app/authz"
|
||||||
"tercul/internal/app/book"
|
"tercul/internal/app/book"
|
||||||
"tercul/internal/app/bookmark"
|
"tercul/internal/app/bookmark"
|
||||||
"tercul/internal/app/category"
|
"tercul/internal/app/category"
|
||||||
"tercul/internal/app/collection"
|
"tercul/internal/app/collection"
|
||||||
"tercul/internal/app/comment"
|
"tercul/internal/app/comment"
|
||||||
|
"tercul/internal/app/contribution"
|
||||||
"tercul/internal/app/like"
|
"tercul/internal/app/like"
|
||||||
"tercul/internal/app/localization"
|
"tercul/internal/app/localization"
|
||||||
"tercul/internal/app/tag"
|
"tercul/internal/app/tag"
|
||||||
"tercul/internal/app/translation"
|
"tercul/internal/app/translation"
|
||||||
"tercul/internal/app/user"
|
"tercul/internal/app/user"
|
||||||
"tercul/internal/app/auth"
|
|
||||||
"tercul/internal/app/work"
|
"tercul/internal/app/work"
|
||||||
"tercul/internal/domain"
|
"tercul/internal/domain"
|
||||||
auth_domain "tercul/internal/domain/auth"
|
auth_domain "tercul/internal/domain/auth"
|
||||||
@ -23,31 +25,30 @@ import (
|
|||||||
platform_auth "tercul/internal/platform/auth"
|
platform_auth "tercul/internal/platform/auth"
|
||||||
)
|
)
|
||||||
|
|
||||||
import "tercul/internal/app/authz"
|
|
||||||
|
|
||||||
// Dependencies holds all external dependencies for the application.
|
// Dependencies holds all external dependencies for the application.
|
||||||
type Dependencies struct {
|
type Dependencies struct {
|
||||||
WorkRepo work_domain.WorkRepository
|
WorkRepo work_domain.WorkRepository
|
||||||
UserRepo domain.UserRepository
|
UserRepo domain.UserRepository
|
||||||
AuthorRepo domain.AuthorRepository
|
AuthorRepo domain.AuthorRepository
|
||||||
TranslationRepo domain.TranslationRepository
|
TranslationRepo domain.TranslationRepository
|
||||||
CommentRepo domain.CommentRepository
|
CommentRepo domain.CommentRepository
|
||||||
LikeRepo domain.LikeRepository
|
LikeRepo domain.LikeRepository
|
||||||
BookmarkRepo domain.BookmarkRepository
|
BookmarkRepo domain.BookmarkRepository
|
||||||
CollectionRepo domain.CollectionRepository
|
CollectionRepo domain.CollectionRepository
|
||||||
TagRepo domain.TagRepository
|
TagRepo domain.TagRepository
|
||||||
CategoryRepo domain.CategoryRepository
|
CategoryRepo domain.CategoryRepository
|
||||||
BookRepo domain.BookRepository
|
BookRepo domain.BookRepository
|
||||||
PublisherRepo domain.PublisherRepository
|
PublisherRepo domain.PublisherRepository
|
||||||
SourceRepo domain.SourceRepository
|
SourceRepo domain.SourceRepository
|
||||||
CopyrightRepo domain.CopyrightRepository
|
CopyrightRepo domain.CopyrightRepository
|
||||||
MonetizationRepo domain.MonetizationRepository
|
MonetizationRepo domain.MonetizationRepository
|
||||||
AnalyticsRepo analytics.Repository
|
ContributionRepo domain.ContributionRepository
|
||||||
AuthRepo auth_domain.AuthRepository
|
AnalyticsRepo analytics.Repository
|
||||||
LocalizationRepo localization_domain.LocalizationRepository
|
AuthRepo auth_domain.AuthRepository
|
||||||
SearchClient search.SearchClient
|
LocalizationRepo localization_domain.LocalizationRepository
|
||||||
AnalyticsService analytics.Service
|
SearchClient search.SearchClient
|
||||||
JWTManager platform_auth.JWTManagement
|
AnalyticsService analytics.Service
|
||||||
|
JWTManager platform_auth.JWTManagement
|
||||||
}
|
}
|
||||||
|
|
||||||
// Application is a container for all the application-layer services.
|
// Application is a container for all the application-layer services.
|
||||||
@ -58,6 +59,7 @@ type Application struct {
|
|||||||
Category *category.Service
|
Category *category.Service
|
||||||
Collection *collection.Service
|
Collection *collection.Service
|
||||||
Comment *comment.Service
|
Comment *comment.Service
|
||||||
|
Contribution *contribution.Service
|
||||||
Like *like.Service
|
Like *like.Service
|
||||||
Tag *tag.Service
|
Tag *tag.Service
|
||||||
Translation *translation.Service
|
Translation *translation.Service
|
||||||
@ -77,6 +79,8 @@ func NewApplication(deps Dependencies) *Application {
|
|||||||
categoryService := category.NewService(deps.CategoryRepo)
|
categoryService := category.NewService(deps.CategoryRepo)
|
||||||
collectionService := collection.NewService(deps.CollectionRepo)
|
collectionService := collection.NewService(deps.CollectionRepo)
|
||||||
commentService := comment.NewService(deps.CommentRepo, authzService, deps.AnalyticsService)
|
commentService := comment.NewService(deps.CommentRepo, authzService, deps.AnalyticsService)
|
||||||
|
contributionCommands := contribution.NewCommands(deps.ContributionRepo, authzService)
|
||||||
|
contributionService := contribution.NewService(contributionCommands)
|
||||||
likeService := like.NewService(deps.LikeRepo, deps.AnalyticsService)
|
likeService := like.NewService(deps.LikeRepo, deps.AnalyticsService)
|
||||||
tagService := tag.NewService(deps.TagRepo)
|
tagService := tag.NewService(deps.TagRepo)
|
||||||
translationService := translation.NewService(deps.TranslationRepo, authzService)
|
translationService := translation.NewService(deps.TranslationRepo, authzService)
|
||||||
@ -92,6 +96,7 @@ func NewApplication(deps Dependencies) *Application {
|
|||||||
Category: categoryService,
|
Category: categoryService,
|
||||||
Collection: collectionService,
|
Collection: collectionService,
|
||||||
Comment: commentService,
|
Comment: commentService,
|
||||||
|
Contribution: contributionService,
|
||||||
Like: likeService,
|
Like: likeService,
|
||||||
Tag: tagService,
|
Tag: tagService,
|
||||||
Translation: translationService,
|
Translation: translationService,
|
||||||
|
|||||||
55
internal/app/contribution/commands.go
Normal file
55
internal/app/contribution/commands.go
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
package contribution
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"tercul/internal/app/authz"
|
||||||
|
"tercul/internal/domain"
|
||||||
|
platform_auth "tercul/internal/platform/auth"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Commands contains the command handlers for the contribution aggregate.
|
||||||
|
type Commands struct {
|
||||||
|
repo domain.ContributionRepository
|
||||||
|
authzSvc *authz.Service
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCommands creates a new Commands handler.
|
||||||
|
func NewCommands(repo domain.ContributionRepository, authzSvc *authz.Service) *Commands {
|
||||||
|
return &Commands{
|
||||||
|
repo: repo,
|
||||||
|
authzSvc: authzSvc,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateContributionInput represents the input for creating a new contribution.
|
||||||
|
type CreateContributionInput struct {
|
||||||
|
Name string
|
||||||
|
Status string
|
||||||
|
WorkID *uint
|
||||||
|
TranslationID *uint
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateContribution creates a new contribution.
|
||||||
|
func (c *Commands) CreateContribution(ctx context.Context, input CreateContributionInput) (*domain.Contribution, error) {
|
||||||
|
actorID, ok := platform_auth.GetUserIDFromContext(ctx)
|
||||||
|
if !ok {
|
||||||
|
return nil, domain.ErrUnauthorized
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Add authorization check using authzSvc if necessary
|
||||||
|
|
||||||
|
contribution := &domain.Contribution{
|
||||||
|
Name: input.Name,
|
||||||
|
Status: input.Status,
|
||||||
|
UserID: actorID,
|
||||||
|
WorkID: input.WorkID,
|
||||||
|
TranslationID: input.TranslationID,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := c.repo.Create(ctx, contribution)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return contribution, nil
|
||||||
|
}
|
||||||
14
internal/app/contribution/service.go
Normal file
14
internal/app/contribution/service.go
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
package contribution
|
||||||
|
|
||||||
|
// Service encapsulates the contribution-related business logic.
|
||||||
|
type Service struct {
|
||||||
|
Commands *Commands
|
||||||
|
// Queries *Queries // Queries can be added here later if needed
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewService creates a new contribution service.
|
||||||
|
func NewService(commands *Commands) *Service {
|
||||||
|
return &Service{
|
||||||
|
Commands: commands,
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"testing"
|
"testing"
|
||||||
|
"tercul/internal/platform/config"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
@ -172,7 +173,9 @@ func TestMergeWork_Integration(t *testing.T) {
|
|||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
// Create real repositories and services pointing to the test DB
|
// Create real repositories and services pointing to the test DB
|
||||||
workRepo := sql.NewWorkRepository(db)
|
cfg, err := config.LoadConfig()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
workRepo := sql.NewWorkRepository(db, cfg)
|
||||||
authzSvc := authz.NewService(workRepo, nil) // Using real repo for authz checks
|
authzSvc := authz.NewService(workRepo, nil) // Using real repo for authz checks
|
||||||
searchClient := &mockSearchClient{} // Mock search client is fine
|
searchClient := &mockSearchClient{} // Mock search client is fine
|
||||||
commands := NewWorkCommands(workRepo, searchClient, authzSvc)
|
commands := NewWorkCommands(workRepo, searchClient, authzSvc)
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import (
|
|||||||
"tercul/internal/app/analytics"
|
"tercul/internal/app/analytics"
|
||||||
"tercul/internal/domain"
|
"tercul/internal/domain"
|
||||||
"tercul/internal/domain/work"
|
"tercul/internal/domain/work"
|
||||||
|
"tercul/internal/platform/config"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"go.opentelemetry.io/otel"
|
"go.opentelemetry.io/otel"
|
||||||
@ -18,7 +19,7 @@ type analyticsRepository struct {
|
|||||||
tracer trace.Tracer
|
tracer trace.Tracer
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAnalyticsRepository(db *gorm.DB) analytics.Repository {
|
func NewAnalyticsRepository(db *gorm.DB, cfg *config.Config) analytics.Repository {
|
||||||
return &analyticsRepository{
|
return &analyticsRepository{
|
||||||
db: db,
|
db: db,
|
||||||
tracer: otel.Tracer("analytics.repository"),
|
tracer: otel.Tracer("analytics.repository"),
|
||||||
|
|||||||
@ -3,6 +3,7 @@ package sql
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"tercul/internal/domain/auth"
|
"tercul/internal/domain/auth"
|
||||||
|
"tercul/internal/platform/config"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"go.opentelemetry.io/otel"
|
"go.opentelemetry.io/otel"
|
||||||
@ -15,7 +16,7 @@ type authRepository struct {
|
|||||||
tracer trace.Tracer
|
tracer trace.Tracer
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAuthRepository(db *gorm.DB) auth.AuthRepository {
|
func NewAuthRepository(db *gorm.DB, cfg *config.Config) auth.AuthRepository {
|
||||||
return &authRepository{
|
return &authRepository{
|
||||||
db: db,
|
db: db,
|
||||||
tracer: otel.Tracer("auth.repository"),
|
tracer: otel.Tracer("auth.repository"),
|
||||||
|
|||||||
@ -3,6 +3,7 @@ package sql
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"tercul/internal/domain"
|
"tercul/internal/domain"
|
||||||
|
"tercul/internal/platform/config"
|
||||||
|
|
||||||
"go.opentelemetry.io/otel"
|
"go.opentelemetry.io/otel"
|
||||||
"go.opentelemetry.io/otel/trace"
|
"go.opentelemetry.io/otel/trace"
|
||||||
@ -16,9 +17,9 @@ 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, cfg *config.Config) domain.AuthorRepository {
|
||||||
return &authorRepository{
|
return &authorRepository{
|
||||||
BaseRepository: NewBaseRepositoryImpl[domain.Author](db),
|
BaseRepository: NewBaseRepositoryImpl[domain.Author](db, cfg),
|
||||||
db: db,
|
db: db,
|
||||||
tracer: otel.Tracer("author.repository"),
|
tracer: otel.Tracer("author.repository"),
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"tercul/internal/data/sql"
|
"tercul/internal/data/sql"
|
||||||
"tercul/internal/domain"
|
"tercul/internal/domain"
|
||||||
|
"tercul/internal/platform/config"
|
||||||
"tercul/internal/testutil"
|
"tercul/internal/testutil"
|
||||||
|
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
@ -17,7 +18,9 @@ type AuthorRepositoryTestSuite struct {
|
|||||||
|
|
||||||
func (s *AuthorRepositoryTestSuite) SetupSuite() {
|
func (s *AuthorRepositoryTestSuite) SetupSuite() {
|
||||||
s.IntegrationTestSuite.SetupSuite(testutil.DefaultTestConfig())
|
s.IntegrationTestSuite.SetupSuite(testutil.DefaultTestConfig())
|
||||||
s.AuthorRepo = sql.NewAuthorRepository(s.DB)
|
cfg, err := config.LoadConfig()
|
||||||
|
s.Require().NoError(err)
|
||||||
|
s.AuthorRepo = sql.NewAuthorRepository(s.DB, cfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *AuthorRepositoryTestSuite) SetupTest() {
|
func (s *AuthorRepositoryTestSuite) SetupTest() {
|
||||||
@ -58,4 +61,4 @@ func (s *AuthorRepositoryTestSuite) TestListByWorkID() {
|
|||||||
|
|
||||||
func TestAuthorRepository(t *testing.T) {
|
func TestAuthorRepository(t *testing.T) {
|
||||||
suite.Run(t, new(AuthorRepositoryTestSuite))
|
suite.Run(t, new(AuthorRepositoryTestSuite))
|
||||||
}
|
}
|
||||||
@ -28,13 +28,15 @@ var (
|
|||||||
type BaseRepositoryImpl[T any] struct {
|
type BaseRepositoryImpl[T any] struct {
|
||||||
db *gorm.DB
|
db *gorm.DB
|
||||||
tracer trace.Tracer
|
tracer trace.Tracer
|
||||||
|
cfg *config.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewBaseRepositoryImpl creates a new BaseRepositoryImpl
|
// NewBaseRepositoryImpl creates a new BaseRepositoryImpl
|
||||||
func NewBaseRepositoryImpl[T any](db *gorm.DB) domain.BaseRepository[T] {
|
func NewBaseRepositoryImpl[T any](db *gorm.DB, cfg *config.Config) domain.BaseRepository[T] {
|
||||||
return &BaseRepositoryImpl[T]{
|
return &BaseRepositoryImpl[T]{
|
||||||
db: db,
|
db: db,
|
||||||
tracer: otel.Tracer("base.repository"),
|
tracer: otel.Tracer("base.repository"),
|
||||||
|
cfg: cfg,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,7 +71,7 @@ func (r *BaseRepositoryImpl[T]) validatePagination(page, pageSize int) (int, int
|
|||||||
}
|
}
|
||||||
|
|
||||||
if pageSize < 1 {
|
if pageSize < 1 {
|
||||||
pageSize = config.Cfg.PageSize
|
pageSize = r.cfg.PageSize
|
||||||
if pageSize < 1 {
|
if pageSize < 1 {
|
||||||
pageSize = 20 // Default page size
|
pageSize = 20 // Default page size
|
||||||
}
|
}
|
||||||
@ -525,7 +527,7 @@ func (r *BaseRepositoryImpl[T]) GetAllForSync(ctx context.Context, batchSize, of
|
|||||||
defer span.End()
|
defer span.End()
|
||||||
|
|
||||||
if batchSize <= 0 {
|
if batchSize <= 0 {
|
||||||
batchSize = config.Cfg.BatchSize
|
batchSize = r.cfg.BatchSize
|
||||||
if batchSize <= 0 {
|
if batchSize <= 0 {
|
||||||
batchSize = 100 // Default batch size
|
batchSize = 100 // Default batch size
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"tercul/internal/data/sql"
|
"tercul/internal/data/sql"
|
||||||
"tercul/internal/domain"
|
"tercul/internal/domain"
|
||||||
|
"tercul/internal/platform/config"
|
||||||
"tercul/internal/testutil"
|
"tercul/internal/testutil"
|
||||||
|
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
@ -16,12 +17,16 @@ import (
|
|||||||
type BaseRepositoryTestSuite struct {
|
type BaseRepositoryTestSuite struct {
|
||||||
testutil.IntegrationTestSuite
|
testutil.IntegrationTestSuite
|
||||||
repo domain.BaseRepository[testutil.TestEntity]
|
repo domain.BaseRepository[testutil.TestEntity]
|
||||||
|
cfg *config.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetupSuite initializes the test suite, database, and repository.
|
// SetupSuite initializes the test suite, database, and repository.
|
||||||
func (s *BaseRepositoryTestSuite) SetupSuite() {
|
func (s *BaseRepositoryTestSuite) SetupSuite() {
|
||||||
s.IntegrationTestSuite.SetupSuite(testutil.DefaultTestConfig())
|
s.IntegrationTestSuite.SetupSuite(testutil.DefaultTestConfig())
|
||||||
s.repo = sql.NewBaseRepositoryImpl[testutil.TestEntity](s.DB)
|
cfg, err := config.LoadConfig()
|
||||||
|
s.Require().NoError(err)
|
||||||
|
s.cfg = cfg
|
||||||
|
s.repo = sql.NewBaseRepositoryImpl[testutil.TestEntity](s.DB, s.cfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetupTest cleans the database before each test.
|
// SetupTest cleans the database before each test.
|
||||||
@ -219,7 +224,7 @@ func (s *BaseRepositoryTestSuite) TestWithTx() {
|
|||||||
// Act
|
// Act
|
||||||
err := s.repo.WithTx(context.Background(), func(tx *gorm.DB) error {
|
err := s.repo.WithTx(context.Background(), func(tx *gorm.DB) error {
|
||||||
entity := &testutil.TestEntity{Name: "TX Commit"}
|
entity := &testutil.TestEntity{Name: "TX Commit"}
|
||||||
repoInTx := sql.NewBaseRepositoryImpl[testutil.TestEntity](tx)
|
repoInTx := sql.NewBaseRepositoryImpl[testutil.TestEntity](tx, s.cfg)
|
||||||
if err := repoInTx.Create(context.Background(), entity); err != nil {
|
if err := repoInTx.Create(context.Background(), entity); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -241,7 +246,7 @@ func (s *BaseRepositoryTestSuite) TestWithTx() {
|
|||||||
// Act
|
// Act
|
||||||
err := s.repo.WithTx(context.Background(), func(tx *gorm.DB) error {
|
err := s.repo.WithTx(context.Background(), func(tx *gorm.DB) error {
|
||||||
entity := &testutil.TestEntity{Name: "TX Rollback"}
|
entity := &testutil.TestEntity{Name: "TX Rollback"}
|
||||||
repoInTx := sql.NewBaseRepositoryImpl[testutil.TestEntity](tx)
|
repoInTx := sql.NewBaseRepositoryImpl[testutil.TestEntity](tx, s.cfg)
|
||||||
if err := repoInTx.Create(context.Background(), entity); err != nil {
|
if err := repoInTx.Create(context.Background(), entity); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -256,4 +261,4 @@ func (s *BaseRepositoryTestSuite) TestWithTx() {
|
|||||||
_, getErr := s.repo.GetByID(context.Background(), createdID)
|
_, getErr := s.repo.GetByID(context.Background(), createdID)
|
||||||
s.ErrorIs(getErr, sql.ErrEntityNotFound, "Entity should not exist after rollback")
|
s.ErrorIs(getErr, sql.ErrEntityNotFound, "Entity should not exist after rollback")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"tercul/internal/domain"
|
"tercul/internal/domain"
|
||||||
|
"tercul/internal/platform/config"
|
||||||
|
|
||||||
"go.opentelemetry.io/otel"
|
"go.opentelemetry.io/otel"
|
||||||
"go.opentelemetry.io/otel/trace"
|
"go.opentelemetry.io/otel/trace"
|
||||||
@ -17,9 +18,9 @@ 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, cfg *config.Config) domain.BookRepository {
|
||||||
return &bookRepository{
|
return &bookRepository{
|
||||||
BaseRepository: NewBaseRepositoryImpl[domain.Book](db),
|
BaseRepository: NewBaseRepositoryImpl[domain.Book](db, cfg),
|
||||||
db: db,
|
db: db,
|
||||||
tracer: otel.Tracer("book.repository"),
|
tracer: otel.Tracer("book.repository"),
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"tercul/internal/data/sql"
|
"tercul/internal/data/sql"
|
||||||
"tercul/internal/domain"
|
"tercul/internal/domain"
|
||||||
|
"tercul/internal/platform/config"
|
||||||
"tercul/internal/testutil"
|
"tercul/internal/testutil"
|
||||||
|
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
@ -17,7 +18,9 @@ type BookRepositoryTestSuite struct {
|
|||||||
|
|
||||||
func (s *BookRepositoryTestSuite) SetupSuite() {
|
func (s *BookRepositoryTestSuite) SetupSuite() {
|
||||||
s.IntegrationTestSuite.SetupSuite(testutil.DefaultTestConfig())
|
s.IntegrationTestSuite.SetupSuite(testutil.DefaultTestConfig())
|
||||||
s.BookRepo = sql.NewBookRepository(s.DB)
|
cfg, err := config.LoadConfig()
|
||||||
|
s.Require().NoError(err)
|
||||||
|
s.BookRepo = sql.NewBookRepository(s.DB, cfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *BookRepositoryTestSuite) SetupTest() {
|
func (s *BookRepositoryTestSuite) SetupTest() {
|
||||||
@ -65,4 +68,4 @@ func (s *BookRepositoryTestSuite) TestFindByISBN() {
|
|||||||
|
|
||||||
func TestBookRepository(t *testing.T) {
|
func TestBookRepository(t *testing.T) {
|
||||||
suite.Run(t, new(BookRepositoryTestSuite))
|
suite.Run(t, new(BookRepositoryTestSuite))
|
||||||
}
|
}
|
||||||
@ -3,6 +3,7 @@ package sql
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"tercul/internal/domain"
|
"tercul/internal/domain"
|
||||||
|
"tercul/internal/platform/config"
|
||||||
|
|
||||||
"go.opentelemetry.io/otel"
|
"go.opentelemetry.io/otel"
|
||||||
"go.opentelemetry.io/otel/trace"
|
"go.opentelemetry.io/otel/trace"
|
||||||
@ -16,9 +17,9 @@ 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, cfg *config.Config) domain.BookmarkRepository {
|
||||||
return &bookmarkRepository{
|
return &bookmarkRepository{
|
||||||
BaseRepository: NewBaseRepositoryImpl[domain.Bookmark](db),
|
BaseRepository: NewBaseRepositoryImpl[domain.Bookmark](db, cfg),
|
||||||
db: db,
|
db: db,
|
||||||
tracer: otel.Tracer("bookmark.repository"),
|
tracer: otel.Tracer("bookmark.repository"),
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
repo "tercul/internal/data/sql"
|
repo "tercul/internal/data/sql"
|
||||||
"tercul/internal/domain"
|
"tercul/internal/domain"
|
||||||
|
"tercul/internal/platform/config"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/DATA-DOG/go-sqlmock"
|
"github.com/DATA-DOG/go-sqlmock"
|
||||||
@ -17,7 +18,9 @@ import (
|
|||||||
func TestNewBookmarkRepository(t *testing.T) {
|
func TestNewBookmarkRepository(t *testing.T) {
|
||||||
db, _, err := newMockDb()
|
db, _, err := newMockDb()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
repo := repo.NewBookmarkRepository(db)
|
cfg, err := config.LoadConfig()
|
||||||
|
require.NoError(t, err)
|
||||||
|
repo := repo.NewBookmarkRepository(db, cfg)
|
||||||
assert.NotNil(t, repo)
|
assert.NotNil(t, repo)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -25,7 +28,9 @@ func TestBookmarkRepository_ListByUserID(t *testing.T) {
|
|||||||
t.Run("should return bookmarks for a given user id", func(t *testing.T) {
|
t.Run("should return bookmarks for a given user id", func(t *testing.T) {
|
||||||
db, mock, err := newMockDb()
|
db, mock, err := newMockDb()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
repo := repo.NewBookmarkRepository(db)
|
cfg, err := config.LoadConfig()
|
||||||
|
require.NoError(t, err)
|
||||||
|
repo := repo.NewBookmarkRepository(db, cfg)
|
||||||
|
|
||||||
userID := uint(1)
|
userID := uint(1)
|
||||||
expectedBookmarks := []domain.Bookmark{
|
expectedBookmarks := []domain.Bookmark{
|
||||||
@ -50,7 +55,9 @@ func TestBookmarkRepository_ListByUserID(t *testing.T) {
|
|||||||
t.Run("should return error if query fails", func(t *testing.T) {
|
t.Run("should return error if query fails", func(t *testing.T) {
|
||||||
db, mock, err := newMockDb()
|
db, mock, err := newMockDb()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
repo := repo.NewBookmarkRepository(db)
|
cfg, err := config.LoadConfig()
|
||||||
|
require.NoError(t, err)
|
||||||
|
repo := repo.NewBookmarkRepository(db, cfg)
|
||||||
|
|
||||||
userID := uint(1)
|
userID := uint(1)
|
||||||
|
|
||||||
@ -69,7 +76,9 @@ func TestBookmarkRepository_ListByWorkID(t *testing.T) {
|
|||||||
t.Run("should return bookmarks for a given work id", func(t *testing.T) {
|
t.Run("should return bookmarks for a given work id", func(t *testing.T) {
|
||||||
db, mock, err := newMockDb()
|
db, mock, err := newMockDb()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
repo := repo.NewBookmarkRepository(db)
|
cfg, err := config.LoadConfig()
|
||||||
|
require.NoError(t, err)
|
||||||
|
repo := repo.NewBookmarkRepository(db, cfg)
|
||||||
|
|
||||||
workID := uint(1)
|
workID := uint(1)
|
||||||
expectedBookmarks := []domain.Bookmark{
|
expectedBookmarks := []domain.Bookmark{
|
||||||
@ -94,7 +103,9 @@ func TestBookmarkRepository_ListByWorkID(t *testing.T) {
|
|||||||
t.Run("should return error if query fails", func(t *testing.T) {
|
t.Run("should return error if query fails", func(t *testing.T) {
|
||||||
db, mock, err := newMockDb()
|
db, mock, err := newMockDb()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
repo := repo.NewBookmarkRepository(db)
|
cfg, err := config.LoadConfig()
|
||||||
|
require.NoError(t, err)
|
||||||
|
repo := repo.NewBookmarkRepository(db, cfg)
|
||||||
|
|
||||||
workID := uint(1)
|
workID := uint(1)
|
||||||
|
|
||||||
@ -107,4 +118,4 @@ func TestBookmarkRepository_ListByWorkID(t *testing.T) {
|
|||||||
assert.Nil(t, bookmarks)
|
assert.Nil(t, bookmarks)
|
||||||
assert.NoError(t, mock.ExpectationsWereMet())
|
assert.NoError(t, mock.ExpectationsWereMet())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"tercul/internal/domain"
|
"tercul/internal/domain"
|
||||||
|
"tercul/internal/platform/config"
|
||||||
|
|
||||||
"go.opentelemetry.io/otel"
|
"go.opentelemetry.io/otel"
|
||||||
"go.opentelemetry.io/otel/trace"
|
"go.opentelemetry.io/otel/trace"
|
||||||
@ -17,9 +18,9 @@ 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, cfg *config.Config) domain.CategoryRepository {
|
||||||
return &categoryRepository{
|
return &categoryRepository{
|
||||||
BaseRepository: NewBaseRepositoryImpl[domain.Category](db),
|
BaseRepository: NewBaseRepositoryImpl[domain.Category](db, cfg),
|
||||||
db: db,
|
db: db,
|
||||||
tracer: otel.Tracer("category.repository"),
|
tracer: otel.Tracer("category.repository"),
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"tercul/internal/data/sql"
|
"tercul/internal/data/sql"
|
||||||
"tercul/internal/domain"
|
"tercul/internal/domain"
|
||||||
|
"tercul/internal/platform/config"
|
||||||
"tercul/internal/testutil"
|
"tercul/internal/testutil"
|
||||||
|
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
@ -17,7 +18,9 @@ type CategoryRepositoryTestSuite struct {
|
|||||||
|
|
||||||
func (s *CategoryRepositoryTestSuite) SetupSuite() {
|
func (s *CategoryRepositoryTestSuite) SetupSuite() {
|
||||||
s.IntegrationTestSuite.SetupSuite(testutil.DefaultTestConfig())
|
s.IntegrationTestSuite.SetupSuite(testutil.DefaultTestConfig())
|
||||||
s.CategoryRepo = sql.NewCategoryRepository(s.DB)
|
cfg, err := config.LoadConfig()
|
||||||
|
s.Require().NoError(err)
|
||||||
|
s.CategoryRepo = sql.NewCategoryRepository(s.DB, cfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *CategoryRepositoryTestSuite) SetupTest() {
|
func (s *CategoryRepositoryTestSuite) SetupTest() {
|
||||||
@ -111,4 +114,4 @@ func (s *CategoryRepositoryTestSuite) TestListByParentID() {
|
|||||||
s.Equal(parent.ID, *cat.ParentID)
|
s.Equal(parent.ID, *cat.ParentID)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -3,6 +3,7 @@ package sql
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"tercul/internal/domain"
|
"tercul/internal/domain"
|
||||||
|
"tercul/internal/platform/config"
|
||||||
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
@ -13,9 +14,9 @@ type cityRepository struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewCityRepository creates a new CityRepository.
|
// NewCityRepository creates a new CityRepository.
|
||||||
func NewCityRepository(db *gorm.DB) domain.CityRepository {
|
func NewCityRepository(db *gorm.DB, cfg *config.Config) domain.CityRepository {
|
||||||
return &cityRepository{
|
return &cityRepository{
|
||||||
BaseRepository: NewBaseRepositoryImpl[domain.City](db),
|
BaseRepository: NewBaseRepositoryImpl[domain.City](db, cfg),
|
||||||
db: db,
|
db: db,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
repo "tercul/internal/data/sql"
|
repo "tercul/internal/data/sql"
|
||||||
"tercul/internal/domain"
|
"tercul/internal/domain"
|
||||||
|
"tercul/internal/platform/config"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/DATA-DOG/go-sqlmock"
|
"github.com/DATA-DOG/go-sqlmock"
|
||||||
@ -17,7 +18,9 @@ import (
|
|||||||
func TestNewCityRepository(t *testing.T) {
|
func TestNewCityRepository(t *testing.T) {
|
||||||
db, _, err := newMockDb()
|
db, _, err := newMockDb()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
repo := repo.NewCityRepository(db)
|
cfg, err := config.LoadConfig()
|
||||||
|
require.NoError(t, err)
|
||||||
|
repo := repo.NewCityRepository(db, cfg)
|
||||||
assert.NotNil(t, repo)
|
assert.NotNil(t, repo)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -25,7 +28,9 @@ func TestCityRepository_ListByCountryID(t *testing.T) {
|
|||||||
t.Run("should return cities for a given country id", func(t *testing.T) {
|
t.Run("should return cities for a given country id", func(t *testing.T) {
|
||||||
db, mock, err := newMockDb()
|
db, mock, err := newMockDb()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
repo := repo.NewCityRepository(db)
|
cfg, err := config.LoadConfig()
|
||||||
|
require.NoError(t, err)
|
||||||
|
repo := repo.NewCityRepository(db, cfg)
|
||||||
|
|
||||||
countryID := uint(1)
|
countryID := uint(1)
|
||||||
expectedCities := []domain.City{
|
expectedCities := []domain.City{
|
||||||
@ -50,7 +55,9 @@ func TestCityRepository_ListByCountryID(t *testing.T) {
|
|||||||
t.Run("should return error if query fails", func(t *testing.T) {
|
t.Run("should return error if query fails", func(t *testing.T) {
|
||||||
db, mock, err := newMockDb()
|
db, mock, err := newMockDb()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
repo := repo.NewCityRepository(db)
|
cfg, err := config.LoadConfig()
|
||||||
|
require.NoError(t, err)
|
||||||
|
repo := repo.NewCityRepository(db, cfg)
|
||||||
|
|
||||||
countryID := uint(1)
|
countryID := uint(1)
|
||||||
|
|
||||||
@ -63,4 +70,4 @@ func TestCityRepository_ListByCountryID(t *testing.T) {
|
|||||||
assert.Nil(t, cities)
|
assert.Nil(t, cities)
|
||||||
assert.NoError(t, mock.ExpectationsWereMet())
|
assert.NoError(t, mock.ExpectationsWereMet())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -3,6 +3,7 @@ package sql
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"tercul/internal/domain"
|
"tercul/internal/domain"
|
||||||
|
"tercul/internal/platform/config"
|
||||||
|
|
||||||
"go.opentelemetry.io/otel"
|
"go.opentelemetry.io/otel"
|
||||||
"go.opentelemetry.io/otel/trace"
|
"go.opentelemetry.io/otel/trace"
|
||||||
@ -16,9 +17,9 @@ 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, cfg *config.Config) domain.CollectionRepository {
|
||||||
return &collectionRepository{
|
return &collectionRepository{
|
||||||
BaseRepository: NewBaseRepositoryImpl[domain.Collection](db),
|
BaseRepository: NewBaseRepositoryImpl[domain.Collection](db, cfg),
|
||||||
db: db,
|
db: db,
|
||||||
tracer: otel.Tracer("collection.repository"),
|
tracer: otel.Tracer("collection.repository"),
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"tercul/internal/data/sql"
|
"tercul/internal/data/sql"
|
||||||
"tercul/internal/domain"
|
"tercul/internal/domain"
|
||||||
|
"tercul/internal/platform/config"
|
||||||
|
|
||||||
"github.com/DATA-DOG/go-sqlmock"
|
"github.com/DATA-DOG/go-sqlmock"
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
@ -28,7 +29,9 @@ func (s *CollectionRepositoryTestSuite) SetupTest() {
|
|||||||
|
|
||||||
s.db = gormDB
|
s.db = gormDB
|
||||||
s.mock = mock
|
s.mock = mock
|
||||||
s.repo = sql.NewCollectionRepository(s.db)
|
cfg, err := config.LoadConfig()
|
||||||
|
s.Require().NoError(err)
|
||||||
|
s.repo = sql.NewCollectionRepository(s.db, cfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *CollectionRepositoryTestSuite) TearDownTest() {
|
func (s *CollectionRepositoryTestSuite) TearDownTest() {
|
||||||
@ -101,4 +104,4 @@ func (s *CollectionRepositoryTestSuite) TestListByWorkID() {
|
|||||||
collections, err := s.repo.ListByWorkID(context.Background(), workID)
|
collections, err := s.repo.ListByWorkID(context.Background(), workID)
|
||||||
s.Require().NoError(err)
|
s.Require().NoError(err)
|
||||||
s.Require().Len(collections, 2)
|
s.Require().Len(collections, 2)
|
||||||
}
|
}
|
||||||
@ -3,6 +3,7 @@ package sql
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"tercul/internal/domain"
|
"tercul/internal/domain"
|
||||||
|
"tercul/internal/platform/config"
|
||||||
|
|
||||||
"go.opentelemetry.io/otel"
|
"go.opentelemetry.io/otel"
|
||||||
"go.opentelemetry.io/otel/trace"
|
"go.opentelemetry.io/otel/trace"
|
||||||
@ -16,9 +17,9 @@ 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, cfg *config.Config) domain.CommentRepository {
|
||||||
return &commentRepository{
|
return &commentRepository{
|
||||||
BaseRepository: NewBaseRepositoryImpl[domain.Comment](db),
|
BaseRepository: NewBaseRepositoryImpl[domain.Comment](db, cfg),
|
||||||
db: db,
|
db: db,
|
||||||
tracer: otel.Tracer("comment.repository"),
|
tracer: otel.Tracer("comment.repository"),
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
repo "tercul/internal/data/sql"
|
repo "tercul/internal/data/sql"
|
||||||
"tercul/internal/domain"
|
"tercul/internal/domain"
|
||||||
|
"tercul/internal/platform/config"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/DATA-DOG/go-sqlmock"
|
"github.com/DATA-DOG/go-sqlmock"
|
||||||
@ -17,7 +18,9 @@ import (
|
|||||||
func TestNewCommentRepository(t *testing.T) {
|
func TestNewCommentRepository(t *testing.T) {
|
||||||
db, _, err := newMockDb()
|
db, _, err := newMockDb()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
repo := repo.NewCommentRepository(db)
|
cfg, err := config.LoadConfig()
|
||||||
|
require.NoError(t, err)
|
||||||
|
repo := repo.NewCommentRepository(db, cfg)
|
||||||
assert.NotNil(t, repo)
|
assert.NotNil(t, repo)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -25,7 +28,9 @@ func TestCommentRepository_ListByUserID(t *testing.T) {
|
|||||||
t.Run("should return comments for a given user id", func(t *testing.T) {
|
t.Run("should return comments for a given user id", func(t *testing.T) {
|
||||||
db, mock, err := newMockDb()
|
db, mock, err := newMockDb()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
repo := repo.NewCommentRepository(db)
|
cfg, err := config.LoadConfig()
|
||||||
|
require.NoError(t, err)
|
||||||
|
repo := repo.NewCommentRepository(db, cfg)
|
||||||
|
|
||||||
userID := uint(1)
|
userID := uint(1)
|
||||||
expectedComments := []domain.Comment{
|
expectedComments := []domain.Comment{
|
||||||
@ -50,7 +55,9 @@ func TestCommentRepository_ListByUserID(t *testing.T) {
|
|||||||
t.Run("should return error if query fails", func(t *testing.T) {
|
t.Run("should return error if query fails", func(t *testing.T) {
|
||||||
db, mock, err := newMockDb()
|
db, mock, err := newMockDb()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
repo := repo.NewCommentRepository(db)
|
cfg, err := config.LoadConfig()
|
||||||
|
require.NoError(t, err)
|
||||||
|
repo := repo.NewCommentRepository(db, cfg)
|
||||||
|
|
||||||
userID := uint(1)
|
userID := uint(1)
|
||||||
|
|
||||||
@ -69,7 +76,9 @@ func TestCommentRepository_ListByWorkID(t *testing.T) {
|
|||||||
t.Run("should return comments for a given work id", func(t *testing.T) {
|
t.Run("should return comments for a given work id", func(t *testing.T) {
|
||||||
db, mock, err := newMockDb()
|
db, mock, err := newMockDb()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
repo := repo.NewCommentRepository(db)
|
cfg, err := config.LoadConfig()
|
||||||
|
require.NoError(t, err)
|
||||||
|
repo := repo.NewCommentRepository(db, cfg)
|
||||||
|
|
||||||
workID := uint(1)
|
workID := uint(1)
|
||||||
expectedComments := []domain.Comment{
|
expectedComments := []domain.Comment{
|
||||||
@ -94,7 +103,9 @@ func TestCommentRepository_ListByWorkID(t *testing.T) {
|
|||||||
t.Run("should return error if query fails", func(t *testing.T) {
|
t.Run("should return error if query fails", func(t *testing.T) {
|
||||||
db, mock, err := newMockDb()
|
db, mock, err := newMockDb()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
repo := repo.NewCommentRepository(db)
|
cfg, err := config.LoadConfig()
|
||||||
|
require.NoError(t, err)
|
||||||
|
repo := repo.NewCommentRepository(db, cfg)
|
||||||
|
|
||||||
workID := uint(1)
|
workID := uint(1)
|
||||||
|
|
||||||
@ -113,7 +124,9 @@ func TestCommentRepository_ListByTranslationID(t *testing.T) {
|
|||||||
t.Run("should return comments for a given translation id", func(t *testing.T) {
|
t.Run("should return comments for a given translation id", func(t *testing.T) {
|
||||||
db, mock, err := newMockDb()
|
db, mock, err := newMockDb()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
repo := repo.NewCommentRepository(db)
|
cfg, err := config.LoadConfig()
|
||||||
|
require.NoError(t, err)
|
||||||
|
repo := repo.NewCommentRepository(db, cfg)
|
||||||
|
|
||||||
translationID := uint(1)
|
translationID := uint(1)
|
||||||
expectedComments := []domain.Comment{
|
expectedComments := []domain.Comment{
|
||||||
@ -138,7 +151,9 @@ func TestCommentRepository_ListByTranslationID(t *testing.T) {
|
|||||||
t.Run("should return error if query fails", func(t *testing.T) {
|
t.Run("should return error if query fails", func(t *testing.T) {
|
||||||
db, mock, err := newMockDb()
|
db, mock, err := newMockDb()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
repo := repo.NewCommentRepository(db)
|
cfg, err := config.LoadConfig()
|
||||||
|
require.NoError(t, err)
|
||||||
|
repo := repo.NewCommentRepository(db, cfg)
|
||||||
|
|
||||||
translationID := uint(1)
|
translationID := uint(1)
|
||||||
|
|
||||||
@ -157,7 +172,9 @@ func TestCommentRepository_ListByParentID(t *testing.T) {
|
|||||||
t.Run("should return comments for a given parent id", func(t *testing.T) {
|
t.Run("should return comments for a given parent id", func(t *testing.T) {
|
||||||
db, mock, err := newMockDb()
|
db, mock, err := newMockDb()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
repo := repo.NewCommentRepository(db)
|
cfg, err := config.LoadConfig()
|
||||||
|
require.NoError(t, err)
|
||||||
|
repo := repo.NewCommentRepository(db, cfg)
|
||||||
|
|
||||||
parentID := uint(1)
|
parentID := uint(1)
|
||||||
expectedComments := []domain.Comment{
|
expectedComments := []domain.Comment{
|
||||||
@ -182,7 +199,9 @@ func TestCommentRepository_ListByParentID(t *testing.T) {
|
|||||||
t.Run("should return error if query fails", func(t *testing.T) {
|
t.Run("should return error if query fails", func(t *testing.T) {
|
||||||
db, mock, err := newMockDb()
|
db, mock, err := newMockDb()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
repo := repo.NewCommentRepository(db)
|
cfg, err := config.LoadConfig()
|
||||||
|
require.NoError(t, err)
|
||||||
|
repo := repo.NewCommentRepository(db, cfg)
|
||||||
|
|
||||||
parentID := uint(1)
|
parentID := uint(1)
|
||||||
|
|
||||||
@ -195,4 +214,4 @@ func TestCommentRepository_ListByParentID(t *testing.T) {
|
|||||||
assert.Nil(t, comments)
|
assert.Nil(t, comments)
|
||||||
assert.NoError(t, mock.ExpectationsWereMet())
|
assert.NoError(t, mock.ExpectationsWereMet())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -3,6 +3,7 @@ package sql
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"tercul/internal/domain"
|
"tercul/internal/domain"
|
||||||
|
"tercul/internal/platform/config"
|
||||||
|
|
||||||
"go.opentelemetry.io/otel"
|
"go.opentelemetry.io/otel"
|
||||||
"go.opentelemetry.io/otel/trace"
|
"go.opentelemetry.io/otel/trace"
|
||||||
@ -16,9 +17,9 @@ type contributionRepository struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewContributionRepository creates a new ContributionRepository.
|
// NewContributionRepository creates a new ContributionRepository.
|
||||||
func NewContributionRepository(db *gorm.DB) domain.ContributionRepository {
|
func NewContributionRepository(db *gorm.DB, cfg *config.Config) domain.ContributionRepository {
|
||||||
return &contributionRepository{
|
return &contributionRepository{
|
||||||
BaseRepository: NewBaseRepositoryImpl[domain.Contribution](db),
|
BaseRepository: NewBaseRepositoryImpl[domain.Contribution](db, cfg),
|
||||||
db: db,
|
db: db,
|
||||||
tracer: otel.Tracer("contribution.repository"),
|
tracer: otel.Tracer("contribution.repository"),
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
repo "tercul/internal/data/sql"
|
repo "tercul/internal/data/sql"
|
||||||
"tercul/internal/domain"
|
"tercul/internal/domain"
|
||||||
|
"tercul/internal/platform/config"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/DATA-DOG/go-sqlmock"
|
"github.com/DATA-DOG/go-sqlmock"
|
||||||
@ -17,7 +18,9 @@ import (
|
|||||||
func TestNewContributionRepository(t *testing.T) {
|
func TestNewContributionRepository(t *testing.T) {
|
||||||
db, _, err := newMockDb()
|
db, _, err := newMockDb()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
repo := repo.NewContributionRepository(db)
|
cfg, err := config.LoadConfig()
|
||||||
|
require.NoError(t, err)
|
||||||
|
repo := repo.NewContributionRepository(db, cfg)
|
||||||
assert.NotNil(t, repo)
|
assert.NotNil(t, repo)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -25,7 +28,9 @@ func TestContributionRepository_ListByUserID(t *testing.T) {
|
|||||||
t.Run("should return contributions for a given user id", func(t *testing.T) {
|
t.Run("should return contributions for a given user id", func(t *testing.T) {
|
||||||
db, mock, err := newMockDb()
|
db, mock, err := newMockDb()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
repo := repo.NewContributionRepository(db)
|
cfg, err := config.LoadConfig()
|
||||||
|
require.NoError(t, err)
|
||||||
|
repo := repo.NewContributionRepository(db, cfg)
|
||||||
|
|
||||||
userID := uint(1)
|
userID := uint(1)
|
||||||
expectedContributions := []domain.Contribution{
|
expectedContributions := []domain.Contribution{
|
||||||
@ -50,7 +55,9 @@ func TestContributionRepository_ListByUserID(t *testing.T) {
|
|||||||
t.Run("should return error if query fails", func(t *testing.T) {
|
t.Run("should return error if query fails", func(t *testing.T) {
|
||||||
db, mock, err := newMockDb()
|
db, mock, err := newMockDb()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
repo := repo.NewContributionRepository(db)
|
cfg, err := config.LoadConfig()
|
||||||
|
require.NoError(t, err)
|
||||||
|
repo := repo.NewContributionRepository(db, cfg)
|
||||||
|
|
||||||
userID := uint(1)
|
userID := uint(1)
|
||||||
|
|
||||||
@ -69,7 +76,9 @@ func TestContributionRepository_ListByReviewerID(t *testing.T) {
|
|||||||
t.Run("should return contributions for a given reviewer id", func(t *testing.T) {
|
t.Run("should return contributions for a given reviewer id", func(t *testing.T) {
|
||||||
db, mock, err := newMockDb()
|
db, mock, err := newMockDb()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
repo := repo.NewContributionRepository(db)
|
cfg, err := config.LoadConfig()
|
||||||
|
require.NoError(t, err)
|
||||||
|
repo := repo.NewContributionRepository(db, cfg)
|
||||||
|
|
||||||
reviewerID := uint(1)
|
reviewerID := uint(1)
|
||||||
expectedContributions := []domain.Contribution{
|
expectedContributions := []domain.Contribution{
|
||||||
@ -94,7 +103,9 @@ func TestContributionRepository_ListByReviewerID(t *testing.T) {
|
|||||||
t.Run("should return error if query fails", func(t *testing.T) {
|
t.Run("should return error if query fails", func(t *testing.T) {
|
||||||
db, mock, err := newMockDb()
|
db, mock, err := newMockDb()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
repo := repo.NewContributionRepository(db)
|
cfg, err := config.LoadConfig()
|
||||||
|
require.NoError(t, err)
|
||||||
|
repo := repo.NewContributionRepository(db, cfg)
|
||||||
|
|
||||||
reviewerID := uint(1)
|
reviewerID := uint(1)
|
||||||
|
|
||||||
@ -113,7 +124,9 @@ func TestContributionRepository_ListByWorkID(t *testing.T) {
|
|||||||
t.Run("should return contributions for a given work id", func(t *testing.T) {
|
t.Run("should return contributions for a given work id", func(t *testing.T) {
|
||||||
db, mock, err := newMockDb()
|
db, mock, err := newMockDb()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
repo := repo.NewContributionRepository(db)
|
cfg, err := config.LoadConfig()
|
||||||
|
require.NoError(t, err)
|
||||||
|
repo := repo.NewContributionRepository(db, cfg)
|
||||||
|
|
||||||
workID := uint(1)
|
workID := uint(1)
|
||||||
expectedContributions := []domain.Contribution{
|
expectedContributions := []domain.Contribution{
|
||||||
@ -138,7 +151,9 @@ func TestContributionRepository_ListByWorkID(t *testing.T) {
|
|||||||
t.Run("should return error if query fails", func(t *testing.T) {
|
t.Run("should return error if query fails", func(t *testing.T) {
|
||||||
db, mock, err := newMockDb()
|
db, mock, err := newMockDb()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
repo := repo.NewContributionRepository(db)
|
cfg, err := config.LoadConfig()
|
||||||
|
require.NoError(t, err)
|
||||||
|
repo := repo.NewContributionRepository(db, cfg)
|
||||||
|
|
||||||
workID := uint(1)
|
workID := uint(1)
|
||||||
|
|
||||||
@ -157,7 +172,9 @@ func TestContributionRepository_ListByTranslationID(t *testing.T) {
|
|||||||
t.Run("should return contributions for a given translation id", func(t *testing.T) {
|
t.Run("should return contributions for a given translation id", func(t *testing.T) {
|
||||||
db, mock, err := newMockDb()
|
db, mock, err := newMockDb()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
repo := repo.NewContributionRepository(db)
|
cfg, err := config.LoadConfig()
|
||||||
|
require.NoError(t, err)
|
||||||
|
repo := repo.NewContributionRepository(db, cfg)
|
||||||
|
|
||||||
translationID := uint(1)
|
translationID := uint(1)
|
||||||
expectedContributions := []domain.Contribution{
|
expectedContributions := []domain.Contribution{
|
||||||
@ -182,7 +199,9 @@ func TestContributionRepository_ListByTranslationID(t *testing.T) {
|
|||||||
t.Run("should return error if query fails", func(t *testing.T) {
|
t.Run("should return error if query fails", func(t *testing.T) {
|
||||||
db, mock, err := newMockDb()
|
db, mock, err := newMockDb()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
repo := repo.NewContributionRepository(db)
|
cfg, err := config.LoadConfig()
|
||||||
|
require.NoError(t, err)
|
||||||
|
repo := repo.NewContributionRepository(db, cfg)
|
||||||
|
|
||||||
translationID := uint(1)
|
translationID := uint(1)
|
||||||
|
|
||||||
@ -201,7 +220,9 @@ func TestContributionRepository_ListByStatus(t *testing.T) {
|
|||||||
t.Run("should return contributions for a given status", func(t *testing.T) {
|
t.Run("should return contributions for a given status", func(t *testing.T) {
|
||||||
db, mock, err := newMockDb()
|
db, mock, err := newMockDb()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
repo := repo.NewContributionRepository(db)
|
cfg, err := config.LoadConfig()
|
||||||
|
require.NoError(t, err)
|
||||||
|
repo := repo.NewContributionRepository(db, cfg)
|
||||||
|
|
||||||
status := "draft"
|
status := "draft"
|
||||||
expectedContributions := []domain.Contribution{
|
expectedContributions := []domain.Contribution{
|
||||||
@ -226,7 +247,9 @@ func TestContributionRepository_ListByStatus(t *testing.T) {
|
|||||||
t.Run("should return error if query fails", func(t *testing.T) {
|
t.Run("should return error if query fails", func(t *testing.T) {
|
||||||
db, mock, err := newMockDb()
|
db, mock, err := newMockDb()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
repo := repo.NewContributionRepository(db)
|
cfg, err := config.LoadConfig()
|
||||||
|
require.NoError(t, err)
|
||||||
|
repo := repo.NewContributionRepository(db, cfg)
|
||||||
|
|
||||||
status := "draft"
|
status := "draft"
|
||||||
|
|
||||||
@ -239,4 +262,4 @@ func TestContributionRepository_ListByStatus(t *testing.T) {
|
|||||||
assert.Nil(t, contributions)
|
assert.Nil(t, contributions)
|
||||||
assert.NoError(t, mock.ExpectationsWereMet())
|
assert.NoError(t, mock.ExpectationsWereMet())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -3,6 +3,7 @@ package sql
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"tercul/internal/domain"
|
"tercul/internal/domain"
|
||||||
|
"tercul/internal/platform/config"
|
||||||
|
|
||||||
"go.opentelemetry.io/otel"
|
"go.opentelemetry.io/otel"
|
||||||
"go.opentelemetry.io/otel/trace"
|
"go.opentelemetry.io/otel/trace"
|
||||||
@ -16,9 +17,9 @@ type copyrightClaimRepository struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewCopyrightClaimRepository creates a new CopyrightClaimRepository.
|
// NewCopyrightClaimRepository creates a new CopyrightClaimRepository.
|
||||||
func NewCopyrightClaimRepository(db *gorm.DB) domain.CopyrightClaimRepository {
|
func NewCopyrightClaimRepository(db *gorm.DB, cfg *config.Config) domain.CopyrightClaimRepository {
|
||||||
return ©rightClaimRepository{
|
return ©rightClaimRepository{
|
||||||
BaseRepository: NewBaseRepositoryImpl[domain.CopyrightClaim](db),
|
BaseRepository: NewBaseRepositoryImpl[domain.CopyrightClaim](db, cfg),
|
||||||
db: db,
|
db: db,
|
||||||
tracer: otel.Tracer("copyright_claim.repository"),
|
tracer: otel.Tracer("copyright_claim.repository"),
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"tercul/internal/domain"
|
"tercul/internal/domain"
|
||||||
|
"tercul/internal/platform/config"
|
||||||
|
|
||||||
"go.opentelemetry.io/otel"
|
"go.opentelemetry.io/otel"
|
||||||
"go.opentelemetry.io/otel/trace"
|
"go.opentelemetry.io/otel/trace"
|
||||||
@ -17,9 +18,9 @@ type copyrightRepository struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewCopyrightRepository creates a new CopyrightRepository.
|
// NewCopyrightRepository creates a new CopyrightRepository.
|
||||||
func NewCopyrightRepository(db *gorm.DB) domain.CopyrightRepository {
|
func NewCopyrightRepository(db *gorm.DB, cfg *config.Config) domain.CopyrightRepository {
|
||||||
return ©rightRepository{
|
return ©rightRepository{
|
||||||
BaseRepository: NewBaseRepositoryImpl[domain.Copyright](db),
|
BaseRepository: NewBaseRepositoryImpl[domain.Copyright](db, cfg),
|
||||||
db: db,
|
db: db,
|
||||||
tracer: otel.Tracer("copyright.repository"),
|
tracer: otel.Tracer("copyright.repository"),
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"tercul/internal/data/sql"
|
"tercul/internal/data/sql"
|
||||||
"tercul/internal/domain"
|
"tercul/internal/domain"
|
||||||
|
"tercul/internal/platform/config"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/DATA-DOG/go-sqlmock"
|
"github.com/DATA-DOG/go-sqlmock"
|
||||||
@ -41,7 +42,9 @@ func (s *CopyrightRepositoryTestSuite) SetupTest() {
|
|||||||
|
|
||||||
s.db = gormDB
|
s.db = gormDB
|
||||||
s.mock = mock
|
s.mock = mock
|
||||||
s.repo = sql.NewCopyrightRepository(s.db)
|
cfg, err := config.LoadConfig()
|
||||||
|
s.Require().NoError(err)
|
||||||
|
s.repo = sql.NewCopyrightRepository(s.db, cfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TearDownTest checks if all expectations were met.
|
// TearDownTest checks if all expectations were met.
|
||||||
@ -236,4 +239,4 @@ func (s *CopyrightRepositoryTestSuite) TestRemoveCopyrightFromSource() {
|
|||||||
err := s.repo.RemoveCopyrightFromSource(context.Background(), sourceID, copyrightID)
|
err := s.repo.RemoveCopyrightFromSource(context.Background(), sourceID, copyrightID)
|
||||||
s.Require().NoError(err)
|
s.Require().NoError(err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"tercul/internal/domain"
|
"tercul/internal/domain"
|
||||||
|
"tercul/internal/platform/config"
|
||||||
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
@ -14,9 +15,9 @@ type countryRepository struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewCountryRepository creates a new CountryRepository.
|
// NewCountryRepository creates a new CountryRepository.
|
||||||
func NewCountryRepository(db *gorm.DB) domain.CountryRepository {
|
func NewCountryRepository(db *gorm.DB, cfg *config.Config) domain.CountryRepository {
|
||||||
return &countryRepository{
|
return &countryRepository{
|
||||||
BaseRepository: NewBaseRepositoryImpl[domain.Country](db),
|
BaseRepository: NewBaseRepositoryImpl[domain.Country](db, cfg),
|
||||||
db: db,
|
db: db,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,6 +3,7 @@ package sql
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"tercul/internal/domain"
|
"tercul/internal/domain"
|
||||||
|
"tercul/internal/platform/config"
|
||||||
|
|
||||||
"go.opentelemetry.io/otel"
|
"go.opentelemetry.io/otel"
|
||||||
"go.opentelemetry.io/otel/trace"
|
"go.opentelemetry.io/otel/trace"
|
||||||
@ -16,9 +17,9 @@ type edgeRepository struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewEdgeRepository creates a new EdgeRepository.
|
// NewEdgeRepository creates a new EdgeRepository.
|
||||||
func NewEdgeRepository(db *gorm.DB) domain.EdgeRepository {
|
func NewEdgeRepository(db *gorm.DB, cfg *config.Config) domain.EdgeRepository {
|
||||||
return &edgeRepository{
|
return &edgeRepository{
|
||||||
BaseRepository: NewBaseRepositoryImpl[domain.Edge](db),
|
BaseRepository: NewBaseRepositoryImpl[domain.Edge](db, cfg),
|
||||||
db: db,
|
db: db,
|
||||||
tracer: otel.Tracer("edge.repository"),
|
tracer: otel.Tracer("edge.repository"),
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"tercul/internal/domain"
|
"tercul/internal/domain"
|
||||||
|
"tercul/internal/platform/config"
|
||||||
|
|
||||||
"go.opentelemetry.io/otel"
|
"go.opentelemetry.io/otel"
|
||||||
"go.opentelemetry.io/otel/trace"
|
"go.opentelemetry.io/otel/trace"
|
||||||
@ -17,9 +18,9 @@ type editionRepository struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewEditionRepository creates a new EditionRepository.
|
// NewEditionRepository creates a new EditionRepository.
|
||||||
func NewEditionRepository(db *gorm.DB) domain.EditionRepository {
|
func NewEditionRepository(db *gorm.DB, cfg *config.Config) domain.EditionRepository {
|
||||||
return &editionRepository{
|
return &editionRepository{
|
||||||
BaseRepository: NewBaseRepositoryImpl[domain.Edition](db),
|
BaseRepository: NewBaseRepositoryImpl[domain.Edition](db, cfg),
|
||||||
db: db,
|
db: db,
|
||||||
tracer: otel.Tracer("edition.repository"),
|
tracer: otel.Tracer("edition.repository"),
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"tercul/internal/domain"
|
"tercul/internal/domain"
|
||||||
|
"tercul/internal/platform/config"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"go.opentelemetry.io/otel"
|
"go.opentelemetry.io/otel"
|
||||||
@ -18,9 +19,9 @@ type emailVerificationRepository struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewEmailVerificationRepository creates a new EmailVerificationRepository.
|
// NewEmailVerificationRepository creates a new EmailVerificationRepository.
|
||||||
func NewEmailVerificationRepository(db *gorm.DB) domain.EmailVerificationRepository {
|
func NewEmailVerificationRepository(db *gorm.DB, cfg *config.Config) domain.EmailVerificationRepository {
|
||||||
return &emailVerificationRepository{
|
return &emailVerificationRepository{
|
||||||
BaseRepository: NewBaseRepositoryImpl[domain.EmailVerification](db),
|
BaseRepository: NewBaseRepositoryImpl[domain.EmailVerification](db, cfg),
|
||||||
db: db,
|
db: db,
|
||||||
tracer: otel.Tracer("email_verification.repository"),
|
tracer: otel.Tracer("email_verification.repository"),
|
||||||
}
|
}
|
||||||
@ -69,4 +70,4 @@ func (r *emailVerificationRepository) MarkAsUsed(ctx context.Context, id uint) e
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -3,6 +3,7 @@ package sql
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"tercul/internal/domain"
|
"tercul/internal/domain"
|
||||||
|
"tercul/internal/platform/config"
|
||||||
|
|
||||||
"go.opentelemetry.io/otel"
|
"go.opentelemetry.io/otel"
|
||||||
"go.opentelemetry.io/otel/trace"
|
"go.opentelemetry.io/otel/trace"
|
||||||
@ -16,9 +17,9 @@ 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, cfg *config.Config) domain.LikeRepository {
|
||||||
return &likeRepository{
|
return &likeRepository{
|
||||||
BaseRepository: NewBaseRepositoryImpl[domain.Like](db),
|
BaseRepository: NewBaseRepositoryImpl[domain.Like](db, cfg),
|
||||||
db: db,
|
db: db,
|
||||||
tracer: otel.Tracer("like.repository"),
|
tracer: otel.Tracer("like.repository"),
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"tercul/internal/domain"
|
"tercul/internal/domain"
|
||||||
"tercul/internal/domain/localization"
|
"tercul/internal/domain/localization"
|
||||||
|
"tercul/internal/platform/config"
|
||||||
|
|
||||||
"go.opentelemetry.io/otel"
|
"go.opentelemetry.io/otel"
|
||||||
"go.opentelemetry.io/otel/trace"
|
"go.opentelemetry.io/otel/trace"
|
||||||
@ -15,7 +16,7 @@ type localizationRepository struct {
|
|||||||
tracer trace.Tracer
|
tracer trace.Tracer
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLocalizationRepository(db *gorm.DB) localization.LocalizationRepository {
|
func NewLocalizationRepository(db *gorm.DB, cfg *config.Config) localization.LocalizationRepository {
|
||||||
return &localizationRepository{
|
return &localizationRepository{
|
||||||
db: db,
|
db: db,
|
||||||
tracer: otel.Tracer("localization.repository"),
|
tracer: otel.Tracer("localization.repository"),
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"tercul/internal/domain"
|
"tercul/internal/domain"
|
||||||
"tercul/internal/domain/work"
|
"tercul/internal/domain/work"
|
||||||
|
"tercul/internal/platform/config"
|
||||||
|
|
||||||
"go.opentelemetry.io/otel"
|
"go.opentelemetry.io/otel"
|
||||||
"go.opentelemetry.io/otel/trace"
|
"go.opentelemetry.io/otel/trace"
|
||||||
@ -17,9 +18,9 @@ type monetizationRepository struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewMonetizationRepository creates a new MonetizationRepository.
|
// NewMonetizationRepository creates a new MonetizationRepository.
|
||||||
func NewMonetizationRepository(db *gorm.DB) domain.MonetizationRepository {
|
func NewMonetizationRepository(db *gorm.DB, cfg *config.Config) domain.MonetizationRepository {
|
||||||
return &monetizationRepository{
|
return &monetizationRepository{
|
||||||
BaseRepository: NewBaseRepositoryImpl[domain.Monetization](db),
|
BaseRepository: NewBaseRepositoryImpl[domain.Monetization](db, cfg),
|
||||||
db: db,
|
db: db,
|
||||||
tracer: otel.Tracer("monetization.repository"),
|
tracer: otel.Tracer("monetization.repository"),
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import (
|
|||||||
"tercul/internal/data/sql"
|
"tercul/internal/data/sql"
|
||||||
"tercul/internal/domain"
|
"tercul/internal/domain"
|
||||||
workdomain "tercul/internal/domain/work"
|
workdomain "tercul/internal/domain/work"
|
||||||
|
"tercul/internal/platform/config"
|
||||||
"tercul/internal/testutil"
|
"tercul/internal/testutil"
|
||||||
|
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
@ -18,7 +19,9 @@ type MonetizationRepositoryTestSuite struct {
|
|||||||
|
|
||||||
func (s *MonetizationRepositoryTestSuite) SetupSuite() {
|
func (s *MonetizationRepositoryTestSuite) SetupSuite() {
|
||||||
s.IntegrationTestSuite.SetupSuite(testutil.DefaultTestConfig())
|
s.IntegrationTestSuite.SetupSuite(testutil.DefaultTestConfig())
|
||||||
s.MonetizationRepo = sql.NewMonetizationRepository(s.DB)
|
cfg, err := config.LoadConfig()
|
||||||
|
s.Require().NoError(err)
|
||||||
|
s.MonetizationRepo = sql.NewMonetizationRepository(s.DB, cfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *MonetizationRepositoryTestSuite) SetupTest() {
|
func (s *MonetizationRepositoryTestSuite) SetupTest() {
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"tercul/internal/domain"
|
"tercul/internal/domain"
|
||||||
|
"tercul/internal/platform/config"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"go.opentelemetry.io/otel"
|
"go.opentelemetry.io/otel"
|
||||||
@ -18,9 +19,9 @@ type passwordResetRepository struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewPasswordResetRepository creates a new PasswordResetRepository.
|
// NewPasswordResetRepository creates a new PasswordResetRepository.
|
||||||
func NewPasswordResetRepository(db *gorm.DB) domain.PasswordResetRepository {
|
func NewPasswordResetRepository(db *gorm.DB, cfg *config.Config) domain.PasswordResetRepository {
|
||||||
return &passwordResetRepository{
|
return &passwordResetRepository{
|
||||||
BaseRepository: NewBaseRepositoryImpl[domain.PasswordReset](db),
|
BaseRepository: NewBaseRepositoryImpl[domain.PasswordReset](db, cfg),
|
||||||
db: db,
|
db: db,
|
||||||
tracer: otel.Tracer("password_reset.repository"),
|
tracer: otel.Tracer("password_reset.repository"),
|
||||||
}
|
}
|
||||||
@ -69,4 +70,4 @@ func (r *passwordResetRepository) MarkAsUsed(ctx context.Context, id uint) error
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"math"
|
"math"
|
||||||
"tercul/internal/domain"
|
"tercul/internal/domain"
|
||||||
|
"tercul/internal/platform/config"
|
||||||
|
|
||||||
"go.opentelemetry.io/otel"
|
"go.opentelemetry.io/otel"
|
||||||
"go.opentelemetry.io/otel/trace"
|
"go.opentelemetry.io/otel/trace"
|
||||||
@ -17,9 +18,9 @@ type placeRepository struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewPlaceRepository creates a new PlaceRepository.
|
// NewPlaceRepository creates a new PlaceRepository.
|
||||||
func NewPlaceRepository(db *gorm.DB) domain.PlaceRepository {
|
func NewPlaceRepository(db *gorm.DB, cfg *config.Config) domain.PlaceRepository {
|
||||||
return &placeRepository{
|
return &placeRepository{
|
||||||
BaseRepository: NewBaseRepositoryImpl[domain.Place](db),
|
BaseRepository: NewBaseRepositoryImpl[domain.Place](db, cfg),
|
||||||
db: db,
|
db: db,
|
||||||
tracer: otel.Tracer("place.repository"),
|
tracer: otel.Tracer("place.repository"),
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,6 +3,7 @@ package sql
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"tercul/internal/domain"
|
"tercul/internal/domain"
|
||||||
|
"tercul/internal/platform/config"
|
||||||
|
|
||||||
"go.opentelemetry.io/otel"
|
"go.opentelemetry.io/otel"
|
||||||
"go.opentelemetry.io/otel/trace"
|
"go.opentelemetry.io/otel/trace"
|
||||||
@ -16,9 +17,9 @@ type publisherRepository struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewPublisherRepository creates a new PublisherRepository.
|
// NewPublisherRepository creates a new PublisherRepository.
|
||||||
func NewPublisherRepository(db *gorm.DB) domain.PublisherRepository {
|
func NewPublisherRepository(db *gorm.DB, cfg *config.Config) domain.PublisherRepository {
|
||||||
return &publisherRepository{
|
return &publisherRepository{
|
||||||
BaseRepository: NewBaseRepositoryImpl[domain.Publisher](db),
|
BaseRepository: NewBaseRepositoryImpl[domain.Publisher](db, cfg),
|
||||||
db: db,
|
db: db,
|
||||||
tracer: otel.Tracer("publisher.repository"),
|
tracer: otel.Tracer("publisher.repository"),
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import (
|
|||||||
"tercul/internal/domain/auth"
|
"tercul/internal/domain/auth"
|
||||||
"tercul/internal/domain/localization"
|
"tercul/internal/domain/localization"
|
||||||
"tercul/internal/domain/work"
|
"tercul/internal/domain/work"
|
||||||
|
"tercul/internal/platform/config"
|
||||||
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
@ -26,31 +27,33 @@ type Repositories struct {
|
|||||||
Source domain.SourceRepository
|
Source domain.SourceRepository
|
||||||
Copyright domain.CopyrightRepository
|
Copyright domain.CopyrightRepository
|
||||||
Monetization domain.MonetizationRepository
|
Monetization domain.MonetizationRepository
|
||||||
|
Contribution domain.ContributionRepository
|
||||||
Analytics analytics.Repository
|
Analytics analytics.Repository
|
||||||
Auth auth.AuthRepository
|
Auth auth.AuthRepository
|
||||||
Localization localization.LocalizationRepository
|
Localization localization.LocalizationRepository
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRepositories creates a new Repositories container
|
// NewRepositories creates a new Repositories container
|
||||||
func NewRepositories(db *gorm.DB) *Repositories {
|
func NewRepositories(db *gorm.DB, cfg *config.Config) *Repositories {
|
||||||
return &Repositories{
|
return &Repositories{
|
||||||
Work: NewWorkRepository(db),
|
Work: NewWorkRepository(db, cfg),
|
||||||
User: NewUserRepository(db),
|
User: NewUserRepository(db, cfg),
|
||||||
Author: NewAuthorRepository(db),
|
Author: NewAuthorRepository(db, cfg),
|
||||||
Translation: NewTranslationRepository(db),
|
Translation: NewTranslationRepository(db, cfg),
|
||||||
Comment: NewCommentRepository(db),
|
Comment: NewCommentRepository(db, cfg),
|
||||||
Like: NewLikeRepository(db),
|
Like: NewLikeRepository(db, cfg),
|
||||||
Bookmark: NewBookmarkRepository(db),
|
Bookmark: NewBookmarkRepository(db, cfg),
|
||||||
Collection: NewCollectionRepository(db),
|
Collection: NewCollectionRepository(db, cfg),
|
||||||
Tag: NewTagRepository(db),
|
Tag: NewTagRepository(db, cfg),
|
||||||
Category: NewCategoryRepository(db),
|
Category: NewCategoryRepository(db, cfg),
|
||||||
Book: NewBookRepository(db),
|
Book: NewBookRepository(db, cfg),
|
||||||
Publisher: NewPublisherRepository(db),
|
Publisher: NewPublisherRepository(db, cfg),
|
||||||
Source: NewSourceRepository(db),
|
Source: NewSourceRepository(db, cfg),
|
||||||
Copyright: NewCopyrightRepository(db),
|
Copyright: NewCopyrightRepository(db, cfg),
|
||||||
Monetization: NewMonetizationRepository(db),
|
Monetization: NewMonetizationRepository(db, cfg),
|
||||||
Analytics: NewAnalyticsRepository(db),
|
Contribution: NewContributionRepository(db, cfg),
|
||||||
Auth: NewAuthRepository(db),
|
Analytics: NewAnalyticsRepository(db, cfg),
|
||||||
Localization: NewLocalizationRepository(db),
|
Auth: NewAuthRepository(db, cfg),
|
||||||
|
Localization: NewLocalizationRepository(db, cfg),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"tercul/internal/domain"
|
"tercul/internal/domain"
|
||||||
|
"tercul/internal/platform/config"
|
||||||
|
|
||||||
"go.opentelemetry.io/otel"
|
"go.opentelemetry.io/otel"
|
||||||
"go.opentelemetry.io/otel/trace"
|
"go.opentelemetry.io/otel/trace"
|
||||||
@ -17,9 +18,9 @@ type sourceRepository struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewSourceRepository creates a new SourceRepository.
|
// NewSourceRepository creates a new SourceRepository.
|
||||||
func NewSourceRepository(db *gorm.DB) domain.SourceRepository {
|
func NewSourceRepository(db *gorm.DB, cfg *config.Config) domain.SourceRepository {
|
||||||
return &sourceRepository{
|
return &sourceRepository{
|
||||||
BaseRepository: NewBaseRepositoryImpl[domain.Source](db),
|
BaseRepository: NewBaseRepositoryImpl[domain.Source](db, cfg),
|
||||||
db: db,
|
db: db,
|
||||||
tracer: otel.Tracer("source.repository"),
|
tracer: otel.Tracer("source.repository"),
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"tercul/internal/domain"
|
"tercul/internal/domain"
|
||||||
|
"tercul/internal/platform/config"
|
||||||
|
|
||||||
"go.opentelemetry.io/otel"
|
"go.opentelemetry.io/otel"
|
||||||
"go.opentelemetry.io/otel/trace"
|
"go.opentelemetry.io/otel/trace"
|
||||||
@ -17,9 +18,9 @@ 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, cfg *config.Config) domain.TagRepository {
|
||||||
return &tagRepository{
|
return &tagRepository{
|
||||||
BaseRepository: NewBaseRepositoryImpl[domain.Tag](db),
|
BaseRepository: NewBaseRepositoryImpl[domain.Tag](db, cfg),
|
||||||
db: db,
|
db: db,
|
||||||
tracer: otel.Tracer("tag.repository"),
|
tracer: otel.Tracer("tag.repository"),
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,6 +3,7 @@ package sql
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"tercul/internal/domain"
|
"tercul/internal/domain"
|
||||||
|
"tercul/internal/platform/config"
|
||||||
|
|
||||||
"go.opentelemetry.io/otel"
|
"go.opentelemetry.io/otel"
|
||||||
"go.opentelemetry.io/otel/trace"
|
"go.opentelemetry.io/otel/trace"
|
||||||
@ -17,9 +18,9 @@ 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, cfg *config.Config) domain.TranslationRepository {
|
||||||
return &translationRepository{
|
return &translationRepository{
|
||||||
BaseRepository: NewBaseRepositoryImpl[domain.Translation](db),
|
BaseRepository: NewBaseRepositoryImpl[domain.Translation](db, cfg),
|
||||||
db: db,
|
db: db,
|
||||||
tracer: otel.Tracer("translation.repository"),
|
tracer: otel.Tracer("translation.repository"),
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"tercul/internal/domain"
|
"tercul/internal/domain"
|
||||||
|
"tercul/internal/platform/config"
|
||||||
|
|
||||||
"go.opentelemetry.io/otel"
|
"go.opentelemetry.io/otel"
|
||||||
"go.opentelemetry.io/otel/trace"
|
"go.opentelemetry.io/otel/trace"
|
||||||
@ -17,9 +18,9 @@ type userProfileRepository struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewUserProfileRepository creates a new UserProfileRepository.
|
// NewUserProfileRepository creates a new UserProfileRepository.
|
||||||
func NewUserProfileRepository(db *gorm.DB) domain.UserProfileRepository {
|
func NewUserProfileRepository(db *gorm.DB, cfg *config.Config) domain.UserProfileRepository {
|
||||||
return &userProfileRepository{
|
return &userProfileRepository{
|
||||||
BaseRepository: NewBaseRepositoryImpl[domain.UserProfile](db),
|
BaseRepository: NewBaseRepositoryImpl[domain.UserProfile](db, cfg),
|
||||||
db: db,
|
db: db,
|
||||||
tracer: otel.Tracer("user_profile.repository"),
|
tracer: otel.Tracer("user_profile.repository"),
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"tercul/internal/domain"
|
"tercul/internal/domain"
|
||||||
|
"tercul/internal/platform/config"
|
||||||
|
|
||||||
"go.opentelemetry.io/otel"
|
"go.opentelemetry.io/otel"
|
||||||
"go.opentelemetry.io/otel/trace"
|
"go.opentelemetry.io/otel/trace"
|
||||||
@ -17,9 +18,9 @@ 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, cfg *config.Config) domain.UserRepository {
|
||||||
return &userRepository{
|
return &userRepository{
|
||||||
BaseRepository: NewBaseRepositoryImpl[domain.User](db),
|
BaseRepository: NewBaseRepositoryImpl[domain.User](db, cfg),
|
||||||
db: db,
|
db: db,
|
||||||
tracer: otel.Tracer("user.repository"),
|
tracer: otel.Tracer("user.repository"),
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"tercul/internal/domain"
|
"tercul/internal/domain"
|
||||||
|
"tercul/internal/platform/config"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"go.opentelemetry.io/otel"
|
"go.opentelemetry.io/otel"
|
||||||
@ -18,9 +19,9 @@ type userSessionRepository struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewUserSessionRepository creates a new UserSessionRepository.
|
// NewUserSessionRepository creates a new UserSessionRepository.
|
||||||
func NewUserSessionRepository(db *gorm.DB) domain.UserSessionRepository {
|
func NewUserSessionRepository(db *gorm.DB, cfg *config.Config) domain.UserSessionRepository {
|
||||||
return &userSessionRepository{
|
return &userSessionRepository{
|
||||||
BaseRepository: NewBaseRepositoryImpl[domain.UserSession](db),
|
BaseRepository: NewBaseRepositoryImpl[domain.UserSession](db, cfg),
|
||||||
db: db,
|
db: db,
|
||||||
tracer: otel.Tracer("user_session.repository"),
|
tracer: otel.Tracer("user_session.repository"),
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"tercul/internal/domain"
|
"tercul/internal/domain"
|
||||||
"tercul/internal/domain/work"
|
"tercul/internal/domain/work"
|
||||||
|
"tercul/internal/platform/config"
|
||||||
|
|
||||||
"go.opentelemetry.io/otel"
|
"go.opentelemetry.io/otel"
|
||||||
"go.opentelemetry.io/otel/trace"
|
"go.opentelemetry.io/otel/trace"
|
||||||
@ -19,9 +20,9 @@ type workRepository struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewWorkRepository creates a new WorkRepository.
|
// NewWorkRepository creates a new WorkRepository.
|
||||||
func NewWorkRepository(db *gorm.DB) work.WorkRepository {
|
func NewWorkRepository(db *gorm.DB, cfg *config.Config) work.WorkRepository {
|
||||||
return &workRepository{
|
return &workRepository{
|
||||||
BaseRepository: NewBaseRepositoryImpl[work.Work](db),
|
BaseRepository: NewBaseRepositoryImpl[work.Work](db, cfg),
|
||||||
db: db,
|
db: db,
|
||||||
tracer: otel.Tracer("work.repository"),
|
tracer: otel.Tracer("work.repository"),
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import (
|
|||||||
"tercul/internal/data/sql"
|
"tercul/internal/data/sql"
|
||||||
"tercul/internal/domain"
|
"tercul/internal/domain"
|
||||||
"tercul/internal/domain/work"
|
"tercul/internal/domain/work"
|
||||||
|
"tercul/internal/platform/config"
|
||||||
"tercul/internal/testutil"
|
"tercul/internal/testutil"
|
||||||
|
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
@ -18,7 +19,9 @@ type WorkRepositoryTestSuite struct {
|
|||||||
|
|
||||||
func (s *WorkRepositoryTestSuite) SetupSuite() {
|
func (s *WorkRepositoryTestSuite) SetupSuite() {
|
||||||
s.IntegrationTestSuite.SetupSuite(testutil.DefaultTestConfig())
|
s.IntegrationTestSuite.SetupSuite(testutil.DefaultTestConfig())
|
||||||
s.WorkRepo = sql.NewWorkRepository(s.DB)
|
cfg, err := config.LoadConfig()
|
||||||
|
s.Require().NoError(err)
|
||||||
|
s.WorkRepo = sql.NewWorkRepository(s.DB, cfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *WorkRepositoryTestSuite) TestCreateWork() {
|
func (s *WorkRepositoryTestSuite) TestCreateWork() {
|
||||||
|
|||||||
@ -31,9 +31,8 @@ type MemoryAnalysisCache struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewMemoryAnalysisCache creates a new MemoryAnalysisCache
|
// NewMemoryAnalysisCache creates a new MemoryAnalysisCache
|
||||||
func NewMemoryAnalysisCache(enabled bool) *MemoryAnalysisCache {
|
func NewMemoryAnalysisCache(cfg *config.Config, enabled bool) *MemoryAnalysisCache {
|
||||||
// capacity from config
|
cap := cfg.NLPMemoryCacheCap
|
||||||
cap := config.Cfg.NLPMemoryCacheCap
|
|
||||||
if cap <= 0 {
|
if cap <= 0 {
|
||||||
cap = 1024
|
cap = 1024
|
||||||
}
|
}
|
||||||
@ -82,13 +81,19 @@ func (c *MemoryAnalysisCache) IsEnabled() bool {
|
|||||||
type RedisAnalysisCache struct {
|
type RedisAnalysisCache struct {
|
||||||
cache cache.Cache
|
cache cache.Cache
|
||||||
enabled bool
|
enabled bool
|
||||||
|
ttl time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRedisAnalysisCache creates a new RedisAnalysisCache
|
// NewRedisAnalysisCache creates a new RedisAnalysisCache
|
||||||
func NewRedisAnalysisCache(cache cache.Cache, enabled bool) *RedisAnalysisCache {
|
func NewRedisAnalysisCache(cfg *config.Config, cache cache.Cache, enabled bool) *RedisAnalysisCache {
|
||||||
|
ttlSeconds := cfg.NLPRedisCacheTTLSeconds
|
||||||
|
if ttlSeconds <= 0 {
|
||||||
|
ttlSeconds = 3600 // default 1 hour
|
||||||
|
}
|
||||||
return &RedisAnalysisCache{
|
return &RedisAnalysisCache{
|
||||||
cache: cache,
|
cache: cache,
|
||||||
enabled: enabled,
|
enabled: enabled,
|
||||||
|
ttl: time.Duration(ttlSeconds) * time.Second,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -113,9 +118,7 @@ func (c *RedisAnalysisCache) Set(ctx context.Context, key string, result *Analys
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TTL from config
|
err := c.cache.Set(ctx, key, result, c.ttl)
|
||||||
ttlSeconds := config.Cfg.NLPRedisCacheTTLSeconds
|
|
||||||
err := c.cache.Set(ctx, key, result, time.Duration(ttlSeconds)*time.Second)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.FromContext(ctx).With("key", key).Error(err, "Failed to cache analysis result")
|
log.FromContext(ctx).With("key", key).Error(err, "Failed to cache analysis result")
|
||||||
return err
|
return err
|
||||||
@ -189,4 +192,4 @@ func (c *CompositeAnalysisCache) Set(ctx context.Context, key string, result *An
|
|||||||
// IsEnabled returns whether caching is enabled
|
// IsEnabled returns whether caching is enabled
|
||||||
func (c *CompositeAnalysisCache) IsEnabled() bool {
|
func (c *CompositeAnalysisCache) IsEnabled() bool {
|
||||||
return c.enabled
|
return c.enabled
|
||||||
}
|
}
|
||||||
@ -19,6 +19,7 @@ type LinguisticsFactory struct {
|
|||||||
|
|
||||||
// NewLinguisticsFactory creates a new LinguisticsFactory with all components
|
// NewLinguisticsFactory creates a new LinguisticsFactory with all components
|
||||||
func NewLinguisticsFactory(
|
func NewLinguisticsFactory(
|
||||||
|
cfg *config.Config,
|
||||||
db *gorm.DB,
|
db *gorm.DB,
|
||||||
cache cache.Cache,
|
cache cache.Cache,
|
||||||
concurrency int,
|
concurrency int,
|
||||||
@ -32,18 +33,18 @@ func NewLinguisticsFactory(
|
|||||||
textAnalyzer = textAnalyzer.WithSentimentProvider(sentimentProvider)
|
textAnalyzer = textAnalyzer.WithSentimentProvider(sentimentProvider)
|
||||||
|
|
||||||
// Wire language detector: lingua-go (configurable)
|
// Wire language detector: lingua-go (configurable)
|
||||||
if config.Cfg.NLPUseLingua {
|
if cfg.NLPUseLingua {
|
||||||
textAnalyzer = textAnalyzer.WithLanguageDetector(NewLinguaLanguageDetector())
|
textAnalyzer = textAnalyzer.WithLanguageDetector(NewLinguaLanguageDetector())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wire keyword provider: lightweight TF-IDF approximation (configurable)
|
// Wire keyword provider: lightweight TF-IDF approximation (configurable)
|
||||||
if config.Cfg.NLPUseTFIDF {
|
if cfg.NLPUseTFIDF {
|
||||||
textAnalyzer = textAnalyzer.WithKeywordProvider(NewTFIDFKeywordProvider())
|
textAnalyzer = textAnalyzer.WithKeywordProvider(NewTFIDFKeywordProvider())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create cache components
|
// Create cache components
|
||||||
memoryCache := NewMemoryAnalysisCache(cacheEnabled)
|
memoryCache := NewMemoryAnalysisCache(cfg, cacheEnabled)
|
||||||
redisCache := NewRedisAnalysisCache(cache, cacheEnabled)
|
redisCache := NewRedisAnalysisCache(cfg, cache, cacheEnabled)
|
||||||
analysisCache := NewCompositeAnalysisCache(memoryCache, redisCache, cacheEnabled)
|
analysisCache := NewCompositeAnalysisCache(memoryCache, redisCache, cacheEnabled)
|
||||||
|
|
||||||
// Create repository
|
// Create repository
|
||||||
@ -105,4 +106,4 @@ func (f *LinguisticsFactory) GetAnalyzer() Analyzer {
|
|||||||
// GetSentimentProvider returns the sentiment provider
|
// GetSentimentProvider returns the sentiment provider
|
||||||
func (f *LinguisticsFactory) GetSentimentProvider() SentimentProvider {
|
func (f *LinguisticsFactory) GetSentimentProvider() SentimentProvider {
|
||||||
return f.sentimentProvider
|
return f.sentimentProvider
|
||||||
}
|
}
|
||||||
@ -1,13 +1,17 @@
|
|||||||
package linguistics
|
package linguistics
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/stretchr/testify/require"
|
"tercul/internal/platform/config"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestFactory_WiresProviders(t *testing.T) {
|
func TestFactory_WiresProviders(t *testing.T) {
|
||||||
// We won't spin a DB/cache here; this is a smoke test of wiring methods
|
// We won't spin a DB/cache here; this is a smoke test of wiring methods
|
||||||
f := NewLinguisticsFactory(nil, nil, 2, true, nil)
|
cfg, err := config.LoadConfig()
|
||||||
|
require.NoError(t, err)
|
||||||
|
f := NewLinguisticsFactory(cfg, nil, nil, 2, true, nil)
|
||||||
ta := f.GetTextAnalyzer().(*BasicTextAnalyzer)
|
ta := f.GetTextAnalyzer().(*BasicTextAnalyzer)
|
||||||
require.NotNil(t, ta)
|
require.NotNil(t, ta)
|
||||||
}
|
}
|
||||||
@ -17,8 +17,8 @@ type BatchProcessor struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewBatchProcessor creates a new BatchProcessor
|
// NewBatchProcessor creates a new BatchProcessor
|
||||||
func NewBatchProcessor(db *gorm.DB) *BatchProcessor {
|
func NewBatchProcessor(db *gorm.DB, cfg *config.Config) *BatchProcessor {
|
||||||
batchSize := config.Cfg.BatchSize
|
batchSize := cfg.BatchSize
|
||||||
if batchSize <= 0 {
|
if batchSize <= 0 {
|
||||||
batchSize = DefaultBatchSize
|
batchSize = DefaultBatchSize
|
||||||
}
|
}
|
||||||
|
|||||||
@ -54,6 +54,6 @@ func (s *SyncJob) SyncEdgesBatch(ctx context.Context, batchSize, offset int) err
|
|||||||
edgeMaps = append(edgeMaps, edgeMap)
|
edgeMaps = append(edgeMaps, edgeMap)
|
||||||
}
|
}
|
||||||
|
|
||||||
batchProcessor := NewBatchProcessor(s.DB)
|
batchProcessor := NewBatchProcessor(s.DB, s.Cfg)
|
||||||
return batchProcessor.CreateObjectsBatch(ctx, "Edge", edgeMaps)
|
return batchProcessor.CreateObjectsBatch(ctx, "Edge", edgeMaps)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -76,6 +76,6 @@ func (s *SyncJob) SyncAllEntities(ctx context.Context) error {
|
|||||||
|
|
||||||
// syncEntities is a generic function to sync a given entity type.
|
// syncEntities is a generic function to sync a given entity type.
|
||||||
func (s *SyncJob) syncEntities(className string, ctx context.Context) error {
|
func (s *SyncJob) syncEntities(className string, ctx context.Context) error {
|
||||||
batchProcessor := NewBatchProcessor(s.DB)
|
batchProcessor := NewBatchProcessor(s.DB, s.Cfg)
|
||||||
return batchProcessor.ProcessAllEntities(ctx, className)
|
return batchProcessor.ProcessAllEntities(ctx, className)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -64,6 +64,6 @@ func RegisterQueueHandlers(srv *asynq.Server, syncJob *SyncJob) {
|
|||||||
mux.HandleFunc(TaskEntitySync, syncJob.HandleEntitySync)
|
mux.HandleFunc(TaskEntitySync, syncJob.HandleEntitySync)
|
||||||
mux.HandleFunc(TaskEdgeSync, syncJob.HandleEdgeSync)
|
mux.HandleFunc(TaskEdgeSync, syncJob.HandleEdgeSync)
|
||||||
if err := srv.Run(mux); err != nil {
|
if err := srv.Run(mux); err != nil {
|
||||||
log.Fatalf("Failed to start asynq server: %v", err)
|
log.Printf("Failed to start asynq server: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,6 +3,7 @@ package sync
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"log"
|
"log"
|
||||||
|
"tercul/internal/platform/config"
|
||||||
|
|
||||||
"github.com/hibiken/asynq"
|
"github.com/hibiken/asynq"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
@ -12,13 +13,15 @@ import (
|
|||||||
type SyncJob struct {
|
type SyncJob struct {
|
||||||
DB *gorm.DB
|
DB *gorm.DB
|
||||||
AsynqClient *asynq.Client
|
AsynqClient *asynq.Client
|
||||||
|
Cfg *config.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSyncJob initializes a new SyncJob.
|
// NewSyncJob initializes a new SyncJob.
|
||||||
func NewSyncJob(db *gorm.DB, aClient *asynq.Client) *SyncJob {
|
func NewSyncJob(db *gorm.DB, aClient *asynq.Client, cfg *config.Config) *SyncJob {
|
||||||
return &SyncJob{
|
return &SyncJob{
|
||||||
DB: db,
|
DB: db,
|
||||||
AsynqClient: aClient,
|
AsynqClient: aClient,
|
||||||
|
Cfg: cfg,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -41,16 +41,17 @@ type JWTManager struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewJWTManager creates a new JWT manager
|
// NewJWTManager creates a new JWT manager
|
||||||
func NewJWTManager() *JWTManager {
|
func NewJWTManager(cfg *config.Config) *JWTManager {
|
||||||
secretKey := config.Cfg.JWTSecret
|
secretKey := cfg.JWTSecret
|
||||||
if secretKey == "" {
|
if secretKey == "" {
|
||||||
secretKey = "default-secret-key-change-in-production"
|
secretKey = "default-secret-key-change-in-production"
|
||||||
}
|
}
|
||||||
|
|
||||||
duration := config.Cfg.JWTExpiration
|
durationInHours := cfg.JWTExpiration
|
||||||
if duration == 0 {
|
if durationInHours <= 0 {
|
||||||
duration = 24 * time.Hour // Default to 24 hours
|
durationInHours = 24 // Default to 24 hours
|
||||||
}
|
}
|
||||||
|
duration := time.Duration(durationInHours) * time.Hour
|
||||||
|
|
||||||
return &JWTManager{
|
return &JWTManager{
|
||||||
secretKey: []byte(secretKey),
|
secretKey: []byte(secretKey),
|
||||||
|
|||||||
@ -54,7 +54,7 @@ func AuthMiddleware(jwtManager *JWTManager) func(http.Handler) http.Handler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RoleMiddleware creates middleware for role-based authorization
|
// RoleMiddleware creates middleware for role-based authorization
|
||||||
func RoleMiddleware(requiredRole string) func(http.Handler) http.Handler {
|
func RoleMiddleware(jwtManager *JWTManager, requiredRole string) func(http.Handler) http.Handler {
|
||||||
return func(next http.Handler) http.Handler {
|
return func(next http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
logger := log.FromContext(r.Context())
|
logger := log.FromContext(r.Context())
|
||||||
@ -65,7 +65,6 @@ func RoleMiddleware(requiredRole string) func(http.Handler) http.Handler {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
jwtManager := NewJWTManager()
|
|
||||||
if err := jwtManager.RequireRole(claims.Role, requiredRole); err != nil {
|
if err := jwtManager.RequireRole(claims.Role, requiredRole); err != nil {
|
||||||
logger.With("user_role", claims.Role).With("required_role", requiredRole).Warn("Authorization failed - insufficient role")
|
logger.With("user_role", claims.Role).With("required_role", requiredRole).Warn("Authorization failed - insufficient role")
|
||||||
http.Error(w, "Forbidden", http.StatusForbidden)
|
http.Error(w, "Forbidden", http.StatusForbidden)
|
||||||
@ -142,13 +141,12 @@ func RequireAuth(ctx context.Context) (*Claims, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RequireRole ensures the user has the required role
|
// RequireRole ensures the user has the required role
|
||||||
func RequireRole(ctx context.Context, requiredRole string) (*Claims, error) {
|
func RequireRole(ctx context.Context, jwtManager *JWTManager, requiredRole string) (*Claims, error) {
|
||||||
claims, err := RequireAuth(ctx)
|
claims, err := RequireAuth(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
jwtManager := NewJWTManager()
|
|
||||||
if err := jwtManager.RequireRole(claims.Role, requiredRole); err != nil {
|
if err := jwtManager.RequireRole(claims.Role, requiredRole); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,24 +6,29 @@ import (
|
|||||||
|
|
||||||
// Config stores all configuration of the application.
|
// Config stores all configuration of the application.
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Environment string `mapstructure:"ENVIRONMENT"`
|
Environment string `mapstructure:"ENVIRONMENT"`
|
||||||
ServerPort string `mapstructure:"SERVER_PORT"`
|
ServerPort string `mapstructure:"SERVER_PORT"`
|
||||||
DBHost string `mapstructure:"DB_HOST"`
|
DBHost string `mapstructure:"DB_HOST"`
|
||||||
DBPort string `mapstructure:"DB_PORT"`
|
DBPort string `mapstructure:"DB_PORT"`
|
||||||
DBUser string `mapstructure:"DB_USER"`
|
DBUser string `mapstructure:"DB_USER"`
|
||||||
DBPassword string `mapstructure:"DB_PASSWORD"`
|
DBPassword string `mapstructure:"DB_PASSWORD"`
|
||||||
DBName string `mapstructure:"DB_NAME"`
|
DBName string `mapstructure:"DB_NAME"`
|
||||||
JWTSecret string `mapstructure:"JWT_SECRET"`
|
JWTSecret string `mapstructure:"JWT_SECRET"`
|
||||||
JWTExpiration int `mapstructure:"JWT_EXPIRATION_HOURS"`
|
JWTExpiration int `mapstructure:"JWT_EXPIRATION_HOURS"`
|
||||||
WeaviateHost string `mapstructure:"WEAVIATE_HOST"`
|
WeaviateHost string `mapstructure:"WEAVIATE_HOST"`
|
||||||
WeaviateScheme string `mapstructure:"WEAVIATE_SCHEME"`
|
WeaviateScheme string `mapstructure:"WEAVIATE_SCHEME"`
|
||||||
MigrationPath string `mapstructure:"MIGRATION_PATH"`
|
MigrationPath string `mapstructure:"MIGRATION_PATH"`
|
||||||
RedisAddr string `mapstructure:"REDIS_ADDR"`
|
RedisAddr string `mapstructure:"REDIS_ADDR"`
|
||||||
RedisPassword string `mapstructure:"REDIS_PASSWORD"`
|
RedisPassword string `mapstructure:"REDIS_PASSWORD"`
|
||||||
RedisDB int `mapstructure:"REDIS_DB"`
|
RedisDB int `mapstructure:"REDIS_DB"`
|
||||||
SyncBatchSize int `mapstructure:"SYNC_BATCH_SIZE"`
|
BatchSize int `mapstructure:"BATCH_SIZE"`
|
||||||
RateLimit int `mapstructure:"RATE_LIMIT"`
|
RateLimit int `mapstructure:"RATE_LIMIT"`
|
||||||
RateLimitBurst int `mapstructure:"RATE_LIMIT_BURST"`
|
RateLimitBurst int `mapstructure:"RATE_LIMIT_BURST"`
|
||||||
|
PageSize int `mapstructure:"PAGE_SIZE"`
|
||||||
|
NLPMemoryCacheCap int `mapstructure:"NLP_MEMORY_CACHE_CAP"`
|
||||||
|
NLPRedisCacheTTLSeconds int `mapstructure:"NLP_REDIS_CACHE_TTL_SECONDS"`
|
||||||
|
NLPUseLingua bool `mapstructure:"NLP_USE_LINGUA"`
|
||||||
|
NLPUseTFIDF bool `mapstructure:"NLP_USE_TFIDF"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadConfig reads configuration from file or environment variables.
|
// LoadConfig reads configuration from file or environment variables.
|
||||||
@ -43,9 +48,13 @@ func LoadConfig() (*Config, error) {
|
|||||||
viper.SetDefault("REDIS_ADDR", "localhost:6379")
|
viper.SetDefault("REDIS_ADDR", "localhost:6379")
|
||||||
viper.SetDefault("REDIS_PASSWORD", "")
|
viper.SetDefault("REDIS_PASSWORD", "")
|
||||||
viper.SetDefault("REDIS_DB", 0)
|
viper.SetDefault("REDIS_DB", 0)
|
||||||
viper.SetDefault("SYNC_BATCH_SIZE", 100)
|
viper.SetDefault("BATCH_SIZE", 100)
|
||||||
viper.SetDefault("RATE_LIMIT", 10)
|
viper.SetDefault("RATE_LIMIT", 10)
|
||||||
viper.SetDefault("RATE_LIMIT_BURST", 100)
|
viper.SetDefault("RATE_LIMIT_BURST", 100)
|
||||||
|
viper.SetDefault("NLP_MEMORY_CACHE_CAP", 1024)
|
||||||
|
viper.SetDefault("NLP_REDIS_CACHE_TTL_SECONDS", 3600)
|
||||||
|
viper.SetDefault("NLP_USE_LINGUA", true)
|
||||||
|
viper.SetDefault("NLP_USE_TFIDF", true)
|
||||||
|
|
||||||
viper.AutomaticEnv()
|
viper.AutomaticEnv()
|
||||||
|
|
||||||
|
|||||||
@ -21,10 +21,12 @@ type RateLimiter struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewRateLimiter creates a new rate limiter
|
// NewRateLimiter creates a new rate limiter
|
||||||
func NewRateLimiter(rate, capacity int) *RateLimiter {
|
func NewRateLimiter(cfg *config.Config) *RateLimiter {
|
||||||
|
rate := cfg.RateLimit
|
||||||
if rate <= 0 {
|
if rate <= 0 {
|
||||||
rate = 10 // default rate: 10 requests per second
|
rate = 10 // default rate: 10 requests per second
|
||||||
}
|
}
|
||||||
|
capacity := cfg.RateLimitBurst
|
||||||
if capacity <= 0 {
|
if capacity <= 0 {
|
||||||
capacity = 100 // default capacity: 100 tokens
|
capacity = 100 // default capacity: 100 tokens
|
||||||
}
|
}
|
||||||
@ -73,28 +75,29 @@ func minF(a, b float64) float64 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RateLimitMiddleware creates a middleware that applies rate limiting
|
// RateLimitMiddleware creates a middleware that applies rate limiting
|
||||||
func RateLimitMiddleware(next http.Handler) http.Handler {
|
func RateLimitMiddleware(cfg *config.Config) func(http.Handler) http.Handler {
|
||||||
rateLimiter := NewRateLimiter(config.Cfg.RateLimit, config.Cfg.RateLimitBurst)
|
rateLimiter := NewRateLimiter(cfg)
|
||||||
|
return func(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// Use X-Client-ID header for client identification in tests
|
||||||
|
clientID := r.Header.Get("X-Client-ID")
|
||||||
|
if clientID == "" {
|
||||||
|
clientID = r.RemoteAddr
|
||||||
|
}
|
||||||
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
// Check if request is allowed
|
||||||
// Use X-Client-ID header for client identification in tests
|
if !rateLimiter.Allow(clientID) {
|
||||||
clientID := r.Header.Get("X-Client-ID")
|
log.FromContext(r.Context()).
|
||||||
if clientID == "" {
|
With("clientID", clientID).
|
||||||
clientID = r.RemoteAddr
|
Warn("Rate limit exceeded")
|
||||||
}
|
|
||||||
|
|
||||||
// Check if request is allowed
|
w.WriteHeader(http.StatusTooManyRequests)
|
||||||
if !rateLimiter.Allow(clientID) {
|
w.Write([]byte("Rate limit exceeded. Please try again later."))
|
||||||
log.FromContext(r.Context()).
|
return
|
||||||
With("clientID", clientID).
|
}
|
||||||
Warn("Rate limit exceeded")
|
|
||||||
|
|
||||||
w.WriteHeader(http.StatusTooManyRequests)
|
// Continue to the next handler
|
||||||
w.Write([]byte("Rate limit exceeded. Please try again later."))
|
next.ServeHTTP(w, r)
|
||||||
return
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Continue to the next handler
|
|
||||||
next.ServeHTTP(w, r)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,10 +4,9 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
|
||||||
|
|
||||||
"tercul/internal/platform/config"
|
"tercul/internal/platform/config"
|
||||||
platformhttp "tercul/internal/platform/http"
|
platformhttp "tercul/internal/platform/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
@ -20,8 +19,8 @@ type RateLimiterSuite struct {
|
|||||||
|
|
||||||
// TestRateLimiter tests the RateLimiter
|
// TestRateLimiter tests the RateLimiter
|
||||||
func (s *RateLimiterSuite) TestRateLimiter() {
|
func (s *RateLimiterSuite) TestRateLimiter() {
|
||||||
// Create a new rate limiter with 2 requests per second and a burst of 3
|
cfg := &config.Config{RateLimit: 2, RateLimitBurst: 3}
|
||||||
limiter := platformhttp.NewRateLimiter(2, 3)
|
limiter := platformhttp.NewRateLimiter(cfg)
|
||||||
|
|
||||||
// Test that the first 3 requests are allowed (burst)
|
// Test that the first 3 requests are allowed (burst)
|
||||||
for i := 0; i < 3; i++ {
|
for i := 0; i < 3; i++ {
|
||||||
@ -49,8 +48,8 @@ func (s *RateLimiterSuite) TestRateLimiter() {
|
|||||||
|
|
||||||
// TestRateLimiterMultipleClients tests the RateLimiter with multiple clients
|
// TestRateLimiterMultipleClients tests the RateLimiter with multiple clients
|
||||||
func (s *RateLimiterSuite) TestRateLimiterMultipleClients() {
|
func (s *RateLimiterSuite) TestRateLimiterMultipleClients() {
|
||||||
// Create a new rate limiter with 2 requests per second and a burst of 3
|
cfg := &config.Config{RateLimit: 2, RateLimitBurst: 3}
|
||||||
limiter := platformhttp.NewRateLimiter(2, 3)
|
limiter := platformhttp.NewRateLimiter(cfg)
|
||||||
|
|
||||||
// Test that the first 3 requests for client1 are allowed (burst)
|
// Test that the first 3 requests for client1 are allowed (burst)
|
||||||
for i := 0; i < 3; i++ {
|
for i := 0; i < 3; i++ {
|
||||||
@ -75,17 +74,15 @@ func (s *RateLimiterSuite) TestRateLimiterMultipleClients() {
|
|||||||
|
|
||||||
// TestRateLimiterMiddleware tests the RateLimiterMiddleware
|
// TestRateLimiterMiddleware tests the RateLimiterMiddleware
|
||||||
func (s *RateLimiterSuite) TestRateLimiterMiddleware() {
|
func (s *RateLimiterSuite) TestRateLimiterMiddleware() {
|
||||||
// Set config to match test expectations
|
cfg := &config.Config{RateLimit: 2, RateLimitBurst: 3}
|
||||||
config.Cfg.RateLimit = 2
|
|
||||||
config.Cfg.RateLimitBurst = 3
|
|
||||||
|
|
||||||
// Create a test handler that always returns 200 OK
|
// Create a test handler that always returns 200 OK
|
||||||
testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Create a rate limiter middleware with 2 requests per second and a burst of 3
|
// Create a rate limiter middleware
|
||||||
middleware := platformhttp.RateLimitMiddleware(testHandler)
|
middleware := platformhttp.RateLimitMiddleware(cfg)(testHandler)
|
||||||
|
|
||||||
// Create a test server
|
// Create a test server
|
||||||
server := httptest.NewServer(middleware)
|
server := httptest.NewServer(middleware)
|
||||||
@ -144,22 +141,22 @@ func TestRateLimiterSuite(t *testing.T) {
|
|||||||
// TestNewRateLimiter tests the NewRateLimiter function
|
// TestNewRateLimiter tests the NewRateLimiter function
|
||||||
func TestNewRateLimiter(t *testing.T) {
|
func TestNewRateLimiter(t *testing.T) {
|
||||||
// Test with valid parameters
|
// Test with valid parameters
|
||||||
limiter := platformhttp.NewRateLimiter(10, 20)
|
limiter := platformhttp.NewRateLimiter(&config.Config{RateLimit: 10, RateLimitBurst: 20})
|
||||||
assert.NotNil(t, limiter, "NewRateLimiter should return a non-nil limiter")
|
assert.NotNil(t, limiter, "NewRateLimiter should return a non-nil limiter")
|
||||||
|
|
||||||
// Test with zero rate (should use default)
|
// Test with zero rate (should use default)
|
||||||
limiter = platformhttp.NewRateLimiter(0, 20)
|
limiter = platformhttp.NewRateLimiter(&config.Config{RateLimit: 0, RateLimitBurst: 20})
|
||||||
assert.NotNil(t, limiter, "NewRateLimiter should return a non-nil limiter with default rate")
|
assert.NotNil(t, limiter, "NewRateLimiter should return a non-nil limiter with default rate")
|
||||||
|
|
||||||
// Test with zero capacity (should use default)
|
// Test with zero capacity (should use default)
|
||||||
limiter = platformhttp.NewRateLimiter(10, 0)
|
limiter = platformhttp.NewRateLimiter(&config.Config{RateLimit: 10, RateLimitBurst: 0})
|
||||||
assert.NotNil(t, limiter, "NewRateLimiter should return a non-nil limiter with default capacity")
|
assert.NotNil(t, limiter, "NewRateLimiter should return a non-nil limiter with default capacity")
|
||||||
|
|
||||||
// Test with negative rate (should use default)
|
// Test with negative rate (should use default)
|
||||||
limiter = platformhttp.NewRateLimiter(-10, 20)
|
limiter = platformhttp.NewRateLimiter(&config.Config{RateLimit: -10, RateLimitBurst: 20})
|
||||||
assert.NotNil(t, limiter, "NewRateLimiter should return a non-nil limiter with default rate")
|
assert.NotNil(t, limiter, "NewRateLimiter should return a non-nil limiter with default rate")
|
||||||
|
|
||||||
// Test with negative capacity (should use default)
|
// Test with negative capacity (should use default)
|
||||||
limiter = platformhttp.NewRateLimiter(10, -20)
|
limiter = platformhttp.NewRateLimiter(&config.Config{RateLimit: 10, RateLimitBurst: -20})
|
||||||
assert.NotNil(t, limiter, "NewRateLimiter should return a non-nil limiter with default capacity")
|
assert.NotNil(t, limiter, "NewRateLimiter should return a non-nil limiter with default capacity")
|
||||||
}
|
}
|
||||||
@ -11,9 +11,10 @@ import (
|
|||||||
"tercul/internal/data/sql"
|
"tercul/internal/data/sql"
|
||||||
"tercul/internal/domain"
|
"tercul/internal/domain"
|
||||||
"tercul/internal/domain/search"
|
"tercul/internal/domain/search"
|
||||||
platform_auth "tercul/internal/platform/auth"
|
|
||||||
"tercul/internal/domain/work"
|
"tercul/internal/domain/work"
|
||||||
"tercul/internal/jobs/linguistics"
|
"tercul/internal/jobs/linguistics"
|
||||||
|
platform_auth "tercul/internal/platform/auth"
|
||||||
|
platform_config "tercul/internal/platform/config"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
@ -106,21 +107,21 @@ func DefaultTestConfig() *TestConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SetupSuite sets up the test suite with the specified configuration
|
// SetupSuite sets up the test suite with the specified configuration
|
||||||
func (s *IntegrationTestSuite) SetupSuite(config *TestConfig) {
|
func (s *IntegrationTestSuite) SetupSuite(testConfig *TestConfig) {
|
||||||
if config == nil {
|
if testConfig == nil {
|
||||||
config = DefaultTestConfig()
|
testConfig = DefaultTestConfig()
|
||||||
}
|
}
|
||||||
|
|
||||||
var dbPath string
|
var dbPath string
|
||||||
if !config.UseInMemoryDB && config.DBPath != "" {
|
if !testConfig.UseInMemoryDB && testConfig.DBPath != "" {
|
||||||
// Clean up previous test database file before starting
|
// Clean up previous test database file before starting
|
||||||
_ = os.Remove(config.DBPath)
|
_ = os.Remove(testConfig.DBPath)
|
||||||
// Ensure directory exists
|
// Ensure directory exists
|
||||||
dir := filepath.Dir(config.DBPath)
|
dir := filepath.Dir(testConfig.DBPath)
|
||||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||||
s.T().Fatalf("Failed to create database directory: %v", err)
|
s.T().Fatalf("Failed to create database directory: %v", err)
|
||||||
}
|
}
|
||||||
dbPath = config.DBPath
|
dbPath = testConfig.DBPath
|
||||||
} else {
|
} else {
|
||||||
// Use in-memory database
|
// Use in-memory database
|
||||||
dbPath = ":memory:"
|
dbPath = ":memory:"
|
||||||
@ -131,7 +132,7 @@ func (s *IntegrationTestSuite) SetupSuite(config *TestConfig) {
|
|||||||
log.New(os.Stdout, "\r\n", log.LstdFlags),
|
log.New(os.Stdout, "\r\n", log.LstdFlags),
|
||||||
logger.Config{
|
logger.Config{
|
||||||
SlowThreshold: time.Second,
|
SlowThreshold: time.Second,
|
||||||
LogLevel: config.LogLevel,
|
LogLevel: testConfig.LogLevel,
|
||||||
IgnoreRecordNotFoundError: true,
|
IgnoreRecordNotFoundError: true,
|
||||||
Colorful: false,
|
Colorful: false,
|
||||||
},
|
},
|
||||||
@ -155,7 +156,12 @@ func (s *IntegrationTestSuite) SetupSuite(config *TestConfig) {
|
|||||||
&domain.TranslationStats{}, &TestEntity{}, &domain.CollectionWork{},
|
&domain.TranslationStats{}, &TestEntity{}, &domain.CollectionWork{},
|
||||||
)
|
)
|
||||||
|
|
||||||
repos := sql.NewRepositories(s.DB)
|
cfg, err := platform_config.LoadConfig()
|
||||||
|
if err != nil {
|
||||||
|
s.T().Fatalf("Failed to load config: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
repos := sql.NewRepositories(s.DB, cfg)
|
||||||
var searchClient search.SearchClient = &mockSearchClient{}
|
var searchClient search.SearchClient = &mockSearchClient{}
|
||||||
analysisRepo := linguistics.NewGORMAnalysisRepository(s.DB)
|
analysisRepo := linguistics.NewGORMAnalysisRepository(s.DB)
|
||||||
sentimentProvider, err := linguistics.NewGoVADERSentimentProvider()
|
sentimentProvider, err := linguistics.NewGoVADERSentimentProvider()
|
||||||
@ -163,30 +169,31 @@ func (s *IntegrationTestSuite) SetupSuite(config *TestConfig) {
|
|||||||
s.T().Fatalf("Failed to create sentiment provider: %v", err)
|
s.T().Fatalf("Failed to create sentiment provider: %v", err)
|
||||||
}
|
}
|
||||||
analyticsService := analytics.NewService(repos.Analytics, analysisRepo, repos.Translation, repos.Work, sentimentProvider)
|
analyticsService := analytics.NewService(repos.Analytics, analysisRepo, repos.Translation, repos.Work, sentimentProvider)
|
||||||
jwtManager := platform_auth.NewJWTManager()
|
jwtManager := platform_auth.NewJWTManager(cfg)
|
||||||
|
|
||||||
deps := app.Dependencies{
|
deps := app.Dependencies{
|
||||||
WorkRepo: repos.Work,
|
WorkRepo: repos.Work,
|
||||||
UserRepo: repos.User,
|
UserRepo: repos.User,
|
||||||
AuthorRepo: repos.Author,
|
AuthorRepo: repos.Author,
|
||||||
TranslationRepo: repos.Translation,
|
TranslationRepo: repos.Translation,
|
||||||
CommentRepo: repos.Comment,
|
CommentRepo: repos.Comment,
|
||||||
LikeRepo: repos.Like,
|
LikeRepo: repos.Like,
|
||||||
BookmarkRepo: repos.Bookmark,
|
BookmarkRepo: repos.Bookmark,
|
||||||
CollectionRepo: repos.Collection,
|
CollectionRepo: repos.Collection,
|
||||||
TagRepo: repos.Tag,
|
TagRepo: repos.Tag,
|
||||||
CategoryRepo: repos.Category,
|
CategoryRepo: repos.Category,
|
||||||
BookRepo: repos.Book,
|
BookRepo: repos.Book,
|
||||||
PublisherRepo: repos.Publisher,
|
PublisherRepo: repos.Publisher,
|
||||||
SourceRepo: repos.Source,
|
SourceRepo: repos.Source,
|
||||||
CopyrightRepo: repos.Copyright,
|
CopyrightRepo: repos.Copyright,
|
||||||
MonetizationRepo: repos.Monetization,
|
MonetizationRepo: repos.Monetization,
|
||||||
AnalyticsRepo: repos.Analytics,
|
ContributionRepo: repos.Contribution,
|
||||||
AuthRepo: repos.Auth,
|
AnalyticsRepo: repos.Analytics,
|
||||||
LocalizationRepo: repos.Localization,
|
AuthRepo: repos.Auth,
|
||||||
SearchClient: searchClient,
|
LocalizationRepo: repos.Localization,
|
||||||
AnalyticsService: analyticsService,
|
SearchClient: searchClient,
|
||||||
JWTManager: jwtManager,
|
AnalyticsService: analyticsService,
|
||||||
|
JWTManager: jwtManager,
|
||||||
}
|
}
|
||||||
s.App = app.NewApplication(deps)
|
s.App = app.NewApplication(deps)
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user