diff --git a/cmd/server/main.go b/cmd/server/main.go index 2d607b1..8c2cdbe 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -2,13 +2,14 @@ package main import ( "context" - "log" + "os" "github.com/jackc/pgx/v5/pgxpool" _ "github.com/jackc/pgx/v5/stdlib" rkboot "github.com/rookie-ninja/rk-boot/v2" rkentry "github.com/rookie-ninja/rk-entry/v2/entry" rkgrpc "github.com/rookie-ninja/rk-grpc/v2/boot" + "go.uber.org/zap" "google.golang.org/grpc" "git.techease.ru/Smart-search/smart-search-back/internal/config" @@ -19,50 +20,51 @@ import ( ) func main() { - cfg, err := config.Load("config/config.yaml") - if err != nil { - log.Fatalf("Failed to load config: %v", err) - } - - ctx := context.Background() - - if err := database.RunMigrations(cfg.DatabaseURL()); err != nil { - log.Fatalf("Failed to run migrations: %v", err) - } - - pool, err := pgxpool.New(ctx, cfg.DatabaseURL()) - if err != nil { - log.Fatalf("Failed to connect to database: %v", err) - } - defer pool.Close() - - if err := pool.Ping(ctx); err != nil { - log.Fatalf("Failed to ping database: %v", err) - } - - log.Println("Successfully connected to database") - boot := rkboot.NewBoot(rkboot.WithBootConfigPath("config/boot.yaml", nil)) - grpcEntry := rkgrpc.GetGrpcEntry("smart-search-service") - if grpcEntry == nil { - log.Fatal("Failed to get gRPC entry from rk-boot") - } - - loggerEntry := rkentry.GlobalAppCtx.GetLoggerEntry("smart-search-service") + loggerEntry := rkentry.GlobalAppCtx.GetLoggerEntry("smart-search-logger") if loggerEntry == nil { loggerEntry = rkentry.GlobalAppCtx.GetLoggerEntryDefault() } logger := loggerEntry.Logger + cfg, err := config.Load("config/config.yaml") + if err != nil { + logger.Fatal("Failed to load config", zap.Error(err)) + } + + ctx := context.Background() + + if err := database.RunMigrations(cfg.DatabaseURL(), logger); err != nil { + logger.Fatal("Failed to run migrations", zap.Error(err)) + } + + pool, err := pgxpool.New(ctx, cfg.DatabaseURL()) + if err != nil { + logger.Fatal("Failed to connect to database", zap.Error(err)) + } + defer pool.Close() + + if err := pool.Ping(ctx); err != nil { + logger.Fatal("Failed to ping database", zap.Error(err)) + } + + logger.Info("Successfully connected to database") + + grpcEntry := rkgrpc.GetGrpcEntry("smart-search-service") + if grpcEntry == nil { + logger.Fatal("Failed to get gRPC entry from rk-boot") + os.Exit(1) + } + sessionRepo := repository.NewSessionRepository(pool) inviteRepo := repository.NewInviteRepository(pool) - sessionCleaner := worker.NewSessionCleaner(ctx, sessionRepo) + sessionCleaner := worker.NewSessionCleaner(ctx, sessionRepo, logger) sessionCleaner.Start() defer sessionCleaner.Stop() - inviteCleaner := worker.NewInviteCleaner(ctx, inviteRepo) + inviteCleaner := worker.NewInviteCleaner(ctx, inviteRepo, logger) inviteCleaner.Start() defer inviteCleaner.Stop() @@ -81,9 +83,9 @@ func main() { boot.Bootstrap(ctx) - log.Println("gRPC server started via rk-boot") + logger.Info("gRPC server started via rk-boot") boot.WaitForShutdownSig(ctx) - log.Println("Server stopped gracefully") + logger.Info("Server stopped gracefully") } diff --git a/config/boot.yaml b/config/boot.yaml index a648c6f..6a6ada0 100644 --- a/config/boot.yaml +++ b/config/boot.yaml @@ -1,16 +1,12 @@ --- logger: - name: smart-search-logger - description: "Application logger for smart-search service" default: true zap: - level: error - development: false + level: info encoding: console outputPaths: ["stdout"] errorOutputPaths: ["stderr"] - disableCaller: false - disableStacktrace: false grpc: - name: smart-search-service @@ -19,16 +15,17 @@ grpc: enableReflection: true enableRkGwOption: true loggerEntry: smart-search-logger - eventEntry: smart-search-logger middleware: logging: enabled: true - loggerEncoding: "console" + loggerEncoding: console loggerOutputPaths: ["stdout"] + eventEncoding: console + eventOutputPaths: [] meta: - enabled: true + enabled: false trace: - enabled: true + enabled: false prometheus: enabled: true auth: diff --git a/go.mod b/go.mod index 3178d64..a5df3ac 100644 --- a/go.mod +++ b/go.mod @@ -17,6 +17,7 @@ require ( github.com/testcontainers/testcontainers-go/modules/postgres v0.40.0 github.com/xuri/excelize/v2 v2.10.0 go.uber.org/zap v1.27.1 + google.golang.org/genproto/googleapis/rpc v0.0.0-20260114163908-3f89685c29c3 google.golang.org/grpc v1.78.0 google.golang.org/protobuf v1.36.11 gopkg.in/yaml.v3 v3.0.1 @@ -95,7 +96,6 @@ require ( github.com/spf13/cast v1.10.0 // indirect github.com/spf13/pflag v1.0.10 // indirect github.com/spf13/viper v1.21.0 // indirect - github.com/stretchr/objx v0.5.2 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/tiendc/go-deepcopy v1.7.1 // indirect github.com/tklauser/go-sysconf v0.3.12 // indirect @@ -126,7 +126,6 @@ require ( golang.org/x/sys v0.40.0 // indirect golang.org/x/text v0.33.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20251029180050-ab9386a59fda // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260114163908-3f89685c29c3 // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect nhooyr.io/websocket v1.8.6 // indirect diff --git a/internal/database/migrations.go b/internal/database/migrations.go index 97a8d6c..86129d9 100644 --- a/internal/database/migrations.go +++ b/internal/database/migrations.go @@ -7,13 +7,14 @@ import ( _ "github.com/jackc/pgx/v5/stdlib" "github.com/pressly/goose/v3" + "go.uber.org/zap" ) -func RunMigrations(databaseURL string) error { - return RunMigrationsFromPath(databaseURL, "migrations") +func RunMigrations(databaseURL string, logger *zap.Logger) error { + return RunMigrationsFromPath(databaseURL, "migrations", logger) } -func RunMigrationsFromPath(databaseURL, migrationsDir string) error { +func RunMigrationsFromPath(databaseURL, migrationsDir string, logger *zap.Logger) error { db, err := sql.Open("pgx", databaseURL) if err != nil { return fmt.Errorf("failed to open database connection for migrations: %w", err) @@ -28,6 +29,8 @@ func RunMigrationsFromPath(databaseURL, migrationsDir string) error { return fmt.Errorf("failed to set goose dialect: %w", err) } + goose.SetLogger(&gooseLogger{logger: logger}) + absPath, err := filepath.Abs(migrationsDir) if err != nil { return fmt.Errorf("failed to resolve migrations path: %w", err) @@ -39,3 +42,15 @@ func RunMigrationsFromPath(databaseURL, migrationsDir string) error { return nil } + +type gooseLogger struct { + logger *zap.Logger +} + +func (l *gooseLogger) Fatalf(format string, v ...interface{}) { + l.logger.Fatal(fmt.Sprintf(format, v...)) +} + +func (l *gooseLogger) Printf(format string, v ...interface{}) { + l.logger.Info(fmt.Sprintf(format, v...)) +} diff --git a/internal/worker/invite_cleaner.go b/internal/worker/invite_cleaner.go index 7cb3680..f4aa9d1 100644 --- a/internal/worker/invite_cleaner.go +++ b/internal/worker/invite_cleaner.go @@ -2,9 +2,10 @@ package worker import ( "context" - "log" "time" + "go.uber.org/zap" + "git.techease.ru/Smart-search/smart-search-back/internal/repository" ) @@ -13,13 +14,15 @@ type InviteCleaner struct { ctx context.Context ticker *time.Ticker done chan bool + logger *zap.Logger } -func NewInviteCleaner(ctx context.Context, inviteRepo repository.InviteRepository) *InviteCleaner { +func NewInviteCleaner(ctx context.Context, inviteRepo repository.InviteRepository, logger *zap.Logger) *InviteCleaner { return &InviteCleaner{ inviteRepo: inviteRepo, ctx: ctx, done: make(chan bool), + logger: logger, } } @@ -36,13 +39,13 @@ func (w *InviteCleaner) Start() { case <-w.done: return case <-w.ctx.Done(): - log.Println("Invite cleaner context cancelled, stopping worker") + w.logger.Info("Invite cleaner context cancelled, stopping worker") return } } }() - log.Println("Invite cleaner worker started (runs every 6 hours)") + w.logger.Info("Invite cleaner worker started (runs every 6 hours)") } func (w *InviteCleaner) Stop() { @@ -53,17 +56,17 @@ func (w *InviteCleaner) Stop() { case w.done <- true: default: } - log.Println("Invite cleaner worker stopped") + w.logger.Info("Invite cleaner worker stopped") } func (w *InviteCleaner) deactivateExpiredInvites() { count, err := w.inviteRepo.DeactivateExpired(w.ctx) if err != nil { - log.Printf("Error deactivating expired invites: %v", err) + w.logger.Error("Error deactivating expired invites", zap.Error(err)) return } if count > 0 { - log.Printf("Deactivated %d expired invite codes", count) + w.logger.Info("Deactivated expired invite codes", zap.Int("count", count)) } } diff --git a/internal/worker/session_cleaner.go b/internal/worker/session_cleaner.go index 97b6ddf..ed542c2 100644 --- a/internal/worker/session_cleaner.go +++ b/internal/worker/session_cleaner.go @@ -2,9 +2,10 @@ package worker import ( "context" - "log" "time" + "go.uber.org/zap" + "git.techease.ru/Smart-search/smart-search-back/internal/repository" ) @@ -13,13 +14,15 @@ type SessionCleaner struct { ctx context.Context ticker *time.Ticker done chan bool + logger *zap.Logger } -func NewSessionCleaner(ctx context.Context, sessionRepo repository.SessionRepository) *SessionCleaner { +func NewSessionCleaner(ctx context.Context, sessionRepo repository.SessionRepository, logger *zap.Logger) *SessionCleaner { return &SessionCleaner{ sessionRepo: sessionRepo, ctx: ctx, done: make(chan bool), + logger: logger, } } @@ -36,13 +39,13 @@ func (w *SessionCleaner) Start() { case <-w.done: return case <-w.ctx.Done(): - log.Println("Session cleaner context cancelled, stopping worker") + w.logger.Info("Session cleaner context cancelled, stopping worker") return } } }() - log.Println("Session cleaner worker started (runs every hour)") + w.logger.Info("Session cleaner worker started (runs every hour)") } func (w *SessionCleaner) Stop() { @@ -53,17 +56,17 @@ func (w *SessionCleaner) Stop() { case w.done <- true: default: } - log.Println("Session cleaner worker stopped") + w.logger.Info("Session cleaner worker stopped") } func (w *SessionCleaner) cleanExpiredSessions() { count, err := w.sessionRepo.DeleteExpired(w.ctx) if err != nil { - log.Printf("Error cleaning expired sessions: %v", err) + w.logger.Error("Error cleaning expired sessions", zap.Error(err)) return } if count > 0 { - log.Printf("Cleaned %d expired sessions", count) + w.logger.Info("Cleaned expired sessions", zap.Int("count", count)) } } diff --git a/internal/worker/worker_test.go b/internal/worker/worker_test.go index c6b864e..4c4bf2e 100644 --- a/internal/worker/worker_test.go +++ b/internal/worker/worker_test.go @@ -8,6 +8,7 @@ import ( "github.com/gojuno/minimock/v3" "github.com/stretchr/testify/suite" + "go.uber.org/zap" "git.techease.ru/Smart-search/smart-search-back/internal/mocks" ) @@ -17,6 +18,7 @@ type WorkerSuite struct { ctx context.Context cancel context.CancelFunc ctrl *minimock.Controller + logger *zap.Logger } func TestWorkerSuite(t *testing.T) { @@ -26,6 +28,7 @@ func TestWorkerSuite(t *testing.T) { func (s *WorkerSuite) SetupTest() { s.ctx, s.cancel = context.WithCancel(context.Background()) s.ctrl = minimock.NewController(s.T()) + s.logger = zap.NewNop() } func (s *WorkerSuite) TearDownTest() { @@ -42,7 +45,7 @@ func (s *WorkerSuite) TestSessionCleaner_StartStop() { return 5, nil }) - cleaner := NewSessionCleaner(s.ctx, sessionRepo) + cleaner := NewSessionCleaner(s.ctx, sessionRepo, s.logger) cleaner.Start() @@ -62,7 +65,7 @@ func (s *WorkerSuite) TestSessionCleaner_ContextCancellation() { return 0, nil }) - cleaner := NewSessionCleaner(s.ctx, sessionRepo) + cleaner := NewSessionCleaner(s.ctx, sessionRepo, s.logger) cleaner.Start() @@ -84,7 +87,7 @@ func (s *WorkerSuite) TestInviteCleaner_StartStop() { return 3, nil }) - cleaner := NewInviteCleaner(s.ctx, inviteRepo) + cleaner := NewInviteCleaner(s.ctx, inviteRepo, s.logger) cleaner.Start() @@ -104,7 +107,7 @@ func (s *WorkerSuite) TestInviteCleaner_ContextCancellation() { return 0, nil }) - cleaner := NewInviteCleaner(s.ctx, inviteRepo) + cleaner := NewInviteCleaner(s.ctx, inviteRepo, s.logger) cleaner.Start() @@ -124,7 +127,7 @@ func (s *WorkerSuite) TestSessionCleaner_ConcurrentStops() { return 0, nil }) - cleaner := NewSessionCleaner(s.ctx, sessionRepo) + cleaner := NewSessionCleaner(s.ctx, sessionRepo, s.logger) cleaner.Start() @@ -151,7 +154,7 @@ func (s *WorkerSuite) TestInviteCleaner_ConcurrentStops() { return 0, nil }) - cleaner := NewInviteCleaner(s.ctx, inviteRepo) + cleaner := NewInviteCleaner(s.ctx, inviteRepo, s.logger) cleaner.Start() @@ -180,7 +183,7 @@ func (s *WorkerSuite) TestSessionCleaner_MultipleStartStop() { return 2, nil }) - cleaner := NewSessionCleaner(s.ctx, sessionRepo) + cleaner := NewSessionCleaner(s.ctx, sessionRepo, s.logger) for i := 0; i < 3; i++ { cleaner.Start() @@ -200,7 +203,7 @@ func (s *WorkerSuite) TestInviteCleaner_MultipleStartStop() { return 1, nil }) - cleaner := NewInviteCleaner(s.ctx, inviteRepo) + cleaner := NewInviteCleaner(s.ctx, inviteRepo, s.logger) for i := 0; i < 3; i++ { cleaner.Start() diff --git a/pkg/errors/errors.go b/pkg/errors/errors.go index fbd22d1..463e200 100644 --- a/pkg/errors/errors.go +++ b/pkg/errors/errors.go @@ -5,6 +5,7 @@ import ( "fmt" "go.uber.org/zap" + "google.golang.org/genproto/googleapis/rpc/errdetails" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) @@ -88,20 +89,31 @@ func ToGRPCError(err error, zapLogger *zap.Logger, method string) error { return status.Error(codes.Internal, "internal server error") } + var grpcCode codes.Code switch appErr.Code { case AuthInvalidCredentials, AuthMissing, AuthInvalidToken, RefreshInvalid: - return status.Error(codes.Unauthenticated, appErr.Message) + grpcCode = codes.Unauthenticated case PermissionDenied: - return status.Error(codes.PermissionDenied, appErr.Message) + grpcCode = codes.PermissionDenied case InviteLimitReached: - return status.Error(codes.ResourceExhausted, appErr.Message) - case InsufficientBalance, InviteInvalidOrExpired: - return status.Error(codes.FailedPrecondition, appErr.Message) + grpcCode = codes.ResourceExhausted + case InsufficientBalance: + grpcCode = codes.FailedPrecondition + case InviteInvalidOrExpired: + grpcCode = codes.NotFound case EmailAlreadyExists: - return status.Error(codes.AlreadyExists, appErr.Message) + grpcCode = codes.AlreadyExists case UserNotFound, RequestNotFound: - return status.Error(codes.NotFound, appErr.Message) + grpcCode = codes.NotFound default: - return status.Error(codes.Unknown, appErr.Message) + grpcCode = codes.Unknown } + + st, err := status.New(grpcCode, appErr.Message).WithDetails(&errdetails.ErrorInfo{ + Reason: appErr.Code, + }) + if err != nil { + return status.Error(grpcCode, appErr.Message) + } + return st.Err() } diff --git a/tests/auth_handler_test.go b/tests/auth_handler_test.go index def4a1b..298ea97 100644 --- a/tests/auth_handler_test.go +++ b/tests/auth_handler_test.go @@ -208,7 +208,7 @@ func (s *IntegrationSuite) TestAuthHandler_RegisterInvalidInviteCode() { st, ok := status.FromError(err) s.True(ok) - s.Equal(codes.FailedPrecondition, st.Code()) + s.Equal(codes.NotFound, st.Code()) } func (s *IntegrationSuite) TestAuthHandler_RegisterExpiredInviteCode() { @@ -232,7 +232,7 @@ func (s *IntegrationSuite) TestAuthHandler_RegisterExpiredInviteCode() { st, ok := status.FromError(err) s.True(ok) - s.Equal(codes.FailedPrecondition, st.Code()) + s.Equal(codes.NotFound, st.Code()) } func (s *IntegrationSuite) TestAuthHandler_RegisterExhaustedInviteCode() { @@ -270,7 +270,7 @@ func (s *IntegrationSuite) TestAuthHandler_RegisterExhaustedInviteCode() { st, ok := status.FromError(err) s.True(ok) - s.Equal(codes.FailedPrecondition, st.Code()) + s.Equal(codes.NotFound, st.Code()) } func (s *IntegrationSuite) TestAuthHandler_RegisterDuplicateEmail() { diff --git a/tests/integration_suite_test.go b/tests/integration_suite_test.go index b563a2f..9f3dee1 100644 --- a/tests/integration_suite_test.go +++ b/tests/integration_suite_test.go @@ -78,7 +78,8 @@ func (s *IntegrationSuite) SetupSuite() { s.T().Logf("PostgreSQL connection string: %s", connStr) s.T().Log("Running migrations...") - err = database.RunMigrationsFromPath(connStr, "../migrations") + logger, _ := zap.NewDevelopment() + err = database.RunMigrationsFromPath(connStr, "../migrations", logger) s.Require().NoError(err) s.T().Log("Creating connection pool...") @@ -94,7 +95,6 @@ func (s *IntegrationSuite) SetupSuite() { s.Require().NoError(err) s.T().Log("Creating gRPC server...") - logger, _ := zap.NewDevelopment() authHandler, userHandler, inviteHandler, requestHandler, supplierHandler := grpchandlers.NewHandlers( pool,