Merge branch 'main' of https://github.com/SamuelTariku/FortuneBet-Backend
This commit is contained in:
commit
2f593c8430
|
|
@ -23,8 +23,7 @@ import (
|
|||
// mongologger "github.com/SamuelTariku/FortuneBet-Backend/internal/logger/mongoLogger"
|
||||
|
||||
// "github.com/SamuelTariku/FortuneBet-Backend/internal/logger/mongoLogger"
|
||||
mockemail "github.com/SamuelTariku/FortuneBet-Backend/internal/mocks/mock_email"
|
||||
mocksms "github.com/SamuelTariku/FortuneBet-Backend/internal/mocks/mock_sms"
|
||||
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/repository"
|
||||
|
||||
// "github.com/SamuelTariku/FortuneBet-Backend/internal/router"
|
||||
|
|
@ -120,10 +119,8 @@ func main() {
|
|||
v := customvalidator.NewCustomValidator(validator.New())
|
||||
|
||||
authSvc := authentication.NewService(store, store, cfg.RefreshExpiry)
|
||||
mockSms := mocksms.NewMockSMS()
|
||||
mockEmail := mockemail.NewMockEmail()
|
||||
|
||||
userSvc := user.NewService(store, store, mockSms, mockEmail)
|
||||
userSvc := user.NewService(store, store, cfg)
|
||||
|
||||
eventSvc := event.New(cfg.Bet365Token, store)
|
||||
oddsSvc := odds.New(store, cfg, logger)
|
||||
|
|
|
|||
5
go.mod
5
go.mod
|
|
@ -71,4 +71,7 @@ require (
|
|||
go.uber.org/zap v1.27.0
|
||||
)
|
||||
|
||||
require go.uber.org/multierr v1.10.0 // indirect
|
||||
require (
|
||||
github.com/resend/resend-go/v2 v2.20.0 // indirect
|
||||
go.uber.org/multierr v1.10.0 // indirect
|
||||
)
|
||||
|
|
|
|||
2
go.sum
2
go.sum
|
|
@ -110,6 +110,8 @@ github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT9
|
|||
github.com/otiai10/mint v1.3.3/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/resend/resend-go/v2 v2.20.0 h1:MrIrgV0aHhwRgmcRPw33Nexn6aGJvCvG2XwfFpAMBGM=
|
||||
github.com/resend/resend-go/v2 v2.20.0/go.mod h1:3YCb8c8+pLiqhtRFXTyFwlLvfjQtluxOr9HEh2BwCkQ=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
|
|
|
|||
|
|
@ -30,6 +30,8 @@ var (
|
|||
ErrInvalidVeliAPIURL = errors.New("Veli API URL is invalid")
|
||||
ErrInvalidVeliOperatorKey = errors.New("Veli operator key is invalid")
|
||||
ErrInvalidVeliSecretKey = errors.New("Veli secret key is invalid")
|
||||
ErrMissingResendApiKey = errors.New("missing Resend Api key")
|
||||
ErrMissingResendSenderEmail = errors.New("missing Resend sender name")
|
||||
)
|
||||
|
||||
type AleaPlayConfig struct {
|
||||
|
|
@ -75,6 +77,8 @@ type Config struct {
|
|||
PopOK domain.PopOKConfig
|
||||
AleaPlay AleaPlayConfig `mapstructure:"alea_play"`
|
||||
VeliGames VeliGamesConfig `mapstructure:"veli_games"`
|
||||
ResendApiKey string
|
||||
ResendSenderEmail string
|
||||
}
|
||||
|
||||
func NewConfig() (*Config, error) {
|
||||
|
|
@ -287,6 +291,19 @@ func (c *Config) loadEnv() error {
|
|||
return ErrMissingBetToken
|
||||
}
|
||||
c.Bet365Token = betToken
|
||||
|
||||
resendApiKey := os.Getenv("RESEND_API_KEY")
|
||||
if resendApiKey == "" {
|
||||
return ErrMissingResendApiKey
|
||||
}
|
||||
c.ResendApiKey = resendApiKey
|
||||
|
||||
resendSenderEmail := os.Getenv("RESEND_SENDER_EMAIL")
|
||||
if resendSenderEmail == "" {
|
||||
return ErrMissingResendSenderEmail
|
||||
}
|
||||
c.ResendSenderEmail = resendSenderEmail
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,17 @@
|
|||
package helpers
|
||||
|
||||
import "github.com/google/uuid"
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand/v2"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
func GenerateID() string {
|
||||
return uuid.New().String()
|
||||
}
|
||||
|
||||
func GenerateOTP() string {
|
||||
num := 100000 + rand.UintN(899999)
|
||||
return fmt.Sprintf("%d", num) // 6 digit random number [100,000 - 999,999]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ func (s *Service) Login(ctx context.Context, email, phone string, password strin
|
|||
// If old refresh token is not revoked, revoke it
|
||||
if err == nil && !oldRefreshToken.Revoked {
|
||||
err = s.tokenStore.RevokeRefreshToken(ctx, oldRefreshToken.Token)
|
||||
if(err != nil) {
|
||||
if err != nil {
|
||||
return LoginSuccess{}, err
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,14 +2,32 @@ package user
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/pkgs/helpers"
|
||||
afro "github.com/amanuelabay/afrosms-go"
|
||||
"github.com/resend/resend-go/v2"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
func (s *Service) SendOtp(ctx context.Context, sentTo string, otpFor domain.OtpFor, medium domain.OtpMedium) error {
|
||||
otpCode := "123456" // Generate OTP code
|
||||
otpCode := helpers.GenerateOTP()
|
||||
|
||||
message := fmt.Sprintf("Welcome to Fortune bets, your OTP is %s please don't share with anyone.", otpCode)
|
||||
|
||||
switch medium {
|
||||
case domain.OtpMediumSms:
|
||||
if err := s.SendSMSOTP(ctx, sentTo, message); err != nil {
|
||||
return err
|
||||
}
|
||||
case domain.OtpMediumEmail:
|
||||
if err := s.SendEmailOTP(ctx, sentTo, message); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
otp := domain.Otp{
|
||||
SentTo: sentTo,
|
||||
|
|
@ -21,19 +39,9 @@ func (s *Service) SendOtp(ctx context.Context, sentTo string, otpFor domain.OtpF
|
|||
ExpiresAt: time.Now().Add(OtpExpiry),
|
||||
}
|
||||
|
||||
err := s.otpStore.CreateOtp(ctx, otp)
|
||||
if err != nil {
|
||||
return err
|
||||
return s.otpStore.CreateOtp(ctx, otp)
|
||||
}
|
||||
|
||||
switch medium {
|
||||
case domain.OtpMediumSms:
|
||||
return s.smsGateway.SendSMSOTP(ctx, sentTo, otpCode)
|
||||
case domain.OtpMediumEmail:
|
||||
return s.emailGateway.SendEmailOTP(ctx, sentTo, otpCode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func hashPassword(plaintextPassword string) ([]byte, error) {
|
||||
hash, err := bcrypt.GenerateFromPassword([]byte(plaintextPassword), 12)
|
||||
if err != nil {
|
||||
|
|
@ -42,3 +50,50 @@ func hashPassword(plaintextPassword string) ([]byte, error) {
|
|||
|
||||
return hash, nil
|
||||
}
|
||||
|
||||
func (s *Service) SendSMSOTP(ctx context.Context, receiverPhone, message string) error {
|
||||
apiKey := s.config.AFRO_SMS_API_KEY
|
||||
senderName := s.config.AFRO_SMS_SENDER_NAME
|
||||
hostURL := s.config.ADRO_SMS_HOST_URL
|
||||
endpoint := "/api/send"
|
||||
|
||||
// API endpoint has been updated
|
||||
// TODO: no need for package for the afro message operations (pretty simple stuff)
|
||||
request := afro.GetRequest(apiKey, endpoint, hostURL)
|
||||
request.BaseURL = "https://api.afromessage.com/api/send"
|
||||
|
||||
request.Method = "GET"
|
||||
request.Sender(senderName)
|
||||
request.To(receiverPhone, message)
|
||||
|
||||
response, err := afro.MakeRequestWithContext(ctx, request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if response["acknowledge"] == "success" {
|
||||
return nil
|
||||
} else {
|
||||
fmt.Println(response["response"].(map[string]interface{}))
|
||||
return errors.New("SMS delivery failed")
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) SendEmailOTP(ctx context.Context, receiverEmail, message string) error {
|
||||
apiKey := s.config.ResendApiKey
|
||||
client := resend.NewClient(apiKey)
|
||||
formattedSenderEmail := "FortuneBets <" + s.config.ResendSenderEmail + ">"
|
||||
params := &resend.SendEmailRequest{
|
||||
From: formattedSenderEmail,
|
||||
To: []string{receiverEmail},
|
||||
Subject: "FortuneBets - One Time Password",
|
||||
Text: message,
|
||||
}
|
||||
|
||||
_, err := client.Emails.Send(params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ package user
|
|||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/config"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -11,19 +13,17 @@ const (
|
|||
type Service struct {
|
||||
userStore UserStore
|
||||
otpStore OtpStore
|
||||
smsGateway SmsGateway
|
||||
emailGateway EmailGateway
|
||||
config *config.Config
|
||||
}
|
||||
|
||||
func NewService(
|
||||
userStore UserStore,
|
||||
otpStore OtpStore, smsGateway SmsGateway,
|
||||
emailGateway EmailGateway,
|
||||
otpStore OtpStore,
|
||||
cfg *config.Config,
|
||||
) *Service {
|
||||
return &Service{
|
||||
userStore: userStore,
|
||||
otpStore: otpStore,
|
||||
smsGateway: smsGateway,
|
||||
emailGateway: emailGateway,
|
||||
config: cfg,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import (
|
|||
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
// loginCustomerReq represents the request body for the LoginCustomer endpoint.
|
||||
type loginCustomerReq struct {
|
||||
Email string `json:"email" validate:"email" example:"john.doe@example.com"`
|
||||
|
|
@ -21,6 +22,7 @@ type loginCustomerRes struct {
|
|||
RefreshToken string `json:"refresh_token"`
|
||||
Role string `json:"role"`
|
||||
}
|
||||
|
||||
// LoginCustomer godoc
|
||||
// @Summary Login customer
|
||||
// @Description Login customer
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package handlers
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
|
|
@ -243,6 +244,7 @@ func (h *Handler) SendResetCode(c *fiber.Ctx) error {
|
|||
|
||||
if err := h.userSvc.SendResetCode(c.Context(), medium, sentTo); err != nil {
|
||||
h.logger.Error("Failed to send reset code", "error", err)
|
||||
fmt.Println(err)
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to send reset code")
|
||||
}
|
||||
|
||||
|
|
@ -250,8 +252,8 @@ func (h *Handler) SendResetCode(c *fiber.Ctx) error {
|
|||
}
|
||||
|
||||
type ResetPasswordReq struct {
|
||||
Email string `json:"email" validate:"email" example:"john.doe@example.com"`
|
||||
PhoneNumber string `json:"phone_number" validate:"required_without=Email" example:"1234567890"`
|
||||
Email string `json:"email,omitempty" validate:"required_without=PhoneNumber,omitempty,email" example:"john.doe@example.com"`
|
||||
PhoneNumber string `json:"phone_number,omitempty" validate:"required_without=Email,omitempty" example:"1234567890"`
|
||||
Password string `json:"password" validate:"required,min=8" example:"newpassword123"`
|
||||
Otp string `json:"otp" validate:"required" example:"123456"`
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user