Handlers, DB, Cache

This commit is contained in:
2026-03-10 19:17:43 +03:00
parent e56b1f1305
commit 968c030939
23 changed files with 566 additions and 34 deletions

View File

@@ -11,6 +11,8 @@ import (
"github.com/prometheus/client_golang/prometheus/promhttp"
"go.uber.org/fx"
"payouts/internal/api/payment"
"payouts/internal/api/user"
"payouts/internal/api/version"
appConfig "payouts/internal/config"
"payouts/internal/service/monitoring"
@@ -18,8 +20,11 @@ import (
// Module is a fx module
var Module = fx.Options(
user.Module,
payment.Module,
version.Module,
monitoring.Module,
fx.Invoke(RegisterRoutes),
)
@@ -32,7 +37,9 @@ type Params struct {
AppConfig *appConfig.App
Version version.Handler
PaymentHandler payment.Handler
UserHandler user.Handler
Version version.Handler
Metrics monitoring.Metrics
}
@@ -54,11 +61,16 @@ func RegisterRoutes(p Params, lc fx.Lifecycle) {
}
apiRouter := router.PathPrefix(BaseRoute).Subrouter()
apiRouter.HandleFunc("/test", func(http.ResponseWriter, *http.Request) {
slog.Info("Test called", slog.String("sample", "value"))
})
// data
userRouter := apiRouter.PathPrefix(user.BaseRoute).Subrouter()
userRouter.HandleFunc(user.RegisterRoute, p.UserHandler.UserRegister).Methods(http.MethodPost)
userRouter.HandleFunc(user.LoginRoute, p.UserHandler.UserLogin).Methods(http.MethodPost)
paymentRouter := apiRouter.PathPrefix(payment.BaseRoute).Subrouter()
paymentRouter.HandleFunc(payment.CreateRoute, p.PaymentHandler.PaymentCreate).Methods(http.MethodPost)
paymentRouter.HandleFunc(payment.CallbackRoute, p.PaymentHandler.PaymentCallback).Methods(http.MethodPost)
// collect api metrics
apiRouter.Use(p.Metrics.GetMiddleware())
router.Handle(p.AppConfig.Metrics.Endpoint, promhttp.Handler())

View File

@@ -0,0 +1,17 @@
package payment
import (
"net/http"
"go.uber.org/fx"
)
var Module = fx.Options(
fx.Provide(NewPaymentHandler),
)
type Handler interface {
GetSbpBanks(http.ResponseWriter, *http.Request)
PaymentCreate(http.ResponseWriter, *http.Request)
PaymentCallback(http.ResponseWriter, *http.Request)
}

View File

@@ -0,0 +1,54 @@
package payment
import (
"net/http"
"go.uber.org/fx"
"payouts/internal/config"
"payouts/internal/service/cache"
"payouts/internal/service/database"
)
const (
BaseRoute = "/payment"
CreateRoute = "/create"
CallbackRoute = "/callback"
BanksRoute = "/sbp/banks"
)
type paymentHandler struct {
dbService database.Service
cacheService cache.Service
}
// Params represents the module input params
type Params struct {
fx.In
AppConfig *config.App
DbService database.Service
CacheService cache.Service
}
func NewPaymentHandler(p Params) (Handler, error) {
return &paymentHandler{
dbService: p.DbService,
cacheService: p.CacheService,
}, nil
}
// GetSbpBanks implements [Handler].
func (p *paymentHandler) GetSbpBanks(http.ResponseWriter, *http.Request) {
panic("unimplemented")
}
// PaymentCreate implements [Handler].
func (p *paymentHandler) PaymentCreate(http.ResponseWriter, *http.Request) {
panic("unimplemented")
}
// PaymentCallback implements [Handler].
func (p *paymentHandler) PaymentCallback(http.ResponseWriter, *http.Request) {
panic("unimplemented")
}

View File

@@ -0,0 +1,16 @@
package user
import (
"net/http"
"go.uber.org/fx"
)
type Handler interface {
UserRegister(http.ResponseWriter, *http.Request)
UserLogin(http.ResponseWriter, *http.Request)
}
var Module = fx.Options(
fx.Provide(NewUserHandler),
)

View File

@@ -0,0 +1,144 @@
package user
import (
"encoding/json"
"errors"
"log/slog"
"net/http"
"time"
"github.com/google/uuid"
"github.com/jinzhu/copier"
"go.uber.org/fx"
"golang.org/x/crypto/bcrypt"
"payouts/internal/config"
"payouts/internal/models"
"payouts/internal/service/cache"
"payouts/internal/service/database"
"payouts/internal/service/database/orm"
)
const (
BaseRoute = "/user"
RegisterRoute = "/register"
LoginRoute = "/login"
)
type userHandler struct {
ttl time.Duration
dbService database.Service
cacheService cache.Service
}
// Params represents the module input params
type Params struct {
fx.In
AppConfig *config.App
DbService database.Service
CacheService cache.Service
}
func NewUserHandler(p Params) (Handler, error) {
return &userHandler{
ttl: p.AppConfig.Cache.TTL,
dbService: p.DbService,
cacheService: p.CacheService,
}, nil
}
// UserRegister implements [Handler].
func (u *userHandler) UserRegister(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
errResponse := func(message string, err error, status int) {
http.Error(w, errors.Join(errors.New(message), err).Error(), status)
}
user := models.UserRegister{}
err := json.NewDecoder(r.Body).Decode(&user)
if err != nil {
slog.Error("Failed to get password hash", slog.String("error", err.Error()))
errResponse("failed to decode request body", err, http.StatusBadRequest)
return
}
if user.Passwd != user.PasswdCfm || len(user.Passwd) == 0 || len(user.Phone) == 0 || len(user.TIN) == 0 {
slog.Error("No required parameters passed")
errResponse("invalid parameters", nil, http.StatusBadRequest)
return
}
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(user.Passwd), bcrypt.DefaultCost)
if err != nil {
slog.Error("Failed to get password hash", slog.String("error", err.Error()))
errResponse("internal error", nil, http.StatusInternalServerError)
return
}
user.PasswdHash = string(hashedPassword)
ormUser := orm.User{}
copier.Copy(&ormUser, user)
// todo: add data validation
err = u.dbService.CreateUser(ormUser, database.WithContext(r.Context()))
if err != nil {
slog.Error("Failed to create user", slog.String("error", err.Error()))
errResponse("failed to create user", err, http.StatusBadRequest)
return
}
w.WriteHeader(http.StatusCreated)
}
// UserLogin implements [Handler].
func (u *userHandler) UserLogin(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
errResponse := func(message string, err error, status int) {
w.Header().Set("Content-Type", "text/plain")
http.Error(w, errors.Join(errors.New(message), err).Error(), status)
}
user := models.UserLoginReq{}
err := json.NewDecoder(r.Body).Decode(&user)
if err != nil {
errResponse("failed to decode request body", err, http.StatusBadRequest)
return
}
if len(user.Phone) == 0 || len(user.Passwd) == 0 {
slog.Error("No required parameters passed")
errResponse("invalid parameters", nil, http.StatusBadRequest)
return
}
ormUser, err := u.dbService.GetUser(orm.User{Phone: user.Phone}, database.WithContext(r.Context()))
if err != nil {
errResponse("invalid credentials", nil, http.StatusUnauthorized)
return
}
err = bcrypt.CompareHashAndPassword([]byte(ormUser.PasswdHash), []byte(user.Passwd))
if err != nil {
errResponse("invalid credentials", nil, http.StatusUnauthorized)
return
}
sessionId := uuid.New().String()
u.cacheService.PutSession(sessionId, ormUser)
resp := models.UserLoginResp{
Token: sessionId,
TokenTtl: time.Now().Add(u.ttl).Unix(),
}
w.Header().Set("Content-Type", "application/json")
err = json.NewEncoder(w).Encode(resp)
if err != nil {
errResponse("failed to encode response", err, http.StatusInternalServerError)
return
}
}