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