Adds CRUD and preview APIs, RBAC permissions, seeded system templates, and migrates OTP email/SMS to template rendering. Co-authored-by: Cursor <cursoragent@cursor.com>
119 lines
2.5 KiB
Go
119 lines
2.5 KiB
Go
package user
|
|
|
|
import (
|
|
"Yimaru-Backend/internal/domain"
|
|
"Yimaru-Backend/internal/pkgs/helpers"
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
"golang.org/x/crypto/bcrypt"
|
|
)
|
|
|
|
func (s *Service) renderOtpMessage(ctx context.Context, otpCode, firstName string) (domain.RenderedEmail, error) {
|
|
return s.emailTemplateSvc.Render(ctx, domain.EmailTemplateSlugOTP, map[string]any{
|
|
"OTP": otpCode,
|
|
"FirstName": firstName,
|
|
"ExpiresMinutes": int(OtpExpiry.Minutes()),
|
|
})
|
|
}
|
|
|
|
func (s *Service) ResendOtp(
|
|
ctx context.Context,
|
|
email, phone string,
|
|
) error {
|
|
|
|
user, err := s.userStore.GetUserByEmailPhone(ctx, email, phone)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
otpCode := helpers.GenerateOTP()
|
|
|
|
otp, err := s.otpStore.GetOtp(ctx, user.ID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
rendered, err := s.renderOtpMessage(ctx, otpCode, user.FirstName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
switch otp.Medium {
|
|
case domain.OtpMediumSms:
|
|
if err := s.messengerSvc.SendAfroMessageSMSLatest(ctx, otp.SentTo, rendered.Text, nil); err != nil {
|
|
return err
|
|
}
|
|
case domain.OtpMediumEmail:
|
|
if err := s.messengerSvc.SendEmail(
|
|
ctx,
|
|
otp.SentTo,
|
|
rendered.Text,
|
|
rendered.HTML,
|
|
rendered.Subject,
|
|
); err != nil {
|
|
return err
|
|
}
|
|
|
|
default:
|
|
return fmt.Errorf("invalid otp medium: %s", otp.Medium)
|
|
}
|
|
|
|
if err := s.otpStore.UpdateOtp(ctx, otpCode, user.ID); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *Service) SendOtp(ctx context.Context, userID int64, sentTo string, otpFor domain.OtpFor, medium domain.OtpMedium, provider domain.SMSProvider) error {
|
|
otpCode := helpers.GenerateOTP()
|
|
|
|
firstName := ""
|
|
if userID > 0 {
|
|
user, err := s.userStore.GetUserByID(ctx, userID)
|
|
if err == nil {
|
|
firstName = user.FirstName
|
|
}
|
|
}
|
|
|
|
rendered, err := s.renderOtpMessage(ctx, otpCode, firstName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
switch medium {
|
|
case domain.OtpMediumSms:
|
|
if err := s.messengerSvc.SendAfroMessageSMSLatest(ctx, sentTo, rendered.Text, nil); err != nil {
|
|
return err
|
|
}
|
|
case domain.OtpMediumEmail:
|
|
if err := s.messengerSvc.SendEmail(ctx, sentTo, rendered.Text, rendered.HTML, rendered.Subject); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
otp := domain.Otp{
|
|
UserID: userID,
|
|
SentTo: sentTo,
|
|
Medium: medium,
|
|
For: otpFor,
|
|
Otp: otpCode,
|
|
Used: false,
|
|
CreatedAt: time.Now(),
|
|
ExpiresAt: time.Now().Add(OtpExpiry),
|
|
}
|
|
|
|
return s.otpStore.CreateOtp(ctx, otp)
|
|
}
|
|
|
|
func hashPassword(plaintextPassword string) ([]byte, error) {
|
|
hash, err := bcrypt.GenerateFromPassword([]byte(plaintextPassword), 12)
|
|
if err != nil {
|
|
return []byte{}, err
|
|
}
|
|
|
|
return hash, nil
|
|
}
|