From 85773148759559fb93736c9753af9ecfca5ce0b7 Mon Sep 17 00:00:00 2001 From: Nikolai Papin Date: Fri, 20 Jun 2025 20:28:50 +0300 Subject: [PATCH 1/3] feat: dbcontext --- backend/go.mod | 4 ++- backend/internal/database/dbContext.go | 43 ++++++++++++++++++++++++++ backend/internal/database/setup.go | 13 ++++++++ sqlc/sqlc.yaml | 2 ++ 4 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 backend/internal/database/dbContext.go create mode 100644 backend/internal/database/setup.go diff --git a/backend/go.mod b/backend/go.mod index 3bc4aa7..a45fad6 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -10,6 +10,7 @@ require ( github.com/swaggo/files v1.0.1 github.com/swaggo/gin-swagger v1.6.0 github.com/swaggo/swag v1.16.4 + go.uber.org/fx v1.24.0 go.uber.org/zap v1.27.0 ) @@ -32,6 +33,7 @@ require ( github.com/goccy/go-json v0.10.5 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.2.10 // indirect @@ -50,11 +52,11 @@ require ( github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.3.0 // indirect go.uber.org/dig v1.19.0 // indirect - go.uber.org/fx v1.24.0 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/arch v0.18.0 // indirect golang.org/x/crypto v0.39.0 // indirect golang.org/x/net v0.41.0 // indirect + golang.org/x/sync v0.15.0 // indirect golang.org/x/sys v0.33.0 // indirect golang.org/x/text v0.26.0 // indirect golang.org/x/tools v0.34.0 // indirect diff --git a/backend/internal/database/dbContext.go b/backend/internal/database/dbContext.go new file mode 100644 index 0000000..7b704a0 --- /dev/null +++ b/backend/internal/database/dbContext.go @@ -0,0 +1,43 @@ +package database + +import ( + "context" + + "github.com/jackc/pgx/v5/pgxpool" + + "easywish/config" +) + +type DbContext interface { + Close() +} + +type dbContextImpl struct { + Pool *pgxpool.Pool + Context context.Context +} + +func NewDbContext() DbContext { + + ctx := context.Background() + pool, err := pgxpool.New(ctx, config.GetConfig().DatabaseUrl) + + if err != nil { + panic(err.Error()) + } + + if err := pool.Ping(context.Background()); err != nil { + panic(err.Error()) + } + + return &dbContextImpl{ + Pool: pool, + Context: ctx, + } +} + +// Close implements DbContext. +func (d *dbContextImpl) Close() { + d.Pool.Close() +} + diff --git a/backend/internal/database/setup.go b/backend/internal/database/setup.go new file mode 100644 index 0000000..0679236 --- /dev/null +++ b/backend/internal/database/setup.go @@ -0,0 +1,13 @@ +package database + +import ( + "go.uber.org/fx" +) + +var Module = fx.Module("database", + fx.Provide( + NewDbContext, + ), +) + + diff --git a/sqlc/sqlc.yaml b/sqlc/sqlc.yaml index 01e204f..eee19b1 100644 --- a/sqlc/sqlc.yaml +++ b/sqlc/sqlc.yaml @@ -7,6 +7,8 @@ sql: go: out: "../backend/internal/database" sql_package: "pgx/v5" + emit_prepared_queries: true + emit_interface: false database: # managed: true uri: "postgresql://postgres:postgres@localhost:5432/postgres?sslmode=disable" From 03c072e67b0f2a999742067e1bec409465b07fc5 Mon Sep 17 00:00:00 2001 From: Nikolai Papin Date: Fri, 20 Jun 2025 20:32:18 +0300 Subject: [PATCH 2/3] feat: dbcontext implements DBTX interface --- backend/internal/database/dbContext.go | 42 +++++++++++++++----------- 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/backend/internal/database/dbContext.go b/backend/internal/database/dbContext.go index 7b704a0..08676f8 100644 --- a/backend/internal/database/dbContext.go +++ b/backend/internal/database/dbContext.go @@ -3,41 +3,47 @@ package database import ( "context" - "github.com/jackc/pgx/v5/pgxpool" - "easywish/config" + + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgconn" + "github.com/jackc/pgx/v5/pgxpool" ) type DbContext interface { + DBTX Close() + BeginTx(ctx context.Context) (pgx.Tx, error) } type dbContextImpl struct { Pool *pgxpool.Pool - Context context.Context } func NewDbContext() DbContext { - - ctx := context.Background() - pool, err := pgxpool.New(ctx, config.GetConfig().DatabaseUrl) - + pool, err := pgxpool.New(context.Background(), config.GetConfig().DatabaseUrl) if err != nil { - panic(err.Error()) - } - - if err := pool.Ping(context.Background()); err != nil { - panic(err.Error()) - } - - return &dbContextImpl{ - Pool: pool, - Context: ctx, + panic("db connection failed: " + err.Error()) } + return &dbContextImpl{Pool: pool} } -// Close implements DbContext. func (d *dbContextImpl) Close() { d.Pool.Close() } +func (d *dbContextImpl) Exec(ctx context.Context, sql string, args ...any) (pgconn.CommandTag, error) { + return d.Pool.Exec(ctx, sql, args...) +} + +func (d *dbContextImpl) Query(ctx context.Context, sql string, args ...any) (pgx.Rows, error) { + return d.Pool.Query(ctx, sql, args...) +} + +func (d *dbContextImpl) QueryRow(ctx context.Context, sql string, args ...any) pgx.Row { + return d.Pool.QueryRow(ctx, sql, args...) +} + +func (d *dbContextImpl) BeginTx(ctx context.Context) (pgx.Tx, error) { + return d.Pool.Begin(ctx) +} From 0c4d618fa40ae3efc85888c69ebb48eef21c6d09 Mon Sep 17 00:00:00 2001 From: Nikolai Papin Date: Sat, 21 Jun 2025 02:27:23 +0300 Subject: [PATCH 3/3] feat: dbcontext abstraction via dependency --- backend/cmd/main.go | 2 ++ backend/docs/docs.go | 26 ++++++++++++++++++++++++++ backend/docs/swagger.json | 26 ++++++++++++++++++++++++++ backend/docs/swagger.yaml | 17 +++++++++++++++++ backend/internal/controllers/auth.go | 19 ++++++++++++++++++- backend/internal/errors/auth.go | 1 + backend/internal/services/auth.go | 25 ++++++++++++++++++++++--- 7 files changed, 112 insertions(+), 4 deletions(-) diff --git a/backend/cmd/main.go b/backend/cmd/main.go index 57612c0..b69e2e8 100644 --- a/backend/cmd/main.go +++ b/backend/cmd/main.go @@ -24,6 +24,7 @@ import ( "easywish/config" docs "easywish/docs" "easywish/internal/controllers" + "easywish/internal/database" "easywish/internal/logger" "easywish/internal/routes" "easywish/internal/services" @@ -43,6 +44,7 @@ func main() { logger.NewLogger, gin.Default, ), + database.Module, services.Module, controllers.Module, routes.Module, diff --git a/backend/docs/docs.go b/backend/docs/docs.go index 044b645..137d474 100644 --- a/backend/docs/docs.go +++ b/backend/docs/docs.go @@ -128,6 +128,17 @@ const docTemplate = `{ "Auth" ], "summary": "Register an account", + "parameters": [ + { + "description": "desc", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/models.RegistrationBeginRequest" + } + } + ], "responses": {} } }, @@ -310,6 +321,21 @@ const docTemplate = `{ "type": "string" } } + }, + "models.RegistrationBeginRequest": { + "type": "object", + "properties": { + "email": { + "type": "string" + }, + "password": { + "description": "TODO: password checking", + "type": "string" + }, + "username": { + "type": "string" + } + } } }, "securityDefinitions": { diff --git a/backend/docs/swagger.json b/backend/docs/swagger.json index 2133584..b1d469a 100644 --- a/backend/docs/swagger.json +++ b/backend/docs/swagger.json @@ -124,6 +124,17 @@ "Auth" ], "summary": "Register an account", + "parameters": [ + { + "description": "desc", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/models.RegistrationBeginRequest" + } + } + ], "responses": {} } }, @@ -306,6 +317,21 @@ "type": "string" } } + }, + "models.RegistrationBeginRequest": { + "type": "object", + "properties": { + "email": { + "type": "string" + }, + "password": { + "description": "TODO: password checking", + "type": "string" + }, + "username": { + "type": "string" + } + } } }, "securityDefinitions": { diff --git a/backend/docs/swagger.yaml b/backend/docs/swagger.yaml index 507b388..66a9a37 100644 --- a/backend/docs/swagger.yaml +++ b/backend/docs/swagger.yaml @@ -21,6 +21,16 @@ definitions: refresh_token: type: string type: object + models.RegistrationBeginRequest: + properties: + email: + type: string + password: + description: 'TODO: password checking' + type: string + username: + type: string + type: object info: contact: {} description: Easy and feature-rich wishlist. @@ -97,6 +107,13 @@ paths: post: consumes: - application/json + parameters: + - description: desc + in: body + name: request + required: true + schema: + $ref: '#/definitions/models.RegistrationBeginRequest' produces: - application/json responses: {} diff --git a/backend/internal/controllers/auth.go b/backend/internal/controllers/auth.go index 23edbf7..0a6cb77 100644 --- a/backend/internal/controllers/auth.go +++ b/backend/internal/controllers/auth.go @@ -1,6 +1,7 @@ package controllers import ( + "easywish/internal/models" "easywish/internal/services" "net/http" @@ -72,9 +73,25 @@ func (a *authControllerImpl) Refresh(c *gin.Context) { // @Tags Auth // @Accept json // @Produce json +// @Param request body models.RegistrationBeginRequest true "desc" // @Router /auth/registrationBegin [post] func (a *authControllerImpl) RegistrationBegin(c *gin.Context) { - c.Status(http.StatusNotImplemented) + + var request models.RegistrationBeginRequest + + if err := c.ShouldBindJSON(&request); err != nil { + c.Status(http.StatusBadRequest) + return + } + + _, err := a.authService.RegistrationBegin(request) + if err != nil { + c.JSON(http.StatusBadRequest, err.Error()) + return + } + + c.Status(http.StatusAccepted) + return } // RegistrationBegin implements AuthController. diff --git a/backend/internal/errors/auth.go b/backend/internal/errors/auth.go index a3d0959..55d13cf 100644 --- a/backend/internal/errors/auth.go +++ b/backend/internal/errors/auth.go @@ -9,4 +9,5 @@ var ( ErrUsernameTaken = errors.New("Provided username is already in use") ErrInvalidCredentials = errors.New("Invalid username, password or TOTP code") ErrInvalidToken = errors.New("Token is invalid or expired") + ErrServerError = errors.New("Internal server error") ) diff --git a/backend/internal/services/auth.go b/backend/internal/services/auth.go index 55dafef..fe50e1c 100644 --- a/backend/internal/services/auth.go +++ b/backend/internal/services/auth.go @@ -1,10 +1,14 @@ package services import ( + "context" "easywish/internal/database" errs "easywish/internal/errors" + "easywish/internal/logger" "easywish/internal/models" "easywish/internal/utils" + + "go.uber.org/zap" ) type AuthService interface { @@ -15,14 +19,29 @@ type AuthService interface { } type authServiceImpl struct { + log logger.Logger + dbctx database.DbContext } -func NewAuthService() AuthService { - return &authServiceImpl{} +func NewAuthService(_log logger.Logger, _dbctx database.DbContext) AuthService { + return &authServiceImpl{log: _log, dbctx: _dbctx} } func (a *authServiceImpl) RegistrationBegin(request models.RegistrationBeginRequest) (bool, error) { - return false, errs.ErrNotImplemented + + ctx := context.Background() + queries := database.New(a.dbctx) + user, err := queries.CreateUser(ctx, request.Username) // TODO: validation + + if err != nil { + a.log.Get().Error("Failed to add user to database", zap.Error(err)) + return false, errs.ErrServerError + } + a.log.Get().Info("Registered a new user", zap.String("username", user.Username)) + + // TODO: Send verification email + + return true, nil } func (a *authServiceImpl) RegistrationComplete(request models.RegistrationBeginRequest) (*models.RegistrationCompleteResponse, error) {