package models import ( "database/sql" "errors" "strings" "time" "github.com/go-sql-driver/mysql" "golang.org/x/crypto/bcrypt" ) type User struct { ID int Username string Email string HashedPassword []byte Created time.Time } type UserModel struct { DB *sql.DB } // Insert func (m *UserModel) Insert(username, email, password string) error { hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), 15) if err != nil { return err } stmt := `INSERT INTO users (username, email, hashed_password, created) VALUES(?, ?, ?, UTC_TIMESTAMP())` _, err = m.DB.Exec(stmt, username, email, string(hashedPassword)) if err != nil { var mySQLError *mysql.MySQLError if errors.As(err, &mySQLError) { if mySQLError.Number == 1062 && strings.Contains(mySQLError.Message, "users_uc_email") { return ErrDuplicateEmail } } return err } return nil } // Authenticate func (m *UserModel) Authenticate(email, password string) (int, error) { var id int var hashedPassword []byte stmt := "SELECT id, hashed_password FROM users WHERE email = ?" err := m.DB.QueryRow(stmt, email).Scan(&id, &hashedPassword) if err != nil { if errors.Is(err, sql.ErrNoRows) { return 0, ErrInvalidCredentials } else { return 0, err } } err = bcrypt.CompareHashAndPassword(hashedPassword, []byte(password)) if err != nil { if errors.Is(err, bcrypt.ErrMismatchedHashAndPassword) { return 0, ErrInvalidCredentials } else { return 0, err } } return id, nil } // Exists func (m *UserModel) Exists(id int) (bool, error) { var exists bool stmt := "SELECT EXISTS(SELECT true FROM users WHERE id = ?)" err := m.DB.QueryRow(stmt, id).Scan(&exists) return exists, err }