Merge pull request #4 from Nucrea/feature/url-shortener
Feature/url shortener
This commit is contained in:
commit
34ad89172a
3
.vscode/launch.json
vendored
3
.vscode/launch.json
vendored
@ -9,7 +9,8 @@
|
||||
"type": "go",
|
||||
"request": "launch",
|
||||
"mode": "auto",
|
||||
"program": "${workspaceFolder}"
|
||||
"program": "${workspaceFolder}",
|
||||
"args": ["-c", "./config_example/config.yaml", "-o", "./log.txt"]
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
create table users (
|
||||
id int generated always as identity,
|
||||
login text unique not null,
|
||||
email text unique not null,
|
||||
secret text not null,
|
||||
name text not null,
|
||||
|
||||
|
||||
2
go.mod
2
go.mod
@ -49,5 +49,7 @@ require (
|
||||
golang.org/x/sys v0.22.0 // indirect
|
||||
golang.org/x/text v0.16.0 // indirect
|
||||
google.golang.org/protobuf v1.34.2 // indirect
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df // indirect
|
||||
)
|
||||
|
||||
4
go.sum
4
go.sum
@ -122,11 +122,15 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IV
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
||||
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE=
|
||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
||||
15
main.go
15
main.go
@ -67,7 +67,7 @@ func main() {
|
||||
logger.Fatal().Err(err).Msg("failed parsing postgres connection string")
|
||||
}
|
||||
|
||||
sqlDb := stdlib.OpenDB(connConf)
|
||||
sqlDb = stdlib.OpenDB(connConf)
|
||||
if err := sqlDb.Ping(); err != nil {
|
||||
logger.Fatal().Err(err).Msg("failed pinging postgres db")
|
||||
}
|
||||
@ -77,6 +77,8 @@ func main() {
|
||||
passwordUtil := utils.NewPasswordUtil()
|
||||
userRepo := repo.NewUserRepo(sqlDb)
|
||||
userCache := repo.NewCacheInmem[string, models.UserDTO](60 * 60)
|
||||
emailRepo := repo.NewEmailRepo()
|
||||
actionTokenRepo := repo.NewActionTokenRepo(sqlDb)
|
||||
|
||||
userService := services.NewUserService(
|
||||
services.UserServiceDeps{
|
||||
@ -84,6 +86,13 @@ func main() {
|
||||
Password: passwordUtil,
|
||||
UserRepo: userRepo,
|
||||
UserCache: userCache,
|
||||
EmailRepo: emailRepo,
|
||||
ActionTokenRepo: actionTokenRepo,
|
||||
},
|
||||
)
|
||||
linkService := services.NewShortlinkSevice(
|
||||
services.NewShortlinkServiceParams{
|
||||
Cache: repo.NewCacheInmem[string, string](7 * 24 * 60 * 60),
|
||||
},
|
||||
)
|
||||
|
||||
@ -95,6 +104,10 @@ func main() {
|
||||
r.Use(middleware.NewRequestLogMiddleware(logger))
|
||||
r.Use(gin.Recovery())
|
||||
|
||||
linkGroup := r.Group("/s")
|
||||
linkGroup.POST("/new", handlers.NewShortlinkCreateHandler(linkService))
|
||||
linkGroup.GET("/:linkId", handlers.NewShortlinkResolveHandler(linkService))
|
||||
|
||||
userGroup := r.Group("/user")
|
||||
userGroup.POST("/create", handlers.NewUserCreateHandler(userService))
|
||||
userGroup.POST("/login", handlers.NewUserLoginHandler(userService))
|
||||
|
||||
61
src/handlers/shortlink_handlers.go
Normal file
61
src/handlers/shortlink_handlers.go
Normal file
@ -0,0 +1,61 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"backend/src/services"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type shortlinkCreateOutput struct {
|
||||
Link string `json:"link"`
|
||||
}
|
||||
|
||||
func NewShortlinkCreateHandler(shortlinkService services.ShortlinkService) gin.HandlerFunc {
|
||||
return func(ctx *gin.Context) {
|
||||
rawUrl := ctx.Query("url")
|
||||
if rawUrl == "" {
|
||||
ctx.AbortWithError(400, fmt.Errorf("no url param"))
|
||||
return
|
||||
}
|
||||
|
||||
u, err := url.Parse(rawUrl)
|
||||
if err != nil {
|
||||
ctx.Data(500, "plain/text", []byte(err.Error()))
|
||||
return
|
||||
}
|
||||
u.Scheme = "https"
|
||||
|
||||
linkId, err := shortlinkService.CreateLink(u.String())
|
||||
if err != nil {
|
||||
ctx.Data(500, "plain/text", []byte(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
resultBody, err := json.Marshal(shortlinkCreateOutput{
|
||||
Link: "https:/nucrea.ru/s/" + linkId,
|
||||
})
|
||||
if err != nil {
|
||||
ctx.AbortWithError(500, err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Data(200, "application/json", resultBody)
|
||||
}
|
||||
}
|
||||
|
||||
func NewShortlinkResolveHandler(shortlinkService services.ShortlinkService) gin.HandlerFunc {
|
||||
return func(ctx *gin.Context) {
|
||||
linkId := ctx.Param("linkId")
|
||||
|
||||
linkUrl, err := shortlinkService.GetLink(linkId)
|
||||
if err != nil {
|
||||
ctx.AbortWithError(500, err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Redirect(301, linkUrl)
|
||||
}
|
||||
}
|
||||
@ -8,14 +8,14 @@ import (
|
||||
)
|
||||
|
||||
type createUserInput struct {
|
||||
Login string
|
||||
Email string
|
||||
Password string
|
||||
Name string
|
||||
}
|
||||
|
||||
type createUserOutput struct {
|
||||
Id string `json:"id"`
|
||||
Login string `json:"login"`
|
||||
Email string `json:"email"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
@ -28,7 +28,7 @@ func NewUserCreateHandler(userService services.UserService) gin.HandlerFunc {
|
||||
}
|
||||
|
||||
dto, err := userService.CreateUser(ctx, services.UserCreateParams{
|
||||
Login: params.Login,
|
||||
Email: params.Email,
|
||||
Password: params.Password,
|
||||
Name: params.Name,
|
||||
})
|
||||
@ -43,7 +43,7 @@ func NewUserCreateHandler(userService services.UserService) gin.HandlerFunc {
|
||||
|
||||
resultBody, err := json.Marshal(createUserOutput{
|
||||
Id: dto.Id,
|
||||
Login: dto.Login,
|
||||
Email: dto.Email,
|
||||
Name: dto.Name,
|
||||
})
|
||||
if err != nil {
|
||||
|
||||
15
src/models/action_token.go
Normal file
15
src/models/action_token.go
Normal file
@ -0,0 +1,15 @@
|
||||
package models
|
||||
|
||||
type ActionTokenTarget int
|
||||
|
||||
const (
|
||||
ActionTokenTargetForgotPassword ActionTokenTarget = iota
|
||||
ActionTokenTargetLogin2FA
|
||||
)
|
||||
|
||||
type ActionTokenDTO struct {
|
||||
Id string
|
||||
UserId string
|
||||
Value string
|
||||
Target ActionTokenTarget
|
||||
}
|
||||
@ -2,7 +2,12 @@ package models
|
||||
|
||||
type UserDTO struct {
|
||||
Id string
|
||||
Login string
|
||||
Email string
|
||||
Secret string
|
||||
Name string
|
||||
}
|
||||
|
||||
type UserUpdateDTO struct {
|
||||
Secret string
|
||||
Name string
|
||||
}
|
||||
|
||||
64
src/repo/action_token.go
Normal file
64
src/repo/action_token.go
Normal file
@ -0,0 +1,64 @@
|
||||
package repo
|
||||
|
||||
import (
|
||||
"backend/src/models"
|
||||
"context"
|
||||
"database/sql"
|
||||
)
|
||||
|
||||
type ActionTokenRepo interface {
|
||||
CreateActionToken(ctx context.Context, dto models.ActionTokenDTO) (*models.ActionTokenDTO, error)
|
||||
PopActionToken(ctx context.Context, userId, value string, target models.ActionTokenTarget) (*models.ActionTokenDTO, error)
|
||||
}
|
||||
|
||||
func NewActionTokenRepo(db *sql.DB) ActionTokenRepo {
|
||||
return &actionTokenRepo{
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
|
||||
type actionTokenRepo struct {
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
func (a *actionTokenRepo) CreateActionToken(ctx context.Context, dto models.ActionTokenDTO) (*models.ActionTokenDTO, error) {
|
||||
query := `
|
||||
insert into
|
||||
action_tokens (user_id, value, target)
|
||||
values ($1, $2, $3)
|
||||
returning id;`
|
||||
row := a.db.QueryRowContext(ctx, query, dto.UserId, dto.Value, dto.Target)
|
||||
|
||||
id := ""
|
||||
if err := row.Scan(&id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &models.ActionTokenDTO{
|
||||
Id: id,
|
||||
UserId: dto.UserId,
|
||||
Value: dto.Value,
|
||||
Target: dto.Target,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (a *actionTokenRepo) PopActionToken(ctx context.Context, userId, value string, target models.ActionTokenTarget) (*models.ActionTokenDTO, error) {
|
||||
query := `
|
||||
delete
|
||||
from action_tokens
|
||||
where user_id=$1 and value=$2 and target=$3
|
||||
returning id;`
|
||||
row := a.db.QueryRowContext(ctx, query, userId, value, target)
|
||||
|
||||
id := ""
|
||||
if err := row.Scan(&id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &models.ActionTokenDTO{
|
||||
Id: id,
|
||||
UserId: userId,
|
||||
Value: value,
|
||||
Target: target,
|
||||
}, nil
|
||||
}
|
||||
48
src/repo/email_repo.go
Normal file
48
src/repo/email_repo.go
Normal file
@ -0,0 +1,48 @@
|
||||
package repo
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"gopkg.in/gomail.v2"
|
||||
)
|
||||
|
||||
const MSG_TEXT = `
|
||||
<html>
|
||||
<head>
|
||||
</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 EmailRepo interface {
|
||||
SendEmailForgotPassword(email, token string)
|
||||
}
|
||||
|
||||
func NewEmailRepo() EmailRepo {
|
||||
return &emailRepo{}
|
||||
}
|
||||
|
||||
type emailRepo struct {
|
||||
// mail *gomail.Dialer
|
||||
}
|
||||
|
||||
func (e *emailRepo) SendEmailForgotPassword(email, token string) {
|
||||
link := "https://nucrea.ru?token=" + token
|
||||
msgText := strings.ReplaceAll(MSG_TEXT, "{{Link}}", link)
|
||||
|
||||
m := gomail.NewMessage()
|
||||
m.SetHeader("From", "email")
|
||||
m.SetHeader("To", email)
|
||||
m.SetHeader("Subject", "Hello!")
|
||||
m.SetBody("text/html", msgText)
|
||||
|
||||
d := gomail.NewDialer("smtp.yandex.ru", 587, "login", "password")
|
||||
|
||||
// Send the email to Bob, Cora and Dan.
|
||||
if err := d.DialAndSend(m); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
@ -16,8 +16,9 @@ import (
|
||||
|
||||
type UserRepo interface {
|
||||
CreateUser(ctx context.Context, dto models.UserDTO) (*models.UserDTO, error)
|
||||
UpdateUser(ctx context.Context, userId string, dto models.UserUpdateDTO) error
|
||||
GetUserById(ctx context.Context, id string) (*models.UserDTO, error)
|
||||
GetUserByLogin(ctx context.Context, login string) (*models.UserDTO, error)
|
||||
GetUserByEmail(ctx context.Context, login string) (*models.UserDTO, error)
|
||||
}
|
||||
|
||||
func NewUserRepo(db *sql.DB) UserRepo {
|
||||
@ -29,8 +30,8 @@ type userRepo struct {
|
||||
}
|
||||
|
||||
func (u *userRepo) CreateUser(ctx context.Context, dto models.UserDTO) (*models.UserDTO, error) {
|
||||
query := `insert into users (login, secret, name) values ($1, $2, $3) returning id;`
|
||||
row := u.db.QueryRowContext(ctx, query, dto.Login, dto.Secret, dto.Name)
|
||||
query := `insert into users (email, secret, name) values ($1, $2, $3) returning id;`
|
||||
row := u.db.QueryRowContext(ctx, query, dto.Email, dto.Secret, dto.Name)
|
||||
|
||||
id := ""
|
||||
if err := row.Scan(&id); err != nil {
|
||||
@ -39,18 +40,28 @@ func (u *userRepo) CreateUser(ctx context.Context, dto models.UserDTO) (*models.
|
||||
|
||||
return &models.UserDTO{
|
||||
Id: id,
|
||||
Login: dto.Login,
|
||||
Email: dto.Email,
|
||||
Secret: dto.Secret,
|
||||
Name: dto.Name,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (u *userRepo) UpdateUser(ctx context.Context, userId string, dto models.UserUpdateDTO) error {
|
||||
query := `update users set secret=$1, name=$2 where id = $3;`
|
||||
_, err := u.db.ExecContext(ctx, query, dto.Secret, dto.Name, userId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *userRepo) GetUserById(ctx context.Context, id string) (*models.UserDTO, error) {
|
||||
query := `select id, login, secret, name from users where id = $1;`
|
||||
query := `select id, email, secret, name from users where id = $1;`
|
||||
row := u.db.QueryRowContext(ctx, query, id)
|
||||
|
||||
dto := &models.UserDTO{}
|
||||
err := row.Scan(&dto.Id, &dto.Login, &dto.Secret, &dto.Name)
|
||||
err := row.Scan(&dto.Id, &dto.Email, &dto.Secret, &dto.Name)
|
||||
if err == nil {
|
||||
return dto, nil
|
||||
}
|
||||
@ -61,12 +72,12 @@ func (u *userRepo) GetUserById(ctx context.Context, id string) (*models.UserDTO,
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func (u *userRepo) GetUserByLogin(ctx context.Context, login string) (*models.UserDTO, error) {
|
||||
query := `select id, login, secret, name from users where login = $1;`
|
||||
func (u *userRepo) GetUserByEmail(ctx context.Context, login string) (*models.UserDTO, error) {
|
||||
query := `select id, email, secret, name from users where email = $1;`
|
||||
row := u.db.QueryRowContext(ctx, query, login)
|
||||
|
||||
dto := &models.UserDTO{}
|
||||
err := row.Scan(&dto.Id, &dto.Login, &dto.Secret, &dto.Name)
|
||||
err := row.Scan(&dto.Id, &dto.Email, &dto.Secret, &dto.Name)
|
||||
if err == nil {
|
||||
return dto, nil
|
||||
}
|
||||
|
||||
60
src/services/shortlink_service.go
Normal file
60
src/services/shortlink_service.go
Normal file
@ -0,0 +1,60 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"backend/src/repo"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ShortlinkService interface {
|
||||
CreateLink(in string) (string, error)
|
||||
GetLink(id string) (string, error)
|
||||
}
|
||||
|
||||
type NewShortlinkServiceParams struct {
|
||||
Endpoint string
|
||||
Cache repo.Cache[string, string]
|
||||
}
|
||||
|
||||
func NewShortlinkSevice(params NewShortlinkServiceParams) ShortlinkService {
|
||||
return &shortlinkService{
|
||||
cache: params.Cache,
|
||||
}
|
||||
}
|
||||
|
||||
type shortlinkService struct {
|
||||
cache repo.Cache[string, string]
|
||||
}
|
||||
|
||||
func (s *shortlinkService) randomStr() string {
|
||||
src := rand.NewSource(time.Now().UnixMicro())
|
||||
randGen := rand.New(src)
|
||||
|
||||
builder := strings.Builder{}
|
||||
for i := 0; i < 9; i++ {
|
||||
offset := 0x41
|
||||
if randGen.Int()%2 == 1 {
|
||||
offset = 0x61
|
||||
}
|
||||
|
||||
byte := offset + (randGen.Int() % 26)
|
||||
builder.WriteRune(rune(byte))
|
||||
}
|
||||
return builder.String()
|
||||
}
|
||||
|
||||
func (s *shortlinkService) CreateLink(in string) (string, error) {
|
||||
str := s.randomStr()
|
||||
s.cache.Set(str, in, 7*24*60*60)
|
||||
return str, nil
|
||||
}
|
||||
|
||||
func (s *shortlinkService) GetLink(id string) (string, error) {
|
||||
val, ok := s.cache.Get(id)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("link does not exist or expired")
|
||||
}
|
||||
return val, nil
|
||||
}
|
||||
@ -6,6 +6,8 @@ import (
|
||||
"backend/src/utils"
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -32,6 +34,8 @@ type UserServiceDeps struct {
|
||||
Password utils.PasswordUtil
|
||||
UserRepo repo.UserRepo
|
||||
UserCache repo.Cache[string, models.UserDTO]
|
||||
EmailRepo repo.EmailRepo
|
||||
ActionTokenRepo repo.ActionTokenRepo
|
||||
}
|
||||
|
||||
type userService struct {
|
||||
@ -39,13 +43,13 @@ type userService struct {
|
||||
}
|
||||
|
||||
type UserCreateParams struct {
|
||||
Login string
|
||||
Email string
|
||||
Password string
|
||||
Name string
|
||||
}
|
||||
|
||||
func (u *userService) CreateUser(ctx context.Context, params UserCreateParams) (*models.UserDTO, error) {
|
||||
exisitngUser, err := u.deps.UserRepo.GetUserByLogin(ctx, params.Login)
|
||||
exisitngUser, err := u.deps.UserRepo.GetUserByEmail(ctx, params.Email)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -63,7 +67,7 @@ func (u *userService) CreateUser(ctx context.Context, params UserCreateParams) (
|
||||
}
|
||||
|
||||
user := models.UserDTO{
|
||||
Login: params.Login,
|
||||
Email: params.Email,
|
||||
Secret: string(secret),
|
||||
Name: params.Name,
|
||||
}
|
||||
@ -78,8 +82,8 @@ func (u *userService) CreateUser(ctx context.Context, params UserCreateParams) (
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (u *userService) AuthenticateUser(ctx context.Context, login, password string) (string, error) {
|
||||
user, err := u.deps.UserRepo.GetUserByLogin(ctx, login)
|
||||
func (u *userService) AuthenticateUser(ctx context.Context, email, password string) (string, error) {
|
||||
user, err := u.deps.UserRepo.GetUserByEmail(ctx, email)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@ -102,6 +106,76 @@ func (u *userService) AuthenticateUser(ctx context.Context, login, password stri
|
||||
return jwt, nil
|
||||
}
|
||||
|
||||
func (u *userService) HelpPasswordForgot(ctx context.Context, userId string) error {
|
||||
user, err := u.getUserById(ctx, userId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
actionToken, err := u.deps.ActionTokenRepo.CreateActionToken(
|
||||
ctx,
|
||||
models.ActionTokenDTO{
|
||||
UserId: user.Id,
|
||||
Value: uuid.New().String(),
|
||||
Target: models.ActionTokenTargetForgotPassword,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
u.deps.EmailRepo.SendEmailForgotPassword(user.Email, actionToken.Value)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *userService) ChangePasswordForgot(ctx context.Context, userId, newPassword, accessCode string) error {
|
||||
user, err := u.getUserById(ctx, userId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
code, err := u.deps.ActionTokenRepo.PopActionToken(ctx, userId, accessCode, models.ActionTokenTargetForgotPassword)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if code == nil {
|
||||
return fmt.Errorf("wrong user access code")
|
||||
}
|
||||
|
||||
return u.updatePassword(ctx, *user, newPassword)
|
||||
}
|
||||
|
||||
func (u *userService) ChangePassword(ctx context.Context, userId, oldPassword, newPassword string) error {
|
||||
user, err := u.getUserById(ctx, userId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !u.deps.Password.Compare(oldPassword, user.Secret) {
|
||||
return ErrUserWrongPassword
|
||||
}
|
||||
|
||||
return u.updatePassword(ctx, *user, newPassword)
|
||||
}
|
||||
|
||||
func (u *userService) updatePassword(ctx context.Context, user models.UserDTO, newPassword string) error {
|
||||
if err := u.deps.Password.Validate(newPassword); err != nil {
|
||||
return ErrUserBadPassword
|
||||
}
|
||||
|
||||
u.deps.UserCache.Del(user.Id)
|
||||
|
||||
newSecret, err := u.deps.Password.Hash(newPassword)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return u.deps.UserRepo.UpdateUser(ctx, user.Id, models.UserUpdateDTO{
|
||||
Secret: newSecret,
|
||||
Name: user.Name,
|
||||
})
|
||||
}
|
||||
|
||||
func (u *userService) getUserById(ctx context.Context, userId string) (*models.UserDTO, error) {
|
||||
if user, ok := u.deps.UserCache.Get(userId); ok {
|
||||
return &user, nil
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user