forked from ebhomengo/niki
				
			feat(service): add admin login and register
This commit is contained in:
		
							parent
							
								
									085989538d
								
							
						
					
					
						commit
						6e0d616036
					
				| 
						 | 
				
			
			@ -32,6 +32,8 @@ kavenegar_sms_provider:
 | 
			
		|||
  otp_template_new_user: ebhomeverify
 | 
			
		||||
  otp_template_registered_user: ebhomeverify
 | 
			
		||||
 | 
			
		||||
admin_auth:
 | 
			
		||||
  sign_key: admin-jwt_secret_test_nik
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,7 +4,8 @@ import (
 | 
			
		|||
	"git.gocasts.ir/ebhomengo/niki/adapter/redis"
 | 
			
		||||
	smsprovider "git.gocasts.ir/ebhomengo/niki/adapter/sms_provider/kavenegar"
 | 
			
		||||
	"git.gocasts.ir/ebhomengo/niki/repository/mysql"
 | 
			
		||||
	authservice "git.gocasts.ir/ebhomengo/niki/service/auth/benefactor"
 | 
			
		||||
	adminauthservice "git.gocasts.ir/ebhomengo/niki/service/auth/admin"
 | 
			
		||||
	benefactorauthservice "git.gocasts.ir/ebhomengo/niki/service/auth/benefactor"
 | 
			
		||||
	benefactorservice "git.gocasts.ir/ebhomengo/niki/service/benefactor/benefactor"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -13,10 +14,11 @@ type HTTPServer struct {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
type Config struct {
 | 
			
		||||
	HTTPServer           HTTPServer               `koanf:"http_server"`
 | 
			
		||||
	Mysql                mysql.Config             `koanf:"mysql"`
 | 
			
		||||
	Auth                 authservice.Config       `koanf:"auth"`
 | 
			
		||||
	Redis                redis.Config             `koanf:"redis"`
 | 
			
		||||
	KavenegarSmsProvider smsprovider.Config       `koanf:"kavenegar_sms_provider"`
 | 
			
		||||
	BenefactorSvc        benefactorservice.Config `koanf:"benefactor_service"`
 | 
			
		||||
	HTTPServer           HTTPServer                   `koanf:"http_server"`
 | 
			
		||||
	Mysql                mysql.Config                 `koanf:"mysql"`
 | 
			
		||||
	Auth                 benefactorauthservice.Config `koanf:"auth"`
 | 
			
		||||
	AdminAuth            adminauthservice.Config      `koanf:"admin_auth"`
 | 
			
		||||
	Redis                redis.Config                 `koanf:"redis"`
 | 
			
		||||
	KavenegarSmsProvider smsprovider.Config           `koanf:"kavenegar_sms_provider"`
 | 
			
		||||
	BenefactorSvc        benefactorservice.Config     `koanf:"benefactor_service"`
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -12,4 +12,5 @@ const (
 | 
			
		|||
	AccessTokenExpireDuration  = time.Hour * 24
 | 
			
		||||
	RefreshTokenExpireDuration = time.Hour * 24 * 7
 | 
			
		||||
	AuthMiddlewareContextKey   = "claims"
 | 
			
		||||
	BcryptCost                 = 3
 | 
			
		||||
)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,25 @@
 | 
			
		|||
package adminhandler
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	adminservice "git.gocasts.ir/ebhomengo/niki/service/admin/admin"
 | 
			
		||||
	adminauthservice "git.gocasts.ir/ebhomengo/niki/service/auth/admin"
 | 
			
		||||
	adminvalidator "git.gocasts.ir/ebhomengo/niki/validator/admin/admin"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Handler struct {
 | 
			
		||||
	authConfig adminauthservice.Config
 | 
			
		||||
	authSvc    adminauthservice.Service
 | 
			
		||||
	adminSvc   adminservice.Service
 | 
			
		||||
	adminVld   adminvalidator.Validator
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func New(authConfig adminauthservice.Config, authSvc adminauthservice.Service,
 | 
			
		||||
	adminSvc adminservice.Service, adminVld adminvalidator.Validator,
 | 
			
		||||
) Handler {
 | 
			
		||||
	return Handler{
 | 
			
		||||
		authConfig: authConfig,
 | 
			
		||||
		authSvc:    authSvc,
 | 
			
		||||
		adminSvc:   adminSvc,
 | 
			
		||||
		adminVld:   adminVld,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,33 @@
 | 
			
		|||
package adminhandler
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	adminserviceparam "git.gocasts.ir/ebhomengo/niki/param/admin/admin"
 | 
			
		||||
	httpmsg "git.gocasts.ir/ebhomengo/niki/pkg/http_msg"
 | 
			
		||||
	"github.com/labstack/echo/v4"
 | 
			
		||||
	"net/http"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func (h Handler) LoginByPhoneNumber(c echo.Context) error {
 | 
			
		||||
	var req adminserviceparam.LoginWithPhoneNumberRequest
 | 
			
		||||
 | 
			
		||||
	if bErr := c.Bind(&req); bErr != nil {
 | 
			
		||||
		return echo.NewHTTPError(http.StatusBadRequest)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if fieldErrors, err := h.adminVld.ValidateLoginWithPhoneNumberRequest(req); err != nil {
 | 
			
		||||
		msg, code := httpmsg.Error(err)
 | 
			
		||||
 | 
			
		||||
		return c.JSON(code, echo.Map{
 | 
			
		||||
			"message": msg,
 | 
			
		||||
			"errors":  fieldErrors,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
	resp, sErr := h.adminSvc.LoginWithPhoneNumber(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,33 @@
 | 
			
		|||
package adminhandler
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	adminserviceparam "git.gocasts.ir/ebhomengo/niki/param/admin/admin"
 | 
			
		||||
	httpmsg "git.gocasts.ir/ebhomengo/niki/pkg/http_msg"
 | 
			
		||||
	"github.com/labstack/echo/v4"
 | 
			
		||||
	"net/http"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func (h Handler) Register(c echo.Context) error {
 | 
			
		||||
	var req adminserviceparam.RegisterRequest
 | 
			
		||||
 | 
			
		||||
	if bErr := c.Bind(&req); bErr != nil {
 | 
			
		||||
		return echo.NewHTTPError(http.StatusBadRequest)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if fieldErrors, err := h.adminVld.ValidateRegisterRequest(req); err != nil {
 | 
			
		||||
		msg, code := httpmsg.Error(err)
 | 
			
		||||
 | 
			
		||||
		return c.JSON(code, echo.Map{
 | 
			
		||||
			"message": msg,
 | 
			
		||||
			"errors":  fieldErrors,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
	resp, sErr := h.adminSvc.Register(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,14 @@
 | 
			
		|||
package adminhandler
 | 
			
		||||
 | 
			
		||||
import "github.com/labstack/echo/v4"
 | 
			
		||||
 | 
			
		||||
func (h Handler) SetRoutes(e *echo.Echo) {
 | 
			
		||||
	r := e.Group("/admins")
 | 
			
		||||
 | 
			
		||||
	//nolint:gocritic
 | 
			
		||||
	//r.POST("/", h.Add).Name = "admin-addkindboxreq"
 | 
			
		||||
	r.POST("/register", h.Register)
 | 
			
		||||
	r.POST("/login-by-phone", h.LoginByPhoneNumber)
 | 
			
		||||
	//nolint:gocritic
 | 
			
		||||
	//r.PATCH("/:id", h.Update).Name = "admin-updatekindboxreq"
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -2,6 +2,10 @@ package httpserver
 | 
			
		|||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	adminhandler "git.gocasts.ir/ebhomengo/niki/delivery/http_server/admin/admin"
 | 
			
		||||
	adminservice "git.gocasts.ir/ebhomengo/niki/service/admin/admin"
 | 
			
		||||
	adminauthservice "git.gocasts.ir/ebhomengo/niki/service/auth/admin"
 | 
			
		||||
	adminvalidator "git.gocasts.ir/ebhomengo/niki/validator/admin/admin"
 | 
			
		||||
 | 
			
		||||
	config "git.gocasts.ir/ebhomengo/niki/config"
 | 
			
		||||
	benefactorbasehandler "git.gocasts.ir/ebhomengo/niki/delivery/http_server/benefactor/base"
 | 
			
		||||
| 
						 | 
				
			
			@ -23,6 +27,7 @@ type Server struct {
 | 
			
		|||
	benefactorHandler           benefactorhandler.Handler
 | 
			
		||||
	benefactorKindBoxReqHandler benefactorkindboxreqhandler.Handler
 | 
			
		||||
	benefactorBaseHandler       benefactorbasehandler.Handler
 | 
			
		||||
	adminHandler                adminhandler.Handler
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func New(
 | 
			
		||||
| 
						 | 
				
			
			@ -33,6 +38,9 @@ func New(
 | 
			
		|||
	benefactorKindBoxReqSvc benefactorkindboxreqservice.Service,
 | 
			
		||||
	benefactorKindBoxReqVld benefactorkindboxreqvalidator.Validator,
 | 
			
		||||
	benefactorAddressSvc benefactoraddressservice.Service,
 | 
			
		||||
	adminSvc adminservice.Service,
 | 
			
		||||
	adminVld adminvalidator.Validator,
 | 
			
		||||
	adminAuthSvc adminauthservice.Service,
 | 
			
		||||
) Server {
 | 
			
		||||
	return Server{
 | 
			
		||||
		Router:                      echo.New(),
 | 
			
		||||
| 
						 | 
				
			
			@ -40,6 +48,7 @@ func New(
 | 
			
		|||
		benefactorHandler:           benefactorhandler.New(cfg.Auth, authSvc, benefactorSvc, benefactorVld, benefactorAddressSvc),
 | 
			
		||||
		benefactorKindBoxReqHandler: benefactorkindboxreqhandler.New(cfg.Auth, authSvc, benefactorKindBoxReqSvc, benefactorKindBoxReqVld),
 | 
			
		||||
		benefactorBaseHandler:       benefactorbasehandler.New(benefactorAddressSvc),
 | 
			
		||||
		adminHandler:                adminhandler.New(cfg.AdminAuth, adminAuthSvc, adminSvc, adminVld),
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -53,6 +62,7 @@ func (s Server) Serve() {
 | 
			
		|||
	s.benefactorHandler.SetRoutes(s.Router)
 | 
			
		||||
	s.benefactorKindBoxReqHandler.SetRoutes(s.Router)
 | 
			
		||||
	s.benefactorBaseHandler.SetRoutes(s.Router)
 | 
			
		||||
	s.adminHandler.SetRoutes(s.Router)
 | 
			
		||||
 | 
			
		||||
	// Start server
 | 
			
		||||
	address := fmt.Sprintf(":%d", s.config.HTTPServer.Port)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,19 +1,23 @@
 | 
			
		|||
package entity
 | 
			
		||||
 | 
			
		||||
import "time"
 | 
			
		||||
 | 
			
		||||
type Admin struct {
 | 
			
		||||
	ID              uint
 | 
			
		||||
	FirstName       string
 | 
			
		||||
	LastName        string
 | 
			
		||||
	PhoneNumber     string
 | 
			
		||||
	Role            AdminRole
 | 
			
		||||
	Address         string
 | 
			
		||||
	Description     string
 | 
			
		||||
	Email           string
 | 
			
		||||
	City            string
 | 
			
		||||
	Gender          Gender
 | 
			
		||||
	Status          AdminStatus
 | 
			
		||||
	Birthday        time.Time
 | 
			
		||||
	StatusChangedAt time.Time
 | 
			
		||||
	ID          uint
 | 
			
		||||
	FirstName   string
 | 
			
		||||
	LastName    string
 | 
			
		||||
	password    string
 | 
			
		||||
	PhoneNumber string
 | 
			
		||||
	Role        AdminRole
 | 
			
		||||
	Description string
 | 
			
		||||
	Email       string
 | 
			
		||||
	Gender      Gender
 | 
			
		||||
	Status      AdminStatus
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *Admin) GetPassword() string {
 | 
			
		||||
 | 
			
		||||
	return a.password
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *Admin) SetPassword(password string) {
 | 
			
		||||
	a.password = password
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -16,6 +16,10 @@ func (s AdminRole) String() string {
 | 
			
		|||
	return AdminRoleStrings[s]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s AdminRole) IsValid() bool {
 | 
			
		||||
	return s > 0 && int(s) <= len(AdminRoleStrings)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// AllAdminRole returns a slice containing all string values of AdminRole.
 | 
			
		||||
func AllAdminRole() []string {
 | 
			
		||||
	roleStrings := make([]string, len(AdminRoleStrings))
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -16,6 +16,10 @@ func (s AdminStatus) String() string {
 | 
			
		|||
	return AdminStatusStrings[s]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s AdminStatus) IsValid() bool {
 | 
			
		||||
	return s > 0 && int(s) <= len(AdminStatusStrings)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// AllAdminStatus returns a slice containing all string values of AdminStatus.
 | 
			
		||||
func AllAdminStatus() []string {
 | 
			
		||||
	statusStrings := make([]string, len(AdminStatusStrings))
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -26,6 +26,10 @@ func AllGender() []string {
 | 
			
		|||
	return statusStrings
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s Gender) IsValid() bool {
 | 
			
		||||
	return s > 0 && int(s) <= len(GenderStrings)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// MapToGender converts a string to the corresponding Gender value.
 | 
			
		||||
func MapToGender(statusStr string) Gender {
 | 
			
		||||
	for status, str := range GenderStrings {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										17
									
								
								main.go
								
								
								
								
							
							
						
						
									
										17
									
								
								main.go
								
								
								
								
							| 
						 | 
				
			
			@ -9,13 +9,17 @@ import (
 | 
			
		|||
	"git.gocasts.ir/ebhomengo/niki/repository/migrator"
 | 
			
		||||
	"git.gocasts.ir/ebhomengo/niki/repository/mysql"
 | 
			
		||||
	mysqladdress "git.gocasts.ir/ebhomengo/niki/repository/mysql/address"
 | 
			
		||||
	mysqladmin "git.gocasts.ir/ebhomengo/niki/repository/mysql/admin"
 | 
			
		||||
	mysqlbenefactor "git.gocasts.ir/ebhomengo/niki/repository/mysql/benefactor"
 | 
			
		||||
	mysqlkindboxreq "git.gocasts.ir/ebhomengo/niki/repository/mysql/kind_box_req"
 | 
			
		||||
	redisotp "git.gocasts.ir/ebhomengo/niki/repository/redis/redis_otp"
 | 
			
		||||
	adminservice "git.gocasts.ir/ebhomengo/niki/service/admin/admin"
 | 
			
		||||
	adminauthservice "git.gocasts.ir/ebhomengo/niki/service/auth/admin"
 | 
			
		||||
	authservice "git.gocasts.ir/ebhomengo/niki/service/auth/benefactor"
 | 
			
		||||
	benefactoraddressservice "git.gocasts.ir/ebhomengo/niki/service/benefactor/address"
 | 
			
		||||
	benefactorservice "git.gocasts.ir/ebhomengo/niki/service/benefactor/benefactor"
 | 
			
		||||
	benefactorkindboxreqservice "git.gocasts.ir/ebhomengo/niki/service/benefactor/kind_box_req"
 | 
			
		||||
	adminvalidator "git.gocasts.ir/ebhomengo/niki/validator/admin/admin"
 | 
			
		||||
	benefactorvalidator "git.gocasts.ir/ebhomengo/niki/validator/benefactor/benefactor"
 | 
			
		||||
	benefactorkindboxreqvalidator "git.gocasts.ir/ebhomengo/niki/validator/benefactor/kind_box_req"
 | 
			
		||||
	_ "github.com/go-sql-driver/mysql"
 | 
			
		||||
| 
						 | 
				
			
			@ -27,8 +31,10 @@ func main() {
 | 
			
		|||
	mgr := migrator.New(cfg.Mysql)
 | 
			
		||||
	mgr.Up()
 | 
			
		||||
 | 
			
		||||
	authSvc, benefactorSvc, benefactorVld, benefactorKindBoxReqSvc, benefactorKindBoxReqVld, benefactorAddressSvc := setupServices(cfg)
 | 
			
		||||
	server := httpserver.New(cfg, benefactorSvc, benefactorVld, authSvc, benefactorKindBoxReqSvc, benefactorKindBoxReqVld, benefactorAddressSvc)
 | 
			
		||||
	authSvc, benefactorSvc, benefactorVld, benefactorKindBoxReqSvc, benefactorKindBoxReqVld, benefactorAddressSvc,
 | 
			
		||||
		adminSvc, adminVld, adminAuthSvc := setupServices(cfg)
 | 
			
		||||
	server := httpserver.New(cfg, benefactorSvc, benefactorVld, authSvc, benefactorKindBoxReqSvc, benefactorKindBoxReqVld,
 | 
			
		||||
		benefactorAddressSvc, adminSvc, adminVld, adminAuthSvc)
 | 
			
		||||
	server.Serve()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -36,7 +42,7 @@ func main() {
 | 
			
		|||
func setupServices(cfg config.Config) (
 | 
			
		||||
	authSvc authservice.Service, benefactorSvc benefactorservice.Service, benefactorVld benefactorvalidator.Validator,
 | 
			
		||||
	benefactorKindBoxReqSvc benefactorkindboxreqservice.Service, benefactorKindBoxReqVld benefactorkindboxreqvalidator.Validator,
 | 
			
		||||
	benefactorAddressSvc benefactoraddressservice.Service,
 | 
			
		||||
	benefactorAddressSvc benefactoraddressservice.Service, adminSvc adminservice.Service, adminVld adminvalidator.Validator, adminAuthSvc adminauthservice.Service,
 | 
			
		||||
) {
 | 
			
		||||
	authSvc = authservice.New(cfg.Auth)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -58,5 +64,10 @@ func setupServices(cfg config.Config) (
 | 
			
		|||
	benefactorKindBoxReqSvc = benefactorkindboxreqservice.New(benefactorKindBoxReqMysql)
 | 
			
		||||
	benefactorKindBoxReqVld = benefactorkindboxreqvalidator.New(benefactorKindBoxReqMysql, benefactorSvc, benefactorAddressSvc)
 | 
			
		||||
 | 
			
		||||
	adminAuthSvc = adminauthservice.New(cfg.AdminAuth)
 | 
			
		||||
	adminMysql := mysqladmin.New(MysqlRepo)
 | 
			
		||||
	adminVld = adminvalidator.New(adminMysql)
 | 
			
		||||
	adminSvc = adminservice.New(adminMysql, adminAuthSvc)
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,13 @@
 | 
			
		|||
package adminserviceparam
 | 
			
		||||
 | 
			
		||||
import "git.gocasts.ir/ebhomengo/niki/entity"
 | 
			
		||||
 | 
			
		||||
type LoginWithPhoneNumberRequest struct {
 | 
			
		||||
	PhoneNumber string `json:"phone_number"`
 | 
			
		||||
	Password    string `json:"password"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type LoginWithPhoneNumberResponse struct {
 | 
			
		||||
	Admin  entity.Admin `json:"admin"`
 | 
			
		||||
	Tokens Tokens       `json:"tokens"`
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,19 @@
 | 
			
		|||
package adminserviceparam
 | 
			
		||||
 | 
			
		||||
import "git.gocasts.ir/ebhomengo/niki/entity"
 | 
			
		||||
 | 
			
		||||
type RegisterRequest struct {
 | 
			
		||||
	FirstName   *string             `json:"first_name"`
 | 
			
		||||
	LastName    *string             `json:"last_name"`
 | 
			
		||||
	Password    *string             `json:"password"`
 | 
			
		||||
	PhoneNumber *string             `json:"phone_number"`
 | 
			
		||||
	Role        *entity.AdminRole   `json:"role"`
 | 
			
		||||
	Description *string             `json:"description"`
 | 
			
		||||
	Email       *string             `json:"email"`
 | 
			
		||||
	Gender      *entity.Gender      `json:"gender"`
 | 
			
		||||
	Status      *entity.AdminStatus `json:"status"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type RegisterResponse struct {
 | 
			
		||||
	Admin entity.Admin
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,6 @@
 | 
			
		|||
package adminserviceparam
 | 
			
		||||
 | 
			
		||||
type Tokens struct {
 | 
			
		||||
	AccessToken  string `json:"access_token"`
 | 
			
		||||
	RefreshToken string `json:"refresh_token"`
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,15 +1,17 @@
 | 
			
		|||
package errmsg
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	ErrorMsgNotFound               = "record not found"
 | 
			
		||||
	ErrorMsgSomethingWentWrong     = "something went wrong"
 | 
			
		||||
	ErrorMsgInvalidInput           = "invalid input"
 | 
			
		||||
	ErrorMsgInvalidStatus          = "invalid status"
 | 
			
		||||
	ErrorMsgPhoneNumberIsNotUnique = "phone number is not unique"
 | 
			
		||||
	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"
 | 
			
		||||
	ErrorMsgNotFound                     = "record not found"
 | 
			
		||||
	ErrorMsgSomethingWentWrong           = "something went wrong"
 | 
			
		||||
	ErrorMsgInvalidInput                 = "invalid input"
 | 
			
		||||
	ErrorMsgInvalidStatus                = "invalid status"
 | 
			
		||||
	ErrorMsgPhoneNumberIsNotUnique       = "phone number is not unique"
 | 
			
		||||
	ErrorMsgEmailIsNotUnique             = "email is not unique"
 | 
			
		||||
	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"
 | 
			
		||||
	ErrorMsgPhoneNumberOrPassIsIncorrect = "phone number or password is incorrect"
 | 
			
		||||
)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,11 @@
 | 
			
		|||
package mysqladmin
 | 
			
		||||
 | 
			
		||||
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,28 @@
 | 
			
		|||
package mysqladmin
 | 
			
		||||
 | 
			
		||||
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) AddAdmin(ctx context.Context, admin entity.Admin) (entity.Admin, error) {
 | 
			
		||||
	const op = "mysqladmin.AddAdmin"
 | 
			
		||||
 | 
			
		||||
	res, err := d.conn.Conn().ExecContext(ctx, `insert into admins(first_name,last_name,password,phone_number,
 | 
			
		||||
                   role,description,email,gender,status) values (?,?,?,?,?,?,?,?,?)`,
 | 
			
		||||
		admin.FirstName, admin.LastName, admin.GetPassword(), admin.PhoneNumber, admin.Role.String(), admin.Description, admin.Email,
 | 
			
		||||
		admin.Gender.String(), admin.Status.String())
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return entity.Admin{}, richerror.New(op).WithErr(err).
 | 
			
		||||
			WithMessage(errmsg.ErrorMsgNotFound).WithKind(richerror.KindUnexpected)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	//nolint
 | 
			
		||||
	// err is always nil
 | 
			
		||||
	id, _ := res.LastInsertId()
 | 
			
		||||
	admin.ID = uint(id)
 | 
			
		||||
 | 
			
		||||
	return admin, nil
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,53 @@
 | 
			
		|||
package mysqladmin
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"database/sql"
 | 
			
		||||
	"errors"
 | 
			
		||||
	errmsg "git.gocasts.ir/ebhomengo/niki/pkg/err_msg"
 | 
			
		||||
	richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func (d DB) AdminExistByPhoneNumber(ctx context.Context, phoneNumber string) (bool, error) {
 | 
			
		||||
	const op = "mysqlbenefactor.IsExistBenefactorByID"
 | 
			
		||||
 | 
			
		||||
	row := d.conn.Conn().QueryRowContext(ctx, `select * from admins where phone_number = ?`, phoneNumber)
 | 
			
		||||
 | 
			
		||||
	_, err := scanAdmin(row)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		sErr := sql.ErrNoRows
 | 
			
		||||
		//TODO-errorsas: second argument to errors.As should not be *error
 | 
			
		||||
		//nolint
 | 
			
		||||
		if errors.As(err, &sErr) {
 | 
			
		||||
			return false, nil
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// TODO - log unexpected error for better observability
 | 
			
		||||
		return false, richerror.New(op).WithErr(err).
 | 
			
		||||
			WithMessage(errmsg.ErrorMsgCantScanQueryResult).WithKind(richerror.KindUnexpected)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return true, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d DB) AdminExistByEmail(ctx context.Context, email string) (bool, error) {
 | 
			
		||||
	const op = "mysqlbenefactor.IsExistBenefactorByID"
 | 
			
		||||
 | 
			
		||||
	row := d.conn.Conn().QueryRowContext(ctx, `select * from admins where email = ?`, email)
 | 
			
		||||
 | 
			
		||||
	_, err := scanAdmin(row)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		sErr := sql.ErrNoRows
 | 
			
		||||
		//TODO-errorsas: second argument to errors.As should not be *error
 | 
			
		||||
		//nolint
 | 
			
		||||
		if errors.As(err, &sErr) {
 | 
			
		||||
			return false, nil
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// TODO - log unexpected error for better observability
 | 
			
		||||
		return false, richerror.New(op).WithErr(err).
 | 
			
		||||
			WithMessage(errmsg.ErrorMsgCantScanQueryResult).WithKind(richerror.KindUnexpected)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return true, nil
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,84 @@
 | 
			
		|||
package mysqladmin
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"database/sql"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"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) GetAdminByPhoneNumber(ctx context.Context, phoneNumber string) (entity.Admin, error) {
 | 
			
		||||
	const op = "mysqlbenefactor.IsExistBenefactorByID"
 | 
			
		||||
 | 
			
		||||
	row := d.conn.Conn().QueryRowContext(ctx, `select * from admins where phone_number = ?`, phoneNumber)
 | 
			
		||||
 | 
			
		||||
	admin, err := scanAdmin(row)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		sErr := sql.ErrNoRows
 | 
			
		||||
		//TODO-errorsas: second argument to errors.As should not be *error
 | 
			
		||||
		//nolint
 | 
			
		||||
		if errors.As(err, &sErr) {
 | 
			
		||||
			return entity.Admin{}, richerror.New(op).WithErr(sErr).
 | 
			
		||||
				WithMessage(errmsg.ErrorMsgNotFound).WithKind(richerror.KindNotFound)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// TODO - log unexpected error for better observability
 | 
			
		||||
		return entity.Admin{}, richerror.New(op).WithErr(err).
 | 
			
		||||
			WithMessage(errmsg.ErrorMsgCantScanQueryResult).WithKind(richerror.KindUnexpected)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return admin, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func scanAdmin(scanner mysql.Scanner) (entity.Admin, error) {
 | 
			
		||||
	var createdAt time.Time
 | 
			
		||||
	var admin entity.Admin
 | 
			
		||||
	var roleStr, statusStr, password string
 | 
			
		||||
	// TODO - use db model and mapper between entity and db model OR use this approach
 | 
			
		||||
 | 
			
		||||
	var adminNullableFields nullableFields
 | 
			
		||||
 | 
			
		||||
	err := scanner.Scan(&admin.ID, &adminNullableFields.firstName,
 | 
			
		||||
		&adminNullableFields.lastName, &password, &admin.PhoneNumber,
 | 
			
		||||
		&roleStr, &adminNullableFields.description,
 | 
			
		||||
		&adminNullableFields.email, &adminNullableFields.genderStr,
 | 
			
		||||
		&statusStr, &createdAt)
 | 
			
		||||
 | 
			
		||||
	admin.Role = entity.MapToAdminRole(roleStr)
 | 
			
		||||
	admin.Status = entity.MapToAdminStatus(statusStr)
 | 
			
		||||
	admin.SetPassword(password)
 | 
			
		||||
	mapNotNullToAdmin(adminNullableFields, &admin)
 | 
			
		||||
 | 
			
		||||
	return admin, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type nullableFields struct {
 | 
			
		||||
	firstName   sql.NullString
 | 
			
		||||
	lastName    sql.NullString
 | 
			
		||||
	description sql.NullString
 | 
			
		||||
	email       sql.NullString
 | 
			
		||||
	genderStr   sql.NullString
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TODO - find the other solution.
 | 
			
		||||
func mapNotNullToAdmin(data nullableFields, admin *entity.Admin) {
 | 
			
		||||
	if data.firstName.Valid {
 | 
			
		||||
		admin.FirstName = data.firstName.String
 | 
			
		||||
	}
 | 
			
		||||
	if data.lastName.Valid {
 | 
			
		||||
		admin.LastName = data.lastName.String
 | 
			
		||||
	}
 | 
			
		||||
	if data.description.Valid {
 | 
			
		||||
		admin.Description = data.description.String
 | 
			
		||||
	}
 | 
			
		||||
	if data.email.Valid {
 | 
			
		||||
		admin.Email = data.email.String
 | 
			
		||||
	}
 | 
			
		||||
	if data.genderStr.Valid {
 | 
			
		||||
		admin.Gender = entity.MapToGender(data.genderStr.String)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,18 @@
 | 
			
		|||
-- +migrate Up
 | 
			
		||||
CREATE TABLE `admins`
 | 
			
		||||
(
 | 
			
		||||
    `id`            INT PRIMARY KEY AUTO_INCREMENT,
 | 
			
		||||
    `first_name`    VARCHAR(191),
 | 
			
		||||
    `last_name`     VARCHAR(191),
 | 
			
		||||
    `password`      TEXT NOT NULL,
 | 
			
		||||
    `phone_number`  VARCHAR(191) NOT NULL UNIQUE,
 | 
			
		||||
    `role`          ENUM('super-admin','admin') NOT NULL,
 | 
			
		||||
    `description`   TEXT,
 | 
			
		||||
    `email`         VARCHAR(191) NOT NULL UNIQUE,
 | 
			
		||||
    `gender`        VARCHAR(191),
 | 
			
		||||
    `status`        VARCHAR(191),
 | 
			
		||||
    `created_at`    TIMESTAMP DEFAULT CURRENT_TIMESTAMP
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
-- +migrate Down
 | 
			
		||||
DROP TABLE `admins`;
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,9 @@
 | 
			
		|||
-- +migrate Up
 | 
			
		||||
-- what can we do for password?
 | 
			
		||||
INSERT INTO `admins` (`id`, `phone_number`, `email`,`password`,`role`,`status`)
 | 
			
		||||
VALUES (1, '09122702856', 'keshvari@gmail.com','Abc123456','super-admin','active');
 | 
			
		||||
 | 
			
		||||
-- +migrate Down
 | 
			
		||||
DELETE
 | 
			
		||||
FROM `admins`
 | 
			
		||||
WHERE id '1' ;
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,40 @@
 | 
			
		|||
package adminservice
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	adminserviceparam "git.gocasts.ir/ebhomengo/niki/param/admin/admin"
 | 
			
		||||
	errmsg "git.gocasts.ir/ebhomengo/niki/pkg/err_msg"
 | 
			
		||||
	richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func (s Service) LoginWithPhoneNumber(ctx context.Context, req adminserviceparam.LoginWithPhoneNumberRequest) (adminserviceparam.LoginWithPhoneNumberResponse, error) {
 | 
			
		||||
	const op = richerror.Op("adminservice.LoginWithPhoneNumber")
 | 
			
		||||
 | 
			
		||||
	admin, err := s.repo.GetAdminByPhoneNumber(ctx, req.PhoneNumber)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return adminserviceparam.LoginWithPhoneNumberResponse{}, richerror.New(op).WithErr(err).WithKind(richerror.KindUnexpected)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if cErr := CompareHash(admin.GetPassword(), req.Password); cErr != nil {
 | 
			
		||||
		return adminserviceparam.LoginWithPhoneNumberResponse{}, richerror.New(op).WithErr(cErr).WithMessage(errmsg.ErrorMsgPhoneNumberOrPassIsIncorrect).WithKind(richerror.KindForbidden)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	accessToken, aErr := s.auth.CreateAccessToken(admin)
 | 
			
		||||
	if aErr != nil {
 | 
			
		||||
		return adminserviceparam.LoginWithPhoneNumberResponse{}, richerror.New(op).WithErr(aErr).WithKind(richerror.KindUnexpected)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	refreshToken, rErr := s.auth.CreateRefreshToken(admin)
 | 
			
		||||
	if rErr != nil {
 | 
			
		||||
		return adminserviceparam.LoginWithPhoneNumberResponse{}, richerror.New(op).WithErr(rErr).WithKind(richerror.KindUnexpected)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return adminserviceparam.LoginWithPhoneNumberResponse{
 | 
			
		||||
		Admin: admin,
 | 
			
		||||
		Tokens: adminserviceparam.Tokens{
 | 
			
		||||
			AccessToken:  accessToken,
 | 
			
		||||
			RefreshToken: refreshToken,
 | 
			
		||||
		},
 | 
			
		||||
	}, nil
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,53 @@
 | 
			
		|||
package adminservice
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"git.gocasts.ir/ebhomengo/niki/entity"
 | 
			
		||||
	adminserviceparam "git.gocasts.ir/ebhomengo/niki/param/admin/admin"
 | 
			
		||||
	richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func (s Service) Register(ctx context.Context, req adminserviceparam.RegisterRequest) (adminserviceparam.RegisterResponse, error) {
 | 
			
		||||
	const op = richerror.Op("adminservice.Register")
 | 
			
		||||
 | 
			
		||||
	var newAdmin entity.Admin
 | 
			
		||||
	if req.FirstName != nil {
 | 
			
		||||
		newAdmin.FirstName = *req.FirstName
 | 
			
		||||
	}
 | 
			
		||||
	if req.LastName != nil {
 | 
			
		||||
		newAdmin.LastName = *req.LastName
 | 
			
		||||
	}
 | 
			
		||||
	if req.PhoneNumber != nil {
 | 
			
		||||
		newAdmin.PhoneNumber = *req.PhoneNumber
 | 
			
		||||
	}
 | 
			
		||||
	if req.Role != nil {
 | 
			
		||||
		newAdmin.Role = *req.Role
 | 
			
		||||
	}
 | 
			
		||||
	if req.Description != nil {
 | 
			
		||||
		newAdmin.LastName = *req.Description
 | 
			
		||||
	}
 | 
			
		||||
	if req.Email != nil {
 | 
			
		||||
		newAdmin.Email = *req.Email
 | 
			
		||||
	}
 | 
			
		||||
	if req.Gender != nil {
 | 
			
		||||
		newAdmin.Gender = *req.Gender
 | 
			
		||||
	}
 | 
			
		||||
	if req.Description != nil {
 | 
			
		||||
		newAdmin.LastName = *req.Description
 | 
			
		||||
	}
 | 
			
		||||
	if req.Email != nil {
 | 
			
		||||
		newAdmin.Status = *req.Status
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if bErr := GenerateHash(req.Password); bErr != nil {
 | 
			
		||||
		return adminserviceparam.RegisterResponse{}, richerror.New(op).WithErr(bErr).WithKind(richerror.KindUnexpected)
 | 
			
		||||
	}
 | 
			
		||||
	newAdmin.SetPassword(*req.Password)
 | 
			
		||||
 | 
			
		||||
	admin, err := s.repo.AddAdmin(ctx, newAdmin)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return adminserviceparam.RegisterResponse{}, richerror.New(op).WithErr(err).WithKind(richerror.KindUnexpected)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return adminserviceparam.RegisterResponse{Admin: admin}, err
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,44 @@
 | 
			
		|||
package adminservice
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"git.gocasts.ir/ebhomengo/niki/config"
 | 
			
		||||
	"git.gocasts.ir/ebhomengo/niki/entity"
 | 
			
		||||
	"golang.org/x/crypto/bcrypt"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type AuthGenerator interface {
 | 
			
		||||
	CreateAccessToken(benefactor entity.Admin) (string, error)
 | 
			
		||||
	CreateRefreshToken(benefactor entity.Admin) (string, error)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Repository interface {
 | 
			
		||||
	AddAdmin(ctx context.Context, admin entity.Admin) (entity.Admin, error)
 | 
			
		||||
	GetAdminByPhoneNumber(ctx context.Context, phoneNumber string) (entity.Admin, error)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Service struct {
 | 
			
		||||
	repo Repository
 | 
			
		||||
	auth AuthGenerator
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func New(repo Repository, auth AuthGenerator) Service {
 | 
			
		||||
	return Service{
 | 
			
		||||
		repo: repo,
 | 
			
		||||
		auth: auth,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func GenerateHash(password *string) error {
 | 
			
		||||
	hashedPassword, bErr := bcrypt.GenerateFromPassword([]byte(*password), config.BcryptCost)
 | 
			
		||||
	if bErr != nil {
 | 
			
		||||
		return fmt.Errorf("bcrypt error: %w", bErr)
 | 
			
		||||
	}
 | 
			
		||||
	*password = string(hashedPassword)
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
func CompareHash(hashedPassword, password string) error {
 | 
			
		||||
	return bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password))
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,16 @@
 | 
			
		|||
package adminauthservice
 | 
			
		||||
 | 
			
		||||
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.AdminRole `json:"role"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c Claims) Valid() error {
 | 
			
		||||
	return c.RegisteredClaims.Valid()
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1 +0,0 @@
 | 
			
		|||
package admin
 | 
			
		||||
| 
						 | 
				
			
			@ -1,6 +1,9 @@
 | 
			
		|||
package admin
 | 
			
		||||
package adminauthservice
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"git.gocasts.ir/ebhomengo/niki/entity"
 | 
			
		||||
	"github.com/golang-jwt/jwt/v4"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -21,3 +24,54 @@ func New(cfg Config) Service {
 | 
			
		|||
		config: cfg,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s Service) CreateAccessToken(admin entity.Admin) (string, error) {
 | 
			
		||||
	return s.createToken(admin.ID, admin.Role, s.config.AccessSubject, s.config.AccessExpirationTime)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s Service) CreateRefreshToken(admin entity.Admin) (string, error) {
 | 
			
		||||
	return s.createToken(admin.ID, admin.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
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s Service) createToken(userID uint, role entity.AdminRole, 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
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -22,7 +22,7 @@ func (s Service) LoginOrRegister(ctx context.Context, req benefactoreparam.Login
 | 
			
		|||
 | 
			
		||||
	_, dErr := s.redisOtp.DeleteCodeByPhoneNumber(ctx, req.PhoneNumber)
 | 
			
		||||
	if dErr != nil {
 | 
			
		||||
		return benefactoreparam.LoginOrRegisterResponse{}, richerror.New(op).WithErr(gErr).WithKind(richerror.KindUnexpected)
 | 
			
		||||
		return benefactoreparam.LoginOrRegisterResponse{}, richerror.New(op).WithErr(dErr).WithKind(richerror.KindUnexpected)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	isExist, benefactor, rErr := s.repo.IsExistBenefactorByPhoneNumber(ctx, req.PhoneNumber)
 | 
			
		||||
| 
						 | 
				
			
			@ -36,19 +36,19 @@ func (s Service) LoginOrRegister(ctx context.Context, req benefactoreparam.Login
 | 
			
		|||
			Role:        entity.UserBenefactorRole,
 | 
			
		||||
		})
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return benefactoreparam.LoginOrRegisterResponse{}, richerror.New(op).WithErr(rErr).WithKind(richerror.KindUnexpected)
 | 
			
		||||
			return benefactoreparam.LoginOrRegisterResponse{}, richerror.New(op).WithErr(err).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)
 | 
			
		||||
	accessToken, aErr := s.auth.CreateAccessToken(benefactor)
 | 
			
		||||
	if aErr != nil {
 | 
			
		||||
		return benefactoreparam.LoginOrRegisterResponse{}, richerror.New(op).WithErr(aErr).WithKind(richerror.KindUnexpected)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	refreshToken, err := s.auth.CreateRefreshToken(benefactor)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
	refreshToken, rErr := s.auth.CreateRefreshToken(benefactor)
 | 
			
		||||
	if rErr != nil {
 | 
			
		||||
		return benefactoreparam.LoginOrRegisterResponse{}, richerror.New(op).WithErr(rErr).WithKind(richerror.KindUnexpected)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,42 @@
 | 
			
		|||
package adminvalidator
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	adminserviceparam "git.gocasts.ir/ebhomengo/niki/param/admin/admin"
 | 
			
		||||
	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) ValidateLoginWithPhoneNumberRequest(req adminserviceparam.LoginWithPhoneNumberRequest) (map[string]string, error) {
 | 
			
		||||
	const op = "adminvalidator.ValidateRegisterRequest"
 | 
			
		||||
 | 
			
		||||
	if err := validation.ValidateStruct(&req,
 | 
			
		||||
		//TODO - add regex
 | 
			
		||||
		validation.Field(&req.Password, validation.Required, validation.NotNil,
 | 
			
		||||
			validation.Length(8, 0)),
 | 
			
		||||
 | 
			
		||||
		validation.Field(&req.PhoneNumber,
 | 
			
		||||
			validation.Required,
 | 
			
		||||
			validation.Match(regexp.MustCompile(phoneNumberRegex)).Error(errmsg.ErrorMsgPhoneNumberIsNotValid),
 | 
			
		||||
			validation.By(v.doesAdminExistByPhoneNumber))); err != nil {
 | 
			
		||||
		fieldErrors := make(map[string]string)
 | 
			
		||||
 | 
			
		||||
		vErr := validation.Errors{}
 | 
			
		||||
		if errors.As(err, &vErr) {
 | 
			
		||||
			for key, value := range vErr {
 | 
			
		||||
				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)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	//nolint
 | 
			
		||||
	return nil, nil
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,53 @@
 | 
			
		|||
package adminvalidator
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	adminserviceparam "git.gocasts.ir/ebhomengo/niki/param/admin/admin"
 | 
			
		||||
	errmsg "git.gocasts.ir/ebhomengo/niki/pkg/err_msg"
 | 
			
		||||
	richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error"
 | 
			
		||||
	"github.com/go-ozzo/ozzo-validation/is"
 | 
			
		||||
	validation "github.com/go-ozzo/ozzo-validation/v4"
 | 
			
		||||
	"regexp"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func (v Validator) ValidateRegisterRequest(req adminserviceparam.RegisterRequest) (map[string]string, error) {
 | 
			
		||||
	const op = "adminvalidator.ValidateRegisterRequest"
 | 
			
		||||
	if err := validation.ValidateStruct(&req,
 | 
			
		||||
		// TODO - add length of code config from benefactor config
 | 
			
		||||
		validation.Field(&req.FirstName,
 | 
			
		||||
			validation.Length(3, 40)),
 | 
			
		||||
		validation.Field(&req.LastName,
 | 
			
		||||
			validation.Length(3, 40)),
 | 
			
		||||
 | 
			
		||||
		//TODO - add regex
 | 
			
		||||
		validation.Field(&req.Password, validation.Required, validation.NotNil,
 | 
			
		||||
			validation.Length(8, 0)),
 | 
			
		||||
		validation.Field(&req.Gender, validation.By(v.IsGenderValid)),
 | 
			
		||||
		validation.Field(&req.Role, validation.By(v.IsRoleValid), validation.Required),
 | 
			
		||||
		validation.Field(&req.Status, validation.By(v.IsStatusValid), validation.Required),
 | 
			
		||||
		validation.Field(&req.Email, validation.Required, is.Email,
 | 
			
		||||
			validation.By(v.doesAdminExistByEmail)),
 | 
			
		||||
 | 
			
		||||
		validation.Field(&req.PhoneNumber,
 | 
			
		||||
			validation.Required,
 | 
			
		||||
			validation.Match(regexp.MustCompile(phoneNumberRegex)).Error(errmsg.ErrorMsgPhoneNumberIsNotValid),
 | 
			
		||||
			validation.By(v.IsPhoneNumberUnique))); err != nil {
 | 
			
		||||
		fieldErrors := make(map[string]string)
 | 
			
		||||
 | 
			
		||||
		vErr := validation.Errors{}
 | 
			
		||||
		if errors.As(err, &vErr) {
 | 
			
		||||
			for key, value := range vErr {
 | 
			
		||||
				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)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	//nolint
 | 
			
		||||
	return nil, nil
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,111 @@
 | 
			
		|||
package adminvalidator
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"git.gocasts.ir/ebhomengo/niki/entity"
 | 
			
		||||
	errmsg "git.gocasts.ir/ebhomengo/niki/pkg/err_msg"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	phoneNumberRegex = "^09\\d{9}$"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Repository interface {
 | 
			
		||||
	AdminExistByPhoneNumber(ctx context.Context, phoneNumber string) (bool, error)
 | 
			
		||||
	AdminExistByEmail(ctx context.Context, email string) (bool, error)
 | 
			
		||||
}
 | 
			
		||||
type Validator struct {
 | 
			
		||||
	repo Repository
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func New(repo Repository) Validator {
 | 
			
		||||
	return Validator{repo: repo}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (v Validator) doesAdminExistByPhoneNumber(value interface{}) error {
 | 
			
		||||
	phoneNumber, ok := value.(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return fmt.Errorf(errmsg.ErrorMsgSomethingWentWrong)
 | 
			
		||||
	}
 | 
			
		||||
	adminExisted, err := v.repo.AdminExistByPhoneNumber(context.Background(), phoneNumber)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf(errmsg.ErrorMsgSomethingWentWrong)
 | 
			
		||||
	}
 | 
			
		||||
	if !adminExisted {
 | 
			
		||||
		return fmt.Errorf(errmsg.ErrorMsgPhoneNumberOrPassIsIncorrect)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (v Validator) IsPhoneNumberUnique(value interface{}) error {
 | 
			
		||||
	phoneNumber, ok := value.(*string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return fmt.Errorf(errmsg.ErrorMsgSomethingWentWrong)
 | 
			
		||||
	}
 | 
			
		||||
	adminExisted, err := v.repo.AdminExistByPhoneNumber(context.Background(), *phoneNumber)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf(errmsg.ErrorMsgSomethingWentWrong)
 | 
			
		||||
	}
 | 
			
		||||
	if adminExisted {
 | 
			
		||||
		return fmt.Errorf(errmsg.ErrorMsgPhoneNumberIsNotUnique)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (v Validator) doesAdminExistByEmail(value interface{}) error {
 | 
			
		||||
	email, ok := value.(*string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return fmt.Errorf(errmsg.ErrorMsgSomethingWentWrong)
 | 
			
		||||
	}
 | 
			
		||||
	adminExisted, err := v.repo.AdminExistByEmail(context.Background(), *email)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf(errmsg.ErrorMsgSomethingWentWrong)
 | 
			
		||||
	}
 | 
			
		||||
	if adminExisted {
 | 
			
		||||
		return fmt.Errorf(errmsg.ErrorMsgPhoneNumberIsNotUnique)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (v Validator) IsRoleValid(value interface{}) error {
 | 
			
		||||
	role, ok := value.(*entity.AdminRole)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return fmt.Errorf(errmsg.ErrorMsgSomethingWentWrong)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if isValid := role.IsValid(); isValid != true {
 | 
			
		||||
		return fmt.Errorf(errmsg.ErrorMsgInvalidInput)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (v Validator) IsGenderValid(value interface{}) error {
 | 
			
		||||
	gender, ok := value.(*entity.Gender)
 | 
			
		||||
	if gender == nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return fmt.Errorf(errmsg.ErrorMsgSomethingWentWrong)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if isValid := gender.IsValid(); isValid != true {
 | 
			
		||||
		return fmt.Errorf(errmsg.ErrorMsgInvalidInput)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (v Validator) IsStatusValid(value interface{}) error {
 | 
			
		||||
	status, ok := value.(*entity.AdminStatus)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return fmt.Errorf(errmsg.ErrorMsgSomethingWentWrong)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if isValid := status.IsValid(); isValid != true {
 | 
			
		||||
		return fmt.Errorf(errmsg.ErrorMsgInvalidInput)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
		Reference in New Issue