Skip to content

Commit

Permalink
lesson 62: verify email API in Go
Browse files Browse the repository at this point in the history
  • Loading branch information
blessedmadukoma committed Oct 5, 2023
1 parent a9ad1b2 commit c5b329c
Show file tree
Hide file tree
Showing 20 changed files with 693 additions and 66 deletions.
30 changes: 30 additions & 0 deletions db/mock/store.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion db/query/user.sql
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ UPDATE users SET
hashed_password = COALESCE(sqlc.narg(hashed_password), hashed_password),
password_changed_at = COALESCE(sqlc.narg(password_changed_at), password_changed_at),
full_name = COALESCE(sqlc.narg(full_name), full_name),
email = COALESCE(sqlc.narg(email), email)
email = COALESCE(sqlc.narg(email), email),
is_email_verified = COALESCE(sqlc.narg(is_email_verified), is_email_verified)
WHERE
username = sqlc.arg(username) -- sqlc.arg used because username param is not nullable
RETURNING *;
Expand Down
13 changes: 12 additions & 1 deletion db/query/verify_email.sql
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,15 @@ INSERT INTO verify_emails (
secret_code
) VALUES (
$1, $2, $3
) RETURNING *;
) RETURNING *;

-- name: UpdateVerifyEmail :one
UPDATE verify_emails
SET
is_used = TRUE
WHERE
id = @id
AND secret_code = @secret_code
AND is_used = FALSE
AND expired_at > NOW()
RETURNING *;
1 change: 1 addition & 0 deletions db/sqlc/querier.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions db/sqlc/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ type Store interface {
Querier
TransferTx(ctx context.Context, arg TransferTxParams) (TransferTxResult, error)
CreateUserTx(ctx context.Context, arg CreateUserTxParams) (CreateUserTxResult, error)
VerifyEmailTx(ctx context.Context, arg VerifyEmailTxParams) (VerifyEmailTxResult, error)
}

// SQLStore provides all functions to execute SQL queries and transactions
Expand Down
48 changes: 48 additions & 0 deletions db/sqlc/tx_verify_email.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package db

import (
"context"
"database/sql"
)

// VerifyEmailTxParams contains the input parameters of the verify email transaction
type VerifyEmailTxParams struct {
EmailId int64
SecretCode string
}

// VerifyEmailTxResult is the result of the verify email transaction
type VerifyEmailTxResult struct {
User User
VerifyEmail VerifyEmail
}

// VerifyEmailTx performs a transaction to verify an email
func (s *SQLStore) VerifyEmailTx(ctx context.Context, arg VerifyEmailTxParams) (VerifyEmailTxResult, error) {
var result VerifyEmailTxResult

err := s.execTx(ctx, func(q *Queries) error {
var err error

result.VerifyEmail, err = q.UpdateVerifyEmail(ctx, UpdateVerifyEmailParams{
ID: arg.EmailId,
SecretCode: arg.SecretCode,
})

if err != nil {
return err
}

result.User, err = q.UpdateUser(ctx, UpdateUserParams{
Username: result.VerifyEmail.Username,
IsEmailVerified: sql.NullBool{
Bool: true,
Valid: true,
},
})

return err
})

return result, err
}
7 changes: 5 additions & 2 deletions db/sqlc/user.sql.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

32 changes: 32 additions & 0 deletions db/sqlc/verify_email.sql.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 9 additions & 1 deletion details.md
Original file line number Diff line number Diff line change
Expand Up @@ -491,4 +491,12 @@
3. ran `make migrateup` to commit the migration into the database
4. created `db/query/verify_email.sql` to generate code. Ran `make sqlc` and `make mock`.
5. updated `task_send_verify_email.go` by adding `CreateVerifyEmail` and imnplementing the mail sender. Updated `processor.go` to include the mailer as a field in the `RedisTaskProcessor` struct.
6. updated `main.go` to include the mailer.
6. updated `main.go` to include the mailer.

