improve coworker service
This commit is contained in:
parent
b4c1004cf2
commit
0db1564012
@ -16,7 +16,7 @@ type inputSendVerify struct {
|
|||||||
func NewUserSendVerifyEmailHandler(log logger.Logger, userService services.UserService) gin.HandlerFunc {
|
func NewUserSendVerifyEmailHandler(log logger.Logger, userService services.UserService) gin.HandlerFunc {
|
||||||
return httpserver.WrapGin(log,
|
return httpserver.WrapGin(log,
|
||||||
func(ctx context.Context, input inputSendVerify) (interface{}, error) {
|
func(ctx context.Context, input inputSendVerify) (interface{}, error) {
|
||||||
err := userService.SendEmailVerifyEmail(ctx, input.Email)
|
err := userService.SendEmailVerifyUser(ctx, input.Email)
|
||||||
return nil, err
|
return nil, err
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
30
cmd/coworker/config.go
Normal file
30
cmd/coworker/config.go
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "backend/pkg/config"
|
||||||
|
|
||||||
|
func LoadConfig(filePath string) (Config, error) {
|
||||||
|
return config.NewFromFile[Config](filePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
App struct {
|
||||||
|
LogFile string `yaml:"logFile"`
|
||||||
|
ServiceUrl string `yaml:"serviceUrl"`
|
||||||
|
}
|
||||||
|
|
||||||
|
Kafka struct {
|
||||||
|
Brokers []string `yaml:"brokers"`
|
||||||
|
Topic string `yaml:"topic"`
|
||||||
|
ConsumerGroupId string `yaml:"consumerGroupId"`
|
||||||
|
} `yaml:"kafka"`
|
||||||
|
|
||||||
|
SMTP ConfigSMTP `yaml:"smtp"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ConfigSMTP struct {
|
||||||
|
Server string `yaml:"server"`
|
||||||
|
Port int `yaml:"port"`
|
||||||
|
Login string `yaml:"login"`
|
||||||
|
Password string `yaml:"password"`
|
||||||
|
Email string `yaml:"email"`
|
||||||
|
}
|
||||||
87
cmd/coworker/emailer.go
Normal file
87
cmd/coworker/emailer.go
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"html/template"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"gopkg.in/gomail.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
const MSG_TEXT = `
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<p>{{.Text}}</p>
|
||||||
|
{{if .Link}}
|
||||||
|
<a href="{{.Link}}">Click</a>link</p>
|
||||||
|
{{end}}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`
|
||||||
|
|
||||||
|
type MailContent struct {
|
||||||
|
Text string
|
||||||
|
Link string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewEmailer(conf ConfigSMTP) (*Emailer, error) {
|
||||||
|
dialer := gomail.NewDialer(conf.Server, conf.Port, conf.Login, conf.Password)
|
||||||
|
|
||||||
|
closer, err := dialer.Dial()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer closer.Close()
|
||||||
|
|
||||||
|
htmlTemplate, err := template.New("verify-email").Parse(MSG_TEXT)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Emailer{
|
||||||
|
senderEmail: conf.Email,
|
||||||
|
htmlTemplate: htmlTemplate,
|
||||||
|
dialer: dialer,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Emailer struct {
|
||||||
|
senderEmail string
|
||||||
|
htmlTemplate *template.Template
|
||||||
|
dialer *gomail.Dialer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Emailer) SendRestorePassword(email, token string) error {
|
||||||
|
return e.sendEmail("Restore your password", email, MailContent{
|
||||||
|
Text: "Token: " + token,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Emailer) SendVerifyUser(email, link string) error {
|
||||||
|
return e.sendEmail("Verify your email", email, MailContent{
|
||||||
|
Text: "You recieved this message due to registration of account. Use this link to verify email:",
|
||||||
|
Link: link,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Emailer) SendPasswordChanged(email string) error {
|
||||||
|
return e.sendEmail("Password changed", email, MailContent{
|
||||||
|
Text: "You recieved this message due to password change",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Emailer) sendEmail(subject, to string, content MailContent) error {
|
||||||
|
builder := &strings.Builder{}
|
||||||
|
if err := e.htmlTemplate.Execute(builder, content); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
m := gomail.NewMessage()
|
||||||
|
m.SetHeader("From", m.FormatAddress(e.senderEmail, "Pet Backend"))
|
||||||
|
m.SetHeader("To", to)
|
||||||
|
m.SetHeader("Subject", subject)
|
||||||
|
m.SetBody("text/html", builder.String())
|
||||||
|
|
||||||
|
return e.dialer.DialAndSend(m)
|
||||||
|
}
|
||||||
@ -5,102 +5,46 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/segmentio/kafka-go"
|
"github.com/segmentio/kafka-go"
|
||||||
"gopkg.in/gomail.v2"
|
|
||||||
"gopkg.in/yaml.v3"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const MSG_TEXT = `
|
type SendEmailEvent struct {
|
||||||
<html>
|
Email string `json:"email"`
|
||||||
<head>
|
Token string `json:"token"`
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<p>This message was sent because you forgot a password</p>
|
|
||||||
<p>To change a password, use <a href="{{.Link}}">this</a>link</p>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
`
|
|
||||||
|
|
||||||
type HtmlTemplate struct {
|
|
||||||
Link string
|
|
||||||
}
|
|
||||||
|
|
||||||
func SendEmailForgotPassword(dialer *gomail.Dialer, from, to, body string) error {
|
|
||||||
m := gomail.NewMessage()
|
|
||||||
m.SetHeader("From", m.FormatAddress(from, "Pet Backend"))
|
|
||||||
m.SetHeader("To", to)
|
|
||||||
m.SetHeader("Subject", "Hello!")
|
|
||||||
m.SetBody("text/html", body)
|
|
||||||
|
|
||||||
return dialer.DialAndSend(m)
|
|
||||||
}
|
|
||||||
|
|
||||||
type Config struct {
|
|
||||||
App struct {
|
|
||||||
LogFile string `yaml:"logFile"`
|
|
||||||
ServiceUrl string `yaml:"serviceUrl"`
|
|
||||||
}
|
|
||||||
|
|
||||||
Kafka struct {
|
|
||||||
Brokers []string `yaml:"brokers"`
|
|
||||||
Topic string `yaml:"topic"`
|
|
||||||
ConsumerGroupId string `yaml:"consumerGroupId"`
|
|
||||||
} `yaml:"kafka"`
|
|
||||||
|
|
||||||
SMTP struct {
|
|
||||||
Server string `yaml:"server"`
|
|
||||||
Port int `yaml:"port"`
|
|
||||||
Login string `yaml:"login"`
|
|
||||||
Password string `yaml:"password"`
|
|
||||||
Email string `yaml:"email"`
|
|
||||||
} `yaml:"smtp"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
configFile, err := os.ReadFile("config.yaml")
|
config, err := LoadConfig("config.yaml")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err.Error())
|
log.Fatal(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
config := &Config{}
|
emailer, err := NewEmailer(config.SMTP)
|
||||||
if err := yaml.Unmarshal(configFile, config); err != nil {
|
if err != nil {
|
||||||
log.Fatal(err.Error())
|
log.Fatal(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
dialer := gomail.NewDialer(config.SMTP.Server, config.SMTP.Port, config.SMTP.Login, config.SMTP.Password)
|
|
||||||
|
|
||||||
r := kafka.NewReader(kafka.ReaderConfig{
|
r := kafka.NewReader(kafka.ReaderConfig{
|
||||||
Brokers: config.Kafka.Brokers,
|
Brokers: config.Kafka.Brokers,
|
||||||
Topic: config.Kafka.Topic,
|
Topic: config.Kafka.Topic,
|
||||||
GroupID: config.Kafka.ConsumerGroupId,
|
GroupID: config.Kafka.ConsumerGroupId,
|
||||||
})
|
})
|
||||||
|
|
||||||
logger, err := logger.New(
|
logger, err := logger.New(ctx, logger.NewLoggerOpts{
|
||||||
ctx,
|
|
||||||
logger.NewLoggerOpts{
|
|
||||||
Debug: true,
|
Debug: true,
|
||||||
OutputFile: config.App.LogFile,
|
OutputFile: config.App.LogFile,
|
||||||
},
|
})
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err.Error())
|
log.Fatal(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Printf("coworker service started\n")
|
logger.Printf("coworker service started\n")
|
||||||
|
|
||||||
template, err := template.New("verify-email").Parse(MSG_TEXT)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
for {
|
||||||
msg, err := r.FetchMessage(ctx)
|
msg, err := r.FetchMessage(ctx)
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
@ -119,27 +63,28 @@ func main() {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
value := struct {
|
if err := handleEvent(config, emailer, msg); err != nil {
|
||||||
Email string `json:"email"`
|
log.Printf("failed to handle event: %s\n", err.Error())
|
||||||
Token string `json:"token"`
|
|
||||||
}{}
|
|
||||||
|
|
||||||
if err := json.Unmarshal(msg.Value, &value); err != nil {
|
|
||||||
log.Fatalf("failed to unmarshal: %s\n", err.Error())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
link := fmt.Sprintf("%s/verify-user?token=%s", config.App.ServiceUrl, value.Token)
|
|
||||||
|
|
||||||
builder := &strings.Builder{}
|
|
||||||
if err := template.Execute(builder, HtmlTemplate{link}); err != nil {
|
|
||||||
log.Printf("failed to execute html template: %s\n", err.Error())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := SendEmailForgotPassword(dialer, config.SMTP.Email, value.Email, builder.String()); err != nil {
|
|
||||||
log.Printf("failed to send email: %s\n", err.Error())
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func handleEvent(config Config, emailer *Emailer, msg kafka.Message) error {
|
||||||
|
event := SendEmailEvent{}
|
||||||
|
if err := json.Unmarshal(msg.Value, &event); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch string(msg.Key) {
|
||||||
|
case "email_forgot_password":
|
||||||
|
return emailer.SendRestorePassword(event.Email, event.Token)
|
||||||
|
case "email_password_changed":
|
||||||
|
return emailer.SendPasswordChanged(event.Email)
|
||||||
|
case "email_verify_user":
|
||||||
|
link := fmt.Sprintf("%s/verify-user?token=%s", config.App.ServiceUrl, event.Token)
|
||||||
|
return emailer.SendVerifyUser(event.Email, link)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("unknown event type")
|
||||||
|
}
|
||||||
|
|||||||
@ -6,6 +6,12 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
EventEmailPasswordChanged = "email_password_changed"
|
||||||
|
EventEmailForgotPassword = "email_forgot_password"
|
||||||
|
EventEmailVerifyUser = "email_verify_user"
|
||||||
|
)
|
||||||
|
|
||||||
func NewEventRepo(kafka *integrations.Kafka) *EventRepo {
|
func NewEventRepo(kafka *integrations.Kafka) *EventRepo {
|
||||||
return &EventRepo{
|
return &EventRepo{
|
||||||
kafka: kafka,
|
kafka: kafka,
|
||||||
@ -32,10 +38,14 @@ func (e *EventRepo) sendEmail(ctx context.Context, email, actionToken, eventType
|
|||||||
return e.kafka.SendMessage(ctx, eventType, valueBytes)
|
return e.kafka.SendMessage(ctx, eventType, valueBytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *EventRepo) SendEmailForgotPassword(ctx context.Context, email, actionToken string) error {
|
func (e *EventRepo) SendEmailPasswordChanged(ctx context.Context, email string) error {
|
||||||
return e.sendEmail(ctx, email, actionToken, "email_forgot_password")
|
return e.sendEmail(ctx, email, "", EventEmailPasswordChanged)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *EventRepo) SendEmailVerifyEmail(ctx context.Context, email, actionToken string) error {
|
func (e *EventRepo) SendEmailForgotPassword(ctx context.Context, email, actionToken string) error {
|
||||||
return e.sendEmail(ctx, email, actionToken, "email_verify_email")
|
return e.sendEmail(ctx, email, actionToken, EventEmailForgotPassword)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *EventRepo) SendEmailVerifyUser(ctx context.Context, email, actionToken string) error {
|
||||||
|
return e.sendEmail(ctx, email, actionToken, EventEmailVerifyUser)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -34,7 +34,7 @@ type UserService interface {
|
|||||||
VerifyEmail(ctx context.Context, actionToken string) error
|
VerifyEmail(ctx context.Context, actionToken string) error
|
||||||
|
|
||||||
SendEmailForgotPassword(ctx context.Context, userId string) error
|
SendEmailForgotPassword(ctx context.Context, userId string) error
|
||||||
SendEmailVerifyEmail(ctx context.Context, email string) error
|
SendEmailVerifyUser(ctx context.Context, email string) error
|
||||||
|
|
||||||
ChangePassword(ctx context.Context, userId, oldPassword, newPassword string) error
|
ChangePassword(ctx context.Context, userId, oldPassword, newPassword string) error
|
||||||
ChangePasswordWithToken(ctx context.Context, actionToken, newPassword string) error
|
ChangePasswordWithToken(ctx context.Context, actionToken, newPassword string) error
|
||||||
@ -94,7 +94,7 @@ func (u *userService) CreateUser(ctx context.Context, params UserCreateParams) (
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := u.sendEmailVerifyEmail(ctx, result.Id, user.Email); err != nil {
|
if err := u.sendEmailVerifyUser(ctx, result.Id, user.Email); err != nil {
|
||||||
u.deps.Logger.Error().Err(err).Msg("error occured on sending email")
|
u.deps.Logger.Error().Err(err).Msg("error occured on sending email")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -162,7 +162,7 @@ func (u *userService) SendEmailForgotPassword(ctx context.Context, email string)
|
|||||||
UserId: user.Id,
|
UserId: user.Id,
|
||||||
Value: uuid.New().String(),
|
Value: uuid.New().String(),
|
||||||
Target: models.ActionTokenTargetForgotPassword,
|
Target: models.ActionTokenTargetForgotPassword,
|
||||||
Expiration: time.Now().Add(1 * time.Hour),
|
Expiration: time.Now().Add(15 * time.Minute),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -172,7 +172,7 @@ func (u *userService) SendEmailForgotPassword(ctx context.Context, email string)
|
|||||||
return u.deps.EventRepo.SendEmailForgotPassword(ctx, user.Email, actionToken.Value)
|
return u.deps.EventRepo.SendEmailForgotPassword(ctx, user.Email, actionToken.Value)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *userService) sendEmailVerifyEmail(ctx context.Context, userId, email string) error {
|
func (u *userService) sendEmailVerifyUser(ctx context.Context, userId, email string) error {
|
||||||
actionToken, err := u.deps.ActionTokenRepo.CreateActionToken(
|
actionToken, err := u.deps.ActionTokenRepo.CreateActionToken(
|
||||||
ctx,
|
ctx,
|
||||||
models.ActionTokenDTO{
|
models.ActionTokenDTO{
|
||||||
@ -186,10 +186,10 @@ func (u *userService) sendEmailVerifyEmail(ctx context.Context, userId, email st
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return u.deps.EventRepo.SendEmailVerifyEmail(ctx, email, actionToken.Value)
|
return u.deps.EventRepo.SendEmailVerifyUser(ctx, email, actionToken.Value)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *userService) SendEmailVerifyEmail(ctx context.Context, email string) error {
|
func (u *userService) SendEmailVerifyUser(ctx context.Context, email string) error {
|
||||||
//user, err := u.getUserById(ctx, userId)
|
//user, err := u.getUserById(ctx, userId)
|
||||||
user, err := u.deps.UserRepo.GetUserByEmail(ctx, email)
|
user, err := u.deps.UserRepo.GetUserByEmail(ctx, email)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -202,7 +202,7 @@ func (u *userService) SendEmailVerifyEmail(ctx context.Context, email string) er
|
|||||||
return fmt.Errorf("user already verified")
|
return fmt.Errorf("user already verified")
|
||||||
}
|
}
|
||||||
|
|
||||||
return u.sendEmailVerifyEmail(ctx, user.Id, user.Email)
|
return u.sendEmailVerifyUser(ctx, user.Id, user.Email)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *userService) ChangePasswordWithToken(ctx context.Context, actionToken, newPassword string) error {
|
func (u *userService) ChangePasswordWithToken(ctx context.Context, actionToken, newPassword string) error {
|
||||||
@ -256,10 +256,18 @@ func (u *userService) updatePassword(ctx context.Context, user models.UserDTO, n
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return u.deps.UserRepo.UpdateUser(ctx, user.Id, models.UserUpdateDTO{
|
if err = u.deps.UserRepo.UpdateUser(ctx, user.Id, models.UserUpdateDTO{
|
||||||
Secret: newSecret,
|
Secret: newSecret,
|
||||||
Name: user.Name,
|
Name: user.Name,
|
||||||
})
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := u.deps.EventRepo.SendEmailPasswordChanged(ctx, user.Email); err != nil {
|
||||||
|
u.deps.Logger.Error().Err(err).Msg("error occured on sending email")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *userService) getUserById(ctx context.Context, userId string) (*models.UserDTO, error) {
|
func (u *userService) getUserById(ctx context.Context, userId string) (*models.UserDTO, error) {
|
||||||
|
|||||||
@ -41,7 +41,7 @@ func NewRequestLogMiddleware(logger log.Logger, tracer trace.Tracer, prometheus
|
|||||||
|
|
||||||
ctxLogger := logger.WithContext(c)
|
ctxLogger := logger.WithContext(c)
|
||||||
|
|
||||||
msg := fmt.Sprintf("Request %s %s %d %v", method, path, statusCode, latency)
|
msg := fmt.Sprintf("%s %s %d %v", method, path, statusCode, latency)
|
||||||
|
|
||||||
if statusCode >= 200 && statusCode < 400 {
|
if statusCode >= 200 && statusCode < 400 {
|
||||||
ctxLogger.Log().Msg(msg)
|
ctxLogger.Log().Msg(msg)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user