// Copyright 2024 Patial Tech. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package user import ( "context" "errors" "fmt" "net/mail" "strings" "time" "gitserver.in/patialtech/rano/config" "gitserver.in/patialtech/rano/db" "gitserver.in/patialtech/rano/db/ent/user" "gitserver.in/patialtech/rano/mailer" "gitserver.in/patialtech/rano/mailer/message" "gitserver.in/patialtech/rano/util/crypto" "gitserver.in/patialtech/rano/util/validate" ) type CreateInput struct { Email string `validate:"email"` Phone string Pwd string `validate:"required"` ConfirmPwd string `validate:"required"` FirstName string `validate:"required"` MiddleName string LastName string RoleID uint8 `validate:"required"` } var ( ErrCreateInpNil = errors.New("user: create input is nil") ErrWrongConfirmPwd = errors.New("user: confirm password does not match") ) // Create user record in DB // // will return created userID on success func Create(ctx context.Context, inp *CreateInput) (int64, error) { // Check for nil input. if inp == nil { return 0, ErrCreateInpNil } // Validate struct. if err := validate.Struct(inp); err != nil { return 0, err } // Compare pwd and comparePwd. if inp.Pwd != inp.ConfirmPwd { return 0, ErrWrongConfirmPwd } h, salt, err := crypto.PasswordHash(inp.Pwd) if err != nil { return 0, err } // Begin a transaction. tx, err := db.Client().BeginTx(ctx, nil) if err != nil { return 0, err } // Save User to DB. u, err := tx.User.Create(). SetEmail(inp.Email). SetPwdHash(h). SetPwdSalt(salt). SetFirstName(inp.FirstName). SetMiddleName(inp.MiddleName). SetLastName(inp.LastName). SetStatus(user.StatusActive). Save(ctx) if err != nil { return 0, errors.New("failed to create user") } // Get a new email-verification token tokenDuration := time.Hour * 6 token, err := newTokenToVerifyEmail(u.ID, tokenDuration) if err != nil { _ = tx.Rollback() return 0, err } // Save token to DB err = tx.VerifyToken.Create(). SetToken(token). SetExpiresAt(time.Now().Add(tokenDuration).UTC()). SetPurpose("VerifyEmail"). SetUserID(u.ID).Exec(ctx) if err != nil { _ = tx.Rollback() return 0, err } name := fullName(inp.FirstName, inp.MiddleName, inp.LastName) // Send a welcome email with a link to verigy email-address. err = mailer.Send( []mail.Address{ {Name: name, Address: inp.Email}, }, &message.Welcome{ Name: name, VerifyURL: config.VerifyEmailURL(token), }, ) if err != nil { _ = tx.Rollback() return 0, err } // Commit transaction err = tx.Commit() if err != nil { return 0, err } // ALL Done! // Created a new user in system. return u.ID, nil } func fullName(fName, mName, lName string) string { name := fmt.Sprintf("%s %s %s", fName, mName, lName) return strings.Join(strings.Fields(name), " ") }