Skip to main content

Overview

The users package defines the User model and provides database operations for user management. Users are stored in the users collection in MongoDB.

User Struct

The User struct is defined in users/model.go:12-22:
users/model.go
type User struct {
    ID          string    `json:"id" bson:"_id" query:"id" form:"id" param:"id"`
    Email       string    `json:"email" bson:"email" query:"email" form:"email" param:"email"`
    Phone       string    `json:"phone" bson:"phone" query:"phone" form:"phone" param:"phone"`
    Password    []byte    `json:"password" bson:"password" query:"password" form:"password" param:"password"`
    Role        string    `json:"role" bson:"role" query:"role" form:"role" param:"role"`
    Status      string    `json:"status" bson:"status" query:"status" form:"status" param:"status"`
    AccountType string    `json:"account_type" bson:"account_type" query:"account_type" form:"account_type" param:"account_type"`
    CreatedAt   time.Time `json:"created_at" bson:"created_at" query:"created_at" form:"created_at" param:"created_at"`
    UpdatedAt   time.Time `json:"updated_at" bson:"updated_at" query:"updated_at" form:"updated_at" param:"updated_at"`
}

Field Definitions

ID
string
required
Unique user identifier. Generated using UUID v4 during registration. Maps to MongoDB’s _id field.
Email
string
required
User’s email address. Used for authentication and communication.
Phone
string
User’s phone number. Optional field for additional contact information.
Password
[]byte
required
Hashed password stored as byte array. Never store plain text passwords. Uses bcrypt with cost factor 14.
Role
string
User’s role in the system (e.g., "admin", "user", "moderator"). Optional field for authorization.
Status
string
required
Account status. Valid values:
  • "pending" - New users awaiting activation
  • "active" - Active users who can log in
  • "suspended" - Temporarily disabled accounts
  • "deleted" - Soft-deleted accounts
AccountType
string
Type of user account (e.g., "free", "premium", "enterprise"). Optional field for subscription management.
CreatedAt
time.Time
required
Timestamp when the user account was created. Set automatically during registration.
UpdatedAt
time.Time
Timestamp of the last account update. Should be updated on profile modifications.

Struct Tags

Each field includes multiple struct tags for different frameworks:
`json:"field_name"`
// Controls JSON encoding/decoding
// Used in API responses
The comprehensive struct tags enable automatic binding from HTTP requests to the User struct using Echo’s binding methods.

CRUD Operations

Create User

The CreateNewUser method inserts a new user into the database (users/model.go:24-30):
users/model.go
func (u *User) CreateNewUser() error {
    _, err := configs.StoreRequestInDb(*u, "users")
    if err != nil {
        return err
    }
    return nil
}
Usage:
user := users.User{
    ID:        uuid.New().String(),
    Email:     "[email protected]",
    Password:  hashedPassword,
    Status:    "pending",
    CreatedAt: time.Now(),
}

err := user.CreateNewUser()
if err != nil {
    // Handle error
}
This is a method on the User struct, so it’s called on an instance: user.CreateNewUser()

Get User by ID

Retrieve a user by their unique identifier (users/model.go:32-36):
users/model.go
func GetUserById(id string) User {
    var user User
    _ = configs.MI.DB.Collection("users").FindOne(context.TODO(), bson.M{"_id": id}).Decode(&user)
    return user
}
id
string
required
The user’s unique identifier (UUID string)
Returns: User struct (returns empty User if not found) Usage:
user := users.GetUserById("550e8400-e29b-41d4-a716-446655440000")
if user.ID == "" {
    // User not found
}
This function ignores errors and returns an empty User struct if the user is not found. Always check if user.ID is empty to verify the user exists.

Get User by Phone

Find a user by phone number (users/model.go:38-45):
users/model.go
func GetUserByPhone(phone string) (User, error) {
    var user User
    err := configs.MI.DB.Collection("users").FindOne(context.TODO(), bson.M{"phone": phone}).Decode(&user)
    if err != nil {
        return user, err
    }
    return user, nil
}
phone
string
required
The phone number to search for
Returns: User struct and error (error is non-nil if user not found) Usage:
user, err := users.GetUserByPhone("+250788123456")
if err != nil {
    // User not found or database error
}
Unlike GetUserById, this function properly returns errors, making it more reliable for error handling.

Get User by Email

Retrieve a user by email address (users/model.go:47-54):
users/model.go
func GetUserByEmail(email string) (User, error) {
    var user User
    err := configs.MI.DB.Collection("users").FindOne(context.TODO(), bson.M{"email": email}).Decode(&user)
    if err != nil {
        return user, err
    }
    return user, nil
}
email
string
required
The email address to search for
Returns: User struct and error Usage:
user, err := users.GetUserByEmail("[email protected]")
if err != nil {
    if err == mongo.ErrNoDocuments {
        // User doesn't exist
    } else {
        // Database error
    }
}
This function is used by the authentication system to validate login credentials.

User Registration Flow

