Handlers, DB, Cache
This commit is contained in:
@@ -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())
|
||||
|
||||
17
internal/api/payment/module.go
Normal file
17
internal/api/payment/module.go
Normal 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)
|
||||
}
|
||||
54
internal/api/payment/payment_handler.go
Normal file
54
internal/api/payment/payment_handler.go
Normal 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")
|
||||
}
|
||||
16
internal/api/user/module.go
Normal file
16
internal/api/user/module.go
Normal 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),
|
||||
)
|
||||
144
internal/api/user/user_handler.go
Normal file
144
internal/api/user/user_handler.go
Normal 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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user