package model import ( "database/sql" "errors" "fmt" "log/slog" "time" "github.com/mattn/go-sqlite3" "golang.org/x/crypto/bcrypt" ) type User struct { ID int Name string Email string HashedPassword []byte CreatedAt time.Time UpdatedAt time.Time } type UserServiceInterface interface { Insert(name, email, password string) (int, error) Authenticate(email, password string) (int, error) Exists(id int) (bool, error) } // TODD add logger to service type UserService struct { DB *sql.DB } func (u *UserService) Insert(name, email, password string) (int, error) { hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), 12) if err != nil { return 0, err } stmt := `INSERT INTO users (name, email, hashed_password) VALUES (?,?,?)` result, err := u.DB.Exec(stmt, name, email, string(hashedPassword)) if err != nil { slog.Debug(fmt.Sprintf("Error encounters on insert: %s", err.Error())) // This is a assertion that err is of type sqlite3.Error. If it is ok is true. if serr, ok := err.(sqlite3.Error); ok { slog.Debug("Error is sqlite3.Error type.") if serr.ExtendedCode == sqlite3.ErrConstraintUnique { slog.Debug("Error is a unique contraint violation.") return 0, ErrDuplicateEmail } } return 0, err } lastId, err := result.LastInsertId() if err != nil { slog.Debug("An error occured when retrieving insert result id.") return 0, err } slog.Debug(fmt.Sprintf("Inserted new user. User pk: %d", int(lastId))) return int(lastId), nil } func (u *UserService) Authenticate(email, password string) (int, error) { var id int var hashedPassword []byte stmt := `SELECT id, hashed_password FROM users WHERE email == ?` err := u.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 } func (u *UserService) Exists(id int) (bool, error) { var exists bool stmt := "SELECT EXISTS(SELECT true FROM users WHERE id = ?)" err := u.DB.QueryRow(stmt, id).Scan(&exists) return exists, err }