forked from ebhomengo/niki
feat(benefactor): add login and register
This commit is contained in:
parent
052f062cca
commit
866c5b42e1
|
@ -0,0 +1,31 @@
|
|||
package redis
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Host string `koanf:"host"`
|
||||
Port int `koanf:"port"`
|
||||
Password string `koanf:"password"`
|
||||
DB int `koanf:"db"`
|
||||
}
|
||||
|
||||
type Adapter struct {
|
||||
client *redis.Client
|
||||
}
|
||||
|
||||
func New(config Config) Adapter {
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: fmt.Sprintf("%s:%d", config.Host, config.Port),
|
||||
Password: config.Password,
|
||||
DB: config.DB,
|
||||
})
|
||||
|
||||
return Adapter{client: rdb}
|
||||
}
|
||||
|
||||
func (a Adapter) Client() *redis.Client {
|
||||
return a.client
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package smsprovider
|
||||
|
||||
type Config struct {
|
||||
Host string `koanf:"host"`
|
||||
Port int `koanf:"port"`
|
||||
}
|
||||
|
||||
type Adapter struct {
|
||||
}
|
||||
|
||||
func New(config Config) Adapter {
|
||||
//rdb := redis.NewClient(&redis.Options{
|
||||
// Addr: fmt.Sprintf("%s:%d", config.Host, config.Port),
|
||||
//})
|
||||
|
||||
return Adapter{}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package smsprovider
|
||||
|
||||
func (a Adapter) SendSms(phoneNumber string, code string) error {
|
||||
return nil
|
||||
}
|
25
config.yml
25
config.yml
|
@ -1,14 +1,33 @@
|
|||
---
|
||||
type: yml
|
||||
|
||||
auth:
|
||||
sign_key: jwt_secret
|
||||
sign_key: jwt_secret_test_nik
|
||||
|
||||
http_server:
|
||||
port: 8080
|
||||
port: 1313
|
||||
|
||||
mysql:
|
||||
port: 3308
|
||||
host: localhost
|
||||
db_name: niki_db
|
||||
username: niki
|
||||
password: nikit0lk2o20
|
||||
password: nikiappt0lk2o20
|
||||
|
||||
redis:
|
||||
port: 6380
|
||||
host: localhost
|
||||
password: ""
|
||||
db: 0
|
||||
|
||||
sms_provider:
|
||||
host: localhost
|
||||
port: 443
|
||||
|
||||
benefactor_service:
|
||||
length_of_otp_code: 5
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"git.gocasts.ir/ebhomengo/niki/adapter/redis"
|
||||
smsprovider "git.gocasts.ir/ebhomengo/niki/adapter/sms_provider"
|
||||
"git.gocasts.ir/ebhomengo/niki/repository/mysql"
|
||||
authservice "git.gocasts.ir/ebhomengo/niki/service/auth/benefactor"
|
||||
benefactorservice "git.gocasts.ir/ebhomengo/niki/service/benefactor/benefactor"
|
||||
)
|
||||
|
||||
type HTTPServer struct {
|
||||
|
@ -9,6 +13,10 @@ type HTTPServer struct {
|
|||
}
|
||||
|
||||
type Config struct {
|
||||
HTTPServer HTTPServer `koanf:"http_server"`
|
||||
Mysql mysql.Config `koanf:"mysql"`
|
||||
HTTPServer HTTPServer `koanf:"http_server"`
|
||||
Mysql mysql.Config `koanf:"mysql"`
|
||||
Auth authservice.Config `koanf:"auth"`
|
||||
Redis redis.Config `koanf:"redis"`
|
||||
SmsProvider smsprovider.Config `koanf:"sms_provider"`
|
||||
BenefactorSvc benefactorservice.Config `koanf:"benefactor_service"`
|
||||
}
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
package config
|
||||
|
||||
import "time"
|
||||
|
||||
const (
|
||||
OptChars = "0123456789"
|
||||
OtpExpireTime time.Duration = 120000 // 2 minutes
|
||||
|
||||
JwtSignKey = "jwt_secret"
|
||||
AccessTokenSubject = "ac"
|
||||
RefreshTokenSubject = "rt"
|
||||
AccessTokenExpireDuration = time.Hour * 24
|
||||
RefreshTokenExpireDuration = time.Hour * 24 * 7
|
||||
AuthMiddlewareContextKey = "claims"
|
||||
)
|
|
@ -0,0 +1,24 @@
|
|||
package benefactorhandler
|
||||
|
||||
import (
|
||||
benefactorauthservice "git.gocasts.ir/ebhomengo/niki/service/auth/benefactor"
|
||||
benefactorservice "git.gocasts.ir/ebhomengo/niki/service/benefactor/benefactor"
|
||||
benefactorvalidator "git.gocasts.ir/ebhomengo/niki/validator/benefactor/benefactor"
|
||||
)
|
||||
|
||||
type Handler struct {
|
||||
authConfig benefactorauthservice.Config
|
||||
benefactorSvc benefactorservice.Service
|
||||
benefactorVld benefactorvalidator.Validator
|
||||
}
|
||||
|
||||
func New(authConfig benefactorauthservice.Config,
|
||||
benefactorSvc benefactorservice.Service,
|
||||
benefactorVld benefactorvalidator.Validator,
|
||||
) Handler {
|
||||
return Handler{
|
||||
authConfig: authConfig,
|
||||
benefactorSvc: benefactorSvc,
|
||||
benefactorVld: benefactorVld,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
package benefactorhandler
|
||||
|
||||
import (
|
||||
benefactoreparam "git.gocasts.ir/ebhomengo/niki/param/benefactor/benefactore"
|
||||
httpmsg "git.gocasts.ir/ebhomengo/niki/pkg/http_msg"
|
||||
"github.com/labstack/echo/v4"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func (h Handler) loginOrRegister(c echo.Context) error {
|
||||
var req benefactoreparam.LoginOrRegisterRequest
|
||||
|
||||
if bErr := c.Bind(&req); bErr != nil {
|
||||
return echo.NewHTTPError(http.StatusBadRequest)
|
||||
}
|
||||
if fieldErrors, err := h.benefactorVld.ValidateLoginRegisterRequest(req); err != nil {
|
||||
msg, code := httpmsg.Error(err)
|
||||
|
||||
return c.JSON(code, echo.Map{
|
||||
"message": msg,
|
||||
"errors": fieldErrors,
|
||||
})
|
||||
}
|
||||
resp, sErr := h.benefactorSvc.LoginOrRegister(c.Request().Context(), req)
|
||||
if sErr != nil {
|
||||
msg, code := httpmsg.Error(sErr)
|
||||
|
||||
return echo.NewHTTPError(code, msg)
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, resp)
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package benefactorhandler
|
||||
|
||||
import "github.com/labstack/echo/v4"
|
||||
|
||||
func (h Handler) SetRoutes(e *echo.Echo) {
|
||||
r := e.Group("/benefactor")
|
||||
|
||||
r.POST("/send-otp", h.SendOtp)
|
||||
r.POST("/login-register", h.loginOrRegister)
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
package benefactorhandler
|
||||
|
||||
import (
|
||||
benefactoreparam "git.gocasts.ir/ebhomengo/niki/param/benefactor/benefactore"
|
||||
httpmsg "git.gocasts.ir/ebhomengo/niki/pkg/http_msg"
|
||||
"github.com/labstack/echo/v4"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func (h Handler) SendOtp(c echo.Context) error {
|
||||
var req benefactoreparam.SendOtpRequest
|
||||
|
||||
if bErr := c.Bind(&req); bErr != nil {
|
||||
return echo.NewHTTPError(http.StatusBadRequest)
|
||||
}
|
||||
if fieldErrors, err := h.benefactorVld.ValidateSendOtpRequest(req); err != nil {
|
||||
msg, code := httpmsg.Error(err)
|
||||
|
||||
return c.JSON(code, echo.Map{
|
||||
"message": msg,
|
||||
"errors": fieldErrors,
|
||||
})
|
||||
}
|
||||
resp, sErr := h.benefactorSvc.SendOtp(c.Request().Context(), req)
|
||||
if sErr != nil {
|
||||
msg, code := httpmsg.Error(sErr)
|
||||
|
||||
return echo.NewHTTPError(code, msg)
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, resp)
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
package benefactorkindboxhandler
|
||||
|
||||
import (
|
||||
authservice "git.gocasts.ir/ebhomengo/niki/service/auth/user"
|
||||
authservice "git.gocasts.ir/ebhomengo/niki/service/auth/benefactor"
|
||||
benefactorkindboxservice "git.gocasts.ir/ebhomengo/niki/service/benefactor/kind_box"
|
||||
benefactorkindboxvalidator "git.gocasts.ir/ebhomengo/niki/validator/benefactor/kind_box"
|
||||
)
|
||||
|
|
|
@ -2,6 +2,9 @@ package httpserver
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
benefactorhandler "git.gocasts.ir/ebhomengo/niki/delivery/http_server/benefactor/benefactor"
|
||||
benefactorservice "git.gocasts.ir/ebhomengo/niki/service/benefactor/benefactor"
|
||||
benefactorvalidator "git.gocasts.ir/ebhomengo/niki/validator/benefactor/benefactor"
|
||||
|
||||
config "git.gocasts.ir/ebhomengo/niki/config"
|
||||
echo "github.com/labstack/echo/v4"
|
||||
|
@ -9,14 +12,16 @@ import (
|
|||
)
|
||||
|
||||
type Server struct {
|
||||
config config.Config
|
||||
Router *echo.Echo
|
||||
config config.Config
|
||||
Router *echo.Echo
|
||||
benefactorHandler benefactorhandler.Handler
|
||||
}
|
||||
|
||||
func New(cfg config.Config) Server {
|
||||
func New(cfg config.Config, benefactorSvc benefactorservice.Service, benefactorVld benefactorvalidator.Validator) Server {
|
||||
return Server{
|
||||
Router: echo.New(),
|
||||
config: cfg,
|
||||
Router: echo.New(),
|
||||
config: cfg,
|
||||
benefactorHandler: benefactorhandler.New(cfg.Auth, benefactorSvc, benefactorVld),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -27,6 +32,7 @@ func (s Server) Serve() {
|
|||
|
||||
// Routes
|
||||
s.Router.GET("/health-check", s.healthCheck)
|
||||
s.benefactorHandler.SetRoutes(s.Router)
|
||||
|
||||
// Start server
|
||||
address := fmt.Sprintf(":%d", s.config.HTTPServer.Port)
|
||||
|
|
|
@ -2,19 +2,35 @@ version: '3.9'
|
|||
|
||||
services:
|
||||
mysql:
|
||||
platform: linux/amd64
|
||||
image: mysql:8.0
|
||||
ports:
|
||||
- 3305:3305
|
||||
- "3308:3306"
|
||||
container_name: niki-database
|
||||
volumes:
|
||||
- ~/apps/mysql:/var/lib/mysql
|
||||
- dbdata:/var/lib/mysql
|
||||
restart: always
|
||||
hostname: mysql
|
||||
container_name: niki_mysql
|
||||
command: [ 'mysqld', '--character-set-server=utf8mb4', '--collation-server=utf8mb4_unicode_ci' ]
|
||||
environment:
|
||||
- MYSQL_ROOT_PASSWORD=niki_user
|
||||
- MYSQL_PASSWORD=NIKI_user@123
|
||||
- MYSQL_USER=user
|
||||
- MYSQL_DATABASE=niki
|
||||
MYSQL_ROOT_PASSWORD: nikiRoo7t0lk2o20
|
||||
MYSQL_DATABASE: niki_db
|
||||
MYSQL_USER: niki
|
||||
MYSQL_PASSWORD: nikiappt0lk2o20
|
||||
|
||||
niki-redis:
|
||||
image: bitnami/redis:6.2
|
||||
container_name: niki-redis
|
||||
restart: always
|
||||
ports:
|
||||
- '6380:6379'
|
||||
# TODO - remove `--save "" --appendonly no` from command to persist data
|
||||
command: redis-server --loglevel warning --protected-mode no --save "" --appendonly no
|
||||
environment:
|
||||
- ALLOW_EMPTY_PASSWORD=yes
|
||||
volumes:
|
||||
- niki-redis-data:/data
|
||||
|
||||
|
||||
|
||||
volumes:
|
||||
dbdata:
|
||||
niki-redis-data:
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
version: '3.9'
|
||||
|
||||
services:
|
||||
mysql:
|
||||
platform: linux/amd64
|
||||
image: mysql:8.0
|
||||
ports:
|
||||
- 3305:3305
|
||||
volumes:
|
||||
- ~/apps/mysql:/var/lib/mysql
|
||||
restart: always
|
||||
hostname: mysql
|
||||
container_name: niki_mysql
|
||||
environment:
|
||||
- MYSQL_ROOT_PASSWORD=root
|
||||
- MYSQL_USER=niki_user
|
||||
- MYSQL_PASSWORD=NIKI_user@123
|
||||
- MYSQL_DATABASE=niki_db
|
||||
|
||||
niki-redis:
|
||||
image: bitnami/redis:6.2
|
||||
container_name: niki-redis
|
||||
restart: always
|
||||
ports:
|
||||
- '6380:6379'
|
||||
# TODO - remove `--save "" --appendonly no` from command to persist data
|
||||
command: redis-server --loglevel warning --protected-mode no --save "" --appendonly no
|
||||
environment:
|
||||
- ALLOW_EMPTY_PASSWORD=yes
|
||||
volumes:
|
||||
- niki-redis-data:/data
|
||||
|
||||
|
||||
|
||||
volumes:
|
||||
dbdata:
|
||||
niki-redis-data:
|
|
@ -3,16 +3,16 @@ package entity
|
|||
import "time"
|
||||
|
||||
type Benefactor struct {
|
||||
ID uint
|
||||
FirstName string
|
||||
LastName string
|
||||
PhoneNumber string
|
||||
Address string
|
||||
Description string
|
||||
Email string
|
||||
City string
|
||||
Gender Gender
|
||||
Status BenefactorStatus
|
||||
Birthday time.Time
|
||||
StatusChangedAt time.Time
|
||||
ID uint
|
||||
FirstName string
|
||||
LastName string
|
||||
PhoneNumber string
|
||||
Address string
|
||||
Description string
|
||||
Email string
|
||||
City string
|
||||
Gender Gender
|
||||
Status BenefactorStatus
|
||||
Birthdate time.Time
|
||||
Role UserRole
|
||||
}
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
package entity
|
||||
|
||||
type UserRole uint
|
||||
|
||||
const (
|
||||
UserBenefactorRole UserRole = iota + 1
|
||||
)
|
||||
|
||||
var UserRoleStrings = map[UserRole]string{
|
||||
UserBenefactorRole: "benefactor",
|
||||
}
|
||||
|
||||
func (s UserRole) String() string {
|
||||
return UserRoleStrings[s]
|
||||
}
|
||||
|
||||
// AllUserRole returns a slice containing all string values of UserRole.
|
||||
func AllUserRole() []string {
|
||||
roleStrings := make([]string, len(UserRoleStrings))
|
||||
for role, str := range UserRoleStrings {
|
||||
roleStrings[int(role)-1] = str
|
||||
}
|
||||
|
||||
return roleStrings
|
||||
}
|
||||
|
||||
// MapToUserRole converts a string to the corresponding UserRole value.
|
||||
func MapToUserRole(roleStr string) UserRole {
|
||||
for role, str := range UserRoleStrings {
|
||||
if str == roleStr {
|
||||
return role
|
||||
}
|
||||
}
|
||||
|
||||
return UserRole(0)
|
||||
}
|
6
go.mod
6
go.mod
|
@ -5,15 +5,20 @@ go 1.21.3
|
|||
require (
|
||||
github.com/go-ozzo/ozzo-validation v3.6.0+incompatible
|
||||
github.com/go-ozzo/ozzo-validation/v4 v4.3.0
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0
|
||||
github.com/knadh/koanf v1.5.0
|
||||
github.com/labstack/echo/v4 v4.11.4
|
||||
github.com/redis/go-redis/v9 v9.4.0
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/fatih/structs v1.1.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.4.9 // indirect
|
||||
github.com/go-gorp/gorp/v3 v3.1.0 // indirect
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
|
||||
github.com/labstack/gommon v0.4.2 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
|
@ -21,6 +26,7 @@ require (
|
|||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
github.com/rubenv/sql-migrate v1.6.0 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasttemplate v1.2.2 // indirect
|
||||
golang.org/x/crypto v0.17.0 // indirect
|
||||
|
|
16
go.sum
16
go.sum
|
@ -27,8 +27,14 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24
|
|||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
|
||||
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
||||
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
|
@ -37,6 +43,8 @@ github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV
|
|||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
|
@ -50,6 +58,8 @@ github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga
|
|||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-gorp/gorp/v3 v3.1.0 h1:ItKF/Vbuj31dmV4jxA1qblpSwkl9g1typ24xoe70IGs=
|
||||
github.com/go-gorp/gorp/v3 v3.1.0/go.mod h1:dLEjIyyRNiXvNZ8PSmzpt1GsWAUK8kjVhEpjH8TixEw=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
|
||||
|
@ -68,6 +78,8 @@ github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7a
|
|||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
|
@ -229,8 +241,12 @@ github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R
|
|||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
||||
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||
github.com/redis/go-redis/v9 v9.4.0 h1:Yzoz33UZw9I/mFhx4MNrB6Fk+XHO1VukNcCa1+lwyKk=
|
||||
github.com/redis/go-redis/v9 v9.4.0/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M=
|
||||
github.com/rhnvrm/simples3 v0.6.1/go.mod h1:Y+3vYm2V7Y4VijFoJHHTrja6OgPrJ2cBti8dPGkC3sA=
|
||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||
github.com/rubenv/sql-migrate v1.6.0 h1:IZpcTlAx/VKXphWEpwWJ7BaMq05tYtE80zYz+8a5Il8=
|
||||
github.com/rubenv/sql-migrate v1.6.0/go.mod h1:m3ilnKP7sNb4eYkLsp6cGdPOl4OBcXM6rcbzU+Oqc5k=
|
||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
|
||||
|
|
44
main.go
44
main.go
|
@ -1,4 +1,48 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"git.gocasts.ir/ebhomengo/niki/adapter/redis"
|
||||
smsprovider "git.gocasts.ir/ebhomengo/niki/adapter/sms_provider"
|
||||
"git.gocasts.ir/ebhomengo/niki/config"
|
||||
httpserver "git.gocasts.ir/ebhomengo/niki/delivery/http_server"
|
||||
"git.gocasts.ir/ebhomengo/niki/repository/migrator"
|
||||
"git.gocasts.ir/ebhomengo/niki/repository/mysql"
|
||||
mysqlbenefactor "git.gocasts.ir/ebhomengo/niki/repository/mysql/benefactor"
|
||||
redisotp "git.gocasts.ir/ebhomengo/niki/repository/redis/redis_otp"
|
||||
authservice "git.gocasts.ir/ebhomengo/niki/service/auth/benefactor"
|
||||
benefactorservice "git.gocasts.ir/ebhomengo/niki/service/benefactor/benefactor"
|
||||
benefactorvalidator "git.gocasts.ir/ebhomengo/niki/validator/benefactor/benefactor"
|
||||
)
|
||||
|
||||
func main() {
|
||||
cfg := config.C()
|
||||
|
||||
// TODO - add command for migrations
|
||||
mgr := migrator.New(cfg.Mysql)
|
||||
mgr.Up()
|
||||
|
||||
_, benefactorSvc, benefactorVld := setupServices(cfg)
|
||||
server := httpserver.New(cfg, benefactorSvc, benefactorVld)
|
||||
server.Serve()
|
||||
}
|
||||
|
||||
func setupServices(cfg config.Config) (
|
||||
authservice.Service, benefactorservice.Service, benefactorvalidator.Validator,
|
||||
) {
|
||||
|
||||
authSvc := authservice.New(cfg.Auth)
|
||||
|
||||
MysqlRepo := mysql.New(cfg.Mysql)
|
||||
|
||||
redisAdapter := redis.New(cfg.Redis)
|
||||
RedisOtp := redisotp.New(redisAdapter)
|
||||
benefactorMysql := mysqlbenefactor.New(MysqlRepo)
|
||||
smsProvider := smsprovider.New(cfg.SmsProvider)
|
||||
authGenerator := authservice.New(cfg.Auth)
|
||||
|
||||
benefactorSvc := benefactorservice.New(cfg.BenefactorSvc, RedisOtp, smsProvider, authGenerator, benefactorMysql)
|
||||
|
||||
benefactorVld := benefactorvalidator.New()
|
||||
|
||||
return authSvc, benefactorSvc, benefactorVld
|
||||
}
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
package benefactoreparam
|
||||
|
||||
type BenefactroInfo struct {
|
||||
ID uint `json:"id"`
|
||||
FirstName string `json:"first_name"`
|
||||
LastName string `json:"last_name"`
|
||||
Role string `json:"role"`
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package benefactoreparam
|
||||
|
||||
type LoginOrRegisterRequest struct {
|
||||
PhoneNumber string `json:"phone_number"`
|
||||
VerificationCode string `json:"verification_code"`
|
||||
}
|
||||
|
||||
type LoginOrRegisterResponse struct {
|
||||
BenefactorInfo BenefactroInfo `json:"benefactore_info"`
|
||||
Tokens Tokens `json:"tokens"`
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
package benefactoreparam
|
||||
|
||||
type SendOtpRequest struct {
|
||||
PhoneNumber string `json:"phone_number"`
|
||||
}
|
||||
type SendOtpResponse struct {
|
||||
PhoneNumber string `json:"phone_number"`
|
||||
/*
|
||||
this just use in test env
|
||||
TODO - remove it after test
|
||||
*/
|
||||
Code string `json:"code"`
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
package benefactoreparam
|
||||
|
||||
type Tokens struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
}
|
|
@ -9,8 +9,7 @@ const (
|
|||
ErrorMsgPhoneNumberIsNotValid = "phone number is not valid"
|
||||
ErrorMsgUserNotAllowed = "user not allowed"
|
||||
ErrorMsgUserNotFound = "benefactor not found"
|
||||
ErrorMsgOtpCodeExist = "please wait a little bit"
|
||||
ErrorMsgOtpCodeIsNotValid = "verification code is not valid"
|
||||
ErrorMsgCantScanQueryResult = "can't scan query result"
|
||||
)
|
||||
|
||||
// const (
|
||||
// ErrorMsgCantScanQueryResult = "can't scan query result"
|
||||
// )
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
package migrator
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"git.gocasts.ir/ebhomengo/niki/repository/mysql"
|
||||
migrate "github.com/rubenv/sql-migrate"
|
||||
)
|
||||
|
||||
type Migrator struct {
|
||||
dialect string
|
||||
dbConfig mysql.Config
|
||||
migrations *migrate.FileMigrationSource
|
||||
}
|
||||
|
||||
// TODO - set migration table name
|
||||
// TODO - add limit to Up and Down method
|
||||
|
||||
func New(dbConfig mysql.Config) Migrator {
|
||||
// OR: Read migrations from a folder:
|
||||
migrations := &migrate.FileMigrationSource{
|
||||
Dir: "./repository/mysql/migrations",
|
||||
}
|
||||
|
||||
return Migrator{dbConfig: dbConfig, dialect: "mysql", migrations: migrations}
|
||||
}
|
||||
|
||||
func (m Migrator) Up() {
|
||||
fmt.Println("mysql add= ", fmt.Sprintf("%s:%s@(%s:%d)/%s?parseTime=true",
|
||||
m.dbConfig.Username, m.dbConfig.Password, m.dbConfig.Host, m.dbConfig.Port, m.dbConfig.DBName))
|
||||
db, err := sql.Open(m.dialect, fmt.Sprintf("%s:%s@(%s:%d)/%s?parseTime=true",
|
||||
m.dbConfig.Username, m.dbConfig.Password, m.dbConfig.Host, m.dbConfig.Port, m.dbConfig.DBName))
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("can't open mysql db: %v", err))
|
||||
}
|
||||
|
||||
n, err := migrate.Exec(db, m.dialect, m.migrations, migrate.Up)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("can't apply migrations: %v", err))
|
||||
}
|
||||
fmt.Printf("Applied %d migrations!\n", n)
|
||||
}
|
||||
|
||||
func (m Migrator) Down() {
|
||||
db, err := sql.Open(m.dialect, fmt.Sprintf("%s:%s@(%s:%d)/%s?parseTime=true",
|
||||
m.dbConfig.Username, m.dbConfig.Password, m.dbConfig.Host, m.dbConfig.Port, m.dbConfig.DBName))
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("can't open mysql db: %v", err))
|
||||
}
|
||||
|
||||
n, err := migrate.Exec(db, m.dialect, m.migrations, migrate.Down)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("can't rollback migrations: %v", err))
|
||||
}
|
||||
fmt.Printf("Rollbacked %d migrations!\n", n)
|
||||
}
|
||||
|
||||
func (m Migrator) Status() {
|
||||
// TODO - add status
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
package mysqlbenefactor
|
||||
|
||||
import (
|
||||
"context"
|
||||
"git.gocasts.ir/ebhomengo/niki/entity"
|
||||
errmsg "git.gocasts.ir/ebhomengo/niki/pkg/err_msg"
|
||||
richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error"
|
||||
)
|
||||
|
||||
func (d DB) CreateBenefactor(ctx context.Context, benefactor entity.Benefactor) (entity.Benefactor, error) {
|
||||
const op = "mysqlbenefactor.CreateBenefactor"
|
||||
|
||||
res, err := d.conn.Conn().Exec(`insert into benefactors(phone_number, status, role) values(?, ?, ?)`,
|
||||
benefactor.PhoneNumber, benefactor.Status.String(), benefactor.Role.String())
|
||||
if err != nil {
|
||||
return entity.Benefactor{}, richerror.New(op).WithErr(err).
|
||||
WithMessage(errmsg.ErrorMsgNotFound).WithKind(richerror.KindUnexpected)
|
||||
}
|
||||
|
||||
// error is always nil
|
||||
id, _ := res.LastInsertId()
|
||||
benefactor.ID = uint(id)
|
||||
|
||||
return benefactor, nil
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
package mysqlbenefactor
|
||||
|
||||
import "git.gocasts.ir/ebhomengo/niki/repository/mysql"
|
||||
|
||||
type DB struct {
|
||||
conn *mysql.DB
|
||||
}
|
||||
|
||||
func New(conn *mysql.DB) *DB {
|
||||
return &DB{
|
||||
conn: conn,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
package mysqlbenefactor
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"git.gocasts.ir/ebhomengo/niki/entity"
|
||||
errmsg "git.gocasts.ir/ebhomengo/niki/pkg/err_msg"
|
||||
richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error"
|
||||
"git.gocasts.ir/ebhomengo/niki/repository/mysql"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (d DB) IsExistBenefactorByPhoneNumber(ctx context.Context, phoneNumber string) (bool, entity.Benefactor, error) {
|
||||
const op = "mysqlbenefactor.IsExistBenefactorByPhoneNumber"
|
||||
|
||||
row := d.conn.Conn().QueryRowContext(ctx, `select * from benefactors where phone_number = ?`, phoneNumber)
|
||||
|
||||
Benefactor, err := scanBenefactor(row)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return false, entity.Benefactor{}, richerror.New(op).WithErr(err).
|
||||
WithMessage(errmsg.ErrorMsgNotFound).WithKind(richerror.KindNotFound)
|
||||
}
|
||||
|
||||
// TODO - log unexpected error for better observability
|
||||
return false, entity.Benefactor{}, richerror.New(op).WithErr(err).
|
||||
WithMessage(errmsg.ErrorMsgCantScanQueryResult).WithKind(richerror.KindUnexpected)
|
||||
}
|
||||
|
||||
return true, Benefactor, nil
|
||||
}
|
||||
|
||||
func scanBenefactor(scanner mysql.Scanner) (entity.Benefactor, error) {
|
||||
var createdAt time.Time
|
||||
var benefactor entity.Benefactor
|
||||
var roleStr, genderStr, statusStr string
|
||||
|
||||
err := scanner.Scan(&benefactor.ID, &benefactor.FirstName,
|
||||
&benefactor.LastName, &benefactor.PhoneNumber,
|
||||
&benefactor.Address, &benefactor.Description,
|
||||
&benefactor.Email, &benefactor.City, &genderStr,
|
||||
&statusStr, &benefactor.Birthdate, &roleStr,
|
||||
&createdAt)
|
||||
|
||||
benefactor.Role = entity.MapToUserRole(roleStr)
|
||||
benefactor.Gender = entity.MapToGender(genderStr)
|
||||
benefactor.Status = entity.MapToBenefactorStatus(statusStr)
|
||||
|
||||
return benefactor, err
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
production:
|
||||
dialect: mysql
|
||||
datasource: niki:nikiappt0lk2o20(localhost:3308)/niki_db?parseTime=true
|
||||
dir: repository/mysql/migration
|
||||
table: gorp_migrations
|
|
@ -0,0 +1,21 @@
|
|||
-- +migrate Up
|
||||
-- please read this article to understand why we use VARCHAR(191)
|
||||
-- https://www.grouparoo.com/blog/varchar-191#why-varchar-and-not-text
|
||||
CREATE TABLE `benefactors` (
|
||||
`id` INT PRIMARY KEY AUTO_INCREMENT,
|
||||
`first_name` VARCHAR(191) ,
|
||||
`last_name` VARCHAR(191) ,
|
||||
`phone_number` VARCHAR(191) NOT NULL UNIQUE,
|
||||
`address` TEXT,
|
||||
`description` TEXT,
|
||||
`email` VARCHAR(191),
|
||||
`city` VARCHAR(191),
|
||||
`gender` VARCHAR(191),
|
||||
`status` VARCHAR(191),
|
||||
`birthdate` TIMESTAMP,
|
||||
`role` ENUM('benefactor') NOT NULL,
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- +migrate Down
|
||||
DROP TABLE `benefactors`;
|
|
@ -0,0 +1,5 @@
|
|||
package mysql
|
||||
|
||||
type Scanner interface {
|
||||
Scan(dest ...any) error
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package redisotp
|
||||
|
||||
import "git.gocasts.ir/ebhomengo/niki/adapter/redis"
|
||||
|
||||
type DB struct {
|
||||
adapter redis.Adapter
|
||||
}
|
||||
|
||||
func New(adapter redis.Adapter) DB {
|
||||
return DB{adapter: adapter}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package redisotp
|
||||
|
||||
import (
|
||||
"context"
|
||||
richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error"
|
||||
)
|
||||
|
||||
func (d DB) IsExistPhoneNumber(ctx context.Context, phoneNumber string) (bool, error) {
|
||||
const op = "redisotp.IsExistPhoneNumber"
|
||||
|
||||
isExist, err := d.adapter.Client().Exists(ctx, phoneNumber).Result()
|
||||
if err != nil {
|
||||
return false, richerror.New(op).WithErr(err).WithKind(richerror.KindUnexpected)
|
||||
}
|
||||
if isExist == 0 {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package redisotp
|
||||
|
||||
import (
|
||||
"context"
|
||||
richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error"
|
||||
)
|
||||
|
||||
func (d DB) GetCodeByPhoneNumber(ctx context.Context, phoneNumber string) (string, error) {
|
||||
const op = "redisotp.GetCodeByPhoneNumber"
|
||||
|
||||
value, err := d.adapter.Client().Get(ctx, phoneNumber).Result()
|
||||
if err != nil {
|
||||
return "", richerror.New(op).WithErr(err).WithKind(richerror.KindUnexpected)
|
||||
}
|
||||
|
||||
return value, nil
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package redisotp
|
||||
|
||||
import (
|
||||
"context"
|
||||
richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (d DB) SaveCodeWithPhoneNumber(ctx context.Context, phoneNumber string, code string, expireTime time.Duration) error {
|
||||
const op = "redisotp.SaveCodeWithPhoneNumber"
|
||||
err := d.adapter.Client().Set(ctx, phoneNumber, code, expireTime).Err()
|
||||
if err != nil {
|
||||
return richerror.New(op).WithErr(err).WithKind(richerror.KindUnexpected)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
package benefactorauthservice
|
||||
|
||||
import (
|
||||
"git.gocasts.ir/ebhomengo/niki/entity"
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
)
|
||||
|
||||
type Claims struct {
|
||||
jwt.RegisteredClaims
|
||||
UserID uint `json:"user_id"`
|
||||
Role entity.UserRole `json:"role"`
|
||||
}
|
||||
|
||||
func (c Claims) Valid() error {
|
||||
return c.RegisteredClaims.Valid()
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
package benefactorauthservice
|
||||
|
||||
import (
|
||||
"git.gocasts.ir/ebhomengo/niki/entity"
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
SignKey string `koanf:"sign_key"`
|
||||
AccessExpirationTime time.Duration `koanf:"access_expiration_time"`
|
||||
RefreshExpirationTime time.Duration `koanf:"refresh_expiration_time"`
|
||||
AccessSubject string `koanf:"access_subject"`
|
||||
RefreshSubject string `koanf:"refresh_subject"`
|
||||
}
|
||||
|
||||
type Service struct {
|
||||
config Config
|
||||
}
|
||||
|
||||
func New(cfg Config) Service {
|
||||
return Service{
|
||||
config: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
func (s Service) CreateAccessToken(benefactor entity.Benefactor) (string, error) {
|
||||
return s.createToken(benefactor.ID, benefactor.Role, s.config.AccessSubject, s.config.AccessExpirationTime)
|
||||
}
|
||||
|
||||
func (s Service) CreateRefreshToken(benefactor entity.Benefactor) (string, error) {
|
||||
return s.createToken(benefactor.ID, benefactor.Role, s.config.RefreshSubject, s.config.RefreshExpirationTime)
|
||||
}
|
||||
|
||||
func (s Service) ParseToken(bearerToken string) (*Claims, error) {
|
||||
//https://pkg.go.dev/github.com/golang-jwt/jwt/v5#example-ParseWithClaims-CustomClaimsType
|
||||
|
||||
tokenStr := strings.Replace(bearerToken, "Bearer ", "", 1)
|
||||
|
||||
token, err := jwt.ParseWithClaims(tokenStr, &Claims{}, func(token *jwt.Token) (interface{}, error) {
|
||||
return []byte(s.config.SignKey), nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if claims, ok := token.Claims.(*Claims); ok && token.Valid {
|
||||
return claims, nil
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
func (s Service) createToken(userID uint, role entity.UserRole, subject string, expireDuration time.Duration) (string, error) {
|
||||
// create a signer for rsa 256
|
||||
// TODO - replace with rsa 256 RS256 - https://github.com/golang-jwt/jwt/blob/main/http_example_test.go
|
||||
|
||||
// set our claims
|
||||
claims := Claims{
|
||||
RegisteredClaims: jwt.RegisteredClaims{
|
||||
Subject: subject,
|
||||
ExpiresAt: jwt.NewNumericDate(time.Now().Add(expireDuration)),
|
||||
},
|
||||
UserID: userID,
|
||||
Role: role,
|
||||
}
|
||||
|
||||
// TODO - add sign method to config
|
||||
accessToken := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
tokenString, err := accessToken.SignedString([]byte(s.config.SignKey))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return tokenString, nil
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
package user
|
|
@ -1 +0,0 @@
|
|||
package user
|
|
@ -1,23 +0,0 @@
|
|||
package user
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
SignKey string `koanf:"sign_key"`
|
||||
AccessExpirationTime time.Duration `koanf:"access_expiration_time"`
|
||||
RefreshExpirationTime time.Duration `koanf:"refresh_expiration_time"`
|
||||
AccessSubject string `koanf:"access_subject"`
|
||||
RefreshSubject string `koanf:"refresh_subject"`
|
||||
}
|
||||
|
||||
type Service struct {
|
||||
config Config
|
||||
}
|
||||
|
||||
func New(cfg Config) Service {
|
||||
return Service{
|
||||
config: cfg,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
package benefactorservice
|
||||
|
||||
import (
|
||||
"context"
|
||||
"git.gocasts.ir/ebhomengo/niki/entity"
|
||||
benefactoreparam "git.gocasts.ir/ebhomengo/niki/param/benefactor/benefactore"
|
||||
errmsg "git.gocasts.ir/ebhomengo/niki/pkg/err_msg"
|
||||
richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error"
|
||||
)
|
||||
|
||||
func (s Service) LoginOrRegister(ctx context.Context, req benefactoreparam.LoginOrRegisterRequest) (benefactoreparam.LoginOrRegisterResponse, error) {
|
||||
const op = "benefactorservice.LoginOrRegister"
|
||||
|
||||
code, gErr := s.redisOtp.GetCodeByPhoneNumber(ctx, req.PhoneNumber)
|
||||
if gErr != nil {
|
||||
return benefactoreparam.LoginOrRegisterResponse{}, richerror.New(op).WithErr(gErr).WithKind(richerror.KindUnexpected)
|
||||
}
|
||||
if code == "" || code != req.VerificationCode {
|
||||
return benefactoreparam.LoginOrRegisterResponse{}, richerror.New(op).WithMessage(errmsg.ErrorMsgOtpCodeIsNotValid).WithKind(richerror.KindForbidden)
|
||||
}
|
||||
|
||||
isExist, benefactor, rErr := s.repo.IsExistBenefactorByPhoneNumber(ctx, req.PhoneNumber)
|
||||
if rErr != nil {
|
||||
return benefactoreparam.LoginOrRegisterResponse{}, richerror.New(op).WithErr(rErr).WithKind(richerror.KindUnexpected)
|
||||
}
|
||||
if !isExist {
|
||||
newBenefactor, err := s.repo.CreateBenefactor(ctx, entity.Benefactor{
|
||||
PhoneNumber: "",
|
||||
Status: entity.BenefactorActiveStatus,
|
||||
Role: entity.UserBenefactorRole,
|
||||
})
|
||||
if err != nil {
|
||||
return benefactoreparam.LoginOrRegisterResponse{}, richerror.New(op).WithErr(rErr).WithKind(richerror.KindUnexpected)
|
||||
}
|
||||
|
||||
benefactor = newBenefactor
|
||||
}
|
||||
|
||||
accessToken, err := s.auth.CreateAccessToken(benefactor)
|
||||
if err != nil {
|
||||
return benefactoreparam.LoginOrRegisterResponse{}, richerror.New(op).WithErr(rErr).WithKind(richerror.KindUnexpected)
|
||||
}
|
||||
|
||||
refreshToken, err := s.auth.CreateRefreshToken(benefactor)
|
||||
if err != nil {
|
||||
return benefactoreparam.LoginOrRegisterResponse{}, richerror.New(op).WithErr(rErr).WithKind(richerror.KindUnexpected)
|
||||
}
|
||||
|
||||
return benefactoreparam.LoginOrRegisterResponse{
|
||||
BenefactorInfo: benefactoreparam.BenefactroInfo{
|
||||
ID: benefactor.ID,
|
||||
FirstName: benefactor.FirstName,
|
||||
LastName: benefactor.LastName,
|
||||
Role: benefactor.Role.String(),
|
||||
},
|
||||
Tokens: benefactoreparam.Tokens{
|
||||
AccessToken: accessToken,
|
||||
RefreshToken: refreshToken,
|
||||
},
|
||||
}, nil
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
package benefactorservice
|
||||
|
||||
import (
|
||||
"context"
|
||||
benefactoreparam "git.gocasts.ir/ebhomengo/niki/param/benefactor/benefactore"
|
||||
errmsg "git.gocasts.ir/ebhomengo/niki/pkg/err_msg"
|
||||
richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error"
|
||||
"math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (s Service) SendOtp(ctx context.Context, req benefactoreparam.SendOtpRequest) (benefactoreparam.SendOtpResponse, error) {
|
||||
const op = "benefactorservice.SendOtp"
|
||||
|
||||
isExist, iErr := s.redisOtp.IsExistPhoneNumber(ctx, req.PhoneNumber)
|
||||
if iErr != nil {
|
||||
return benefactoreparam.SendOtpResponse{}, richerror.New(op).WithErr(iErr).WithKind(richerror.KindUnexpected)
|
||||
}
|
||||
if isExist {
|
||||
return benefactoreparam.SendOtpResponse{}, richerror.New(op).WithMessage(errmsg.ErrorMsgOtpCodeExist).WithKind(richerror.KindForbidden)
|
||||
}
|
||||
|
||||
newCode := s.generateVerificationCode()
|
||||
spErr := s.redisOtp.SaveCodeWithPhoneNumber(ctx, req.PhoneNumber, newCode, s.config.OtpExpireTime)
|
||||
if spErr != nil {
|
||||
return benefactoreparam.SendOtpResponse{}, richerror.New(op).WithErr(spErr).WithKind(richerror.KindUnexpected)
|
||||
}
|
||||
|
||||
//TODO- use goroutine
|
||||
sErr := s.smsProviderClient.SendSms(req.PhoneNumber, newCode)
|
||||
if sErr != nil {
|
||||
return benefactoreparam.SendOtpResponse{}, richerror.New(op).WithErr(sErr).WithKind(richerror.KindUnexpected)
|
||||
}
|
||||
|
||||
// we use code in sendOtpResponse until sms provider will implement
|
||||
return benefactoreparam.SendOtpResponse{
|
||||
PhoneNumber: req.PhoneNumber,
|
||||
Code: newCode, //TODO - have to remove it in production
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s Service) generateVerificationCode() string {
|
||||
rand.NewSource(time.Now().UnixNano())
|
||||
result := make([]byte, s.config.LengthOfOtpCode)
|
||||
for i := 0; i < s.config.LengthOfOtpCode; i++ {
|
||||
result[i] = s.config.OtpChars[rand.Intn(len(s.config.OtpChars))]
|
||||
}
|
||||
return string(result)
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
package benefactorservice
|
||||
|
||||
import (
|
||||
"context"
|
||||
"git.gocasts.ir/ebhomengo/niki/entity"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
LengthOfOtpCode int `koanf:"length_of_otp_code"`
|
||||
OtpChars string `koanf:"otp_chars"`
|
||||
OtpExpireTime time.Duration `koanf:"otp_expire_time"`
|
||||
}
|
||||
|
||||
type Repository interface {
|
||||
IsExistBenefactorByPhoneNumber(ctx context.Context, phoneNumber string) (bool, entity.Benefactor, error)
|
||||
CreateBenefactor(ctx context.Context, benefactor entity.Benefactor) (entity.Benefactor, error)
|
||||
}
|
||||
|
||||
type AuthGenerator interface {
|
||||
CreateAccessToken(benefactor entity.Benefactor) (string, error)
|
||||
CreateRefreshToken(benefactor entity.Benefactor) (string, error)
|
||||
}
|
||||
|
||||
type RedisOtp interface {
|
||||
IsExistPhoneNumber(ctx context.Context, phoneNumber string) (bool, error)
|
||||
SaveCodeWithPhoneNumber(ctx context.Context, phoneNumber string, code string, expireTime time.Duration) error
|
||||
GetCodeByPhoneNumber(ctx context.Context, phoneNumber string) (string, error)
|
||||
}
|
||||
|
||||
type SmsProviderClient interface {
|
||||
SendSms(phoneNumber string, code string) error
|
||||
}
|
||||
|
||||
type Service struct {
|
||||
config Config
|
||||
redisOtp RedisOtp
|
||||
smsProviderClient SmsProviderClient
|
||||
auth AuthGenerator
|
||||
repo Repository
|
||||
}
|
||||
|
||||
func New(cfg Config, redisOtp RedisOtp, smsProviderClient SmsProviderClient,
|
||||
auth AuthGenerator, repo Repository) Service {
|
||||
|
||||
return Service{
|
||||
config: cfg,
|
||||
redisOtp: redisOtp,
|
||||
smsProviderClient: smsProviderClient,
|
||||
auth: auth,
|
||||
repo: repo,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
package benefactorvalidator
|
||||
|
||||
import (
|
||||
benefactoreparam "git.gocasts.ir/ebhomengo/niki/param/benefactor/benefactore"
|
||||
errmsg "git.gocasts.ir/ebhomengo/niki/pkg/err_msg"
|
||||
richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error"
|
||||
validation "github.com/go-ozzo/ozzo-validation/v4"
|
||||
|
||||
"regexp"
|
||||
)
|
||||
|
||||
func (v Validator) ValidateLoginRegisterRequest(req benefactoreparam.LoginOrRegisterRequest) (map[string]string, error) {
|
||||
const op = "benefactorvalidator.ValidateRegisterRequest"
|
||||
|
||||
if err := validation.ValidateStruct(&req,
|
||||
// TODO - add length of code config from benefactor config
|
||||
//validation.Field(&req.VerificationCode,
|
||||
// validation.Required,
|
||||
// validation.Length(3, 50)),
|
||||
|
||||
validation.Field(&req.PhoneNumber,
|
||||
validation.Required,
|
||||
validation.Match(regexp.MustCompile(phoneNumberRegex)).Error(errmsg.ErrorMsgPhoneNumberIsNotValid))); err != nil {
|
||||
fieldErrors := make(map[string]string)
|
||||
|
||||
errV, ok := err.(validation.Errors)
|
||||
if ok {
|
||||
for key, value := range errV {
|
||||
if value != nil {
|
||||
fieldErrors[key] = value.Error()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return fieldErrors, richerror.New(op).WithMessage(errmsg.ErrorMsgInvalidInput).
|
||||
WithKind(richerror.KindInvalid).
|
||||
WithMeta(map[string]interface{}{"req": req}).WithErr(err)
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
package benefactorvalidator
|
||||
|
||||
import (
|
||||
benefactoreparam "git.gocasts.ir/ebhomengo/niki/param/benefactor/benefactore"
|
||||
errmsg "git.gocasts.ir/ebhomengo/niki/pkg/err_msg"
|
||||
richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error"
|
||||
validation "github.com/go-ozzo/ozzo-validation/v4"
|
||||
|
||||
"regexp"
|
||||
)
|
||||
|
||||
func (v Validator) ValidateSendOtpRequest(req benefactoreparam.SendOtpRequest) (map[string]string, error) {
|
||||
const op = "benefactorvalidator.ValidateSendOtpRequest"
|
||||
|
||||
if err := validation.ValidateStruct(&req,
|
||||
validation.Field(&req.PhoneNumber,
|
||||
validation.Required,
|
||||
validation.Match(regexp.MustCompile(phoneNumberRegex)).Error(errmsg.ErrorMsgPhoneNumberIsNotValid))); err != nil {
|
||||
fieldErrors := make(map[string]string)
|
||||
|
||||
errV, ok := err.(validation.Errors)
|
||||
if ok {
|
||||
for key, value := range errV {
|
||||
if value != nil {
|
||||
fieldErrors[key] = value.Error()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return fieldErrors, richerror.New(op).WithMessage(errmsg.ErrorMsgInvalidInput).
|
||||
WithKind(richerror.KindInvalid).
|
||||
WithMeta(map[string]interface{}{"req": req}).WithErr(err)
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
package benefactorvalidator
|
||||
|
||||
const (
|
||||
phoneNumberRegex = "^09[0-9]{9}$"
|
||||
)
|
||||
|
||||
type Validator struct {
|
||||
}
|
||||
|
||||
func New() Validator {
|
||||
return Validator{}
|
||||
}
|
Loading…
Reference in New Issue