Skip to content

Commit

Permalink
feat(email): create and send verify email
Browse files Browse the repository at this point in the history
  • Loading branch information
eizyc committed Jun 29, 2024
1 parent 43ca5de commit 10347d9
Show file tree
Hide file tree
Showing 15 changed files with 236 additions and 9 deletions.
3 changes: 2 additions & 1 deletion app.env
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ REFRESH_TOKEN_DURATION=24h
REDIS_ADDRESS=0.0.0.0:6379
EMAIL_SENDER_NAME=Simple Bank
EMAIL_SENDER_ADDRESS=[email protected]
EMAIL_SENDER_PASSWORD=prgctnuzmwqlkcrn
EMAIL_SENDER_PASSWORD=prgctnuzmwqlkcrn
EMAIL_VERIFY_ADDRESS=https://localhost:8080/v1/verify_email
3 changes: 3 additions & 0 deletions db/migration/000004_add_verify_emails.down.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
DROP TABLE IF EXISTS "verify_emails" CASCADE;

ALTER TABLE "users" DROP COLUMN "is_email_verified";
13 changes: 13 additions & 0 deletions db/migration/000004_add_verify_emails.up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
CREATE TABLE "verify_emails" (
"id" bigserial PRIMARY KEY,
"username" varchar NOT NULL,
"email" varchar NOT NULL,
"secret_code" varchar NOT NULL,
"is_used" bool NOT NULL DEFAULT false,
"created_at" timestamptz NOT NULL DEFAULT (now()),
"expired_at" timestamptz NOT NULL DEFAULT (now() + interval '15 minutes')
);

ALTER TABLE "verify_emails" ADD FOREIGN KEY ("username") REFERENCES "users" ("username");

ALTER TABLE "users" ADD COLUMN "is_email_verified" bool NOT NULL DEFAULT false;
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.

19 changes: 19 additions & 0 deletions db/query/verify_email.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
-- name: CreateVerifyEmail :one
INSERT INTO verify_emails (
username,
email,
secret_code
) VALUES (
$1, $2, $3
) 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 *;
11 changes: 11 additions & 0 deletions db/sqlc/models.go

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

2 changes: 2 additions & 0 deletions db/sqlc/querier.go

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

9 changes: 6 additions & 3 deletions db/sqlc/user.sql.go

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

73 changes: 73 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.

11 changes: 11 additions & 0 deletions doc/db.dbml
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,21 @@ Table user as U {
hashed_password varchar [not null]
full_name varchar [not null]
email varchar [not null]
is_email_verified bool [not null, default: false]
password_changed_at timestamptz [not null,default: '0001-01-01 00:00:00Z']
created_at timestamptz [not null,default: `now()`]
}

Table verify_emails {
id bigserial [pk]
username varchar [ref: > U.username, not null]
email varchar [not null]
secret_code varchar [not null]
is_used bool [not null, default: false]
created_at timestamptz [not null, default: `now()`]
expired_at timestamptz [not null, default: `now() + interval '15 minutes'`]
}

// Aliases of double precision = float8.
Table accounts as A {
id bigserial [pk]
Expand Down
15 changes: 14 additions & 1 deletion doc/schema.sql
Original file line number Diff line number Diff line change
@@ -1,16 +1,27 @@
-- SQL dump generated using DBML (dbml.dbdiagram.io)
-- Database: PostgreSQL
-- Generated at: 2024-06-25T05:38:58.025Z
-- Generated at: 2024-06-29T05:11:30.162Z

CREATE TABLE "user" (
"username" varchar PRIMARY KEY,
"hashed_password" varchar NOT NULL,
"full_name" varchar NOT NULL,
"email" varchar NOT NULL,
"is_email_verified" bool NOT NULL DEFAULT false,
"password_changed_at" timestamptz NOT NULL DEFAULT '0001-01-01 00:00:00Z',
"created_at" timestamptz NOT NULL DEFAULT (now())
);

CREATE TABLE "verify_emails" (
"id" bigserial PRIMARY KEY,
"username" varchar NOT NULL,
"email" varchar NOT NULL,
"secret_code" varchar NOT NULL,
"is_used" bool NOT NULL DEFAULT false,
"created_at" timestamptz NOT NULL DEFAULT (now()),
"expired_at" timestamptz NOT NULL DEFAULT (now() + interval '15 minutes')
);

CREATE TABLE "accounts" (
"id" bigserial PRIMARY KEY,
"owner" varchar NOT NULL,
Expand Down Expand Up @@ -61,6 +72,8 @@ COMMENT ON COLUMN "entries"."amount" IS 'can be negative or positive';

COMMENT ON COLUMN "transfers"."amount" IS 'must be positive';

ALTER TABLE "verify_emails" ADD FOREIGN KEY ("username") REFERENCES "user" ("username");

ALTER TABLE "accounts" ADD FOREIGN KEY ("owner") REFERENCES "user" ("username");

ALTER TABLE "entries" ADD FOREIGN KEY ("account_id") REFERENCES "accounts" ("id");
Expand Down
7 changes: 5 additions & 2 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/eizyc/simplebank/api"
db "github.com/eizyc/simplebank/db/sqlc"
"github.com/eizyc/simplebank/gapi"
"github.com/eizyc/simplebank/mail"
"github.com/eizyc/simplebank/pb"
"github.com/eizyc/simplebank/util"
"github.com/eizyc/simplebank/worker"
Expand Down Expand Up @@ -56,7 +57,7 @@ func main() {

taskDistributor := worker.NewRedisTaskDistributor(redisOpt)

go runTaskProcessor(redisOpt, store)
go runTaskProcessor(config, redisOpt, store)
go runGatewayServer(config, store, taskDistributor)
runGrpcServer(config, store, taskDistributor)

Expand All @@ -76,10 +77,12 @@ func runDBMigration(migrationURL string, dbSource string) {
}

func runTaskProcessor(
config util.Config,
redisOpt asynq.RedisClientOpt,
store db.Store,
) {
taskProcessor := worker.NewRedisTaskProcessor(redisOpt, store)
mailer := mail.NewGmailSender(config.EmailSenderName, config.EmailSenderAddress, config.EmailSenderPassword)
taskProcessor := worker.NewRedisTaskProcessor(redisOpt, store, mailer)

log.Info().Msg("start task processor")
err := taskProcessor.Start()
Expand Down
16 changes: 16 additions & 0 deletions util/config.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package util

import (
"fmt"
"time"

"github.com/spf13/viper"
Expand All @@ -22,6 +23,7 @@ type Config struct {
EmailSenderName string `mapstructure:"EMAIL_SENDER_NAME"`
EmailSenderAddress string `mapstructure:"EMAIL_SENDER_ADDRESS"`
EmailSenderPassword string `mapstructure:"EMAIL_SENDER_PASSWORD"`
EmailVerifyAddress string `mapstructure:"EMAIL_VERIFY_ADDRESS"`
}

// LoadConfig reads configuration from file or environment variables.
Expand All @@ -40,3 +42,17 @@ func LoadConfig(path string) (config Config, err error) {
err = viper.Unmarshal(&config)
return
}

// GetConfigValue is a generic function that retrieves a value from Viper
// based on the provided key. It returns the value as an interface{} and an error.
func GetConfigValue(key string) (interface{}, error) {
// Check if the key exists in the configuration
if !viper.IsSet(key) {
return nil, fmt.Errorf("config key '%s' not found", key)
}

// Get the value for the key
value := viper.Get(key)

return value, nil
}
5 changes: 4 additions & 1 deletion worker/processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"

db "github.com/eizyc/simplebank/db/sqlc"
"github.com/eizyc/simplebank/mail"
"github.com/hibiken/asynq"
"github.com/redis/go-redis/v9"
"github.com/rs/zerolog/log"
Expand All @@ -23,9 +24,10 @@ type TaskProcessor interface {
type RedisTaskProcessor struct {
server *asynq.Server
store db.Store
mailer mail.EmailSender
}

func NewRedisTaskProcessor(redisOpt asynq.RedisClientOpt, store db.Store) TaskProcessor {
func NewRedisTaskProcessor(redisOpt asynq.RedisClientOpt, store db.Store, mailer mail.EmailSender) TaskProcessor {
logger := NewLogger()
redis.SetLogger(logger)
server := asynq.NewServer(
Expand All @@ -46,6 +48,7 @@ func NewRedisTaskProcessor(redisOpt asynq.RedisClientOpt, store db.Store) TaskPr
return &RedisTaskProcessor{
server: server,
store: store,
mailer: mailer,
}
}

Expand Down
Loading

0 comments on commit 10347d9

Please sign in to comment.