When doing it right sucks

When in doubt, stop...doubting Posted by Art Mills

Apr 23, 2023

And no it isn't all the time

Though it may be most of the time.

I am writing a silly website for my daughter who is a high school diver. I think the site has some interesting potential if I can actually do enough to make it interesting. I am just getting rolling. Because I am me, I elected a quick, dirty, clean way to accomplish a thing.



const (
    Athlete   UserType = "Athlete"
    Coach     UserType = "Coach"
    Owner     UserType = "Owner"
    Supporter UserType = "Supporter"
)

type User struct {
    gorm.Model
    Email     string   gorm:"unique;not null" json:"email"
    Password  string   gorm:"not null" json:"-"
    UserName  string   gorm:"unique;not null" json:"username"
    FirstName string   gorm:"not null" json:"firstname"
    LastName  string   gorm:"not null" json:"lastname"
    Admin     bool     gorm:"default:false" json:"admin"
    UserType  UserType gorm:"type:text;not null;check:user_type IN ('Athlete', 'Coach', 'Owner', 'Supporter')" json:"usertype"
}

So I have Users obviously. And the users can be of various types. Probably only four types forever. I knew when I did this the RIGHT way was to create a UserTypes DB table. But I decided against that figuring expediency was fine.

This is true.

But this is wrong. I am chugging along and felt this nagging at me. I reached out to a DBA buddy who works at Amazon and he correctly, though in a heartless, uncaring fashion confirmed I was doing it wrong. So I figured before I got too far in, I'd make the change.

I went to this.



type User struct {
    gorm.Model
    Email      string   gorm:"unique;not null" json:"email"
    Password   string   gorm:"not null" json:"-"
    UserName   string   gorm:"unique;not null" json:"username"
    FirstName  string   gorm:"not null" json:"firstname"
    LastName   string   gorm:"not null" json:"lastname"
    Admin      bool     gorm:"default:false" json:"admin"
    UserTypeID uint     gorm:"not null" json:"usertype_id"
    UserType   UserType gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"user_type"
}

type UserType struct {
    gorm.Model
    Name string gorm:"unique;not null" json:"name"
}

So little changed.

EVERYTHING CHANGED

The first usable page is sign up. Well, that had a drop down of the four constants populated. But that needed to become a query. Easy enough. I figured an hour, or two tops to get it all rolling. My UserCreate should have been working. Look at it. Tell me it should have been working.



// UserCreate creates a new user
func UserCreate(email string, password string, username string, firstname string, lastname string, usertypeName string) (*User, error) {
	hshPasswd, err := helpers.HashPassword(password)
	if err != nil {
		log.Printf("Error hashing password: %v", err)
		return nil, errors.New("Error hashing password")
	}

	var usertype UserType
	if err := db.Database.Where("name = ?", usertypeName).First(&usertype).Error; err != nil {
		log.Printf("Error finding usertype: %v", err)
		return nil, errors.New("Error finding usertype")
	}

	entry := User{
		Email:     email,
		Password:  hshPasswd,
		UserName:  username,
		FirstName: firstname,
		LastName:  lastname,
		UserType:  usertype,
	}

	result := db.Database.Create(&entry)
	if result.Error != nil {
		log.Printf("Error creating user: %v", result.Error)
		return nil, errors.New("Error creating user")
	}

	return &entry, nil
}

That wouldn't create a user.



func TestUserCreate(t *testing.T) {
	LoadEnv()
	db.Connect()

	usertype := UserType{Name: "Test User Type"}
	db.Database.Create(&usertype)

	// Test data
	email := "test@example.com"
	password := "test_password"
	username := "test_username"
	firstname := "Test"
	lastname := "User"
	usertypeName := usertype.Name

	// Call UserCreate function
	user, err := UserCreate(email, password, username, firstname, lastname, usertypeName)

	// Assert no error occurred
	assert.NoError(t, err)

	// Assert the created user has the expected values
	assert.NotNil(t, user)
	assert.NotZero(t, user.ID)
	assert.Equal(t, email, user.Email)
	assert.Equal(t, username, user.UserName)
	assert.Equal(t, firstname, user.FirstName)
	assert.Equal(t, lastname, user.LastName)
	assert.Equal(t, usertype.ID, user.UserTypeID)

	// Check if the password is hashed
	assert.NotEqual(t, password, user.Password)
	assert.True(t, helpers.CheckPasswordHash(password, user.Password))

	// Cleanup
	db.Database.Unscoped().Delete(user)
	db.Database.Unscoped().Delete(&usertype)
}

That wouldn't test creating a user.

Failure.

BUZZZZ!!!

Wrong.

Except it was always right. The problem is here. It's just not in these functions which work fine.

So where is the problem?



UserType  UserType gorm:"type:text;not null;check:user_type IN ('Athlete', 'Coach', 'Owner', 'Supporter')" json:"usertype"

You see, when that first table got migrated it put a CHECK constraint on the Users table. So even though the code above was right, until I blew the DB away, it wasn't. And I figure there is a way other than blowing it away to fix it, but I had nothing in it and blowing it away made me feel good.

The moral of the story is it took 12 hours of time to get the site PRECISELY back to where it was before I started.

And now I waiver on the doing it right or being satisfied answer :).

Hoping being satisfied wins out next time.

Cheers.