Files
payouts/internal/api/user/user_handler.go

145 lines
3.7 KiB
Go

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
}
}