add service
All checks were successful
Deploy Smart Search Backend Test / deploy (push) Successful in 2m55s
All checks were successful
Deploy Smart Search Backend Test / deploy (push) Successful in 2m55s
This commit is contained in:
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
3
go.mod
3
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
|
||||
|
||||
@@ -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...))
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user