Dwi Wahyudi
Senior Software Engineer (Ruby, Golang, Java)
In previous part, we’ve completed our endpoint for user registration and sending the email verification/confirmation to account’s email afterward, inside that email, user can find and click a link of an endpoint to verify/confirm the email address. Now in this article, we’ll create such endpoint.
Update in Repo
Update in Account Email Confirmation Repo
First thing we need to do is to add a new query for this activity:
- update
account_email_confirmation, based on some criteria:confirmation_hashparameter.- non expired confirmation hash (
created_atmust be newer than 6 hours ago). is_activeis true.
- If found and successfully updated, return the
account_id.
And here’s the query that we’ll add to repo/accountemailconfirmationrepo/pg_query.go:
updateByConfirmationHash = `
UPDATE
account_email_confirmation
SET
is_active = FALSE
WHERE
confirmation_hash = $1
AND is_active IS TRUE
AND created_at > NOW() - INTERVAL '6 HOURS'
RETURNING
account_id;`
If no such data is found, than we’re going to return this new error type (in entity/error.go).
ErrConfirmationHashNotFound = fmt.Errorf("invalid confirmation hash")
Now, it’s time to create the repo method in repo/accountrepo/pg_tx.go for executing such statement, do note that this query returns account_id, so here we are using QueryRowContext and Scan.
func (a *accountEmailConfirmationPg) DeactivateAndReturnAccountIDByConfirmationHashTx(
ctx context.Context, tx sql.Tx, confirmationHash string,
) (int64, error) {
var accountID int64
err := tx.QueryRowContext(ctx, updateByConfirmationHash, confirmationHash).Scan(&accountID)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return 0, entity.ErrConfirmationHashNotFound
}
return 0, err
}
return accountID, nil
}
And let’s not forget to expose this implementation to AccountEmailConfirmationRepo interface in repo/accountemailconfirmationrepo/account_email_confirmation_repo.go:
AccountEmailConfirmationRepo interface {
// .. skipped for brevity
DeactivateAndReturnAccountIDByConfirmationHashTx(
ctx context.Context, tx sql.Tx, confirmationHash string,
) (int64, error)
}
Update in Account Repo
After getting account_id from above repo method, we can then update email_verified field of account. Let’s add new query in repo/accountrepo/pg_query.go:
verifyEmail = `UPDATE account SET email_verified = TRUE WHERE id = $1;`
Let’s execute such query with this new method in repo/accountrepo/pg_tx.go:
func (a *accountPg) VerifyEmailTx(ctx context.Context, tx sql.Tx, accountID int64) error {
_, err := tx.ExecContext(ctx, verifyEmail, accountID)
if err != nil {
return err
}
return nil
}
Add it to repo/accountrepo/account_repo.go:
AccountRepo interface {
// .. skipped for brevity
VerifyEmailTx(ctx context.Context, tx sql.Tx, accountID int64) error
}
Update in Registration Domain Repo
Let’s call those 2 new methods in repo/registrationdomainrepo/registration_domain_repo.go:
// .. skipped for brevity
type (
RegistrationDomainRepo interface {
// .. skipped for brevity
AccountVerifyEmail(ctx context.Context, confirmationHash string) error
}
// .. skipped for brevity
func (r *registrationDomain) AccountVerifyEmail(
ctx context.Context, confirmationHash string,
) error {
err := repocommon.WithTransaction(ctx, r.db.Write.DB, func(tx sql.Tx) error {
accountID, err := r.accountEmailConfirmationRepo.
DeactivateAndReturnAccountIDByConfirmationHashTx(ctx, tx, confirmationHash)
if err != nil {
return err
}
err = r.accountRepo.VerifyEmailTx(ctx, tx, accountID)
if err != nil {
return err
}
return nil
})
if err != nil {
return err
}
return nil
}
We update account’s email confirmation and get ira accountID, and finally pass it to VerifyEmailTx method.
Update in Service and Restful Layers
Update in Registration Domain Service
Our new code in service/registrationdomainservice/registration_domain_service.go will be simple and small:
// .. skipped for brevity
type (
RegistrationDomainService interface {
// .. skipped for brevity
AccountVerifyEmail(ctx context.Context, confirmationHash string) error
}
// .. skipped for brevity
func (r *registrationDomainService) AccountVerifyEmail(
ctx context.Context, confirmationHash string,
) error {
err := r.registrationDomainRepo.AccountVerifyEmail(ctx, confirmationHash)
if err != nil {
return err
}
return nil
}
Update in Registration Domain Restful
Our new restful handler will be like this, in restful/v1/registrationrestful/registration_restful.go:
// .. skipped for brevity
type (
RegistrationRestful interface {
// .. skipped for brevity
AccountVerifyEmail(w http.ResponseWriter, r *http.Request)
}
// .. skipped for brevity
func (reg *registrationRestful) AccountVerifyEmail(w http.ResponseWriter, r *http.Request) {
fmt.Println("tessst")
confirmationHash := r.URL.Query().Get("confirmation_hash")
if confirmationHash == "" {
httpcommon.SetResponse(w, http.StatusBadRequest, "bad request")
return
}
err := reg.registrationDomainService.AccountVerifyEmail(r.Context(), confirmationHash)
if err != nil {
if errors.Is(err, entity.ErrConfirmationHashNotFound) {
httpcommon.SetResponse(w, http.StatusUnprocessableEntity, "confirmation hash not found")
return
}
httpcommon.SetResponse(w, http.StatusInternalServerError, "unknown error")
return
}
httpcommon.SetResponse(w, http.StatusCreated, "account email verified")
}
Add this restful handler to our new restful route:
// .. skipped for brevity
r.Route("/v1", func(v1Route chi.Router) {
v1Route.Route("/accounts", func(accountRoute chi.Router) {
accountRoute.Route("/registration", func(registrationRoute chi.Router) {
registrationRoute.Post("/", registrationRestful.EmailConfirmation)
registrationRoute.Get("/email_confirmation", registrationRestful.AccountVerifyEmail) // new route
})
})
})
Testing Our API
When successfully verified, we will confirm that the new API endpoint returns 201:

Which means, account’s email is already verified. When trying again, we should return 422:
