add: contactController,tests & refactored tests

This commit is contained in:
$(pass /github/name)
2024-08-18 14:17:17 +02:00
parent 6441ad3ef9
commit ae0ec8bf88
17 changed files with 886 additions and 320 deletions

View File

@@ -1,5 +1,5 @@
{
"base_url": "https://domain.de",
"BaseURL": "https://domain.de",
"db": {
"Path": "data/db.sqlite3"
},
@@ -18,5 +18,9 @@
},
"auth": {
"APIKey": ""
},
"recipients": {
"ContactForm": "contacts@server.com",
"UserRegistration": "registration@server.com"
}
}

View File

@@ -43,12 +43,18 @@ type TemplateConfig struct {
StaticPath string `json:"StaticPath" default:"templates/css" envconfig:"TEMPLATE_STATIC_PATH"`
}
type RecipientsConfig struct {
ContactForm string `json:"ContactForm" envconfig:"RECIPIENT_CONTACT_FORM"`
UserRegistration string `json:"UserRegistration" envconfig:"RECIPIENT_USER_REGISTRATION"`
}
type Config struct {
ConfigFilePath string `json:"config_file_path" envconfig:"CONFIG_FILE_PATH"`
BaseURL string `json:"base_url" envconfig:"BASE_URL"`
Auth AuthenticationConfig `json:"auth"`
DB DatabaseConfig `json:"db"`
Templates TemplateConfig `json:"templates"`
Recipients RecipientsConfig `json:"recipients"`
ConfigFilePath string `json:"config_file_path" envconfig:"CONFIG_FILE_PATH"`
BaseURL string `json:"BaseUrl" envconfig:"BASE_URL"`
DB DatabaseConfig `json:"db"`
SMTP SMTPConfig `json:"smtp"`
}
@@ -60,6 +66,7 @@ var (
DB DatabaseConfig
Templates TemplateConfig
SMTP SMTPConfig
Recipients RecipientsConfig
)
// LoadConfig initializes the configuration by reading from a file and environment variables.
@@ -86,6 +93,7 @@ func LoadConfig() {
Templates = CFG.Templates
SMTP = CFG.SMTP
BaseURL = CFG.BaseURL
Recipients = CFG.Recipients
}
// readFile reads the configuration from the specified file path into the provided Config struct.

View File

@@ -15,30 +15,33 @@ type ContactController struct {
EmailService *services.EmailService
}
type contactData struct {
email string `validate:"required,email"`
name string
message string `validate:"required"`
Email string `form:"email" validate:"required,email"`
Name string `form:"name"`
Message string `form:"message" validate:"required"`
Honeypot string `form:"username" validate:"eq="`
}
func (cc *ContactController) RelayContactRequest(c *gin.Context) {
var msgData contactData
if c.Query("username") != "" {
if err := c.ShouldBind(&msgData); err != nil {
// A bot is talking to us
c.JSON(http.StatusNotAcceptable, gin.H{"error": "Not Acceptable"})
return
}
msgData.name = c.Query("name")
msgData.email = c.Query("email")
msgData.message = c.Query("message")
validate := validator.New()
if err := validate.Struct(msgData); err != nil {
logger.Error.Printf("Couldn't validate contact form data: %v", err)
c.JSON(http.StatusNotAcceptable, gin.H{"error": "Couldn't validate contact form data"})
return
}
if err := cc.EmailService.RelayContactFormMessage(&msgData.email, &msgData.name, &msgData.message); err != nil {
if err := cc.EmailService.RelayContactFormMessage(msgData.Email, msgData.Name, msgData.Message); err != nil {
logger.Error.Printf("Couldn't send contact message mail: %v", err)
c.JSON(http.StatusInternalServerError, gin.H{"error": "Couldn't send mail"})
return
}
c.JSON(http.StatusOK, "Your message has been sent")
c.JSON(http.StatusAccepted, "Your message has been sent")
}

View File

@@ -0,0 +1,157 @@
package controllers
import (
"fmt"
"net/http"
"net/http/httptest"
"net/url"
"strings"
"testing"
"GoMembership/internal/config"
"GoMembership/internal/constants"
"GoMembership/internal/utils"
"github.com/gin-gonic/gin"
)
type RelayContactRequestTest struct {
Input url.Values
Name string
WantResponse int
Assert bool
}
func TestContactController(t *testing.T) {
tests := getContactData()
for _, tt := range tests {
t.Run(tt.Name, func(t *testing.T) {
if err := runSingleTest(&tt); err != nil {
t.Errorf("Test failed: %v", err.Error())
}
})
}
}
func (rt *RelayContactRequestTest) SetupContext() (*gin.Context, *httptest.ResponseRecorder) {
return GetMockedFormContext(rt.Input, "/contact")
}
func (rt *RelayContactRequestTest) RunHandler(c *gin.Context) {
Cc.RelayContactRequest(c)
}
func (rt *RelayContactRequestTest) ValidateResponse(w *httptest.ResponseRecorder) error {
if w.Code != rt.WantResponse {
return fmt.Errorf("Didn't get the expected response code: got: %v; expected: %v", w.Code, rt.WantResponse)
}
return nil
}
func (rt *RelayContactRequestTest) ValidateResult() error {
messages := utils.SMTPGetMessages()
for _, message := range messages {
mail, err := utils.DecodeMail(message.MsgRequest())
if err != nil {
return err
}
if strings.Contains(mail.Subject, constants.MailContactSubject) {
if err := checkContactRequestMail(mail, rt); err != nil {
return err
}
} else {
return fmt.Errorf("Subject not expected: %v", mail.Subject)
}
}
return nil
}
func checkContactRequestMail(mail *utils.Email, rt *RelayContactRequestTest) error {
if !strings.Contains(mail.To, config.Recipients.ContactForm) {
return fmt.Errorf("Contact Information didn't reach the admin! Recipient was: %v instead of %v", mail.To, config.Recipients.ContactForm)
}
if !strings.Contains(mail.From, config.SMTP.User) {
return fmt.Errorf("Contact Information was sent from unexpected address! Sender was: %v instead of %v", mail.From, config.SMTP.User)
}
//Check if all the relevant data has been passed to the mail.
if !strings.Contains(mail.Body, rt.Input.Get("name")) {
return fmt.Errorf("User name(%v) has not been rendered in contact mail.", rt.Input.Get("name"))
}
if !strings.Contains(mail.Body, rt.Input.Get("message")) {
return fmt.Errorf("User message(%v) has not been rendered in contact mail.", rt.Input.Get("message"))
}
return nil
}
func getBaseRequest() *url.Values {
return &url.Values{
"username": {""},
"name": {"My-First and-Last-Name"},
"email": {"name@domain.de"},
"message": {"My message to the world"},
}
}
func customizeRequest(updates map[string]string) *url.Values {
form := getBaseRequest()
for key, value := range updates {
form.Set(key, value)
}
return form
}
func getContactData() []RelayContactRequestTest {
return []RelayContactRequestTest{
{
Name: "mail empty, should fail",
WantResponse: http.StatusNotAcceptable,
Assert: false,
Input: *customizeRequest(
map[string]string{
"email": "",
}),
},
{
Name: "mail invalid, should fail",
WantResponse: http.StatusNotAcceptable,
Assert: false,
Input: *customizeRequest(
map[string]string{
"email": "novalid#email.de",
}),
},
{
Name: "No message should fail",
WantResponse: http.StatusNotAcceptable,
Assert: true,
Input: *customizeRequest(
map[string]string{
"message": "",
}),
},
{
Name: "Honeypot set, should fail",
WantResponse: http.StatusNotAcceptable,
Assert: true,
Input: *customizeRequest(
map[string]string{
"username": "I'm a bot",
}),
},
{
Name: "Correct message, should pass",
WantResponse: http.StatusAccepted,
Assert: true,
Input: *customizeRequest(
map[string]string{}),
},
}
}

View File

@@ -0,0 +1,178 @@
package controllers
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"net/url"
"os"
"strconv"
"testing"
"log"
"github.com/gin-gonic/gin"
"GoMembership/internal/config"
"GoMembership/internal/database"
"GoMembership/internal/models"
"GoMembership/internal/repositories"
"GoMembership/internal/services"
"GoMembership/internal/utils"
"GoMembership/pkg/logger"
)
type TestCase interface {
SetupContext() (*gin.Context, *httptest.ResponseRecorder)
RunHandler(*gin.Context)
ValidateResponse(*httptest.ResponseRecorder) error
ValidateResult() error
}
const (
Host = "127.0.0.1"
Port int = 2525
)
var (
Uc *UserController
Mc *MembershipController
Cc *ContactController
)
func TestSuite(t *testing.T) {
_ = deleteTestDB("test.db")
if err := database.InitDB("test.db"); err != nil {
log.Fatalf("Failed to create DB: %#v", err)
}
if err := os.Setenv("SMTP_HOST", Host); err != nil {
log.Fatalf("Error setting environment variable: %v", err)
}
if err := os.Setenv("SMTP_PORT", strconv.Itoa(Port)); err != nil {
log.Fatalf("Error setting environment variable: %v", err)
}
if err := os.Setenv("BASE_URL", "http://"+Host+":2525"); err != nil {
log.Fatalf("Error setting environment variable: %v", err)
}
config.LoadConfig()
utils.SMTPStart(Host, Port)
emailService := services.NewEmailService(config.SMTP.Host, config.SMTP.Port, config.SMTP.User, config.SMTP.Password)
var consentRepo repositories.ConsentRepositoryInterface = &repositories.ConsentRepository{}
consentService := &services.ConsentService{Repo: consentRepo}
var bankAccountRepo repositories.BankAccountRepositoryInterface = &repositories.BankAccountRepository{}
bankAccountService := &services.BankAccountService{Repo: bankAccountRepo}
var membershipRepo repositories.MembershipRepositoryInterface = &repositories.MembershipRepository{}
var subscriptionRepo repositories.SubscriptionModelsRepositoryInterface = &repositories.SubscriptionModelsRepository{}
membershipService := &services.MembershipService{Repo: membershipRepo, SubscriptionRepo: subscriptionRepo}
var userRepo repositories.UserRepositoryInterface = &repositories.UserRepository{}
userService := &services.UserService{Repo: userRepo}
Uc = &UserController{Service: userService, EmailService: emailService, ConsentService: consentService, BankAccountService: bankAccountService, MembershipService: membershipService}
Mc = &MembershipController{Service: *membershipService}
Cc = &ContactController{EmailService: emailService}
if err := initSubscriptionPlans(); err != nil {
log.Fatalf("Failed to init Subscription plans: %#v", err)
}
// Run all tests
// code := m.Run()
t.Run("userController", func(t *testing.T) {
TestUserController(t)
})
t.Run("contactController", func(t *testing.T) {
TestContactController(t)
})
t.Run("membershipController", func(t *testing.T) {
TestMembershipController(t)
})
if err := utils.SMTPStop(); err != nil {
log.Fatalf("Failed to stop SMTP Mockup Server: %#v", err)
}
if err := deleteTestDB("test.db"); err != nil {
log.Fatalf("Failed to tear down DB: %#v", err)
}
}
func initSubscriptionPlans() error {
subscription := models.SubscriptionModel{
Name: "Basic",
Details: "Test Plan",
MonthlyFee: 2,
HourlyRate: 3,
}
result := database.DB.Create(&subscription)
if result.Error != nil {
return result.Error
}
return nil
}
func GetMockedJSONContext(jsonStr []byte, url string) (*gin.Context, *httptest.ResponseRecorder) {
gin.SetMode(gin.TestMode)
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
var err error
c.Request, err = http.NewRequest("POST", url, bytes.NewBuffer(jsonStr))
if err != nil {
log.Fatalf("Failed to create new Request: %#v", err)
}
c.Request.Header.Set("Content-Type", "application/json")
return c, w
}
func GetMockedFormContext(formData url.Values, url string) (*gin.Context, *httptest.ResponseRecorder) {
gin.SetMode(gin.TestMode)
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
req, err := http.NewRequest("POST", url, bytes.NewBufferString(formData.Encode()))
if err != nil {
log.Fatalf("Failed to create new Request: %#v", err)
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
c.Request = req
return c, w
}
func deleteTestDB(dbPath string) error {
err := os.Remove(dbPath)
if err != nil {
return err
}
return nil
}
func runSingleTest(tc TestCase) error {
c, w := tc.SetupContext()
tc.RunHandler(c)
if err := tc.ValidateResponse(w); err != nil {
return err
}
return tc.ValidateResult()
}
func GenerateInputJSON(aStruct interface{}) string {
// Marshal the object into JSON
jsonBytes, err := json.Marshal(aStruct)
if err != nil {
logger.Error.Fatalf("Couldn't generate JSON: %#v\nERROR: %#v", aStruct, err)
return ""
}
return string(jsonBytes)
}

View File

@@ -40,15 +40,17 @@ func (mc *MembershipController) RegisterSubscription(c *gin.Context) {
c.JSON(http.StatusExpectationFailed, "API Key is missing")
return
}
logger.Info.Printf("registering subscription: %+v", regData)
// Register Subscription
id, err := mc.Service.RegisterSubscription(&regData.Model)
if err != nil {
logger.Error.Printf("Couldn't register Membershipmodel: %v", err)
c.JSON(http.StatusInternalServerError, "Couldn't register Membershipmodel")
c.JSON(int(id), "Couldn't register Membershipmodel")
return
}
regData.Model.ID = id
logger.Info.Printf("registering subscription: %+v", regData)
c.JSON(http.StatusCreated, gin.H{
"status": "success",
"id": id,
})
}

View File

@@ -0,0 +1,119 @@
package controllers
import (
"fmt"
"net/http"
"net/http/httptest"
"testing"
"GoMembership/internal/config"
"GoMembership/internal/models"
"github.com/gin-gonic/gin"
)
type RegisterSubscriptionTest struct {
WantDBData map[string]interface{}
Input string
Name string
WantResponse int
Assert bool
}
func TestMembershipController(t *testing.T) {
tests := getSubscriptionData()
for _, tt := range tests {
t.Run(tt.Name, func(t *testing.T) {
if err := runSingleTest(&tt); err != nil {
t.Errorf("Test failed: %v", err.Error())
}
})
}
}
func (rt *RegisterSubscriptionTest) SetupContext() (*gin.Context, *httptest.ResponseRecorder) {
return GetMockedJSONContext([]byte(rt.Input), "register/subscription")
}
func (rt *RegisterSubscriptionTest) RunHandler(c *gin.Context) {
Mc.RegisterSubscription(c)
}
func (rt *RegisterSubscriptionTest) ValidateResponse(w *httptest.ResponseRecorder) error {
if w.Code != rt.WantResponse {
return fmt.Errorf("Didn't get the expected response code: got: %v; expected: %v", w.Code, rt.WantResponse)
}
return nil
}
func (rt *RegisterSubscriptionTest) ValidateResult() error {
return validateSubscription(rt.Assert, rt.WantDBData)
}
func validateSubscription(assert bool, wantDBData map[string]interface{}) error {
subscriptions, err := Mc.Service.GetSubscriptions(wantDBData)
if err != nil {
return fmt.Errorf("Error in database ops: %#v", err)
}
if assert != (len(*subscriptions) != 0) {
return fmt.Errorf("Subscription entry query didn't met expectation: %v != %#v", assert, *subscriptions)
}
return nil
}
func getBaseSubscription() MembershipData {
return MembershipData{
APIKey: config.Auth.APIKEY,
Model: models.SubscriptionModel{
Name: "Just a Subscription",
Details: "A subscription detail",
MonthlyFee: 12.0,
HourlyRate: 14.0,
},
}
}
func customizeSubscription(customize func(MembershipData) MembershipData) MembershipData {
subscription := getBaseSubscription()
return customize(subscription)
}
func getSubscriptionData() []RegisterSubscriptionTest {
return []RegisterSubscriptionTest{
{
Name: "No Details should fail",
WantResponse: http.StatusNotAcceptable,
WantDBData: map[string]interface{}{"name": "Just a Subscription"},
Assert: false,
Input: GenerateInputJSON(
customizeSubscription(func(subscription MembershipData) MembershipData {
subscription.Model.Details = ""
return subscription
})),
},
{
Name: "No Model Name should fail",
WantResponse: http.StatusNotAcceptable,
WantDBData: map[string]interface{}{"name": ""},
Assert: false,
Input: GenerateInputJSON(
customizeSubscription(func(subscription MembershipData) MembershipData {
subscription.Model.Name = ""
return subscription
})),
},
{
Name: "correct entry should pass",
WantResponse: http.StatusCreated,
WantDBData: map[string]interface{}{"name": "Just a Subscription"},
Assert: true,
Input: GenerateInputJSON(
customizeSubscription(func(subscription MembershipData) MembershipData {
subscription.Model.Conditions = "Some Condition"
subscription.Model.IncludedPerYear = 0
subscription.Model.IncludedPerMonth = 1
return subscription
})),
},
}
}

View File

@@ -88,7 +88,7 @@ func (uc *UserController) RegisterUser(c *gin.Context) {
}
// Notify admin of new user registration
if err := uc.EmailService.NotifyAdminOfNewUser(&regData.User); err != nil {
if err := uc.EmailService.SendRegistrationNotification(&regData.User); err != nil {
logger.Error.Printf("Failed to notify admin of new user registration: %v", err)
// Proceed without returning error since user registration is successful
}
@@ -112,6 +112,7 @@ func (uc *UserController) VerifyMailHandler(c *gin.Context) {
c.HTML(http.StatusUnauthorized, "verification_error.html", gin.H{"ErrorMessage": "Emailadresse wurde schon bestätigt. Sollte dies nicht der Fall sein, wende Dich bitte an info@carsharing-hasloh.de."})
return
}
logger.Info.Printf("User: %#v", user)
uc.EmailService.SendWelcomeEmail(user)
c.HTML(http.StatusOK, "verification_success.html", gin.H{"FirstName": user.FirstName})

View File

@@ -1,130 +1,66 @@
package controllers
import (
"bytes"
"encoding/json"
"fmt"
"strconv"
"strings"
"net/http"
"net/http/httptest"
"os"
"net/url"
"path/filepath"
"regexp"
"strings"
"testing"
"time"
"github.com/gin-gonic/gin"
smtpmock "github.com/mocktools/go-smtp-mock/v2"
"GoMembership/internal/config"
"GoMembership/internal/constants"
"GoMembership/internal/database"
"GoMembership/internal/models"
"GoMembership/internal/repositories"
"GoMembership/internal/services"
"GoMembership/internal/utils"
"GoMembership/pkg/logger"
)
type test struct {
name string
input string
wantDBData map[string]interface{}
wantResponse uint16
assert bool
type RegisterUserTest struct {
WantDBData map[string]interface{}
Name string
Input string
WantResponse int
Assert bool
}
// type RegistrationData struct {
// User models.User `json:"user"`
// }
func (rt *RegisterUserTest) SetupContext() (*gin.Context, *httptest.ResponseRecorder) {
return GetMockedJSONContext([]byte(rt.Input), "register")
}
const (
Host = "127.0.0.1"
Port int = 2525
User = "alex@mail.de"
Pass = "secret"
AdminMail = "admin@mail.de"
)
func (rt *RegisterUserTest) RunHandler(c *gin.Context) {
Uc.RegisterUser(c)
}
var (
uc UserController
)
func (rt *RegisterUserTest) ValidateResponse(w *httptest.ResponseRecorder) error {
if w.Code != rt.WantResponse {
return fmt.Errorf("Didn't get the expected response code: got: %v; expected: %v", w.Code, rt.WantResponse)
}
return nil
}
func (rt *RegisterUserTest) ValidateResult() error {
return validateUser(rt.Assert, rt.WantDBData)
}
func TestUserController(t *testing.T) {
_ = deleteTestDB("test.db")
if err := database.InitDB("test.db"); err != nil {
t.Errorf("Failed to create DB: %#v", err)
}
if err := os.Setenv("SMTP_HOST", Host); err != nil {
t.Errorf("Error setting environment variable: %v", err)
}
if err := os.Setenv("SMTP_PORT", strconv.Itoa(Port)); err != nil {
t.Errorf("Error setting environment variable: %v", err)
}
if err := os.Setenv("ADMIN_MAIL", AdminMail); err != nil {
t.Errorf("Error setting environment variable: %v", err)
}
if err := os.Setenv("SMTP_USER", User); err != nil {
t.Errorf("Error setting environment variable: %v", err)
}
if err := os.Setenv("SMTP_PASS", Pass); err != nil {
t.Errorf("Error setting environment variable: %v", err)
}
config.LoadConfig()
utils.SMTPStart(Host, Port)
emailService := services.NewEmailService(config.SMTP.Host, config.SMTP.Port, config.SMTP.User, config.SMTP.Password, config.SMTP.AdminEmail)
var consentRepo repositories.ConsentRepositoryInterface = &repositories.ConsentRepository{}
consentService := &services.ConsentService{Repo: consentRepo}
var bankAccountRepo repositories.BankAccountRepositoryInterface = &repositories.BankAccountRepository{}
bankAccountService := &services.BankAccountService{Repo: bankAccountRepo}
var membershipRepo repositories.MembershipRepositoryInterface = &repositories.MembershipRepository{}
var subscriptionRepo repositories.SubscriptionModelsRepositoryInterface = &repositories.SubscriptionModelsRepository{}
membershipService := &services.MembershipService{Repo: membershipRepo, SubscriptionRepo: subscriptionRepo}
var userRepo repositories.UserRepositoryInterface = &repositories.UserRepository{}
userService := &services.UserService{Repo: userRepo}
uc = UserController{Service: userService, EmailService: emailService, ConsentService: consentService, BankAccountService: bankAccountService, MembershipService: membershipService}
if err := initSubscriptionPlans(); err != nil {
t.Errorf("Failed to init Susbcription plans: %#v", err)
}
tests := getTestUsers()
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Run(tt.Name, func(t *testing.T) {
if err := runSingleTest(&tt); err != nil {
t.Errorf("Test failed: %v", err.Error())
}
})
}
if err := utils.SMTPStop(); err != nil {
t.Errorf("Failed to stop SMTP Mockup Server: %#v", err)
}
if err := deleteTestDB("test.db"); err != nil {
t.Errorf("Failed to tear down DB: %#v", err)
}
}
func runSingleTest(tt *test) error {
c, w := getMockedContext([]byte(tt.input))
uc.RegisterUser(c)
if w.Code != int(tt.wantResponse) {
return fmt.Errorf("Didn't get the expected response code: got: %v; expected: %v", w.Code, tt.wantResponse)
}
return validateUser(tt.assert, tt.wantDBData)
}
func validateUser(assert bool, wantDBData map[string]interface{}) error {
users, err := uc.Service.GetUsers(wantDBData)
users, err := Uc.Service.GetUsers(wantDBData)
if err != nil {
return fmt.Errorf("Error in database ops: %#v", err)
}
@@ -137,131 +73,179 @@ func validateUser(assert bool, wantDBData map[string]interface{}) error {
//check for email delivery
messages := utils.SMTPGetMessages()
for _, message := range messages {
if strings.Contains(message.MsgRequest(), constants.MailWelcomeSubject) {
if err := checkWelcomeMail(&message); err != nil {
mail, err := utils.DecodeMail(message.MsgRequest())
if err != nil {
return err
}
} else if strings.Contains(message.MsgRequest(), constants.MailRegistrationSubject) {
if err := checkRegistrationMail(&message, &(*users)[0]); err != nil {
if strings.Contains(mail.Subject, constants.MailRegistrationSubject) {
if err := checkRegistrationMail(mail, &(*users)[0]); err != nil {
return err
}
} else if strings.Contains(message.MsgRequest(), constants.MailVerificationSubject) {
if err := checkVerificationMail(&message, &(*users)[0]); err != nil {
} else if strings.Contains(mail.Subject, constants.MailVerificationSubject) {
if err := checkVerificationMail(mail, &(*users)[0]); err != nil {
return err
}
verifiedUsers, err := Uc.Service.GetUsers(wantDBData)
if err != nil {
return err
}
if (*verifiedUsers)[0].Status != constants.VerifiedStatus {
return fmt.Errorf("Users status isn't verified after email verification. Status is: %#v", (*verifiedUsers)[0].Status)
}
} else {
return fmt.Errorf("Subject not expected: %v", message.MsgRequest())
return fmt.Errorf("Subject not expected: %v", mail.Subject)
}
}
}
return nil
}
func checkWelcomeMail(message *smtpmock.Message) error {
return nil
}
func checkWelcomeMail(message *utils.Email, user *models.User) error {
func checkRegistrationMail(message *smtpmock.Message, user *models.User) error {
if !strings.Contains(message.To, user.Email) {
return fmt.Errorf("Registration Information didn't reach the user! Recipient was: %v instead of %v", message.To, user.Email)
}
if !strings.Contains(message.From, config.SMTP.User) {
return fmt.Errorf("Registration Information was sent from unexpected address! Sender was: %v instead of %v", message.From, config.SMTP.User)
}
for _, responses := range message.RcpttoRequestResponse() {
if !strings.Contains(responses[0], AdminMail) {
return fmt.Errorf("Registration Information didn't reach admin! Recipient was: %v instead of %v", responses[0], AdminMail)
}
}
if !strings.Contains(message.MailfromRequest(), User) {
return fmt.Errorf("Registration Information was sent from unexpected address! Sender was: %v instead of %v", message.MailfromRequest(), User)
}
//Check if all the relevant data has been passed to the mail.
if !strings.Contains(message.MsgRequest(), user.FirstName+" "+user.LastName) {
return fmt.Errorf("User first and last name(%v) has not been rendered in registration mail.", user.FirstName+" "+user.LastName)
if !strings.Contains(message.Body, user.FirstName) {
return fmt.Errorf("User first name(%v) has not been rendered in registration mail.", user.FirstName)
}
if !strings.Contains(message.MsgRequest(), fmt.Sprintf("Preis/Monat</strong>: %v", user.Membership.SubscriptionModel.MonthlyFee)) {
if !strings.Contains(message.Body, fmt.Sprintf("Preis/Monat</strong>: %v", user.Membership.SubscriptionModel.MonthlyFee)) {
return fmt.Errorf("Users monthly subscription fee(%v) has not been rendered in registration mail.", user.Membership.SubscriptionModel.MonthlyFee)
}
if !strings.Contains(message.MsgRequest(), fmt.Sprintf("Preis/h</strong>: %v", user.Membership.SubscriptionModel.HourlyRate)) {
if !strings.Contains(message.Body, fmt.Sprintf("Preis/h</strong>: %v", user.Membership.SubscriptionModel.HourlyRate)) {
return fmt.Errorf("Users hourly subscription fee(%v) has not been rendered in registration mail.", user.Membership.SubscriptionModel.HourlyRate)
}
if user.Company != "" && !strings.Contains(message.MsgRequest(), user.Company) {
if user.Company != "" && !strings.Contains(message.Body, user.Company) {
return fmt.Errorf("Users Company(%v) has not been rendered in registration mail.", user.Company)
}
if !strings.Contains(message.MsgRequest(), fmt.Sprintf("Mitgliedsnr:</strong> %v", user.Membership.ID)) {
if !strings.Contains(message.Body, fmt.Sprintf("Mitgliedsnummer</strong>: %v", user.Membership.ID)) {
return fmt.Errorf("Users membership Id(%v) has not been rendered in registration mail.", user.Membership.ID)
}
if !strings.Contains(message.MsgRequest(), user.Address+","+user.ZipCode) {
return fmt.Errorf("Users address(%v) has not been rendered in registration mail.", user.Address+","+user.ZipCode)
}
if !strings.Contains(message.MsgRequest(), user.City) {
return fmt.Errorf("Users city(%v) has not been rendered in registration mail.", user.City)
}
if !strings.Contains(message.MsgRequest(), user.DateOfBirth.Format("20060102")) {
return fmt.Errorf("Users birthday(%v) has not been rendered in registration mail.", user.DateOfBirth.Format("20060102"))
}
if !strings.Contains(message.MsgRequest(), "Email:</strong> "+user.Email) {
return fmt.Errorf("Users email(%v) has not been rendered in registration mail.", user.Email)
}
if !strings.Contains(message.MsgRequest(), user.Phone) {
return fmt.Errorf("Users phone(%v) has not been rendered in registration mail.", user.Phone)
}
if !strings.Contains(message.MsgRequest(), user.BankAccount.IBAN) {
return fmt.Errorf("Users IBAN(%v) has not been rendered in registration mail.", user.BankAccount.IBAN)
}
if !strings.Contains(message.MsgRequest(), config.BaseURL) {
if !strings.Contains(message.Body, config.BaseURL) {
return fmt.Errorf("Base Url (%v) has not been rendered in registration mail.", config.BaseURL)
}
return nil
}
func checkVerificationMail(message *smtpmock.Message, user *models.User) error {
for _, responses := range message.RcpttoRequestResponse() {
if !strings.Contains(responses[0], "RCPT TO:<"+user.Email) {
return fmt.Errorf("Registration Information didn't reach client! Recipient was: %v instead of %v", responses[0], user.Email)
func checkRegistrationMail(message *utils.Email, user *models.User) error {
if !strings.Contains(message.To, config.Recipients.UserRegistration) {
return fmt.Errorf("Registration Information didn't reach admin! Recipient was: %v instead of %v", message.To, config.Recipients.UserRegistration)
}
if !strings.Contains(message.From, config.SMTP.User) {
return fmt.Errorf("Registration Information was sent from unexpected address! Sender was: %v instead of %v", message.From, config.SMTP.User)
}
// the email is encoded with a lowercase %3d while the url.encodeQuery returns an uppercase %3D. Therefore we remove the last char(padded base64 '='
if !strings.Contains(message.MsgRequest(), user.Verification.VerificationToken[:len(user.Verification.VerificationToken)-1]) {
return fmt.Errorf("Users Verification link token(%v) has not been rendered in email verification mail. %v", user.Verification.VerificationToken, message.MsgRequest())
//Check if all the relevant data has been passed to the mail.
if !strings.Contains(message.Body, user.FirstName+" "+user.LastName) {
return fmt.Errorf("User first and last name(%v) has not been rendered in registration mail.", user.FirstName+" "+user.LastName)
}
if !strings.Contains(message.MsgRequest(), config.BaseURL) {
if !strings.Contains(message.Body, fmt.Sprintf("Preis/Monat</strong>: %v", user.Membership.SubscriptionModel.MonthlyFee)) {
return fmt.Errorf("Users monthly subscription fee(%v) has not been rendered in registration mail.", user.Membership.SubscriptionModel.MonthlyFee)
}
if !strings.Contains(message.Body, fmt.Sprintf("Preis/h</strong>: %v", user.Membership.SubscriptionModel.HourlyRate)) {
return fmt.Errorf("Users hourly subscription fee(%v) has not been rendered in registration mail.", user.Membership.SubscriptionModel.HourlyRate)
}
if user.Company != "" && !strings.Contains(message.Body, user.Company) {
return fmt.Errorf("Users Company(%v) has not been rendered in registration mail.", user.Company)
}
if !strings.Contains(message.Body, fmt.Sprintf("Mitgliedsnr:</strong> %v", user.Membership.ID)) {
return fmt.Errorf("Users membership Id(%v) has not been rendered in registration mail.", user.Membership.ID)
}
if !strings.Contains(message.Body, user.Address+","+user.ZipCode) {
return fmt.Errorf("Users address(%v) has not been rendered in registration mail.", user.Address+" sv,"+user.ZipCode)
}
if !strings.Contains(message.Body, user.City) {
return fmt.Errorf("Users city(%v) has not been rendered in registration mail.", user.City)
}
if !strings.Contains(message.Body, user.DateOfBirth.Format("20060102")) {
return fmt.Errorf("Users birthday(%v) has not been rendered in registration mail.", user.DateOfBirth.Format("20060102"))
}
if !strings.Contains(message.Body, "Email:</strong> "+user.Email) {
return fmt.Errorf("Users email(%v) has not been rendered in registration mail.", user.Email)
}
if !strings.Contains(message.Body, user.Phone) {
return fmt.Errorf("Users phone(%v) has not been rendered in registration mail.", user.Phone)
}
if !strings.Contains(message.Body, user.BankAccount.IBAN) {
return fmt.Errorf("Users IBAN(%v) has not been rendered in registration mail.", user.BankAccount.IBAN)
}
if !strings.Contains(message.Body, config.BaseURL) {
return fmt.Errorf("Base Url (%v) has not been rendered in registration mail.", config.BaseURL)
}
return nil
}
func checkVerificationMail(message *utils.Email, user *models.User) error {
if !strings.Contains(message.To, user.Email) {
return fmt.Errorf("Registration Information didn't reach client! Recipient was: %v instead of %v", message.To, user.Email)
}
verificationURL, err := getVerificationURL(message.Body)
if err != nil {
return fmt.Errorf("Error parsing verification URL: %#v", err.Error())
}
if !strings.Contains(verificationURL, user.Verification.VerificationToken) {
return fmt.Errorf("Users Verification link token(%v) has not been rendered in email verification mail. %v", user.Verification.VerificationToken, verificationURL)
}
if !strings.Contains(message.Body, config.BaseURL) {
return fmt.Errorf("Base Url (%v) has not been rendered in email verification mail.", config.BaseURL)
}
return nil
}
func initSubscriptionPlans() error {
subscription := models.SubscriptionModel{
Name: "Basic",
Details: "Test Plan",
MonthlyFee: 2,
HourlyRate: 3,
// open the provided link:
if err := verifyMail(verificationURL); err != nil {
return err
}
result := database.DB.Create(&subscription)
if result.Error != nil {
return result.Error
}
return nil
}
func getMockedContext(jsonStr []byte) (*gin.Context, *httptest.ResponseRecorder) {
gin.SetMode(gin.TestMode)
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
var err error
c.Request, err = http.NewRequest("POST", "/register", bytes.NewBuffer(jsonStr))
if err != nil {
logger.Error.Fatalf("Failed to create new Request: %#v", err)
}
c.Request.Header.Set("Content-Type", "application/json")
return c, w
}
func deleteTestDB(dbPath string) error {
err := os.Remove(dbPath)
messages := utils.SMTPGetMessages()
for _, message := range messages {
mail, err := utils.DecodeMail(message.MsgRequest())
if err != nil {
return err
}
if err := checkWelcomeMail(mail, user); err != nil {
return err
}
}
return nil
}
func verifyMail(verificationURL string) error {
gin.SetMode(gin.TestMode)
router := gin.New()
router.LoadHTMLGlob(filepath.Join(config.Templates.HTMLPath, "*")) // Adjust the path to your HTML templates
router.GET("/backend/verify", Uc.VerifyMailHandler)
wv := httptest.NewRecorder()
cv, _ := gin.CreateTestContext(wv)
var err error
cv.Request, err = http.NewRequest("GET", verificationURL, nil)
if err != nil {
return fmt.Errorf("Failed to create new GET Request: %v", err.Error())
}
router.ServeHTTP(wv, cv.Request)
if wv.Code != 200 {
return fmt.Errorf("Didn't get the expected response code: got: %v; expected: %v", wv.Code, 200)
}
return nil
}
func getVerificationURL(mailBody string) (string, error) {
re := regexp.MustCompile(`"([^"]*verify[^"]*)"`)
// Find the matching URL in the email content
match := re.FindStringSubmatch(mailBody)
if len(match) == 0 {
return "", fmt.Errorf("No mail verification link found in email body: %#v", mailBody)
}
verificationURL, err := url.QueryUnescape(match[1])
if err != nil {
return "", fmt.Errorf("Error decoding URL: %v", err)
}
logger.Info.Printf("VerificationURL: %#v", verificationURL)
return verificationURL, nil
}
// TEST DATA:
@@ -283,158 +267,151 @@ func getBaseUser() models.User {
}
}
func generateInputJSON(customize func(models.User) models.User) string {
func customizeInput(customize func(models.User) models.User) *RegistrationData {
user := getBaseUser()
user = customize(user) // Apply the customization
regData := RegistrationData{User: user}
jsonBytes, err := json.Marshal(regData)
if err != nil {
logger.Error.Printf("couldn't generate Json from User: %#v\nERROR: %#v", regData, err)
return ""
}
return string(jsonBytes)
return &RegistrationData{User: user}
}
func getTestUsers() []test {
return []test{
func getTestUsers() []RegisterUserTest {
return []RegisterUserTest{
{
name: "birthday < 18 should fail",
wantResponse: http.StatusNotAcceptable,
wantDBData: map[string]interface{}{"email": "john.doe@example.com"},
assert: false,
input: generateInputJSON(func(user models.User) models.User {
Name: "birthday < 18 should fail",
WantResponse: http.StatusNotAcceptable,
WantDBData: map[string]interface{}{"email": "john.doe@example.com"},
Assert: false,
Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
user.DateOfBirth = time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC)
return user
}),
})),
},
{
name: "FirstName empty, should fail",
wantResponse: http.StatusNotAcceptable,
wantDBData: map[string]interface{}{"email": "john.doe@example.com"},
assert: false,
input: generateInputJSON(func(user models.User) models.User {
Name: "FirstName empty, should fail",
WantResponse: http.StatusNotAcceptable,
WantDBData: map[string]interface{}{"email": "john.doe@example.com"},
Assert: false,
Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
user.FirstName = ""
return user
}),
})),
},
{
name: "LastName Empty should fail",
wantResponse: http.StatusNotAcceptable,
wantDBData: map[string]interface{}{"email": "john.doe@example.com"},
assert: false,
input: generateInputJSON(func(user models.User) models.User {
Name: "LastName Empty should fail",
WantResponse: http.StatusNotAcceptable,
WantDBData: map[string]interface{}{"email": "john.doe@example.com"},
Assert: false,
Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
user.LastName = ""
return user
}),
})),
},
{
name: "EMail wrong format should fail",
wantResponse: http.StatusNotAcceptable,
wantDBData: map[string]interface{}{"email": "johnexample.com"},
assert: false,
input: generateInputJSON(func(user models.User) models.User {
Name: "EMail wrong format should fail",
WantResponse: http.StatusNotAcceptable,
WantDBData: map[string]interface{}{"email": "johnexample.com"},
Assert: false,
Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
user.Email = "johnexample.com"
return user
}),
})),
},
{
name: "Missing Zip Code should fail",
wantResponse: http.StatusNotAcceptable,
wantDBData: map[string]interface{}{"email": "john.doe@example.com"},
assert: false,
input: generateInputJSON(func(user models.User) models.User {
Name: "Missing Zip Code should fail",
WantResponse: http.StatusNotAcceptable,
WantDBData: map[string]interface{}{"email": "john.doe@example.com"},
Assert: false,
Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
user.ZipCode = ""
return user
}),
})),
},
{
name: "Missing Address should fail",
wantResponse: http.StatusNotAcceptable,
wantDBData: map[string]interface{}{"email": "john.doe@example.com"},
assert: false,
input: generateInputJSON(func(user models.User) models.User {
Name: "Missing Address should fail",
WantResponse: http.StatusNotAcceptable,
WantDBData: map[string]interface{}{"email": "john.doe@example.com"},
Assert: false,
Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
user.Address = ""
return user
}),
})),
},
{
name: "Missing City should fail",
wantResponse: http.StatusNotAcceptable,
wantDBData: map[string]interface{}{"email": "john.doe@example.com"},
assert: false,
input: generateInputJSON(func(user models.User) models.User {
Name: "Missing City should fail",
WantResponse: http.StatusNotAcceptable,
WantDBData: map[string]interface{}{"email": "john.doe@example.com"},
Assert: false,
Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
user.City = ""
return user
}),
})),
},
{
name: "Missing IBAN should fail",
wantResponse: http.StatusNotAcceptable,
wantDBData: map[string]interface{}{"email": "john.doe@example.com"},
assert: false,
input: generateInputJSON(func(user models.User) models.User {
Name: "Missing IBAN should fail",
WantResponse: http.StatusNotAcceptable,
WantDBData: map[string]interface{}{"email": "john.doe@example.com"},
Assert: false,
Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
user.BankAccount.IBAN = ""
return user
}),
})),
},
{
name: "Invalid IBAN should fail",
wantResponse: http.StatusNotAcceptable,
wantDBData: map[string]interface{}{"email": "john.doe@example.com"},
assert: false,
input: generateInputJSON(func(user models.User) models.User {
Name: "Invalid IBAN should fail",
WantResponse: http.StatusNotAcceptable,
WantDBData: map[string]interface{}{"email": "john.doe@example.com"},
Assert: false,
Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
user.BankAccount.IBAN = "DE1234234123134"
return user
}),
})),
},
{
name: "Missing subscription plan should fail",
wantResponse: http.StatusNotAcceptable,
wantDBData: map[string]interface{}{"email": "john.doe@example.com"},
assert: false,
input: generateInputJSON(func(user models.User) models.User {
Name: "Missing subscription plan should fail",
WantResponse: http.StatusNotAcceptable,
WantDBData: map[string]interface{}{"email": "john.doe@example.com"},
Assert: false,
Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
user.Membership.SubscriptionModel.Name = ""
return user
}),
})),
},
{
name: "Invalid subscription plan should fail",
wantResponse: http.StatusNotFound,
wantDBData: map[string]interface{}{"email": "john.doe@example.com"},
assert: false,
input: generateInputJSON(func(user models.User) models.User {
Name: "Invalid subscription plan should fail",
WantResponse: http.StatusNotFound,
WantDBData: map[string]interface{}{"email": "john.doe@example.com"},
Assert: false,
Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
user.Membership.SubscriptionModel.Name = "NOTEXISTENTPLAN"
return user
}),
})),
},
{
name: "Correct Entry should pass",
wantResponse: http.StatusCreated,
wantDBData: map[string]interface{}{"Email": "john.doe@example.com"},
assert: true,
input: generateInputJSON(func(user models.User) models.User { return user }),
Name: "Correct Entry should pass",
WantResponse: http.StatusCreated,
WantDBData: map[string]interface{}{"Email": "john.doe@example.com"},
Assert: true,
Input: GenerateInputJSON(customizeInput(func(user models.User) models.User { return user })),
},
{
name: "Email duplicate should fail",
wantResponse: http.StatusConflict,
wantDBData: map[string]interface{}{"first_name": "Jane"},
assert: false,
input: generateInputJSON(func(user models.User) models.User {
Name: "Email duplicate should fail",
WantResponse: http.StatusConflict,
WantDBData: map[string]interface{}{"first_name": "Jane"},
Assert: false,
Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
user.FirstName = "Jane"
return user
}),
})),
},
{
name: "Company present should pass",
wantResponse: http.StatusCreated,
wantDBData: map[string]interface{}{"Email": "john.doe2@example.com"},
assert: true,
input: generateInputJSON(func(user models.User) models.User {
Name: "Company present should pass",
WantResponse: http.StatusCreated,
WantDBData: map[string]interface{}{"Email": "john.doe2@example.com"},
Assert: true,
Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
user.Email = "john.doe2@example.com"
user.Company = "ACME"
return user
}),
})),
},
}
}

View File

@@ -3,6 +3,8 @@ package repositories
import (
"GoMembership/internal/database"
"gorm.io/gorm"
"GoMembership/internal/models"
)
@@ -10,6 +12,7 @@ type SubscriptionModelsRepositoryInterface interface {
CreateSubscriptionModel(subscriptionModel *models.SubscriptionModel) (int64, error)
GetMembershipModelNames() ([]string, error)
GetModelByName(modelname *string) (*models.SubscriptionModel, error)
GetSubscriptions(where map[string]interface{}) (*[]models.SubscriptionModel, error)
}
type SubscriptionModelsRepository struct{}
@@ -38,3 +41,15 @@ func (sr *SubscriptionModelsRepository) GetMembershipModelNames() ([]string, err
}
return names, nil
}
func (sr *SubscriptionModelsRepository) GetSubscriptions(where map[string]interface{}) (*[]models.SubscriptionModel, error) {
var subscriptions []models.SubscriptionModel
result := database.DB.Where(where).Find(&subscriptions)
if result.Error != nil {
if result.Error == gorm.ErrRecordNotFound {
return nil, gorm.ErrRecordNotFound
}
return nil, result.Error
}
return &subscriptions, nil
}

View File

@@ -6,7 +6,7 @@ import (
"github.com/gin-gonic/gin"
)
func RegisterRoutes(router *gin.Engine, userController *controllers.UserController, membershipcontroller *controllers.MembershipController) {
func RegisterRoutes(router *gin.Engine, userController *controllers.UserController, membershipcontroller *controllers.MembershipController, contactController *controllers.ContactController) {
router.GET("/backend/verify", userController.VerifyMailHandler)
router.POST("/backend/api/register", userController.RegisterUser)
router.POST("/backend/api/register/subscription", membershipcontroller.RegisterSubscription)

View File

@@ -30,7 +30,7 @@ func Run() {
logger.Error.Fatalf("Couldn't init database: %v", err)
}
emailService := services.NewEmailService(config.SMTP.Host, config.SMTP.Port, config.SMTP.User, config.SMTP.Password, config.SMTP.AdminEmail)
emailService := services.NewEmailService(config.SMTP.Host, config.SMTP.Port, config.SMTP.User, config.SMTP.Password)
var consentRepo repositories.ConsentRepositoryInterface = &repositories.ConsentRepository{}
consentService := &services.ConsentService{Repo: consentRepo}
@@ -47,6 +47,7 @@ func Run() {
userController := &controllers.UserController{Service: userService, EmailService: emailService, ConsentService: consentService, BankAccountService: bankAccountService, MembershipService: membershipService}
membershipController := &controllers.MembershipController{Service: *membershipService}
contactController := &controllers.ContactController{EmailService: emailService}
router := gin.Default()
// gin.SetMode(gin.ReleaseMode)
router.Static(config.Templates.StaticPath, "./style")
@@ -55,7 +56,7 @@ func Run() {
router.Use(gin.Logger())
// router.Use(middlewares.LoggerMiddleware())
routes.RegisterRoutes(router, userController, membershipController)
routes.RegisterRoutes(router, userController, membershipController, contactController)
// create subrouter for teh authenticated area /account
// also pthprefix matches everything below /account
// accountRouter := router.PathPrefix("/account").Subrouter()

View File

@@ -14,12 +14,11 @@ import (
type EmailService struct {
dialer *gomail.Dialer
adminEmail string
}
func NewEmailService(host string, port int, username, password, adminEmail string) *EmailService {
func NewEmailService(host string, port int, username string, password string) *EmailService {
dialer := gomail.NewDialer(host, port, username, password)
return &EmailService{dialer: dialer, adminEmail: adminEmail}
return &EmailService{dialer: dialer}
}
func (s *EmailService) SendEmail(to string, subject string, body string) error {
@@ -87,10 +86,10 @@ func (s *EmailService) SendWelcomeEmail(user *models.User) error {
Company string
FirstName string
MembershipModel string
BASEURL string
MembershipID int64
MembershipFee float32
RentalFee float32
BASEURL string
}{
Company: user.Company,
FirstName: user.FirstName,
@@ -110,24 +109,24 @@ func (s *EmailService) SendWelcomeEmail(user *models.User) error {
return s.SendEmail(user.Email, subject, body)
}
func (s *EmailService) NotifyAdminOfNewUser(user *models.User) error {
func (s *EmailService) SendRegistrationNotification(user *models.User) error {
// Prepare data to be injected into the template
data := struct {
City string
Email string
FirstName string
DateOfBirth string
LastName string
MembershipModel string
Address string
IBAN string
FirstName string
Email string
Phone string
DateOfBirth string
City string
Company string
ZipCode string
BASEURL string
MembershipID int64
RentalFee float32
MembershipFee float32
BASEURL string
}{
Company: user.Company,
FirstName: user.FirstName,
@@ -152,7 +151,7 @@ func (s *EmailService) NotifyAdminOfNewUser(user *models.User) error {
logger.Error.Print("Couldn't send admin notification mail")
return err
}
return s.SendEmail(config.SMTP.AdminEmail, subject, body)
return s.SendEmail(config.Recipients.UserRegistration, subject, body)
}
func (s *EmailService) RelayContactFormMessage(sender string, name string, message string) error {
@@ -171,5 +170,5 @@ func (s *EmailService) RelayContactFormMessage(sender string, name string, messa
logger.Error.Print("Couldn't send contact form message mail")
return err
}
return s.SendEmail(config.SMTP.AdminEmail, subject, body)
return s.SendEmail(config.Recipients.ContactForm, subject, body)
}

View File

@@ -1,11 +1,15 @@
package services
import (
"net/http"
"slices"
"time"
"github.com/go-playground/validator/v10"
"GoMembership/internal/models"
"GoMembership/internal/repositories"
"GoMembership/pkg/errors"
"slices"
"time"
)
type MembershipServiceInterface interface {
@@ -14,6 +18,7 @@ type MembershipServiceInterface interface {
RegisterSubscription(subscription *models.SubscriptionModel) (int64, error)
GetMembershipModelNames() ([]string, error)
GetModelByName(modelname *string) (*models.SubscriptionModel, error)
GetSubscriptions(where map[string]interface{}) (*[]models.SubscriptionModel, error)
}
type MembershipService struct {
@@ -32,6 +37,9 @@ func (service *MembershipService) FindMembershipByUserID(userID int64) (*models.
// Membership_Subscriptions
func (service *MembershipService) RegisterSubscription(subscription *models.SubscriptionModel) (int64, error) {
if err := validateSubscriptionData(subscription); err != nil {
return http.StatusNotAcceptable, err
}
return service.SubscriptionRepo.CreateSubscriptionModel(subscription)
}
@@ -50,3 +58,15 @@ func (service *MembershipService) GetModelByName(modelname *string) (*models.Sub
}
return service.SubscriptionRepo.GetModelByName(modelname)
}
func (service *MembershipService) GetSubscriptions(where map[string]interface{}) (*[]models.SubscriptionModel, error) {
return service.SubscriptionRepo.GetSubscriptions(where)
}
func validateSubscriptionData(subscription *models.SubscriptionModel) error {
validate := validator.New()
validate.RegisterValidation("subscriptionModel", func(fl validator.FieldLevel) bool { return true })
validate.RegisterValidation("membershipField", func(fl validator.FieldLevel) bool { return true })
return validate.Struct(subscription)
}

View File

@@ -34,8 +34,7 @@ func (service *UserService) RegisterUser(user *models.User) (int64, string, erro
}
user.Salt = base64.StdEncoding.EncodeToString(salt)
*/
err := validateRegistrationData(user)
if err != nil {
if err := validateRegistrationData(user); err != nil {
return http.StatusNotAcceptable, "", err
}

View File

@@ -1,10 +1,26 @@
package utils
import (
"bytes"
"crypto/rand"
"encoding/base64"
"io"
"mime"
"mime/quotedprintable"
"net/mail"
"strings"
)
type Email struct {
MimeVersion string
Date string
From string
To string
Subject string
ContentType string
Body string
}
func GenerateRandomString(length int) (string, error) {
bytes := make([]byte, length)
_, err := rand.Read(bytes)
@@ -17,3 +33,70 @@ func GenerateRandomString(length int) (string, error) {
func GenerateVerificationToken() (string, error) {
return GenerateRandomString(32)
}
func DecodeMail(message string) (*Email, error) {
msg, err := mail.ReadMessage(strings.NewReader(message))
if err != nil {
return nil, err
}
decodedBody, err := io.ReadAll(msg.Body)
if err != nil {
return nil, err
}
decodedBodyString, err := DecodeQuotedPrintable(string(decodedBody))
if err != nil {
return nil, err
}
decodedSubject, err := DecodeRFC2047(msg.Header.Get("Subject"))
if err != nil {
return nil, err
}
email := &Email{}
// Populate the headers
email.MimeVersion = msg.Header.Get("Mime-Version")
email.Date = msg.Header.Get("Date")
email.From = msg.Header.Get("From")
email.To = msg.Header.Get("To")
email.Subject = decodedSubject
email.Body = decodedBodyString
email.ContentType = msg.Header.Get("Content-Type")
return email, nil
}
func DecodeRFC2047(encoded string) (string, error) {
decoder := new(mime.WordDecoder)
decoded, err := decoder.DecodeHeader(encoded)
if err != nil {
return "", err
}
return decoded, nil
}
func DecodeQuotedPrintable(encodedString string) (string, error) {
// Decode quoted-printable encoding
reader := quotedprintable.NewReader(strings.NewReader(encodedString))
decodedBytes := new(bytes.Buffer)
_, err := decodedBytes.ReadFrom(reader)
if err != nil {
return "", err
}
return decodedBytes.String(), nil
}
func EncodeQuotedPrintable(s string) string {
var buf bytes.Buffer
// Use Quoted-Printable encoder
qp := quotedprintable.NewWriter(&buf)
// Write the UTF-8 encoded string to the Quoted-Printable encoder
qp.Write([]byte(s))
qp.Close()
// Encode the result into a MIME header
return mime.QEncoding.Encode("UTF-8", buf.String())
}