diff --git a/go.mod b/go.mod index b9d737b..953466e 100644 --- a/go.mod +++ b/go.mod @@ -5,8 +5,10 @@ go 1.24.1 require ( github.com/Masterminds/squirrel v1.5.4 github.com/fatih/color v1.18.0 + github.com/georgysavva/scany/v2 v2.1.4 github.com/jackc/pgx/v5 v5.7.4 github.com/joho/godotenv v1.5.1 + github.com/pkg/errors v0.9.1 golang.org/x/crypto v0.37.0 google.golang.org/grpc v1.65.0 google.golang.org/protobuf v1.36.6 diff --git a/go.sum b/go.sum index c2dd7c6..0961648 100644 --- a/go.sum +++ b/go.sum @@ -1,10 +1,16 @@ github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM= github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= +github.com/cockroachdb/cockroach-go/v2 v2.2.0 h1:/5znzg5n373N/3ESjHF5SMLxiW4RKB05Ql//KWfeTFs= +github.com/cockroachdb/cockroach-go/v2 v2.2.0/go.mod h1:u3MiKYGupPPjkn3ozknpMUpxPaNLTFWAya419/zv6eI= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= +github.com/georgysavva/scany/v2 v2.1.4 h1:nrzHEJ4oQVRoiKmocRqA1IyGOmM/GQOEsg9UjMR5Ip4= +github.com/georgysavva/scany/v2 v2.1.4/go.mod h1:fqp9yHZzM/PFVa3/rYEC57VmDx+KDch0LoqrJzkvtos= +github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= +github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= @@ -21,19 +27,25 @@ github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= +github.com/lib/pq v1.10.0 h1:Zx5DJFEYQXio93kgXnQ09fXNiUKsqv4OUEu2UtGcB1E= +github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= diff --git a/internal/api/user/service.go b/internal/api/user/service.go index 62bb833..ffabf57 100644 --- a/internal/api/user/service.go +++ b/internal/api/user/service.go @@ -13,6 +13,7 @@ type Service interface { Update(ctx context.Context, id int64, name *string, email *string) error Delete(ctx context.Context, id int64) error } + type Implementation struct { desc.UnimplementedAuthV1Server userService Service diff --git a/internal/app/app.go b/internal/app/app.go index ea8d52d..9b39f61 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -75,7 +75,7 @@ func (a *App) initGRPC(ctx context.Context) error { reflection.Register(a.grpcServer) desc.RegisterAuthV1Server(a.grpcServer, a.serviceProvider.UserAPIImpl(ctx)) - a.serviceProvider.cls.Add(func() error { + a.serviceProvider.GetCloser().Add(func() error { a.grpcServer.GracefulStop() return nil }) @@ -83,11 +83,11 @@ func (a *App) initGRPC(ctx context.Context) error { } func (a *App) runGRPCServer() error { - a.serviceProvider.Log().Info("server listening", slog.String("Addr", a.serviceProvider.GRPCConfig().Address())) + a.serviceProvider.GetLog().Info("server listening", slog.String("Addr", a.serviceProvider.GRPCConfig().Address())) l, err := net.Listen("tcp", a.serviceProvider.GRPCConfig().Address()) if err != nil { - a.serviceProvider.Log().Error("failed to listen", sl.Err(err)) + a.serviceProvider.GetLog().Error("failed to listen", sl.Err(err)) return err } diff --git a/internal/app/service_provider.go b/internal/app/service_provider.go index fb1bba5..6f14804 100644 --- a/internal/app/service_provider.go +++ b/internal/app/service_provider.go @@ -7,14 +7,18 @@ import ( "os" "syscall" + pgClient "github.com/Str1m/auth/internal/client/db/postgres" + "github.com/Str1m/auth/internal/client/db/transaction" + modelService "github.com/Str1m/auth/internal/model" + "github.com/Str1m/auth/internal/service/user" + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgxpool" + userAPI "github.com/Str1m/auth/internal/api/user" "github.com/Str1m/auth/internal/closer" "github.com/Str1m/auth/internal/config/env" "github.com/Str1m/auth/internal/lib/logger/handlers/slogpretty" - modelService "github.com/Str1m/auth/internal/model" - "github.com/Str1m/auth/internal/service/user" "github.com/Str1m/auth/internal/storage/users/postgres" - "github.com/jackc/pgx/v5/pgxpool" ) const ( @@ -23,11 +27,12 @@ const ( envProd = "prod" ) -type Storage interface { - Create(ctx context.Context, info *modelService.UserInfo, hashedPassword []byte) (int64, error) - Get(ctx context.Context, id int64) (*modelService.User, error) - Update(ctx context.Context, id int64, name, email *string) error - Delete(ctx context.Context, id int64) error +type StorageConfig interface { + DSN() string +} + +type GRPCConfig interface { + Address() string } type Service interface { @@ -37,23 +42,31 @@ type Service interface { Delete(ctx context.Context, id int64) error } -type StorageConfig interface { - DSN() string +type Storage interface { + Create(ctx context.Context, info *modelService.UserInfo, hashedPassword []byte) (int64, error) + Get(ctx context.Context, id int64) (*modelService.User, error) + Update(ctx context.Context, id int64, name, email *string) error + Delete(ctx context.Context, id int64) error } -type GRPCConfig interface { - Address() string +type Transactor interface { + BeginTx(ctx context.Context, txOptions pgx.TxOptions) (pgx.Tx, error) +} + +type TxManager interface { + ReadCommitted(ctx context.Context, f pgClient.Handler) error } type ServiceProvider struct { cls *closer.Closer log *slog.Logger - repoConfig StorageConfig - grpcConfig GRPCConfig + storageConfig StorageConfig + grpcConfig GRPCConfig - pgPool *pgxpool.Pool - userRepository Storage + dbClient *pgClient.ClientPG + txManager *transaction.TxManager + dbLayer Storage userService Service userAPI *userAPI.Implementation @@ -63,7 +76,7 @@ func newServiceProvider() *ServiceProvider { return &ServiceProvider{} } -func (s *ServiceProvider) Closer() *closer.Closer { +func (s *ServiceProvider) GetCloser() *closer.Closer { if s.cls == nil { s.cls = closer.New(os.Interrupt, syscall.SIGTERM) } @@ -71,7 +84,7 @@ func (s *ServiceProvider) Closer() *closer.Closer { return s.cls } -func (s *ServiceProvider) Log() *slog.Logger { +func (s *ServiceProvider) GetLog() *slog.Logger { if s.log == nil { env := os.Getenv("ENV") switch env { @@ -90,15 +103,15 @@ func (s *ServiceProvider) Log() *slog.Logger { return s.log } -func (s *ServiceProvider) RepoConfig() StorageConfig { - if s.repoConfig == nil { +func (s *ServiceProvider) GetStorageConfig() StorageConfig { + if s.storageConfig == nil { cfg, err := env.NewPGConfig() if err != nil { log.Fatalf("failed to get pg config: %s", err.Error()) } - s.repoConfig = cfg + s.storageConfig = cfg } - return s.repoConfig + return s.storageConfig } func (s *ServiceProvider) GRPCConfig() GRPCConfig { @@ -112,35 +125,36 @@ func (s *ServiceProvider) GRPCConfig() GRPCConfig { return s.grpcConfig } -func (s *ServiceProvider) PGPool(ctx context.Context) *pgxpool.Pool { - if s.pgPool == nil { - pool, err := pgxpool.New(ctx, s.RepoConfig().DSN()) +func (s *ServiceProvider) GetDBClient(ctx context.Context) *pgClient.ClientPG { + if s.dbClient == nil { + p, err := pgxpool.New(ctx, s.GetStorageConfig().DSN()) if err != nil { - log.Fatalf("failed to connect to database: %s", err.Error()) - } - if err = pool.Ping(ctx); err != nil { - log.Fatalf("ping error: %s", err.Error()) + log.Fatalln("err") } - s.Closer().Add(func() error { - pool.Close() - return nil - }) - s.pgPool = pool + + s.dbClient = pgClient.NewClient(p) } - return s.pgPool + + return s.dbClient } -func (s *ServiceProvider) UserRepository(ctx context.Context) Storage { - if s.userRepository == nil { - s.userRepository = postgres.NewRepository(s.PGPool(ctx)) +func (s *ServiceProvider) GetTxManager(ctx context.Context) *transaction.TxManager { + if s.txManager == nil { + s.txManager = transaction.NewTransactionManager(s.GetDBClient(ctx)) } + return s.txManager +} - return s.userRepository +func (s *ServiceProvider) GetDBLayer(ctx context.Context) Storage { + if s.dbLayer == nil { + s.dbLayer = postgres.NewStoragePG(s.GetDBClient(ctx)) + } + return s.dbLayer } func (s *ServiceProvider) UserService(ctx context.Context) Service { if s.userService == nil { - s.userService = user.NewService(s.Log(), s.UserRepository(ctx)) + s.userService = user.NewService(s.GetLog(), s.GetDBLayer(ctx), s.GetTxManager(ctx)) } return s.userService } diff --git a/internal/client/db/postgres/client.go b/internal/client/db/postgres/client.go new file mode 100644 index 0000000..24dfe25 --- /dev/null +++ b/internal/client/db/postgres/client.go @@ -0,0 +1,88 @@ +package postgres + +import ( + "context" + + "github.com/georgysavva/scany/v2/pgxscan" + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgconn" + "github.com/jackc/pgx/v5/pgxpool" +) + +type Handler func(ctx context.Context) error + +type Query struct { + Name string + QueryRaw string +} +type key string + +const ( + TxKey key = "tx" +) + +type ClientPG struct { + db *pgxpool.Pool +} + +func NewClient(db *pgxpool.Pool) *ClientPG { + return &ClientPG{db: db} +} + +func (p *ClientPG) GetDB() *pgxpool.Pool { + return p.db +} + +func (p *ClientPG) ExecContext(ctx context.Context, q Query, args ...any) (pgconn.CommandTag, error) { + tx, ok := ctx.Value(TxKey).(pgx.Tx) + if ok { + return tx.Exec(ctx, q.QueryRaw, args...) + } + return p.db.Exec(ctx, q.QueryRaw, args...) +} + +func (p *ClientPG) QueryContext(ctx context.Context, q Query, args ...any) (pgx.Rows, error) { + tx, ok := ctx.Value(TxKey).(pgx.Tx) + if ok { + return tx.Query(ctx, q.QueryRaw, args...) + } + return p.db.Query(ctx, q.QueryRaw, args...) +} + +func (p *ClientPG) QueryRowContext(ctx context.Context, q Query, args ...any) pgx.Row { + tx, ok := ctx.Value(TxKey).(pgx.Tx) + if ok { + return tx.QueryRow(ctx, q.QueryRaw, args...) + } + + return p.db.QueryRow(ctx, q.QueryRaw, args...) +} + +func (p *ClientPG) Ping(ctx context.Context) error { + return p.db.Ping(ctx) +} + +func (p *ClientPG) Close() { + p.db.Close() +} + +func (p *ClientPG) ScanOneContext(ctx context.Context, dest any, q Query, args ...any) error { + row, err := p.QueryContext(ctx, q, args...) + if err != nil { + return err + } + return pgxscan.ScanOne(dest, row) +} + +func (p *ClientPG) ScanAllContext(ctx context.Context, dest any, q Query, args ...any) error { + row, err := p.QueryContext(ctx, q, args...) + if err != nil { + return err + } + + return pgxscan.ScanAll(dest, row) +} + +func MakeContextTx(ctx context.Context, tx pgx.Tx) context.Context { + return context.WithValue(ctx, TxKey, tx) +} diff --git a/internal/client/db/transaction/transaction.go b/internal/client/db/transaction/transaction.go new file mode 100644 index 0000000..bf23315 --- /dev/null +++ b/internal/client/db/transaction/transaction.go @@ -0,0 +1,65 @@ +package transaction + +import ( + "context" + + "github.com/Str1m/auth/internal/client/db/postgres" + "github.com/jackc/pgx/v5" + "github.com/pkg/errors" +) + +// TODO: Проверить работу транзакций + +type TxManager struct { + db *postgres.ClientPG +} + +func NewTransactionManager(db *postgres.ClientPG) *TxManager { + return &TxManager{ + db: db, + } +} + +func (m *TxManager) ReadCommitted(ctx context.Context, f postgres.Handler) error { + txOpts := pgx.TxOptions{IsoLevel: pgx.ReadCommitted} + return m.transaction(ctx, txOpts, f) +} + +func (m *TxManager) transaction(ctx context.Context, opts pgx.TxOptions, f postgres.Handler) (err error) { + tx, ok := ctx.Value(postgres.TxKey).(pgx.Tx) + if ok { + return f(ctx) + } + + tx, err = m.db.GetDB().BeginTx(ctx, opts) + if err != nil { + return err + } + + ctx = postgres.MakeContextTx(ctx, tx) + + defer func() { + if r := recover(); r != nil { + err = errors.Errorf("panic recovered: %v", r) + } + + if err != nil { + if errRollback := tx.Rollback(ctx); err != nil { + err = errors.Wrapf(err, "errRollback: %v", errRollback) + } + return + } + + if err == nil { + err = tx.Commit(ctx) + if err != nil { + err = errors.Wrap(err, "commit failed") + } + } + }() + + if err = f(ctx); err != nil { + err = errors.Wrap(err, "failed exec code inside transaction") + } + return nil +} diff --git a/internal/service/service.go b/internal/service/service.go index bb3368d..2b030cb 100644 --- a/internal/service/service.go +++ b/internal/service/service.go @@ -1,6 +1,8 @@ package service -import "errors" +import ( + "errors" +) var ( ErrPassNotEqual = errors.New("passwords are not equal") diff --git a/internal/service/user/create.go b/internal/service/user/create.go index f10adbc..553bac1 100644 --- a/internal/service/user/create.go +++ b/internal/service/user/create.go @@ -22,10 +22,9 @@ func (s *Service) Create(ctx context.Context, userInfo *modelService.UserInfo) ( userInfo.Password, userInfo.PasswordConfirm = "", "" - id, err := s.UserRepository.Create(ctx, userInfo, hashedPassword) + id, err := s.UserDBClient.Create(ctx, userInfo, hashedPassword) if err != nil { return 0, fmt.Errorf("%s: %w", op, err) } - return id, nil } diff --git a/internal/service/user/delete.go b/internal/service/user/delete.go index 4e06727..72f3d53 100644 --- a/internal/service/user/delete.go +++ b/internal/service/user/delete.go @@ -7,7 +7,7 @@ import ( func (s *Service) Delete(ctx context.Context, id int64) error { const op = "service.user.Delete" - err := s.UserRepository.Delete(ctx, id) + err := s.UserDBClient.Delete(ctx, id) if err != nil { return fmt.Errorf("%s: %w", op, err) } diff --git a/internal/service/user/get.go b/internal/service/user/get.go index 8d9a683..a7beb02 100644 --- a/internal/service/user/get.go +++ b/internal/service/user/get.go @@ -9,7 +9,7 @@ import ( func (s *Service) Get(ctx context.Context, id int64) (*modelService.User, error) { const op = "service.user.Get" - user, err := s.UserRepository.Get(ctx, id) + user, err := s.UserDBClient.Get(ctx, id) if err != nil { return nil, fmt.Errorf("%s: %w", op, err) } diff --git a/internal/service/user/service.go b/internal/service/user/service.go index 651b498..3158072 100644 --- a/internal/service/user/service.go +++ b/internal/service/user/service.go @@ -4,24 +4,26 @@ import ( "context" "log/slog" + "github.com/Str1m/auth/internal/client/db/transaction" modelService "github.com/Str1m/auth/internal/model" ) -type Repository interface { +type DBLayer interface { Create(ctx context.Context, info *modelService.UserInfo, hashedPassword []byte) (int64, error) Get(ctx context.Context, id int64) (*modelService.User, error) Update(ctx context.Context, id int64, name, email *string) error Delete(ctx context.Context, id int64) error } - type Service struct { - log *slog.Logger - UserRepository Repository + log *slog.Logger + UserDBClient DBLayer + txManager *transaction.TxManager } -func NewService(log *slog.Logger, authRepo Repository) *Service { +func NewService(log *slog.Logger, dbClient DBLayer, txManager *transaction.TxManager) *Service { return &Service{ - log: log, - UserRepository: authRepo, + log: log, + UserDBClient: dbClient, + txManager: txManager, } } diff --git a/internal/service/user/update.go b/internal/service/user/update.go index 25f1546..1e8d891 100644 --- a/internal/service/user/update.go +++ b/internal/service/user/update.go @@ -7,7 +7,7 @@ import ( func (s *Service) Update(ctx context.Context, id int64, name *string, email *string) error { const op = "service.user.Update" - err := s.UserRepository.Update(ctx, id, name, email) + err := s.UserDBClient.Update(ctx, id, name, email) if err != nil { return fmt.Errorf("%s: %w", op, err) } diff --git a/internal/storage/storage.go b/internal/storage/storage.go index 4ff8c3f..7dac14f 100644 --- a/internal/storage/storage.go +++ b/internal/storage/storage.go @@ -1,6 +1,8 @@ package storage -import "errors" +import ( + "errors" +) var ( ErrUserNotFound = errors.New("user not found") diff --git a/internal/storage/users/model/user.go b/internal/storage/users/model/user.go index f5a0727..7118b68 100644 --- a/internal/storage/users/model/user.go +++ b/internal/storage/users/model/user.go @@ -7,18 +7,18 @@ import ( ) type User struct { - ID int64 - Name string - Email string - Role desc.Role - CreatedAt time.Time - UpdatedAt time.Time + ID int64 `db:"id"` + Name string `db:"name"` + Email string `db:"email"` + Role desc.Role `db:"role"` + CreatedAt time.Time `db:"created_at"` + UpdatedAt time.Time `db:"updated_at"` } type UserInfo struct { - Name string - Email string - Password string - PasswordConfirm string - Role desc.Role + Name string `db:"id"` + Email string `db:"email"` + Password string `db:"password"` + PasswordConfirm string `db:"password_confirm"` + Role desc.Role `db:"role"` } diff --git a/internal/storage/users/postgres/repository.go b/internal/storage/users/postgres/postgres.go similarity index 68% rename from internal/storage/users/postgres/repository.go rename to internal/storage/users/postgres/postgres.go index e5a8143..ea80499 100644 --- a/internal/storage/users/postgres/repository.go +++ b/internal/storage/users/postgres/postgres.go @@ -3,7 +3,9 @@ package postgres import ( "context" "fmt" + "log" + "github.com/Str1m/auth/internal/client/db/postgres" modelService "github.com/Str1m/auth/internal/model" "github.com/Str1m/auth/internal/storage" @@ -11,7 +13,6 @@ import ( modelRepo "github.com/Str1m/auth/internal/storage/users/model" sq "github.com/Masterminds/squirrel" - "github.com/jackc/pgx/v5/pgxpool" ) const ( @@ -26,19 +27,19 @@ const ( updatedAtColumn = "updated_at" ) -type Repo struct { - db *pgxpool.Pool +type StoragePG struct { + client *postgres.ClientPG } -func NewRepository(db *pgxpool.Pool) *Repo { - return &Repo{db: db} +func NewStoragePG(db *postgres.ClientPG) *StoragePG { + return &StoragePG{client: db} } -func (r *Repo) Close() { - r.db.Close() +func (r *StoragePG) Close() { + r.client.Close() } -func (r *Repo) Create(ctx context.Context, info *modelService.UserInfo, hashedPassword []byte) (int64, error) { +func (r *StoragePG) Create(ctx context.Context, info *modelService.UserInfo, hashedPassword []byte) (int64, error) { const op = "repository.users.Create" builder := sq.Insert(tableName). @@ -51,18 +52,22 @@ func (r *Repo) Create(ctx context.Context, info *modelService.UserInfo, hashedPa return 0, fmt.Errorf("%s: %w", op, err) } + q := postgres.Query{ + Name: op, + QueryRaw: query, + } + var id int64 - err = r.db.QueryRow(ctx, query, args...).Scan(&id) + err = r.client.QueryRowContext(ctx, q, args...).Scan(&id) + if err != nil { return 0, fmt.Errorf("%s: %w", op, err) } - return id, nil } -func (r *Repo) Get(ctx context.Context, id int64) (*modelService.User, error) { +func (r *StoragePG) Get(ctx context.Context, id int64) (*modelService.User, error) { const op = "repository.users.Get" - builder := sq.Select(idColumn, nameColumn, emailColumn, roleColumn, createdAtColumn, updatedAtColumn). PlaceholderFormat(sq.Dollar). From(tableName). @@ -73,21 +78,21 @@ func (r *Repo) Get(ctx context.Context, id int64) (*modelService.User, error) { if err != nil { return nil, fmt.Errorf("%s: %w", op, err) } - err = r.db.QueryRow(ctx, query, args...).Scan( - &user.ID, - &user.Name, - &user.Email, - &user.Role, - &user.CreatedAt, - &user.UpdatedAt) + q := postgres.Query{ + Name: op, + QueryRaw: query, + } + err = r.client.ScanOneContext(ctx, &user, q, args...) + if err != nil { + log.Printf("Error: %s", err.Error()) return nil, fmt.Errorf("%s: %w", op, err) } return converter.ToUserFromStorage(&user), nil } -func (r *Repo) Update(ctx context.Context, id int64, name, email *string) error { +func (r *StoragePG) Update(ctx context.Context, id int64, name, email *string) error { const op = "repository.users.Update" builder := sq.Update(tableName). @@ -107,7 +112,12 @@ func (r *Repo) Update(ctx context.Context, id int64, name, email *string) error return fmt.Errorf("%s: %w", op, err) } - result, err := r.db.Exec(ctx, query, args...) + q := postgres.Query{ + Name: op, + QueryRaw: query, + } + + result, err := r.client.ExecContext(ctx, q, args...) if err != nil { return fmt.Errorf("%s: %w", op, err) } @@ -119,7 +129,7 @@ func (r *Repo) Update(ctx context.Context, id int64, name, email *string) error return nil } -func (r *Repo) Delete(ctx context.Context, id int64) error { +func (r *StoragePG) Delete(ctx context.Context, id int64) error { const op = "repository.users.Delete" builder := sq.Delete(tableName). @@ -131,7 +141,12 @@ func (r *Repo) Delete(ctx context.Context, id int64) error { return fmt.Errorf("%s: %w", op, err) } - result, err := r.db.Exec(ctx, query, args...) + q := postgres.Query{ + Name: op, + QueryRaw: query, + } + + result, err := r.client.ExecContext(ctx, q, args...) if err != nil { return fmt.Errorf("%s: %w", op, err) }