mirror of
https://github.com/SamyRai/tercul-backend.git
synced 2025-12-27 05:11:34 +00:00
Merge pull request #16 from SamyRai/fix/complete-pending-tasks
Fix: Complete pending tasks and improve code quality
This commit is contained in:
commit
37a007b08c
105
cmd/api/main.go
105
cmd/api/main.go
@ -112,84 +112,83 @@ func main() {
|
|||||||
log.Fatal(err, "Failed to create sentiment provider")
|
log.Fatal(err, "Failed to create sentiment provider")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create platform components
|
||||||
|
jwtManager := auth.NewJWTManager()
|
||||||
|
|
||||||
// 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
|
||||||
|
deps := app.Dependencies{
|
||||||
|
WorkRepo: repos.Work,
|
||||||
|
UserRepo: repos.User,
|
||||||
|
AuthorRepo: repos.Author,
|
||||||
|
TranslationRepo: repos.Translation,
|
||||||
|
CommentRepo: repos.Comment,
|
||||||
|
LikeRepo: repos.Like,
|
||||||
|
BookmarkRepo: repos.Bookmark,
|
||||||
|
CollectionRepo: repos.Collection,
|
||||||
|
TagRepo: repos.Tag,
|
||||||
|
CategoryRepo: repos.Category,
|
||||||
|
BookRepo: repos.Book,
|
||||||
|
PublisherRepo: repos.Publisher,
|
||||||
|
SourceRepo: repos.Source,
|
||||||
|
CopyrightRepo: repos.Copyright,
|
||||||
|
MonetizationRepo: repos.Monetization,
|
||||||
|
AnalyticsRepo: repos.Analytics,
|
||||||
|
AuthRepo: repos.Auth,
|
||||||
|
LocalizationRepo: repos.Localization,
|
||||||
|
SearchClient: searchClient,
|
||||||
|
AnalyticsService: analyticsService,
|
||||||
|
JWTManager: jwtManager,
|
||||||
|
}
|
||||||
|
|
||||||
// Create application
|
// Create application
|
||||||
application := app.NewApplication(repos, searchClient, analyticsService)
|
application := app.NewApplication(deps)
|
||||||
|
|
||||||
// Create GraphQL server
|
// Create GraphQL server
|
||||||
resolver := &graph.Resolver{
|
resolver := &graph.Resolver{
|
||||||
App: application,
|
App: application,
|
||||||
}
|
}
|
||||||
|
|
||||||
jwtManager := auth.NewJWTManager()
|
// Create handlers
|
||||||
srv := NewServerWithAuth(resolver, jwtManager, metrics, obsLogger)
|
apiHandler := NewServerWithAuth(resolver, jwtManager, metrics, obsLogger)
|
||||||
graphQLServer := &http.Server{
|
playgroundHandler := playground.Handler("GraphQL Playground", "/query")
|
||||||
|
metricsHandler := observability.PrometheusHandler(reg)
|
||||||
|
|
||||||
|
// Consolidate handlers into a single mux
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
mux.Handle("/query", apiHandler)
|
||||||
|
mux.Handle("/playground", playgroundHandler)
|
||||||
|
mux.Handle("/metrics", metricsHandler)
|
||||||
|
|
||||||
|
// Create a single HTTP server
|
||||||
|
mainServer := &http.Server{
|
||||||
Addr: config.Cfg.ServerPort,
|
Addr: config.Cfg.ServerPort,
|
||||||
Handler: srv,
|
Handler: mux,
|
||||||
}
|
}
|
||||||
log.Info(fmt.Sprintf("GraphQL server created successfully on port %s", config.Cfg.ServerPort))
|
log.Info(fmt.Sprintf("API server listening on port %s", config.Cfg.ServerPort))
|
||||||
|
|
||||||
// Create GraphQL playground
|
// Start the main server in a goroutine
|
||||||
playgroundHandler := playground.Handler("GraphQL", "/query")
|
|
||||||
playgroundServer := &http.Server{
|
|
||||||
Addr: config.Cfg.PlaygroundPort,
|
|
||||||
Handler: playgroundHandler,
|
|
||||||
}
|
|
||||||
log.Info(fmt.Sprintf("GraphQL playground created successfully on port %s", config.Cfg.PlaygroundPort))
|
|
||||||
|
|
||||||
// Create metrics server
|
|
||||||
metricsServer := &http.Server{
|
|
||||||
Addr: ":9090",
|
|
||||||
Handler: observability.PrometheusHandler(reg),
|
|
||||||
}
|
|
||||||
log.Info("Metrics server created successfully on port :9090")
|
|
||||||
|
|
||||||
// Start HTTP servers in goroutines
|
|
||||||
go func() {
|
go func() {
|
||||||
log.Info(fmt.Sprintf("Starting GraphQL server on port %s", config.Cfg.ServerPort))
|
if err := mainServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||||
if err := graphQLServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
log.Fatal(err, "Failed to start server")
|
||||||
log.Fatal(err, "Failed to start GraphQL server")
|
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
go func() {
|
// Wait for interrupt signal to gracefully shutdown the server
|
||||||
log.Info(fmt.Sprintf("Starting GraphQL playground on port %s", config.Cfg.PlaygroundPort))
|
|
||||||
if err := playgroundServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
|
||||||
log.Fatal(err, "Failed to start GraphQL playground")
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
log.Info("Starting metrics server on port :9090")
|
|
||||||
if err := metricsServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
|
||||||
log.Fatal(err, "Failed to start metrics server")
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
// Wait for interrupt signal to gracefully shutdown the servers
|
|
||||||
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...")
|
||||||
log.Info("Shutting down servers...")
|
|
||||||
|
|
||||||
// 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 := graphQLServer.Shutdown(ctx); err != nil {
|
if err := mainServer.Shutdown(ctx); err != nil {
|
||||||
log.Error(err, "GraphQL server forced to shutdown")
|
log.Error(err, "Server forced to shutdown")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := playgroundServer.Shutdown(ctx); err != nil {
|
log.Info("Server shut down successfully")
|
||||||
log.Error(err, "GraphQL playground forced to shutdown")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := metricsServer.Shutdown(ctx); err != nil {
|
|
||||||
log.Error(err, "Metrics server forced to shutdown")
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info("All servers shutdown successfully")
|
|
||||||
}
|
}
|
||||||
11
go.mod
11
go.mod
@ -17,6 +17,7 @@ require (
|
|||||||
github.com/prometheus/client_golang v1.20.5
|
github.com/prometheus/client_golang v1.20.5
|
||||||
github.com/redis/go-redis/v9 v9.13.0
|
github.com/redis/go-redis/v9 v9.13.0
|
||||||
github.com/rs/zerolog v1.34.0
|
github.com/rs/zerolog v1.34.0
|
||||||
|
github.com/spf13/viper v1.21.0
|
||||||
github.com/stretchr/testify v1.11.1
|
github.com/stretchr/testify v1.11.1
|
||||||
github.com/vektah/gqlparser/v2 v2.5.30
|
github.com/vektah/gqlparser/v2 v2.5.30
|
||||||
github.com/weaviate/weaviate v1.33.0-rc.1
|
github.com/weaviate/weaviate v1.33.0-rc.1
|
||||||
@ -47,6 +48,7 @@ require (
|
|||||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
github.com/elastic/go-sysinfo v1.15.4 // indirect
|
github.com/elastic/go-sysinfo v1.15.4 // indirect
|
||||||
github.com/elastic/go-windows v1.0.2 // indirect
|
github.com/elastic/go-windows v1.0.2 // indirect
|
||||||
|
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||||
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
|
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
|
||||||
github.com/go-faster/city v1.0.1 // indirect
|
github.com/go-faster/city v1.0.1 // indirect
|
||||||
github.com/go-faster/errors v0.7.1 // indirect
|
github.com/go-faster/errors v0.7.1 // indirect
|
||||||
@ -92,6 +94,7 @@ require (
|
|||||||
github.com/oklog/ulid v1.3.1 // indirect
|
github.com/oklog/ulid v1.3.1 // indirect
|
||||||
github.com/opentracing/opentracing-go v1.2.0 // indirect
|
github.com/opentracing/opentracing-go v1.2.0 // indirect
|
||||||
github.com/paulmach/orb v0.11.1 // indirect
|
github.com/paulmach/orb v0.11.1 // indirect
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||||
github.com/pierrec/lz4/v4 v4.1.22 // indirect
|
github.com/pierrec/lz4/v4 v4.1.22 // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||||
@ -101,12 +104,17 @@ require (
|
|||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||||
github.com/robfig/cron/v3 v3.0.1 // indirect
|
github.com/robfig/cron/v3 v3.0.1 // indirect
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||||
|
github.com/sagikazarmark/locafero v0.11.0 // indirect
|
||||||
github.com/segmentio/asm v1.2.0 // indirect
|
github.com/segmentio/asm v1.2.0 // indirect
|
||||||
github.com/sethvargo/go-retry v0.3.0 // indirect
|
github.com/sethvargo/go-retry v0.3.0 // indirect
|
||||||
github.com/shopspring/decimal v1.4.0 // indirect
|
github.com/shopspring/decimal v1.4.0 // indirect
|
||||||
github.com/sosodev/duration v1.3.1 // indirect
|
github.com/sosodev/duration v1.3.1 // indirect
|
||||||
github.com/spf13/cast v1.7.0 // indirect
|
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
|
||||||
|
github.com/spf13/afero v1.15.0 // indirect
|
||||||
|
github.com/spf13/cast v1.10.0 // indirect
|
||||||
|
github.com/spf13/pflag v1.0.10 // indirect
|
||||||
github.com/stretchr/objx v0.5.2 // indirect
|
github.com/stretchr/objx v0.5.2 // indirect
|
||||||
|
github.com/subosito/gotenv v1.6.0 // indirect
|
||||||
github.com/tursodatabase/libsql-client-go v0.0.0-20240902231107-85af5b9d094d // indirect
|
github.com/tursodatabase/libsql-client-go v0.0.0-20240902231107-85af5b9d094d // indirect
|
||||||
github.com/urfave/cli/v2 v2.27.7 // indirect
|
github.com/urfave/cli/v2 v2.27.7 // indirect
|
||||||
github.com/vertica/vertica-sql-go v1.3.3 // indirect
|
github.com/vertica/vertica-sql-go v1.3.3 // indirect
|
||||||
@ -118,6 +126,7 @@ require (
|
|||||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||||
go.opentelemetry.io/otel/metric v1.38.0 // indirect
|
go.opentelemetry.io/otel/metric v1.38.0 // indirect
|
||||||
go.uber.org/multierr v1.11.0 // indirect
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
|
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||||
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // indirect
|
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // indirect
|
||||||
golang.org/x/mod v0.26.0 // indirect
|
golang.org/x/mod v0.26.0 // indirect
|
||||||
golang.org/x/net v0.42.0 // indirect
|
golang.org/x/net v0.42.0 // indirect
|
||||||
|
|||||||
22
go.sum
22
go.sum
@ -94,6 +94,8 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7
|
|||||||
github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
|
github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
|
||||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||||
|
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||||
|
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
|
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
|
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
|
||||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||||
@ -315,6 +317,8 @@ github.com/paulmach/orb v0.11.1 h1:3koVegMC4X/WeiXYz9iswopaTwMem53NzTJuTF20JzU=
|
|||||||
github.com/paulmach/orb v0.11.1/go.mod h1:5mULz1xQfs3bmQm63QEJA6lNGujuRafwA5S/EnuLaLU=
|
github.com/paulmach/orb v0.11.1/go.mod h1:5mULz1xQfs3bmQm63QEJA6lNGujuRafwA5S/EnuLaLU=
|
||||||
github.com/paulmach/protoscan v0.2.1/go.mod h1:SpcSwydNLrxUGSDvXvO0P7g7AuhJ7lcKfDlhJCDw2gY=
|
github.com/paulmach/protoscan v0.2.1/go.mod h1:SpcSwydNLrxUGSDvXvO0P7g7AuhJ7lcKfDlhJCDw2gY=
|
||||||
github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE=
|
github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||||
github.com/pemistahl/lingua-go v1.4.0 h1:ifYhthrlW7iO4icdubwlduYnmwU37V1sbNrwhKBR4rM=
|
github.com/pemistahl/lingua-go v1.4.0 h1:ifYhthrlW7iO4icdubwlduYnmwU37V1sbNrwhKBR4rM=
|
||||||
github.com/pemistahl/lingua-go v1.4.0/go.mod h1:ECuM1Hp/3hvyh7k8aWSqNCPlTxLemFZsRjocUf3KgME=
|
github.com/pemistahl/lingua-go v1.4.0/go.mod h1:ECuM1Hp/3hvyh7k8aWSqNCPlTxLemFZsRjocUf3KgME=
|
||||||
github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU=
|
github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU=
|
||||||
@ -359,6 +363,8 @@ github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
|
|||||||
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
|
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
|
github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc=
|
||||||
|
github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik=
|
||||||
github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
|
github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
|
||||||
github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
|
github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
|
||||||
github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
|
github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
|
||||||
@ -372,10 +378,18 @@ github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMB
|
|||||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||||
github.com/sosodev/duration v1.3.1 h1:qtHBDMQ6lvMQsL15g4aopM4HEfOaYuhWBw3NPTtlqq4=
|
github.com/sosodev/duration v1.3.1 h1:qtHBDMQ6lvMQsL15g4aopM4HEfOaYuhWBw3NPTtlqq4=
|
||||||
github.com/sosodev/duration v1.3.1/go.mod h1:RQIBBX0+fMLc/D9+Jb/fwvVmo0eZvDDEERAikUR6SDg=
|
github.com/sosodev/duration v1.3.1/go.mod h1:RQIBBX0+fMLc/D9+Jb/fwvVmo0eZvDDEERAikUR6SDg=
|
||||||
github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w=
|
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw=
|
||||||
github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U=
|
||||||
|
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
|
||||||
|
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
|
||||||
|
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
|
||||||
|
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
|
||||||
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
||||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||||
|
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
||||||
|
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
|
github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=
|
||||||
|
github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||||
@ -387,6 +401,8 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
|||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
|
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||||
|
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||||
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||||
github.com/tursodatabase/libsql-client-go v0.0.0-20240902231107-85af5b9d094d h1:dOMI4+zEbDI37KGb0TI44GUAwxHF9cMsIoDTJ7UmgfU=
|
github.com/tursodatabase/libsql-client-go v0.0.0-20240902231107-85af5b9d094d h1:dOMI4+zEbDI37KGb0TI44GUAwxHF9cMsIoDTJ7UmgfU=
|
||||||
github.com/tursodatabase/libsql-client-go v0.0.0-20240902231107-85af5b9d094d/go.mod h1:l8xTsYB90uaVdMHXMCxKKLSgw5wLYBwBKKefNIUnm9s=
|
github.com/tursodatabase/libsql-client-go v0.0.0-20240902231107-85af5b9d094d/go.mod h1:l8xTsYB90uaVdMHXMCxKKLSgw5wLYBwBKKefNIUnm9s=
|
||||||
@ -445,6 +461,8 @@ go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
|
|||||||
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
|
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
|
||||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||||
|
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||||
|
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
|
golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
|
||||||
|
|||||||
@ -15,13 +15,41 @@ import (
|
|||||||
"tercul/internal/app/user"
|
"tercul/internal/app/user"
|
||||||
"tercul/internal/app/auth"
|
"tercul/internal/app/auth"
|
||||||
"tercul/internal/app/work"
|
"tercul/internal/app/work"
|
||||||
"tercul/internal/data/sql"
|
"tercul/internal/domain"
|
||||||
|
auth_domain "tercul/internal/domain/auth"
|
||||||
|
localization_domain "tercul/internal/domain/localization"
|
||||||
"tercul/internal/domain/search"
|
"tercul/internal/domain/search"
|
||||||
|
work_domain "tercul/internal/domain/work"
|
||||||
platform_auth "tercul/internal/platform/auth"
|
platform_auth "tercul/internal/platform/auth"
|
||||||
)
|
)
|
||||||
|
|
||||||
import "tercul/internal/app/authz"
|
import "tercul/internal/app/authz"
|
||||||
|
|
||||||
|
// Dependencies holds all external dependencies for the application.
|
||||||
|
type Dependencies struct {
|
||||||
|
WorkRepo work_domain.WorkRepository
|
||||||
|
UserRepo domain.UserRepository
|
||||||
|
AuthorRepo domain.AuthorRepository
|
||||||
|
TranslationRepo domain.TranslationRepository
|
||||||
|
CommentRepo domain.CommentRepository
|
||||||
|
LikeRepo domain.LikeRepository
|
||||||
|
BookmarkRepo domain.BookmarkRepository
|
||||||
|
CollectionRepo domain.CollectionRepository
|
||||||
|
TagRepo domain.TagRepository
|
||||||
|
CategoryRepo domain.CategoryRepository
|
||||||
|
BookRepo domain.BookRepository
|
||||||
|
PublisherRepo domain.PublisherRepository
|
||||||
|
SourceRepo domain.SourceRepository
|
||||||
|
CopyrightRepo domain.CopyrightRepository
|
||||||
|
MonetizationRepo domain.MonetizationRepository
|
||||||
|
AnalyticsRepo analytics.Repository
|
||||||
|
AuthRepo auth_domain.AuthRepository
|
||||||
|
LocalizationRepo localization_domain.LocalizationRepository
|
||||||
|
SearchClient search.SearchClient
|
||||||
|
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.
|
||||||
type Application struct {
|
type Application struct {
|
||||||
Author *author.Service
|
Author *author.Service
|
||||||
@ -41,22 +69,21 @@ type Application struct {
|
|||||||
Analytics analytics.Service
|
Analytics analytics.Service
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewApplication(repos *sql.Repositories, searchClient search.SearchClient, analyticsService analytics.Service) *Application {
|
func NewApplication(deps Dependencies) *Application {
|
||||||
jwtManager := platform_auth.NewJWTManager()
|
authzService := authz.NewService(deps.WorkRepo, deps.TranslationRepo)
|
||||||
authzService := authz.NewService(repos.Work, repos.Translation)
|
authorService := author.NewService(deps.AuthorRepo)
|
||||||
authorService := author.NewService(repos.Author)
|
bookService := book.NewService(deps.BookRepo, authzService)
|
||||||
bookService := book.NewService(repos.Book, authzService)
|
bookmarkService := bookmark.NewService(deps.BookmarkRepo, deps.AnalyticsService)
|
||||||
bookmarkService := bookmark.NewService(repos.Bookmark, analyticsService)
|
categoryService := category.NewService(deps.CategoryRepo)
|
||||||
categoryService := category.NewService(repos.Category)
|
collectionService := collection.NewService(deps.CollectionRepo)
|
||||||
collectionService := collection.NewService(repos.Collection)
|
commentService := comment.NewService(deps.CommentRepo, authzService, deps.AnalyticsService)
|
||||||
commentService := comment.NewService(repos.Comment, authzService, analyticsService)
|
likeService := like.NewService(deps.LikeRepo, deps.AnalyticsService)
|
||||||
likeService := like.NewService(repos.Like, analyticsService)
|
tagService := tag.NewService(deps.TagRepo)
|
||||||
tagService := tag.NewService(repos.Tag)
|
translationService := translation.NewService(deps.TranslationRepo, authzService)
|
||||||
translationService := translation.NewService(repos.Translation, authzService)
|
userService := user.NewService(deps.UserRepo, authzService)
|
||||||
userService := user.NewService(repos.User, authzService)
|
localizationService := localization.NewService(deps.LocalizationRepo)
|
||||||
localizationService := localization.NewService(repos.Localization)
|
authService := auth.NewService(deps.UserRepo, deps.JWTManager)
|
||||||
authService := auth.NewService(repos.User, jwtManager)
|
workService := work.NewService(deps.WorkRepo, deps.SearchClient, authzService)
|
||||||
workService := work.NewService(repos.Work, searchClient, authzService)
|
|
||||||
|
|
||||||
return &Application{
|
return &Application{
|
||||||
Author: authorService,
|
Author: authorService,
|
||||||
@ -73,6 +100,6 @@ func NewApplication(repos *sql.Repositories, searchClient search.SearchClient, a
|
|||||||
Auth: authService,
|
Auth: authService,
|
||||||
Authz: authzService,
|
Authz: authzService,
|
||||||
Work: workService,
|
Work: workService,
|
||||||
Analytics: analyticsService,
|
Analytics: deps.AnalyticsService,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
12
internal/platform/cache/redis_cache.go
vendored
12
internal/platform/cache/redis_cache.go
vendored
@ -5,11 +5,11 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"tercul/internal/platform/config"
|
||||||
|
"tercul/internal/platform/log"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/redis/go-redis/v9"
|
"github.com/redis/go-redis/v9"
|
||||||
"tercul/internal/platform/config"
|
|
||||||
"tercul/internal/platform/log"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// RedisCache implements the Cache interface using Redis
|
// RedisCache implements the Cache interface using Redis
|
||||||
@ -37,12 +37,12 @@ func NewRedisCache(client *redis.Client, keyGenerator KeyGenerator, defaultExpir
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewDefaultRedisCache creates a new RedisCache with default settings
|
// NewDefaultRedisCache creates a new RedisCache with default settings
|
||||||
func NewDefaultRedisCache() (*RedisCache, error) {
|
func NewDefaultRedisCache(cfg *config.Config) (*RedisCache, error) {
|
||||||
// Create Redis client from config
|
// Create Redis client from config
|
||||||
client := redis.NewClient(&redis.Options{
|
client := redis.NewClient(&redis.Options{
|
||||||
Addr: config.Cfg.RedisAddr,
|
Addr: cfg.RedisAddr,
|
||||||
Password: config.Cfg.RedisPassword,
|
Password: cfg.RedisPassword,
|
||||||
DB: config.Cfg.RedisDB,
|
DB: cfg.RedisDB,
|
||||||
})
|
})
|
||||||
|
|
||||||
// Test connection
|
// Test connection
|
||||||
|
|||||||
@ -1,161 +1,57 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"github.com/spf13/viper"
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Config holds all configuration for the application
|
// Config stores all configuration of the application.
|
||||||
type Config struct {
|
type Config struct {
|
||||||
// Database configuration
|
Environment string `mapstructure:"ENVIRONMENT"`
|
||||||
DBHost string
|
ServerPort string `mapstructure:"SERVER_PORT"`
|
||||||
DBPort string
|
DBHost string `mapstructure:"DB_HOST"`
|
||||||
DBUser string
|
DBPort string `mapstructure:"DB_PORT"`
|
||||||
DBPassword string
|
DBUser string `mapstructure:"DB_USER"`
|
||||||
DBName string
|
DBPassword string `mapstructure:"DB_PASSWORD"`
|
||||||
DBSSLMode string
|
DBName string `mapstructure:"DB_NAME"`
|
||||||
DBTimeZone string
|
JWTSecret string `mapstructure:"JWT_SECRET"`
|
||||||
|
JWTExpiration int `mapstructure:"JWT_EXPIRATION_HOURS"`
|
||||||
// Weaviate configuration
|
WeaviateHost string `mapstructure:"WEAVIATE_HOST"`
|
||||||
WeaviateScheme string
|
WeaviateScheme string `mapstructure:"WEAVIATE_SCHEME"`
|
||||||
WeaviateHost string
|
MigrationPath string `mapstructure:"MIGRATION_PATH"`
|
||||||
|
RedisAddr string `mapstructure:"REDIS_ADDR"`
|
||||||
// Redis configuration
|
RedisPassword string `mapstructure:"REDIS_PASSWORD"`
|
||||||
RedisAddr string
|
RedisDB int `mapstructure:"REDIS_DB"`
|
||||||
RedisPassword string
|
SyncBatchSize int `mapstructure:"SYNC_BATCH_SIZE"`
|
||||||
RedisDB int
|
RateLimit int `mapstructure:"RATE_LIMIT"`
|
||||||
|
RateLimitBurst int `mapstructure:"RATE_LIMIT_BURST"`
|
||||||
// Application configuration
|
|
||||||
Port string
|
|
||||||
ServerPort string
|
|
||||||
PlaygroundPort string
|
|
||||||
Environment string
|
|
||||||
LogLevel string
|
|
||||||
|
|
||||||
// Performance configuration
|
|
||||||
BatchSize int
|
|
||||||
PageSize int
|
|
||||||
RetryInterval time.Duration
|
|
||||||
MaxRetries int
|
|
||||||
|
|
||||||
// Security configuration
|
|
||||||
RateLimit int // Requests per second
|
|
||||||
RateLimitBurst int // Maximum burst size
|
|
||||||
JWTSecret string
|
|
||||||
JWTExpiration time.Duration
|
|
||||||
|
|
||||||
// NLP providers configuration
|
|
||||||
NLPUseLingua bool
|
|
||||||
NLPUseVADER bool
|
|
||||||
NLPUseTFIDF bool
|
|
||||||
|
|
||||||
// NLP cache configuration
|
|
||||||
NLPMemoryCacheCap int
|
|
||||||
NLPRedisCacheTTLSeconds int
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cfg is the global configuration instance
|
// LoadConfig reads configuration from file or environment variables.
|
||||||
var Cfg Config
|
func LoadConfig() (*Config, error) {
|
||||||
|
viper.SetDefault("ENVIRONMENT", "development")
|
||||||
|
viper.SetDefault("SERVER_PORT", ":8080")
|
||||||
|
viper.SetDefault("DB_HOST", "localhost")
|
||||||
|
viper.SetDefault("DB_PORT", "5432")
|
||||||
|
viper.SetDefault("DB_USER", "user")
|
||||||
|
viper.SetDefault("DB_PASSWORD", "password")
|
||||||
|
viper.SetDefault("DB_NAME", "tercul")
|
||||||
|
viper.SetDefault("JWT_SECRET", "secret")
|
||||||
|
viper.SetDefault("JWT_EXPIRATION_HOURS", 24)
|
||||||
|
viper.SetDefault("WEAVIATE_HOST", "localhost:8080")
|
||||||
|
viper.SetDefault("WEAVIATE_SCHEME", "http")
|
||||||
|
viper.SetDefault("MIGRATION_PATH", "internal/data/migrations")
|
||||||
|
viper.SetDefault("REDIS_ADDR", "localhost:6379")
|
||||||
|
viper.SetDefault("REDIS_PASSWORD", "")
|
||||||
|
viper.SetDefault("REDIS_DB", 0)
|
||||||
|
viper.SetDefault("SYNC_BATCH_SIZE", 100)
|
||||||
|
viper.SetDefault("RATE_LIMIT", 10)
|
||||||
|
viper.SetDefault("RATE_LIMIT_BURST", 100)
|
||||||
|
|
||||||
// LoadConfig loads configuration from environment variables
|
viper.AutomaticEnv()
|
||||||
func LoadConfig() {
|
|
||||||
Cfg = Config{
|
|
||||||
// Database configuration
|
|
||||||
DBHost: getEnv("DB_HOST", "localhost"),
|
|
||||||
DBPort: getEnv("DB_PORT", "5432"),
|
|
||||||
DBUser: getEnv("DB_USER", "postgres"),
|
|
||||||
DBPassword: getEnv("DB_PASSWORD", "postgres"),
|
|
||||||
DBName: getEnv("DB_NAME", "tercul"),
|
|
||||||
DBSSLMode: getEnv("DB_SSLMODE", "disable"),
|
|
||||||
DBTimeZone: getEnv("DB_TIMEZONE", "UTC"),
|
|
||||||
|
|
||||||
// Weaviate configuration
|
var config Config
|
||||||
WeaviateScheme: getEnv("WEAVIATE_SCHEME", "http"),
|
if err := viper.Unmarshal(&config); err != nil {
|
||||||
WeaviateHost: getEnv("WEAVIATE_HOST", "localhost:8080"),
|
return nil, err
|
||||||
|
|
||||||
// Redis configuration
|
|
||||||
RedisAddr: getEnv("REDIS_ADDR", "127.0.0.1:6379"),
|
|
||||||
RedisPassword: getEnv("REDIS_PASSWORD", ""),
|
|
||||||
RedisDB: getEnvAsInt("REDIS_DB", 0),
|
|
||||||
|
|
||||||
// Application configuration
|
|
||||||
Port: getEnv("PORT", "8080"),
|
|
||||||
ServerPort: getEnv("SERVER_PORT", "8080"),
|
|
||||||
PlaygroundPort: getEnv("PLAYGROUND_PORT", "8081"),
|
|
||||||
Environment: getEnv("ENVIRONMENT", "development"),
|
|
||||||
LogLevel: getEnv("LOG_LEVEL", "info"),
|
|
||||||
|
|
||||||
// Performance configuration
|
|
||||||
BatchSize: getEnvAsInt("BATCH_SIZE", 100),
|
|
||||||
PageSize: getEnvAsInt("PAGE_SIZE", 20),
|
|
||||||
RetryInterval: time.Duration(getEnvAsInt("RETRY_INTERVAL_SECONDS", 2)) * time.Second,
|
|
||||||
MaxRetries: getEnvAsInt("MAX_RETRIES", 3),
|
|
||||||
|
|
||||||
// Security configuration
|
|
||||||
RateLimit: getEnvAsInt("RATE_LIMIT", 10), // 10 requests per second by default
|
|
||||||
RateLimitBurst: getEnvAsInt("RATE_LIMIT_BURST", 50), // 50 burst requests by default
|
|
||||||
JWTSecret: getEnv("JWT_SECRET", ""),
|
|
||||||
JWTExpiration: time.Duration(getEnvAsInt("JWT_EXPIRATION_HOURS", 24)) * time.Hour,
|
|
||||||
|
|
||||||
// NLP providers configuration (enabled by default)
|
|
||||||
NLPUseLingua: getEnvAsBool("NLP_USE_LINGUA", true),
|
|
||||||
NLPUseVADER: getEnvAsBool("NLP_USE_VADER", true),
|
|
||||||
NLPUseTFIDF: getEnvAsBool("NLP_USE_TFIDF", true),
|
|
||||||
|
|
||||||
// NLP cache configuration
|
|
||||||
NLPMemoryCacheCap: getEnvAsInt("NLP_MEMORY_CACHE_CAP", 1024),
|
|
||||||
NLPRedisCacheTTLSeconds: getEnvAsInt("NLP_REDIS_CACHE_TTL_SECONDS", 86400),
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("Configuration loaded: Environment=%s, LogLevel=%s", Cfg.Environment, Cfg.LogLevel)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetDSN returns the database connection string
|
|
||||||
func (c *Config) GetDSN() string {
|
|
||||||
return fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=%s TimeZone=%s",
|
|
||||||
c.DBHost, c.DBPort, c.DBUser, c.DBPassword, c.DBName, c.DBSSLMode, c.DBTimeZone)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper functions for environment variables
|
|
||||||
|
|
||||||
// getEnv gets an environment variable or returns a default value
|
|
||||||
func getEnv(key, defaultValue string) string {
|
|
||||||
value, exists := os.LookupEnv(key)
|
|
||||||
if !exists {
|
|
||||||
return defaultValue
|
|
||||||
}
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
|
|
||||||
// getEnvAsInt gets an environment variable as an integer or returns a default value
|
|
||||||
func getEnvAsInt(key string, defaultValue int) int {
|
|
||||||
valueStr := getEnv(key, "")
|
|
||||||
if valueStr == "" {
|
|
||||||
return defaultValue
|
|
||||||
}
|
|
||||||
value, err := strconv.Atoi(valueStr)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Warning: Invalid value for %s, using default: %v", key, err)
|
|
||||||
return defaultValue
|
|
||||||
}
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
|
|
||||||
// getEnvAsBool gets an environment variable as a boolean or returns a default value
|
|
||||||
func getEnvAsBool(key string, defaultValue bool) bool {
|
|
||||||
valueStr := getEnv(key, "")
|
|
||||||
if valueStr == "" {
|
|
||||||
return defaultValue
|
|
||||||
}
|
|
||||||
switch valueStr {
|
|
||||||
case "1", "true", "TRUE", "True", "yes", "YES", "Yes", "on", "ON", "On":
|
|
||||||
return true
|
|
||||||
case "0", "false", "FALSE", "False", "no", "NO", "No", "off", "OFF", "Off":
|
|
||||||
return false
|
|
||||||
default:
|
|
||||||
return defaultValue
|
|
||||||
}
|
}
|
||||||
|
return &config, nil
|
||||||
}
|
}
|
||||||
@ -3,24 +3,23 @@ package db
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"tercul/internal/observability"
|
"tercul/internal/observability"
|
||||||
|
"tercul/internal/platform/config"
|
||||||
|
"tercul/internal/platform/log"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gorm.io/driver/postgres"
|
"gorm.io/driver/postgres"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
gormlogger "gorm.io/gorm/logger"
|
gormlogger "gorm.io/gorm/logger"
|
||||||
"tercul/internal/platform/config"
|
|
||||||
"tercul/internal/platform/log"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// DB is a global database connection instance
|
// Connect establishes a connection to the database using the provided configuration.
|
||||||
var DB *gorm.DB
|
// It returns the database connection and any error encountered.
|
||||||
|
func Connect(cfg *config.Config, metrics *observability.Metrics) (*gorm.DB, error) {
|
||||||
|
log.Info(fmt.Sprintf("Connecting to database: host=%s db=%s", cfg.DBHost, cfg.DBName))
|
||||||
|
|
||||||
// Connect establishes a connection to the database using configuration settings
|
dsn := fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%s sslmode=disable",
|
||||||
// It returns the database connection and any error encountered
|
cfg.DBHost, cfg.DBUser, cfg.DBPassword, cfg.DBName, cfg.DBPort)
|
||||||
func Connect(metrics *observability.Metrics) (*gorm.DB, error) {
|
|
||||||
log.Info(fmt.Sprintf("Connecting to database: host=%s db=%s", config.Cfg.DBHost, config.Cfg.DBName))
|
|
||||||
|
|
||||||
dsn := config.Cfg.GetDSN()
|
|
||||||
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{
|
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{
|
||||||
Logger: gormlogger.Default.LogMode(gormlogger.Warn),
|
Logger: gormlogger.Default.LogMode(gormlogger.Warn),
|
||||||
})
|
})
|
||||||
@ -33,9 +32,6 @@ func Connect(metrics *observability.Metrics) (*gorm.DB, error) {
|
|||||||
return nil, fmt.Errorf("failed to register prometheus plugin: %w", err)
|
return nil, fmt.Errorf("failed to register prometheus plugin: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the global DB instance
|
|
||||||
DB = db
|
|
||||||
|
|
||||||
// Get the underlying SQL DB instance
|
// Get the underlying SQL DB instance
|
||||||
sqlDB, err := db.DB()
|
sqlDB, err := db.DB()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -47,18 +43,18 @@ func Connect(metrics *observability.Metrics) (*gorm.DB, error) {
|
|||||||
sqlDB.SetMaxIdleConns(5) // Idle connections
|
sqlDB.SetMaxIdleConns(5) // Idle connections
|
||||||
sqlDB.SetConnMaxLifetime(30 * time.Minute)
|
sqlDB.SetConnMaxLifetime(30 * time.Minute)
|
||||||
|
|
||||||
log.Info(fmt.Sprintf("Successfully connected to database: host=%s db=%s", config.Cfg.DBHost, config.Cfg.DBName))
|
log.Info(fmt.Sprintf("Successfully connected to database: host=%s db=%s", cfg.DBHost, cfg.DBName))
|
||||||
|
|
||||||
return db, nil
|
return db, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close closes the database connection
|
// Close closes the database connection.
|
||||||
func Close() error {
|
func Close(db *gorm.DB) error {
|
||||||
if DB == nil {
|
if db == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
sqlDB, err := DB.DB()
|
sqlDB, err := db.DB()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get SQL DB instance: %w", err)
|
return fmt.Errorf("failed to get SQL DB instance: %w", err)
|
||||||
}
|
}
|
||||||
@ -66,16 +62,12 @@ func Close() error {
|
|||||||
return sqlDB.Close()
|
return sqlDB.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
// InitDB initializes the database connection and runs migrations
|
// InitDB initializes the database connection.
|
||||||
// It returns the database connection and any error encountered
|
func InitDB(cfg *config.Config, metrics *observability.Metrics) (*gorm.DB, error) {
|
||||||
func InitDB(metrics *observability.Metrics) (*gorm.DB, error) {
|
|
||||||
// Connect to the database
|
// Connect to the database
|
||||||
db, err := Connect(metrics)
|
db, err := Connect(cfg, metrics)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Migrations are now handled by a separate tool
|
|
||||||
|
|
||||||
return db, nil
|
return db, nil
|
||||||
}
|
}
|
||||||
@ -163,7 +163,32 @@ 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)
|
||||||
s.App = app.NewApplication(repos, searchClient, analyticsService)
|
jwtManager := platform_auth.NewJWTManager()
|
||||||
|
|
||||||
|
deps := app.Dependencies{
|
||||||
|
WorkRepo: repos.Work,
|
||||||
|
UserRepo: repos.User,
|
||||||
|
AuthorRepo: repos.Author,
|
||||||
|
TranslationRepo: repos.Translation,
|
||||||
|
CommentRepo: repos.Comment,
|
||||||
|
LikeRepo: repos.Like,
|
||||||
|
BookmarkRepo: repos.Bookmark,
|
||||||
|
CollectionRepo: repos.Collection,
|
||||||
|
TagRepo: repos.Tag,
|
||||||
|
CategoryRepo: repos.Category,
|
||||||
|
BookRepo: repos.Book,
|
||||||
|
PublisherRepo: repos.Publisher,
|
||||||
|
SourceRepo: repos.Source,
|
||||||
|
CopyrightRepo: repos.Copyright,
|
||||||
|
MonetizationRepo: repos.Monetization,
|
||||||
|
AnalyticsRepo: repos.Analytics,
|
||||||
|
AuthRepo: repos.Auth,
|
||||||
|
LocalizationRepo: repos.Localization,
|
||||||
|
SearchClient: searchClient,
|
||||||
|
AnalyticsService: analyticsService,
|
||||||
|
JWTManager: jwtManager,
|
||||||
|
}
|
||||||
|
s.App = app.NewApplication(deps)
|
||||||
|
|
||||||
// Create a default admin user for tests
|
// Create a default admin user for tests
|
||||||
adminUser := &domain.User{
|
adminUser := &domain.User{
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user