The complete user registration process in auth/controller.go:30-48:
auth/controller.go
func createNewUser(email string, password []byte) (users.User, error) {
    uid := uuid.New().String()
    hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), 14)
    if err != nil {
        return users.User{}, err
    }
    user := users.User{
        Email:     email,
        Status:    "pending",
        Password:  hashedPassword,
        ID:        uid,
        CreatedAt: time.Now(),
    }
    err = user.CreateNewUser()
    if err != nil {
        return users.User{}, err
    }
    return user, nil
}
Registration Steps:
  1. Generate unique UUID for user ID
  2. Hash password with bcrypt (cost 14)
  3. Create User struct with "pending" status
  4. Set creation timestamp
  5. Insert into database
  6. Return created user
New users are created with status: "pending" and cannot log in until their status is changed to "active".

User Login Validation

The login validation process in auth/controller.go:13-27:
auth/controller.go
func processUserLogin(email, password string) (string, bool, error) {
    user, err := users.GetUserByEmail(email)
    if err != nil {
        return "", false, err
    }
    if user.Status != "active" {
        return "", false, fmt.Errorf("user is not active")
    }
    errr := bcrypt.CompareHashAndPassword(user.Password, []byte(password))
    if errr != nil {
        return "", false, fmt.Errorf("the password provided is not valid")
    }

    return user.ID, true, nil
}
Validation Steps:
  1. Fetch user by email
  2. Check if status is "active"
  3. Compare password hash with bcrypt
  4. Return user ID on success

Status Management

Status Values

pending
string
Default status for new users. Cannot log in.
active
string
Verified and active users. Can log in and use the system.
suspended
string
Temporarily disabled accounts. Cannot log in.
deleted
string
Soft-deleted accounts. Cannot log in.

Updating User Status

To activate a pending user:
updateData := bson.M{
    "status": "active",
    "updated_at": time.Now(),
}

_, err := configs.UpdateRequestInDb(userID, updateData, "users")
if err != nil {
    // Handle error
}
Consider implementing email verification before changing status from "pending" to "active".

Password Management

Hashing

Passwords are hashed using bcrypt with cost factor 14:
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), 14)
Cost Factor 14:
  • Provides strong security against brute force attacks
  • ~180ms hashing time on modern hardware
  • 2^14 (16,384) hashing iterations

Verification

Password verification during login:
err := bcrypt.CompareHashAndPassword(user.Password, []byte(password))
if err != nil {
    // Invalid password
}
Never log or expose password hashes. They are sensitive security credentials.

Missing Functionality

The following common user operations are not yet implemented:

Update User

// Not implemented - suggested implementation
func (u *User) UpdateUser() error {
    u.UpdatedAt = time.Now()
    _, err := configs.UpdateRequestInDb(u.ID, *u, "users")
    return err
}

Delete User

// Not implemented - suggested soft delete
func (u *User) DeleteUser() error {
    updateData := bson.M{
        "status": "deleted",
        "updated_at": time.Now(),
    }
    _, err := configs.UpdateRequestInDb(u.ID, updateData, "users")
    return err
}

List Users

// Not implemented - suggested implementation
func GetAllUsers(filter bson.M) ([]User, error) {
    cursor, err := configs.MI.DB.Collection("users").Find(context.TODO(), filter)
    if err != nil {
        return nil, err
    }
    defer cursor.Close(context.TODO())

    var users []User
    if err = cursor.All(context.TODO(), &users); err != nil {
        return nil, err
    }
    return users, nil
}

Best Practices

Data Validation

No input validation is currently implemented. Consider adding:
// Email validation
func (u *User) ValidateEmail() error {
    emailRegex := regexp.MustCompile(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`)
    if !emailRegex.MatchString(u.Email) {
        return fmt.Errorf("invalid email format")
    }
    return nil
}

// Password strength validation
func ValidatePassword(password string) error {
    if len(password) < 8 {
        return fmt.Errorf("password must be at least 8 characters")
    }
    // Add more validation rules
    return nil
}

Security Considerations

  1. Unique Email Index - Add a unique index on email field to prevent duplicates:
indexModel := mongo.IndexModel{
    Keys:    bson.D{{Key: "email", Value: 1}},
    Options: options.Index().SetUnique(true),
}
  1. Password in JSON - Consider excluding password from JSON responses:
Password []byte `json:"-" bson:"password"`
  1. Sensitive Data Logging - Never log full user objects that contain passwords.

Usage Examples

Complete Registration Flow

import (
    "backend/users"
    "github.com/google/uuid"
    "golang.org/x/crypto/bcrypt"
    "time"
)

// Hash password
hashedPassword, err := bcrypt.GenerateFromPassword([]byte("userPassword123"), 14)
if err != nil {
    return err
}

// Create user
user := users.User{
    ID:        uuid.New().String(),
    Email:     "[email protected]",
    Phone:     "+250788123456",
    Password:  hashedPassword,
    Status:    "pending",
    Role:      "user",
    CreatedAt: time.Now(),
}

// Save to database
err = user.CreateNewUser()
if err != nil {
    return err
}

Check User Existence

user, err := users.GetUserByEmail("[email protected]")
if err != nil {
    if err == mongo.ErrNoDocuments {
        // User doesn't exist - safe to register
    } else {
        // Database error
    }
} else {
    // User already exists
}

Next Steps

Authentication

See how the User model integrates with JWT authentication

Database

Learn about MongoDB operations and configuration

Build docs developers (and LLMs) love