mirror of
https://github.com/SamyRai/tercul-backend.git
synced 2025-12-27 00:31:35 +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")
|
||||
}
|
||||
|
||||
// Create platform components
|
||||
jwtManager := auth.NewJWTManager()
|
||||
|
||||
// Create application services
|
||||
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
|
||||
application := app.NewApplication(repos, searchClient, analyticsService)
|
||||
application := app.NewApplication(deps)
|
||||
|
||||
// Create GraphQL server
|
||||
resolver := &graph.Resolver{
|
||||
App: application,
|
||||
}
|
||||
|
||||
jwtManager := auth.NewJWTManager()
|
||||
srv := NewServerWithAuth(resolver, jwtManager, metrics, obsLogger)
|
||||
graphQLServer := &http.Server{
|
||||
// Create handlers
|
||||
apiHandler := NewServerWithAuth(resolver, jwtManager, metrics, obsLogger)
|
||||
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,
|
||||
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
|
||||
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
|
||||
// Start the main server in a goroutine
|
||||
go func() {
|
||||
log.Info(fmt.Sprintf("Starting GraphQL server on port %s", config.Cfg.ServerPort))
|
||||
if err := graphQLServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||
log.Fatal(err, "Failed to start GraphQL server")
|
||||
if err := mainServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||
log.Fatal(err, "Failed to start server")
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
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
|
||||
// Wait for interrupt signal to gracefully shutdown the server
|
||||
quit := make(chan os.Signal, 1)
|
||||
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
|
||||
<-quit
|
||||
|
||||
log.Info("Shutting down servers...")
|
||||
log.Info("Shutting down server...")
|
||||
|
||||
// Graceful shutdown
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if err := graphQLServer.Shutdown(ctx); err != nil {
|
||||
log.Error(err, "GraphQL server forced to shutdown")
|
||||
if err := mainServer.Shutdown(ctx); err != nil {
|
||||
log.Error(err, "Server forced to shutdown")
|
||||
}
|
||||
|
||||
if err := playgroundServer.Shutdown(ctx); err != nil {
|
||||
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")
|
||||
log.Info("Server shut down successfully")
|
||||
}
|
||||
11
go.mod
11
go.mod
@ -17,6 +17,7 @@ require (
|
||||
github.com/prometheus/client_golang v1.20.5
|
||||
github.com/redis/go-redis/v9 v9.13.0
|
||||
github.com/rs/zerolog v1.34.0
|
||||
github.com/spf13/viper v1.21.0
|
||||
github.com/stretchr/testify v1.11.1
|
||||
github.com/vektah/gqlparser/v2 v2.5.30
|
||||
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/elastic/go-sysinfo v1.15.4 // 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/go-faster/city v1.0.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/opentracing/opentracing-go v1.2.0 // 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/pkg/errors v0.9.1 // 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/robfig/cron/v3 v3.0.1 // 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/sethvargo/go-retry v0.3.0 // indirect
|
||||
github.com/shopspring/decimal v1.4.0 // 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/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/tursodatabase/libsql-client-go v0.0.0-20240902231107-85af5b9d094d // indirect
|
||||
github.com/urfave/cli/v2 v2.27.7 // 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/otel/metric v1.38.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/mod v0.26.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/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
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/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
|
||||
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/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/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/go.mod h1:ECuM1Hp/3hvyh7k8aWSqNCPlTxLemFZsRjocUf3KgME=
|
||||
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/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/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/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
|
||||
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/sosodev/duration v1.3.1 h1:qtHBDMQ6lvMQsL15g4aopM4HEfOaYuhWBw3NPTtlqq4=
|
||||
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/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw=
|
||||
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/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.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
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.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
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/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=
|
||||
@ -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/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
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-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
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/auth"
|
||||
"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"
|
||||
work_domain "tercul/internal/domain/work"
|
||||
platform_auth "tercul/internal/platform/auth"
|
||||
)
|
||||
|
||||
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.
|
||||
type Application struct {
|
||||
Author *author.Service
|
||||
@ -41,22 +69,21 @@ type Application struct {
|
||||
Analytics analytics.Service
|
||||
}
|
||||
|
||||
func NewApplication(repos *sql.Repositories, searchClient search.SearchClient, analyticsService analytics.Service) *Application {
|
||||
jwtManager := platform_auth.NewJWTManager()
|
||||
authzService := authz.NewService(repos.Work, repos.Translation)
|
||||
authorService := author.NewService(repos.Author)
|
||||
bookService := book.NewService(repos.Book, authzService)
|
||||
bookmarkService := bookmark.NewService(repos.Bookmark, analyticsService)
|
||||
categoryService := category.NewService(repos.Category)
|
||||
collectionService := collection.NewService(repos.Collection)
|
||||
commentService := comment.NewService(repos.Comment, authzService, analyticsService)
|
||||
likeService := like.NewService(repos.Like, analyticsService)
|
||||
tagService := tag.NewService(repos.Tag)
|
||||
translationService := translation.NewService(repos.Translation, authzService)
|
||||
userService := user.NewService(repos.User, authzService)
|
||||
localizationService := localization.NewService(repos.Localization)
|
||||
authService := auth.NewService(repos.User, jwtManager)
|
||||
workService := work.NewService(repos.Work, searchClient, authzService)
|
||||
func NewApplication(deps Dependencies) *Application {
|
||||
authzService := authz.NewService(deps.WorkRepo, deps.TranslationRepo)
|
||||
authorService := author.NewService(deps.AuthorRepo)
|
||||
bookService := book.NewService(deps.BookRepo, authzService)
|
||||
bookmarkService := bookmark.NewService(deps.BookmarkRepo, deps.AnalyticsService)
|
||||
categoryService := category.NewService(deps.CategoryRepo)
|
||||
collectionService := collection.NewService(deps.CollectionRepo)
|
||||
commentService := comment.NewService(deps.CommentRepo, authzService, deps.AnalyticsService)
|
||||
likeService := like.NewService(deps.LikeRepo, deps.AnalyticsService)
|
||||
tagService := tag.NewService(deps.TagRepo)
|
||||
translationService := translation.NewService(deps.TranslationRepo, authzService)
|
||||
userService := user.NewService(deps.UserRepo, authzService)
|
||||
localizationService := localization.NewService(deps.LocalizationRepo)
|
||||
authService := auth.NewService(deps.UserRepo, deps.JWTManager)
|
||||
workService := work.NewService(deps.WorkRepo, deps.SearchClient, authzService)
|
||||
|
||||
return &Application{
|
||||
Author: authorService,
|
||||
@ -73,6 +100,6 @@ func NewApplication(repos *sql.Repositories, searchClient search.SearchClient, a
|
||||
Auth: authService,
|
||||
Authz: authzService,
|
||||
Work: workService,
|
||||
Analytics: analyticsService,
|
||||
Analytics: deps.AnalyticsService,
|
||||
}
|
||||
}
|
||||
14
internal/platform/cache/redis_cache.go
vendored
14
internal/platform/cache/redis_cache.go
vendored
@ -5,11 +5,11 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"tercul/internal/platform/config"
|
||||
"tercul/internal/platform/log"
|
||||
"time"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
"tercul/internal/platform/config"
|
||||
"tercul/internal/platform/log"
|
||||
)
|
||||
|
||||
// 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
|
||||
func NewDefaultRedisCache() (*RedisCache, error) {
|
||||
func NewDefaultRedisCache(cfg *config.Config) (*RedisCache, error) {
|
||||
// Create Redis client from config
|
||||
client := redis.NewClient(&redis.Options{
|
||||
Addr: config.Cfg.RedisAddr,
|
||||
Password: config.Cfg.RedisPassword,
|
||||
DB: config.Cfg.RedisDB,
|
||||
Addr: cfg.RedisAddr,
|
||||
Password: cfg.RedisPassword,
|
||||
DB: cfg.RedisDB,
|
||||
})
|
||||
|
||||
// Test connection
|
||||
@ -208,4 +208,4 @@ func (c *RedisCache) InvalidateEntityType(ctx context.Context, entityType string
|
||||
}
|
||||
|
||||
return iter.Err()
|
||||
}
|
||||
}
|
||||
@ -1,161 +1,57 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// Config holds all configuration for the application
|
||||
// Config stores all configuration of the application.
|
||||
type Config struct {
|
||||
// Database configuration
|
||||
DBHost string
|
||||
DBPort string
|
||||
DBUser string
|
||||
DBPassword string
|
||||
DBName string
|
||||
DBSSLMode string
|
||||
DBTimeZone string
|
||||
|
||||
// Weaviate configuration
|
||||
WeaviateScheme string
|
||||
WeaviateHost string
|
||||
|
||||
// Redis configuration
|
||||
RedisAddr string
|
||||
RedisPassword string
|
||||
RedisDB int
|
||||
|
||||
// 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
|
||||
Environment string `mapstructure:"ENVIRONMENT"`
|
||||
ServerPort string `mapstructure:"SERVER_PORT"`
|
||||
DBHost string `mapstructure:"DB_HOST"`
|
||||
DBPort string `mapstructure:"DB_PORT"`
|
||||
DBUser string `mapstructure:"DB_USER"`
|
||||
DBPassword string `mapstructure:"DB_PASSWORD"`
|
||||
DBName string `mapstructure:"DB_NAME"`
|
||||
JWTSecret string `mapstructure:"JWT_SECRET"`
|
||||
JWTExpiration int `mapstructure:"JWT_EXPIRATION_HOURS"`
|
||||
WeaviateHost string `mapstructure:"WEAVIATE_HOST"`
|
||||
WeaviateScheme string `mapstructure:"WEAVIATE_SCHEME"`
|
||||
MigrationPath string `mapstructure:"MIGRATION_PATH"`
|
||||
RedisAddr string `mapstructure:"REDIS_ADDR"`
|
||||
RedisPassword string `mapstructure:"REDIS_PASSWORD"`
|
||||
RedisDB int `mapstructure:"REDIS_DB"`
|
||||
SyncBatchSize int `mapstructure:"SYNC_BATCH_SIZE"`
|
||||
RateLimit int `mapstructure:"RATE_LIMIT"`
|
||||
RateLimitBurst int `mapstructure:"RATE_LIMIT_BURST"`
|
||||
}
|
||||
|
||||
// Cfg is the global configuration instance
|
||||
var Cfg Config
|
||||
// LoadConfig reads configuration from file or environment variables.
|
||||
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
|
||||
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"),
|
||||
viper.AutomaticEnv()
|
||||
|
||||
// Weaviate configuration
|
||||
WeaviateScheme: getEnv("WEAVIATE_SCHEME", "http"),
|
||||
WeaviateHost: getEnv("WEAVIATE_HOST", "localhost:8080"),
|
||||
|
||||
// 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),
|
||||
var config Config
|
||||
if err := viper.Unmarshal(&config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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 (
|
||||
"fmt"
|
||||
"tercul/internal/observability"
|
||||
"tercul/internal/platform/config"
|
||||
"tercul/internal/platform/log"
|
||||
"time"
|
||||
|
||||
"gorm.io/driver/postgres"
|
||||
"gorm.io/gorm"
|
||||
gormlogger "gorm.io/gorm/logger"
|
||||
"tercul/internal/platform/config"
|
||||
"tercul/internal/platform/log"
|
||||
)
|
||||
|
||||
// DB is a global database connection instance
|
||||
var DB *gorm.DB
|
||||
// Connect establishes a connection to the database using the provided configuration.
|
||||
// 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
|
||||
// It returns the database connection and any error encountered
|
||||
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 := fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%s sslmode=disable",
|
||||
cfg.DBHost, cfg.DBUser, cfg.DBPassword, cfg.DBName, cfg.DBPort)
|
||||
|
||||
dsn := config.Cfg.GetDSN()
|
||||
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{
|
||||
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)
|
||||
}
|
||||
|
||||
// Set the global DB instance
|
||||
DB = db
|
||||
|
||||
// Get the underlying SQL DB instance
|
||||
sqlDB, err := db.DB()
|
||||
if err != nil {
|
||||
@ -47,18 +43,18 @@ func Connect(metrics *observability.Metrics) (*gorm.DB, error) {
|
||||
sqlDB.SetMaxIdleConns(5) // Idle connections
|
||||
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
|
||||
}
|
||||
|
||||
// Close closes the database connection
|
||||
func Close() error {
|
||||
if DB == nil {
|
||||
// Close closes the database connection.
|
||||
func Close(db *gorm.DB) error {
|
||||
if db == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
sqlDB, err := DB.DB()
|
||||
sqlDB, err := db.DB()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get SQL DB instance: %w", err)
|
||||
}
|
||||
@ -66,16 +62,12 @@ func Close() error {
|
||||
return sqlDB.Close()
|
||||
}
|
||||
|
||||
// InitDB initializes the database connection and runs migrations
|
||||
// It returns the database connection and any error encountered
|
||||
func InitDB(metrics *observability.Metrics) (*gorm.DB, error) {
|
||||
// InitDB initializes the database connection.
|
||||
func InitDB(cfg *config.Config, metrics *observability.Metrics) (*gorm.DB, error) {
|
||||
// Connect to the database
|
||||
db, err := Connect(metrics)
|
||||
db, err := Connect(cfg, metrics)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Migrations are now handled by a separate tool
|
||||
|
||||
return db, nil
|
||||
}
|
||||
}
|
||||
@ -163,7 +163,32 @@ func (s *IntegrationTestSuite) SetupSuite(config *TestConfig) {
|
||||
s.T().Fatalf("Failed to create sentiment provider: %v", err)
|
||||
}
|
||||
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
|
||||
adminUser := &domain.User{
|
||||
|
||||
Loading…
Reference in New Issue
Block a user