Skip to main content
gFly v1.15.1
Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Back to homepage
Edit page

Services

Services

Note
IMPORTANT!
The service file should be placed in directory app/services

Service is a relatively important component of an application. It holds all the main logic, determining the effectiveness and correctness of the application.

The service will be used by the Controller, Console (Command/Schedule/Queue) Or called from other Services.

The service will also help you call through external third-party used by the application.

So always think carefully and name the service files appropriately to suit your application. A small suggestion for naming and grouping services with the same purpose together. For example, there is a service auth_services.go that will contain logic:

  • SignIn(signIn dto.SignIn) to login to system.
  • SignUp(signUp dto.SignUp) to register new account.
  • SignOut(jwtToken string) to signout system.
  • RefreshJWTToken(jwtToken, refreshToken string) to refresh JWT token.

Or another example of actions forgot and reset password is password_services.go and will contain logic:

  • ForgotPassword(forgotPassword dto.ForgotPassword) to send email with a link have code to reset new password.
  • ChangePassword(resetPassword dto.ResetPassword) to update new password from reset link was sent.

Full source code

package services

import (
    "errors"
    "fmt"
    "gfly/app/domain/repository"
    "gfly/app/modules/auth/dto"
    "gfly/app/modules/auth/notifications"
    "github.com/gflydev/core/log"
    "github.com/gflydev/core/utils"
    mb "github.com/gflydev/db"
    dbNull "github.com/gflydev/db/null"
    "github.com/gflydev/notification"
    "time"
)

// ====================================================================
// ========================= Main functions ===========================
// ====================================================================

// ForgotPassword processes a forgot password request by generating a reset
// token and sending a password reset email.
//
// This function handles the forgot password flow by:
// 1. Looking up the user by their email address
// 2. Generating a secure reset token using SHA256 hash of email + timestamp
// 3. Saving the reset token to the user's record
// 4. Sending a password reset email with instructions
//
// Parameters:
//   - forgotPassword: dto.ForgotPassword struct containing the user's email address
//
// Returns:
//   - error: nil if successful, otherwise:
//   - "invalid input data" if user email is not found
//   - "service error" if database update or email notification fails
//
// Example usage:
//
//    err := ForgotPassword(dto.ForgotPassword{
//        Username: "user@example.com"
//    })
func ForgotPassword(forgotPassword dto.ForgotPassword) error {
    // Get user by ID.
    user := repository.Pool.GetUserByEmail(forgotPassword.Username)
    // Item not found error
    if user == nil {
        return errors.New("invalid input data")
    }

    // Create a new SHA256 hash.
    hash := utils.Sha256(user.Email, time.Now().Unix())

    user.Token = dbNull.String(interpolateToken(hash))
    user.UpdatedAt = time.Now()

    if err := mb.UpdateModel(user); err != nil {
        return errors.New("service error")
    }

    // Send notification via mail
    if err := notification.Send(notifications.ResetPassword{
        Email: user.Email,
    }); err != nil {
        log.Errorf("Service forgot password error '%v'", err)

        return errors.New("service error")
    }

    return nil
}

// ChangePassword processes a password reset request and updates the user's password.
//
// This function handles the password reset flow by:
// 1. Verifying the reset token and retrieving associated user
// 2. Clearing the reset token
// 3. Updating the password with a new hashed value
// 4. Sending email notification about password change
//
// Parameters:
//   - resetPassword: dto.ResetPassword struct containing the new password and reset token
//
// Returns:
//   - error: nil if successful, otherwise:
//   - "invalid input data" if token is invalid/expired
//   - "service error" if database update or email notification fails
//
// Example usage:
//
//    err := ChangePassword(dto.ResetPassword{
//        Password: "newpass123",
//        Token: "abc123token",
//    })
func ChangePassword(resetPassword dto.ResetPassword) error {
    // Get user by ID.
    user := repository.Pool.GetUserByToken(interpolateToken(resetPassword.Token))
    // Item not found error
    if user == nil {
        return errors.New("invalid input data")
    }

    user.Token = dbNull.String("")
    user.UpdatedAt = time.Now()
    user.Password = utils.GeneratePassword(resetPassword.Password)

    if err := mb.UpdateModel(user); err != nil {
        log.Errorf("Change password error '%v'", err)

        return errors.New("service error")
    }

    // Send notification via mail
    if err := notification.Send(notifications.ChangePassword{
        Email: user.Email,
        Name:  user.Fullname,
    }); err != nil {
        log.Errorf("Change password error '%v'", err)

        return errors.New("service error")
    }

    return nil
}

// ====================================================================
// ======================== Helper Functions ==========================
// ====================================================================

func interpolateToken(token string) string {
    return fmt.Sprintf("reset_password:%s", token)
}
Note
TIP
You should not create each processing logic as a file or combine too much logic into one file because it will not bring too many semantic benefits. This will be difficult to find. Let’s create a service file with a bit more open semantics.
Note

IMPORTANT!
The Service is used in the following cases:

  • Controller —> Service
  • Service <—> Service
  • Console —> Service