62. Verify Email API in Go
1. duplicated `rpc_create_user.proto` to `rpc_verify_email.proto` and replaced all `CreateUser` texts to `VerifyEmail`.
2. added the newly created rpc file to `service_simple_bank.proto` and ran `makke proto`. Reloaded vs code to clear errors.
3. duplicated `rpc_create_user.go` to `rpc_verify_email.go`, added validation for email ID and secret_code in `gavlidator/validator.go`.
4. updated `verify_email.sql` to include new query to update the verify email and `user.sql` to update `is_email_verified` field. Ran `make sqlc`.
5. Added `VerifyEmailTx(ctx context.Context, arg VerifyEmailTxParams) (VerifyEmailTxResult, error)` to the `Store` interface and ran `make mock`.
6. created `tx_verify_email.go` to handle transaction for verifying email and updating the database tables.
2 changes: 1 addition & 1 deletion docs/statik/statik.go

Large diffs are not rendered by default.

47 changes: 47 additions & 0 deletions docs/swagger/simple_bank.swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,45 @@
"User"
]
}
},
"/v1/verify-email": {
"get": {
"summary": "Verify email",
"description": "Use this API to verify user's email address",
"operationId": "SimpleBank_VerifyEmail",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/pbVerifyEmailResponse"
}
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "#/definitions/rpcStatus"
}
}
},
"parameters": [
{
"name": "emailId",
"in": "query",
"required": false,
"type": "string",
"format": "int64"
},
{
"name": "secretCode",
"in": "query",
"required": false,
"type": "string"
}
],
"tags": [
"User"
]
}
}
},
"definitions": {
Expand Down Expand Up @@ -233,6 +272,14 @@
}
}
},
"pbVerifyEmailResponse": {
"type": "object",
"properties": {
"isVerified": {
"type": "boolean"
}
}
},
"protobufAny": {
"type": "object",
"properties": {
Expand Down
47 changes: 47 additions & 0 deletions gapi/rpc_verify_email.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package gapi

import (
"context"

db "github.com/blessedmadukoma/go-simple-bank/db/sqlc"
"github.com/blessedmadukoma/go-simple-bank/gvalidator"
"github.com/blessedmadukoma/go-simple-bank/pb"
"google.golang.org/genproto/googleapis/rpc/errdetails"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)

func (srv *Server) VerifyEmail(ctx context.Context, req *pb.VerifyEmailRequest) (*pb.VerifyEmailResponse, error) {
violations := validateVerifyEmailRequest(req)
if violations != nil {
return nil, invalidArgumentError(violations)
}

txResult, err := srv.store.VerifyEmailTx(ctx, db.VerifyEmailTxParams{
EmailId: req.GetEmailId(),
SecretCode: req.GetSecretCode(),
})

if err != nil {
return nil, status.Errorf(codes.Internal, "failed to veriy email: %v", err)
}

rsp := &pb.VerifyEmailResponse{
IsVerified: txResult.User.IsEmailVerified,
}

return rsp, nil
}

// validateVerifyEmailRequest validates the request body of VerifyEmailRequest.
func validateVerifyEmailRequest(req *pb.VerifyEmailRequest) (violations []*errdetails.BadRequest_FieldViolation) {
if err := gvalidator.ValidateEmailID(req.GetEmailId()); err != nil {
violations = append(violations, fieldViolation("email_id", err))
}

if err := gvalidator.ValidateSecretCode(req.GetSecretCode()); err != nil {
violations = append(violations, fieldViolation("secret_code", err))
}

return violations
}
12 changes: 12 additions & 0 deletions gvalidator/validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,15 @@ func ValidateEmail(email string) error {

return nil
}

func ValidateEmailID(value int64) error {
if value <= 0 {
return fmt.Errorf("must be a positive integer")
}

return nil
}

func ValidateSecretCode(value string) error {
return ValidateString(value, 32, 128)
}
Loading

0 comments on commit c5b329c

Please sign in to comment.