feat: 0.0.1
This commit is contained in:
146 files changed
+10444
No files matched your search
@@ -0,0 +1,22 @@
|
||||
package worker
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/hibiken/asynq"
|
||||
)
|
||||
|
||||
type TaskDistributor interface {
|
||||
DestributeTaskSendVerifyEmail(ctx context.Context, payload *SendVerifyEmailPayload, opts ...asynq.Option) error
|
||||
}
|
||||
|
||||
type RedisTaskDistributor struct {
|
||||
client *asynq.Client
|
||||
}
|
||||
|
||||
func NewRedisTaskDistributor(redisOpt asynq.RedisClientOpt) TaskDistributor {
|
||||
client := asynq.NewClient(redisOpt)
|
||||
return &RedisTaskDistributor{
|
||||
client: client,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package worker
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type Logger struct{}
|
||||
|
||||
func NewLogger() *Logger {
|
||||
return &Logger{}
|
||||
}
|
||||
|
||||
func (l *Logger) Print(level zerolog.Level, args ...any) {
|
||||
log.WithLevel(level).Msg(fmt.Sprint(args...))
|
||||
}
|
||||
|
||||
func (l *Logger) Printf(ctx context.Context, format string, v ...any) {
|
||||
log.WithLevel(zerolog.DebugLevel).Msgf(format, v...)
|
||||
}
|
||||
|
||||
func (l *Logger) Debug(args ...any) {
|
||||
l.Print(zerolog.DebugLevel, args...)
|
||||
}
|
||||
|
||||
func (l *Logger) Info(args ...any) {
|
||||
l.Print(zerolog.InfoLevel, args...)
|
||||
}
|
||||
|
||||
func (l *Logger) Warn(args ...any) {
|
||||
l.Print(zerolog.WarnLevel, args...)
|
||||
}
|
||||
|
||||
func (l *Logger) Error(args ...any) {
|
||||
l.Print(zerolog.ErrorLevel, args...)
|
||||
}
|
||||
|
||||
func (l *Logger) Fatal(args ...any) {
|
||||
l.Print(zerolog.FatalLevel, args...)
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/FernandoJVideira/simple_bank/internal/worker (interfaces: TaskDistributor)
|
||||
//
|
||||
// Generated by this command:
|
||||
//
|
||||
// mockgen --package mockwk --destination internal/worker/mock/distributor.go github.com/FernandoJVideira/simple_bank/internal/worker TaskDistributor
|
||||
//
|
||||
|
||||
// Package mockwk is a generated GoMock package.
|
||||
package mockwk
|
||||
|
||||
import (
|
||||
context "context"
|
||||
reflect "reflect"
|
||||
|
||||
worker "github.com/FernandoJVideira/simple_bank/internal/worker"
|
||||
asynq "github.com/hibiken/asynq"
|
||||
gomock "go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
// MockTaskDistributor is a mock of TaskDistributor interface.
|
||||
type MockTaskDistributor struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockTaskDistributorMockRecorder
|
||||
isgomock struct{}
|
||||
}
|
||||
|
||||
// MockTaskDistributorMockRecorder is the mock recorder for MockTaskDistributor.
|
||||
type MockTaskDistributorMockRecorder struct {
|
||||
mock *MockTaskDistributor
|
||||
}
|
||||
|
||||
// NewMockTaskDistributor creates a new mock instance.
|
||||
func NewMockTaskDistributor(ctrl *gomock.Controller) *MockTaskDistributor {
|
||||
mock := &MockTaskDistributor{ctrl: ctrl}
|
||||
mock.recorder = &MockTaskDistributorMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||
func (m *MockTaskDistributor) EXPECT() *MockTaskDistributorMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// DestributeTaskSendVerifyEmail mocks base method.
|
||||
func (m *MockTaskDistributor) DestributeTaskSendVerifyEmail(ctx context.Context, payload *worker.SendVerifyEmailPayload, opts ...asynq.Option) error {
|
||||
m.ctrl.T.Helper()
|
||||
varargs := []any{ctx, payload}
|
||||
for _, a := range opts {
|
||||
varargs = append(varargs, a)
|
||||
}
|
||||
ret := m.ctrl.Call(m, "DestributeTaskSendVerifyEmail", varargs...)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// DestributeTaskSendVerifyEmail indicates an expected call of DestributeTaskSendVerifyEmail.
|
||||
func (mr *MockTaskDistributorMockRecorder) DestributeTaskSendVerifyEmail(ctx, payload any, opts ...any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
varargs := append([]any{ctx, payload}, opts...)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DestributeTaskSendVerifyEmail", reflect.TypeOf((*MockTaskDistributor)(nil).DestributeTaskSendVerifyEmail), varargs...)
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package worker
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/FernandoJVideira/simple_bank/internal/config"
|
||||
db "github.com/FernandoJVideira/simple_bank/internal/db/sqlc"
|
||||
"github.com/FernandoJVideira/simple_bank/internal/mailer"
|
||||
"github.com/hibiken/asynq"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
const (
|
||||
CriticalQueue = "critical"
|
||||
DefaultQueue = "default"
|
||||
)
|
||||
|
||||
type TaskProcessor interface {
|
||||
Start() error
|
||||
Shutdown()
|
||||
ProcessTaskSendVerifyEmail(ctx context.Context, task *asynq.Task) error
|
||||
}
|
||||
|
||||
type RedisTaskProcessor struct {
|
||||
server *asynq.Server
|
||||
store db.Store
|
||||
mailer mailer.MailSender
|
||||
config config.Config
|
||||
}
|
||||
|
||||
func NewRedisTaskProcessor(redisOpt asynq.RedisClientOpt, store db.Store, mailer mailer.MailSender, config config.Config) TaskProcessor {
|
||||
logger := NewLogger()
|
||||
redis.SetLogger(logger)
|
||||
|
||||
server := asynq.NewServer(
|
||||
redisOpt,
|
||||
asynq.Config{
|
||||
Queues: map[string]int{
|
||||
CriticalQueue: 10,
|
||||
DefaultQueue: 5,
|
||||
},
|
||||
ErrorHandler: asynq.ErrorHandlerFunc(func(ctx context.Context, task *asynq.Task, err error) {
|
||||
log.Error().Err(err).Str("type", task.Type()).Bytes("payload", task.Payload()).Msg("process task failed")
|
||||
}),
|
||||
Logger: NewLogger(),
|
||||
},
|
||||
)
|
||||
return &RedisTaskProcessor{
|
||||
server: server,
|
||||
store: store,
|
||||
mailer: mailer,
|
||||
config: config,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *RedisTaskProcessor) Start() error {
|
||||
mux := asynq.NewServeMux()
|
||||
|
||||
mux.HandleFunc(TaskSendVerifyEmail, r.ProcessTaskSendVerifyEmail)
|
||||
|
||||
return r.server.Start(mux)
|
||||
}
|
||||
|
||||
func (r *RedisTaskProcessor) Shutdown() {
|
||||
r.server.Shutdown()
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package worker
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
db "github.com/FernandoJVideira/simple_bank/internal/db/sqlc"
|
||||
"github.com/FernandoJVideira/simple_bank/internal/mailer"
|
||||
"github.com/FernandoJVideira/simple_bank/pkg/testutil"
|
||||
"github.com/hibiken/asynq"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
const TaskSendVerifyEmail = "task:send_verify_email"
|
||||
|
||||
type SendVerifyEmailPayload struct {
|
||||
Username string `json:"username"`
|
||||
}
|
||||
|
||||
func (r *RedisTaskDistributor) DestributeTaskSendVerifyEmail(ctx context.Context, payload *SendVerifyEmailPayload, opts ...asynq.Option) error {
|
||||
jsonPayload, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal SendVerifyEmailPayload: %w", err)
|
||||
}
|
||||
|
||||
task := asynq.NewTask(TaskSendVerifyEmail, jsonPayload, opts...)
|
||||
info, err := r.client.EnqueueContext(ctx, task)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to enqueue task %s: %w", TaskSendVerifyEmail, err)
|
||||
}
|
||||
|
||||
log.Info().Str("type", task.Type()).Bytes("payload", task.Payload()).Str("queue", info.Queue).Int("max_retries", info.MaxRetry).Msgf("enqueued task")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *RedisTaskProcessor) ProcessTaskSendVerifyEmail(ctx context.Context, task *asynq.Task) error {
|
||||
var payload SendVerifyEmailPayload
|
||||
|
||||
if err := json.Unmarshal(task.Payload(), &payload); err != nil {
|
||||
return fmt.Errorf("failed to unmarshal payload: %w", asynq.SkipRetry)
|
||||
}
|
||||
|
||||
usr, err := r.store.GetUser(ctx, payload.Username)
|
||||
if err != nil {
|
||||
if errors.Is(err, db.ErrRecordNotFound) {
|
||||
return fmt.Errorf("user %s not found: %w", payload.Username, asynq.SkipRetry)
|
||||
}
|
||||
return fmt.Errorf("failed to get user %s: %w", payload.Username, err)
|
||||
}
|
||||
|
||||
verifyEmail, err := r.store.CreateVerifyEmail(ctx, db.CreateVerifyEmailParams{
|
||||
Username: usr.Username,
|
||||
Email: usr.Email,
|
||||
SecretCode: testutil.RandomString(32),
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create verify email for user %s: %w", usr.Username, err)
|
||||
}
|
||||
|
||||
vars := struct {
|
||||
ServiceName string
|
||||
Username string
|
||||
ActivationURL string
|
||||
}{
|
||||
ServiceName: r.config.ServiceName,
|
||||
Username: usr.Username,
|
||||
ActivationURL: fmt.Sprintf("%s/verify_email?id=%d&code=%s", r.config.FrontendURL, verifyEmail.ID, verifyEmail.SecretCode),
|
||||
}
|
||||
|
||||
to := []string{usr.Email}
|
||||
|
||||
err = r.mailer.Send(mailer.UserWelcomeTemplate, vars, to, nil, nil, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to send verify email to user %s: %w", usr.Username, err)
|
||||
}
|
||||
|
||||
log.Info().Str("type", task.Type()).Bytes("payload", task.Payload()).Str("email", usr.Email).Msg("sent verify email to user")
|
||||
return nil
|
||||
}
|
||||
Reference in new issue
Block a user