Compare commits

...

7 Commits

Author SHA1 Message Date
Alex
dbf7aca078 add: Tests for driverslicence 2024-09-29 21:18:02 +02:00
Alex
b99a5010a7 add default for userCreation 2024-09-29 21:17:25 +02:00
Alex
8a581da1d8 add driversLicence model 2024-09-29 21:16:58 +02:00
Alex
41738753f0 add subscriptions to currentUser JSON for frontend 2024-09-29 21:14:03 +02:00
Alex
33561692b6 membership status now int; add auto SEPA mandateref upon user creation 2024-09-29 21:12:49 +02:00
Alex
31cfe21695 clean up of bank-service 2024-09-29 21:08:59 +02:00
Alex
36bd75bbeb add where clause default to membershipservice 2024-09-29 21:07:22 +02:00
14 changed files with 376 additions and 117 deletions

View File

@@ -7,6 +7,25 @@ type roles struct {
Admin int8 Admin int8
} }
type licences struct {
AM string
A1 string
A2 string
A string
B string
C1 string
C string
D1 string
D string
BE string
C1E string
CE string
D1E string
DE string
L string
T string
}
const ( const (
UnverifiedStatus = iota + 1 UnverifiedStatus = iota + 1
VerifiedStatus VerifiedStatus
@@ -29,6 +48,25 @@ var Roles = roles{
Admin: 8, Admin: 8,
} }
var Licences = licences{
AM: "AM",
A1: "A1",
A2: "A2",
A: "A",
B: "B",
C1: "C1",
C: "C",
D1: "D1",
D: "D",
BE: "BE",
C1E: "C1E",
CE: "CE",
D1E: "D1E",
DE: "DE",
L: "L",
T: "T",
}
const PRIV_VIEW = 1 const PRIV_VIEW = 1
const PRIV_ADD = 2 const PRIV_ADD = 2
const PRIV_EDIT = 4 const PRIV_EDIT = 4

View File

@@ -98,8 +98,9 @@ func TestSuite(t *testing.T) {
var subscriptionRepo repositories.SubscriptionModelsRepositoryInterface = &repositories.SubscriptionModelsRepository{} var subscriptionRepo repositories.SubscriptionModelsRepositoryInterface = &repositories.SubscriptionModelsRepository{}
membershipService := &services.MembershipService{Repo: membershipRepo, SubscriptionRepo: subscriptionRepo} membershipService := &services.MembershipService{Repo: membershipRepo, SubscriptionRepo: subscriptionRepo}
var licenceRepo repositories.DriversLicenceInterface = &repositories.DriversLicenceRepository{}
var userRepo repositories.UserRepositoryInterface = &repositories.UserRepository{} var userRepo repositories.UserRepositoryInterface = &repositories.UserRepository{}
userService := &services.UserService{Repo: userRepo} userService := &services.UserService{Repo: userRepo, Licences: licenceRepo}
Uc = &UserController{Service: userService, EmailService: emailService, ConsentService: consentService, BankAccountService: bankAccountService, MembershipService: membershipService} Uc = &UserController{Service: userService, EmailService: emailService, ConsentService: consentService, BankAccountService: bankAccountService, MembershipService: membershipService}
Mc = &MembershipController{Service: *membershipService} Mc = &MembershipController{Service: *membershipService}
@@ -133,9 +134,9 @@ func TestSuite(t *testing.T) {
log.Fatalf("Failed to stop SMTP Mockup Server: %#v", err) log.Fatalf("Failed to stop SMTP Mockup Server: %#v", err)
} }
if err := deleteTestDB("test.db"); err != nil { // if err := deleteTestDB("test.db"); err != nil {
log.Fatalf("Failed to tear down DB: %#v", err) // log.Fatalf("Failed to tear down DB: %#v", err)
} // }
} }
func initSubscriptionPlans() error { func initSubscriptionPlans() error {
@@ -221,6 +222,7 @@ func getBaseUser() models.User {
Phone: "01738484993", Phone: "01738484993",
BankAccount: models.BankAccount{IBAN: "DE89370400440532013000"}, BankAccount: models.BankAccount{IBAN: "DE89370400440532013000"},
Membership: models.Membership{SubscriptionModel: models.SubscriptionModel{Name: "Basic"}}, Membership: models.Membership{SubscriptionModel: models.SubscriptionModel{Name: "Basic"}},
DriversLicence: models.DriversLicence{},
ProfilePicture: "", ProfilePicture: "",
Password: "password123", Password: "password123",
Company: "", Company: "",

View File

@@ -61,6 +61,18 @@ func (uc *UserController) UpdateHandler(c *gin.Context) {
c.JSON(http.StatusForbidden, gin.H{"error": "You are not authorized to update this user"}) c.JSON(http.StatusForbidden, gin.H{"error": "You are not authorized to update this user"})
return return
} }
if user.Membership.SubscriptionModel.Name == "" {
logger.Error.Printf("No subscription model provided: %v", user.Email)
c.JSON(http.StatusNotAcceptable, gin.H{"error": "No subscription model provided"})
return
}
selectedModel, err := uc.MembershipService.GetModelByName(&user.Membership.SubscriptionModel.Name)
if err != nil {
logger.Error.Printf("%v:No subscription model found: %#v", user.Email, err)
c.JSON(http.StatusNotFound, gin.H{"error": "Not a valid subscription model"})
return
}
user.Membership.SubscriptionModel = *selectedModel
// TODO: If it's not an admin, prevent changes to critical fields // TODO: If it's not an admin, prevent changes to critical fields
// if userRole != constants.Roles.Admin { // if userRole != constants.Roles.Admin {
// existingUser, err := uc.Service.GetUserByID(jwtUserID) // existingUser, err := uc.Service.GetUserByID(jwtUserID)
@@ -111,7 +123,17 @@ func (uc *UserController) CurrentUserHandler(c *gin.Context) {
return return
} }
c.JSON(http.StatusOK, user.Safe()) subscriptions, err := uc.MembershipService.GetSubscriptions(nil)
if err != nil {
logger.Error.Printf("Error retrieving subscriptions: %v", err)
c.JSON(http.StatusInternalServerError, gin.H{"error": "Error retrieving subscriptions."})
return
}
c.JSON(http.StatusOK, gin.H{
"user": user.Safe(),
"subscriptions": subscriptions,
})
} }
func (uc *UserController) LogoutHandler(c *gin.Context) { func (uc *UserController) LogoutHandler(c *gin.Context) {
@@ -187,7 +209,6 @@ func (uc *UserController) RegisterUser(c *gin.Context) {
c.JSON(http.StatusNotAcceptable, gin.H{"error": "No subscription model provided"}) c.JSON(http.StatusNotAcceptable, gin.H{"error": "No subscription model provided"})
return return
} }
logger.Error.Printf("user.membership: %#v", regData.User.Membership)
selectedModel, err := uc.MembershipService.GetModelByName(&regData.User.Membership.SubscriptionModel.Name) selectedModel, err := uc.MembershipService.GetModelByName(&regData.User.Membership.SubscriptionModel.Name)
if err != nil { if err != nil {
logger.Error.Printf("%v:No subscription model found: %#v", regData.User.Email, err) logger.Error.Printf("%v:No subscription model found: %#v", regData.User.Email, err)
@@ -195,7 +216,6 @@ func (uc *UserController) RegisterUser(c *gin.Context) {
return return
} }
regData.User.Membership.SubscriptionModel = *selectedModel regData.User.Membership.SubscriptionModel = *selectedModel
// logger.Info.Printf("REGISTERING user: %#v", regData.User)
regData.User.RoleID = constants.Roles.Member regData.User.RoleID = constants.Roles.Member

View File

@@ -22,6 +22,7 @@ import (
"GoMembership/internal/constants" "GoMembership/internal/constants"
"GoMembership/internal/middlewares" "GoMembership/internal/middlewares"
"GoMembership/internal/models" "GoMembership/internal/models"
"GoMembership/internal/repositories"
"GoMembership/internal/utils" "GoMembership/internal/utils"
"GoMembership/pkg/logger" "GoMembership/pkg/logger"
@@ -285,7 +286,7 @@ func testCurrentUserHandler(t *testing.T, loginEmail string, loginCookie http.Co
for _, tt := range tests { for _, tt := range tests {
logger.Error.Print("==============================================================") logger.Error.Print("==============================================================")
logger.Error.Printf("Testing : %v", tt.name) logger.Error.Printf("CurrentUser Testing : %v", tt.name)
logger.Error.Print("==============================================================") logger.Error.Print("==============================================================")
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
@@ -303,11 +304,14 @@ func testCurrentUserHandler(t *testing.T, loginEmail string, loginCookie http.Co
assert.Equal(t, tt.expectedStatus, w.Code) assert.Equal(t, tt.expectedStatus, w.Code)
if tt.expectedStatus == http.StatusOK { if tt.expectedStatus == http.StatusOK {
var response models.User var response struct {
User models.User `json:"user"`
Subscriptions []models.SubscriptionModel `json:"subscriptions"`
}
err := json.Unmarshal(w.Body.Bytes(), &response) err := json.Unmarshal(w.Body.Bytes(), &response)
assert.NoError(t, err) assert.NoError(t, err)
// logger.Error.Printf("response: %#v", response)
assert.Equal(t, tt.expectedUserMail, response.Email) assert.Equal(t, tt.expectedUserMail, response.User.Email)
var newCookie *http.Cookie var newCookie *http.Cookie
for _, cookie := range w.Result().Cookies() { for _, cookie := range w.Result().Cookies() {
if cookie.Name == "jwt" { if cookie.Name == "jwt" {
@@ -346,6 +350,18 @@ func validateUser(assert bool, wantDBData map[string]interface{}) error {
return fmt.Errorf("User entry query didn't met expectation: %v != %#v", assert, *users) return fmt.Errorf("User entry query didn't met expectation: %v != %#v", assert, *users)
} }
if assert { if assert {
user := (*users)[0]
// Check for mandate reference
if user.BankAccount.MandateReference == "" {
return fmt.Errorf("Mandate reference not generated for user: %s", user.Email)
}
// Validate mandate reference format
expected := user.GenerateMandateReference()
if !strings.HasPrefix(user.BankAccount.MandateReference, expected) {
return fmt.Errorf("Mandate reference is invalid. Expected: %s, Got: %s", expected, user.BankAccount.MandateReference)
}
//check for email delivery //check for email delivery
messages := utils.SMTPGetMessages() messages := utils.SMTPGetMessages()
for _, message := range messages { for _, message := range messages {
@@ -355,12 +371,12 @@ func validateUser(assert bool, wantDBData map[string]interface{}) error {
return err return err
} }
if strings.Contains(mail.Subject, constants.MailRegistrationSubject) { if strings.Contains(mail.Subject, constants.MailRegistrationSubject) {
if err := checkRegistrationMail(mail, &(*users)[0]); err != nil { if err := checkRegistrationMail(mail, &user); err != nil {
logger.Error.Printf("Error in checkRegistrationMail: %#v", err) logger.Error.Printf("Error in checkRegistrationMail: %#v", err)
return err return err
} }
} else if strings.Contains(mail.Subject, constants.MailVerificationSubject) { } else if strings.Contains(mail.Subject, constants.MailVerificationSubject) {
if err := checkVerificationMail(mail, &(*users)[0]); err != nil { if err := checkVerificationMail(mail, &user); err != nil {
logger.Error.Printf("Error in checkVerificationMail: %#v", err) logger.Error.Printf("Error in checkVerificationMail: %#v", err)
return err return err
} }
@@ -413,16 +429,6 @@ func testUpdateUser(t *testing.T, loginEmail string, loginCookie http.Cookie) {
}, },
expectedStatus: http.StatusAccepted, expectedStatus: http.StatusAccepted,
}, },
{
name: "Password Update",
setupCookie: func(req *http.Request) {
req.AddCookie(&loginCookie)
},
updateFunc: func(u *models.User) {
u.Password = "NewPassword"
},
expectedStatus: http.StatusAccepted,
},
{ {
name: "Valid Update, invalid cookie", name: "Valid Update, invalid cookie",
setupCookie: func(req *http.Request) { setupCookie: func(req *http.Request) {
@@ -444,11 +450,80 @@ func testUpdateUser(t *testing.T, loginEmail string, loginCookie http.Cookie) {
}, },
updateFunc: func(u *models.User) { updateFunc: func(u *models.User) {
u.Password = "" u.Password = ""
u.FirstName = "John Updated"
u.LastName = "Doe Updated"
u.Phone = "01738484994"
u.Email = "invalid-email" u.Email = "invalid-email"
}, },
expectedStatus: http.StatusBadRequest, expectedStatus: http.StatusBadRequest,
expectedError: "Invalid user data", expectedError: "Invalid user data",
}, },
{
name: "Change LicenceNumber",
setupCookie: func(req *http.Request) {
req.AddCookie(&loginCookie)
},
updateFunc: func(u *models.User) {
u.Password = ""
u.FirstName = "John Updated"
u.LastName = "Doe Updated"
u.Phone = "01738484994"
u.DriversLicence.LicenceNumber = "NEWNUMBER"
},
expectedStatus: http.StatusAccepted,
},
{
name: "Add category",
setupCookie: func(req *http.Request) {
req.AddCookie(&loginCookie)
},
updateFunc: func(u *models.User) {
u.Password = ""
u.FirstName = "John Updated"
u.LastName = "Doe Updated"
u.Phone = "01738484994"
u.DriversLicence.LicenceNumber = "NEWNUMBER"
var licenceRepo repositories.DriversLicenceInterface = &repositories.DriversLicenceRepository{}
category, err := licenceRepo.FindCategoryByName("B")
assert.NoError(t, err)
u.DriversLicence.LicenceCategories = []models.LicenceCategory{category}
},
expectedStatus: http.StatusAccepted,
},
{
name: "Add 2 categories",
setupCookie: func(req *http.Request) {
req.AddCookie(&loginCookie)
},
updateFunc: func(u *models.User) {
u.Password = ""
u.FirstName = "John Updated"
u.LastName = "Doe Updated"
u.Phone = "01738484994"
u.DriversLicence.LicenceNumber = "NEWNUMBER"
var licenceRepo repositories.DriversLicenceInterface = &repositories.DriversLicenceRepository{}
category, err := licenceRepo.FindCategoryByName("B")
category2, err := licenceRepo.FindCategoryByName("BE")
assert.NoError(t, err)
u.DriversLicence.LicenceCategories = []models.LicenceCategory{category, category2}
},
expectedStatus: http.StatusAccepted,
},
{
name: "Delete all categories",
setupCookie: func(req *http.Request) {
req.AddCookie(&loginCookie)
},
updateFunc: func(u *models.User) {
u.Password = ""
u.FirstName = "John Updated"
u.LastName = "Doe Updated"
u.Phone = "01738484994"
u.DriversLicence.LicenceNumber = "NEWNUMBER"
u.DriversLicence.LicenceCategories = []models.LicenceCategory{}
},
expectedStatus: http.StatusAccepted,
},
{ {
name: "User ID mismatch while not admin", name: "User ID mismatch while not admin",
setupCookie: func(req *http.Request) { setupCookie: func(req *http.Request) {
@@ -457,11 +532,28 @@ func testUpdateUser(t *testing.T, loginEmail string, loginCookie http.Cookie) {
updateFunc: func(u *models.User) { updateFunc: func(u *models.User) {
u.Password = "" u.Password = ""
u.ID = 1 u.ID = 1
u.LastName = "Doe Updated"
u.Phone = "01738484994"
u.DriversLicence.LicenceNumber = "NEWNUMBER"
u.FirstName = "John Missing ID" u.FirstName = "John Missing ID"
}, },
expectedStatus: http.StatusForbidden, expectedStatus: http.StatusForbidden,
expectedError: "You are not authorized to update this user", expectedError: "You are not authorized to update this user",
}, },
{
name: "Password Update",
setupCookie: func(req *http.Request) {
req.AddCookie(&loginCookie)
},
updateFunc: func(u *models.User) {
u.Password = ""
u.LastName = "Doe Updated"
u.Phone = "01738484994"
u.DriversLicence.LicenceNumber = "NEWNUMBER"
u.Password = "NewPassword"
},
expectedStatus: http.StatusAccepted,
},
// { // {
// name: "Non-existent User", // name: "Non-existent User",
// setupCookie: func(req *http.Request) { // setupCookie: func(req *http.Request) {
@@ -476,7 +568,6 @@ func testUpdateUser(t *testing.T, loginEmail string, loginCookie http.Cookie) {
// expectedError: "User not found", // expectedError: "User not found",
// }, // },
} }
for _, tt := range tests { for _, tt := range tests {
logger.Error.Print("==============================================================") logger.Error.Print("==============================================================")
logger.Error.Printf("Update Testing : %v", tt.name) logger.Error.Printf("Update Testing : %v", tt.name)
@@ -485,13 +576,13 @@ func testUpdateUser(t *testing.T, loginEmail string, loginCookie http.Cookie) {
// Create a copy of the user and apply the updates // Create a copy of the user and apply the updates
updatedUser := user updatedUser := user
tt.updateFunc(&updatedUser) tt.updateFunc(&updatedUser)
// Convert user to JSON // Convert user to JSON
jsonData, err := json.Marshal(updatedUser) jsonData, err := json.Marshal(updatedUser)
if err != nil { if err != nil {
t.Fatalf("Failed to marshal user data: %v", err) t.Fatalf("Failed to marshal user data: %v", err)
} }
logger.Error.Printf("Updated User: %#v", updatedUser)
// Create request // Create request
req, _ := http.NewRequest("PUT", "/users/"+strconv.FormatUint(uint64(user.ID), 10), bytes.NewBuffer(jsonData)) req, _ := http.NewRequest("PUT", "/users/"+strconv.FormatUint(uint64(user.ID), 10), bytes.NewBuffer(jsonData))
req.Header.Set("Content-Type", "application/json") req.Header.Set("Content-Type", "application/json")
@@ -523,20 +614,59 @@ func testUpdateUser(t *testing.T, loginEmail string, loginCookie http.Cookie) {
// Verify the update in the database // Verify the update in the database
updatedUserFromDB, err := Uc.Service.GetUserByID(user.ID) updatedUserFromDB, err := Uc.Service.GetUserByID(user.ID)
updatedUserFromDB.UpdatedAt = updatedUser.UpdatedAt assert.NoError(t, err)
updatedUserFromDB.Membership.UpdatedAt = updatedUser.Membership.UpdatedAt
updatedUserFromDB.BankAccount.UpdatedAt = updatedUser.BankAccount.UpdatedAt
updatedUserFromDB.Verification.UpdatedAt = updatedUser.Verification.UpdatedAt
updatedUserFromDB.Membership.SubscriptionModel.UpdatedAt = updatedUser.Membership.SubscriptionModel.UpdatedAt
if updatedUser.Password == "" { if updatedUser.Password == "" {
assert.Equal(t, user.Password, (*updatedUserFromDB).Password) assert.Equal(t, user.Password, (*updatedUserFromDB).Password)
} else { } else {
assert.NotEqual(t, user.Password, (*updatedUserFromDB).Password) assert.NotEqual(t, user.Password, (*updatedUserFromDB).Password)
updatedUser.Password = ""
} }
updatedUserFromDB.Password = "" updatedUserFromDB.Password = ""
assert.NoError(t, err) updatedUser.Password = ""
assert.Equal(t, updatedUser, *updatedUserFromDB, "Updated user in DB does not match expected user") assert.Equal(t, updatedUser.FirstName, updatedUserFromDB.FirstName, "FirstName mismatch")
assert.Equal(t, updatedUser.LastName, updatedUserFromDB.LastName, "LastName mismatch")
assert.Equal(t, updatedUser.Email, updatedUserFromDB.Email, "Email mismatch")
assert.Equal(t, updatedUser.DateOfBirth, updatedUserFromDB.DateOfBirth, "DateOfBirth mismatch")
assert.Equal(t, updatedUser.Company, updatedUserFromDB.Company, "Company mismatch")
assert.Equal(t, updatedUser.Phone, updatedUserFromDB.Phone, "Phone mismatch")
assert.Equal(t, updatedUser.Notes, updatedUserFromDB.Notes, "Notes mismatch")
assert.Equal(t, updatedUser.ProfilePicture, updatedUserFromDB.ProfilePicture, "ProfilePicture mismatch")
assert.Equal(t, updatedUser.Address, updatedUserFromDB.Address, "Address mismatch")
assert.Equal(t, updatedUser.ZipCode, updatedUserFromDB.ZipCode, "ZipCode mismatch")
assert.Equal(t, updatedUser.City, updatedUserFromDB.City, "City mismatch")
assert.Equal(t, updatedUser.PaymentStatus, updatedUserFromDB.PaymentStatus, "PaymentStatus mismatch")
assert.Equal(t, updatedUser.Status, updatedUserFromDB.Status, "Status mismatch")
assert.Equal(t, updatedUser.RoleID, updatedUserFromDB.RoleID, "RoleID mismatch")
// For nested structs, you might want to compare individual fields
assert.Equal(t, updatedUser.BankAccount.Bank, updatedUserFromDB.BankAccount.Bank, "BankAccount.Bank mismatch")
assert.Equal(t, updatedUser.BankAccount.AccountHolderName, updatedUserFromDB.BankAccount.AccountHolderName, "BankAccount.AccountHolderName mismatch")
assert.Equal(t, updatedUser.BankAccount.IBAN, updatedUserFromDB.BankAccount.IBAN, "BankAccount.IBAN mismatch")
assert.Equal(t, updatedUser.BankAccount.BIC, updatedUserFromDB.BankAccount.BIC, "BankAccount.BIC mismatch")
assert.Equal(t, updatedUser.BankAccount.MandateReference, updatedUserFromDB.BankAccount.MandateReference, "BankAccount.MandateReference mismatch")
assert.Equal(t, updatedUser.Membership.StartDate, updatedUserFromDB.Membership.StartDate, "Membership.StartDate mismatch")
assert.Equal(t, updatedUser.Membership.EndDate, updatedUserFromDB.Membership.EndDate, "Membership.EndDate mismatch")
assert.Equal(t, updatedUser.Membership.Status, updatedUserFromDB.Membership.Status, "Membership.Status mismatch")
assert.Equal(t, updatedUser.Membership.SubscriptionModelID, updatedUserFromDB.Membership.SubscriptionModelID, "Membership.SubscriptionModelID mismatch")
assert.Equal(t, updatedUser.Membership.ParentMembershipID, updatedUserFromDB.Membership.ParentMembershipID, "Membership.ParentMembershipID mismatch")
assert.Equal(t, updatedUser.DriversLicence.Status, updatedUserFromDB.DriversLicence.Status, "DriversLicence.Status mismatch")
assert.Equal(t, updatedUser.DriversLicence.LicenceNumber, updatedUserFromDB.DriversLicence.LicenceNumber, "DriversLicence.LicenceNumber mismatch")
assert.Equal(t, updatedUser.DriversLicence.IssuedDate, updatedUserFromDB.DriversLicence.IssuedDate, "DriversLicence.IssuedDate mismatch")
assert.Equal(t, updatedUser.DriversLicence.ExpirationDate, updatedUserFromDB.DriversLicence.ExpirationDate, "DriversLicence.ExpirationDate mismatch")
assert.Equal(t, updatedUser.DriversLicence.IssuingCountry, updatedUserFromDB.DriversLicence.IssuingCountry, "DriversLicence.IssuingCountry mismatch")
// For slices or more complex nested structures, you might want to use deep equality checks
assert.ElementsMatch(t, updatedUser.Consents, updatedUserFromDB.Consents, "Consents mismatch")
if len(updatedUser.DriversLicence.LicenceCategories) > 0 {
for i := range updatedUser.DriversLicence.LicenceCategories {
assert.Equal(t, updatedUser.DriversLicence.LicenceCategories[i].Category, updatedUserFromDB.DriversLicence.LicenceCategories[i].Category, "LicenceCategory Category mismatch at index %d", i)
}
} else {
assert.Emptyf(t, updatedUserFromDB.DriversLicence.LicenceCategories, "Categories aren't empty when they should")
}
} }
}) })
} }
@@ -822,7 +952,7 @@ func getTestUsers() []RegisterUserTest {
{ {
Name: "Correct Entry should pass", Name: "Correct Entry should pass",
WantResponse: http.StatusCreated, WantResponse: http.StatusCreated,
WantDBData: map[string]interface{}{"Email": "john.doe@example.com"}, WantDBData: map[string]interface{}{"email": "john.doe@example.com"},
Assert: true, Assert: true,
Input: GenerateInputJSON(customizeInput(func(user models.User) models.User { return user })), Input: GenerateInputJSON(customizeInput(func(user models.User) models.User { return user })),
}, },
@@ -839,7 +969,7 @@ func getTestUsers() []RegisterUserTest {
{ {
Name: "Company present should pass", Name: "Company present should pass",
WantResponse: http.StatusCreated, WantResponse: http.StatusCreated,
WantDBData: map[string]interface{}{"Email": "john.doe2@example.com"}, WantDBData: map[string]interface{}{"email": "john.doe2@example.com"},
Assert: true, Assert: true,
Input: GenerateInputJSON(customizeInput(func(user models.User) models.User { Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
user.Email = "john.doe2@example.com" user.Email = "john.doe2@example.com"
@@ -850,7 +980,7 @@ func getTestUsers() []RegisterUserTest {
{ {
Name: "Subscription constraints not entered; should fail", Name: "Subscription constraints not entered; should fail",
WantResponse: http.StatusNotAcceptable, WantResponse: http.StatusNotAcceptable,
WantDBData: map[string]interface{}{"Email": "john.junior.doe@example.com"}, WantDBData: map[string]interface{}{"email": "john.junior.doe@example.com"},
Assert: false, Assert: false,
Input: GenerateInputJSON(customizeInput(func(user models.User) models.User { Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
user.Email = "john.junior.doe@example.com" user.Email = "john.junior.doe@example.com"
@@ -861,7 +991,7 @@ func getTestUsers() []RegisterUserTest {
{ {
Name: "Subscription constraints wrong; should fail", Name: "Subscription constraints wrong; should fail",
WantResponse: http.StatusNotAcceptable, WantResponse: http.StatusNotAcceptable,
WantDBData: map[string]interface{}{"Email": "john.junior.doe@example.com"}, WantDBData: map[string]interface{}{"email": "john.junior.doe@example.com"},
Assert: false, Assert: false,
Input: GenerateInputJSON(customizeInput(func(user models.User) models.User { Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
user.Email = "john.junior.doe@example.com" user.Email = "john.junior.doe@example.com"
@@ -873,7 +1003,7 @@ func getTestUsers() []RegisterUserTest {
{ {
Name: "Subscription constraints correct, should pass", Name: "Subscription constraints correct, should pass",
WantResponse: http.StatusCreated, WantResponse: http.StatusCreated,
WantDBData: map[string]interface{}{"Email": "john.junior.doe@example.com"}, WantDBData: map[string]interface{}{"email": "john.junior.doe@example.com"},
Assert: true, Assert: true,
Input: GenerateInputJSON(customizeInput(func(user models.User) models.User { Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
user.Email = "john.junior.doe@example.com" user.Email = "john.junior.doe@example.com"
@@ -882,5 +1012,16 @@ func getTestUsers() []RegisterUserTest {
return user return user
})), })),
}, },
{
Name: "Correct Entry with Mandate Reference",
WantResponse: http.StatusCreated,
WantDBData: map[string]interface{}{"email": "john.mandate@example.com"},
Assert: true,
Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
user.Email = "john.mandate@example.com"
user.BankAccount.IBAN = "DE89370400440532013000"
return user
})),
},
} }
} }

View File

@@ -27,6 +27,8 @@ func Open(dbPath string, adminMail string) error {
&models.Membership{}, &models.Membership{},
&models.Consent{}, &models.Consent{},
&models.Verification{}, &models.Verification{},
&models.DriversLicence{},
&models.LicenceCategory{},
&models.BankAccount{}); err != nil { &models.BankAccount{}); err != nil {
logger.Error.Fatalf("Couldn't create database: %v", err) logger.Error.Fatalf("Couldn't create database: %v", err)
return err return err
@@ -51,6 +53,13 @@ func Open(dbPath string, adminMail string) error {
return err return err
} }
categories := createLicenceCategories()
for _, model := range categories {
result := db.Create(&model)
if result.Error != nil {
return result.Error
}
}
admin, err := createAdmin(adminMail, createdModel.ID) admin, err := createAdmin(adminMail, createdModel.ID)
if err != nil { if err != nil {
return err return err
@@ -73,6 +82,28 @@ func createSubscriptionModels() []models.SubscriptionModel {
}, },
} }
} }
func createLicenceCategories() []models.LicenceCategory {
return []models.LicenceCategory{
{Category: "AM"},
{Category: "A1"},
{Category: "A2"},
{Category: "A"},
{Category: "B"},
{Category: "C1"},
{Category: "C"},
{Category: "D1"},
{Category: "D"},
{Category: "BE"},
{Category: "C1E"},
{Category: "CE"},
{Category: "D1E"},
{Category: "DE"},
{Category: "R"},
{Category: "L"},
}
}
// TODO: Landing page to create an admin // TODO: Landing page to create an admin
func createAdmin(userMail string, subscriptionModelID uint) (*models.User, error) { func createAdmin(userMail string, subscriptionModelID uint) (*models.User, error) {

View File

@@ -0,0 +1,27 @@
package models
import (
"time"
"gorm.io/gorm"
)
type DriversLicence struct {
gorm.Model
Status int8 `json:"licence_status" validate:"omitempty,number"`
LicenceNumber string `json:"licence_number" validate:"safe_content"`
IssuedDate time.Time `json:"licence_issued_date" validate:"omitempty,lte"`
ExpirationDate time.Time `json:"licence_expiration_date" validate:"omitempty,gt"`
IssuingCountry string `json:"licence_country" validate:"safe_content"`
LicenceCategories []LicenceCategory `json:"licence_categories" gorm:"many2many:licence_2_categories"`
}
type LicenceCategory struct {
gorm.Model
Category string `json:"licence_category" validate:"safe_content"`
}
// func (d *DriversLicence) BeforeCreate(tx *gorm.DB) (err error) {
// d.Status = constants.UnverifiedStatus
// return
// }

View File

@@ -7,7 +7,7 @@ type Membership struct {
UpdatedAt time.Time UpdatedAt time.Time
StartDate time.Time `json:"start_date"` StartDate time.Time `json:"start_date"`
EndDate time.Time `json:"end_date"` EndDate time.Time `json:"end_date"`
Status string `json:"status" validate:"safe_content"` Status int8 `json:"status" validate:"safe_content"`
SubscriptionModel SubscriptionModel `gorm:"foreignKey:SubscriptionModelID" json:"subscription_model"` SubscriptionModel SubscriptionModel `gorm:"foreignKey:SubscriptionModelID" json:"subscription_model"`
SubscriptionModelID uint `json:"subsription_model_id"` SubscriptionModelID uint `json:"subsription_model_id"`
ParentMembershipID uint `json:"parent_member_id" validate:"omitempty,omitnil,number"` ParentMembershipID uint `json:"parent_member_id" validate:"omitempty,omitnil,number"`

View File

@@ -1,7 +1,7 @@
package models package models
import ( import (
"GoMembership/internal/constants" "fmt"
"time" "time"
"github.com/alexedwards/argon2id" "github.com/alexedwards/argon2id"
@@ -9,36 +9,45 @@ import (
) )
type User struct { type User struct {
DateOfBirth time.Time `gorm:"not null" json:"date_of_birth" validate:"required,age"`
Company string `json:"company" validate:"omitempty,omitnil,safe_content"`
Phone string `json:"phone" validate:"omitempty,omitnil,safe_content"`
Notes *string `json:"notes,safe_content"`
FirstName string `gorm:"not null" json:"first_name" validate:"required,safe_content"`
Password string `json:"password" validate:"required_unless=RoleID 0,safe_content"`
Email string `gorm:"unique;not null" json:"email" validate:"required,email,safe_content"`
LastName string `gorm:"not null" json:"last_name" validate:"required,safe_content"`
ProfilePicture string `json:"profile_picture" validate:"omitempty,omitnil,image,safe_content"`
Address string `gorm:"not null" json:"address" validate:"required,safe_content"`
ZipCode string `gorm:"not null" json:"zip_code" validate:"required,alphanum,safe_content"`
City string `form:"not null" json:"city" validate:"required,alphaunicode,safe_content"`
Consents []Consent `gorm:"constraint:OnUpdate:CASCADE"`
BankAccount BankAccount `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"bank_account"`
Verification Verification `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"`
Membership Membership `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"membership"`
ID int64 `gorm:"primaryKey" json:"id"`
PaymentStatus int8 `json:"payment_status"`
Status int8 `json:"status"`
RoleID int8 `json:"role_id"`
gorm.Model gorm.Model
DateOfBirth time.Time `gorm:"not null" json:"date_of_birth" validate:"required,age"`
Company string `json:"company" validate:"omitempty,omitnil,safe_content"`
Phone string `json:"phone" validate:"omitempty,omitnil,safe_content"`
Notes string `json:"notes" validate:"safe_content"`
FirstName string `gorm:"not null" json:"first_name" validate:"required,safe_content"`
Password string `json:"password" validate:"required_unless=RoleID 0,safe_content"`
Email string `gorm:"unique;not null" json:"email" validate:"required,email,safe_content"`
LastName string `gorm:"not null" json:"last_name" validate:"required,safe_content"`
ProfilePicture string `json:"profile_picture" validate:"omitempty,omitnil,image,safe_content"`
Address string `gorm:"not null" json:"address" validate:"required,safe_content"`
ZipCode string `gorm:"not null" json:"zip_code" validate:"required,alphanum,safe_content"`
City string `form:"not null" json:"city" validate:"required,alphaunicode,safe_content"`
Consents []Consent `gorm:"constraint:OnUpdate:CASCADE"`
BankAccount BankAccount `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"bank_account"`
BankAccountID uint
Verification Verification `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"`
VerificationID uint
Membership Membership `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"membership"`
MembershipID uint
DriversLicence DriversLicence `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"drivers_licence"`
DriversLicenceID uint
ID uint `json:"id"`
PaymentStatus int8 `json:"payment_status"`
Status int8 `json:"status"`
RoleID int8 `json:"role_id"`
} }
// BeforeCreate sets the default value for Status func (u *User) AfterCreate(tx *gorm.DB) (err error) {
func (u *User) BeforeCreate(tx *gorm.DB) (err error) { if u.BankAccount.ID != 0 && u.BankAccount.MandateReference == "" {
if u.Status == 0 { // Assuming 0 is an unset value mandateReference := u.GenerateMandateReference()
u.Status = constants.UnverifiedStatus
u.PaymentStatus = constants.AwaitingPaymentStatus return tx.Model(&u.BankAccount).Update("MandateReference", mandateReference).Error
} }
return return nil
}
func (u *User) GenerateMandateReference() string {
return fmt.Sprintf("%s%d%s", time.Now().Format("20060102"), u.ID, u.BankAccount.IBAN)
} }
func (u *User) PasswordMatches(plaintextPassword string) (bool, error) { func (u *User) PasswordMatches(plaintextPassword string) (bool, error) {

View File

@@ -0,0 +1,25 @@
package repositories
import (
"GoMembership/internal/database"
"GoMembership/internal/models"
)
type DriversLicenceInterface interface {
FindCategoryByName(categoryName string) (models.LicenceCategory, error)
FindCategoriesByIDs(ids []uint) ([]models.LicenceCategory, error)
}
type DriversLicenceRepository struct{}
func (r *DriversLicenceRepository) FindCategoriesByIDs(ids []uint) ([]models.LicenceCategory, error) {
var categories []models.LicenceCategory
err := database.DB.Where("id IN ?", ids).Find(&categories).Error
return categories, err
}
func (r *DriversLicenceRepository) FindCategoryByName(categoryName string) (models.LicenceCategory, error) {
var category models.LicenceCategory
err := database.DB.Where("category = ?", categoryName).First(&category).Error
return category, err
}

View File

@@ -68,12 +68,10 @@ func (ur *UserRepository) UpdateUser(user *models.User) (*models.User, error) {
func (ur *UserRepository) GetUsers(where map[string]interface{}) (*[]models.User, error) { func (ur *UserRepository) GetUsers(where map[string]interface{}) (*[]models.User, error) {
var users []models.User var users []models.User
result := database.DB. result := database.DB.
Preload("Consents"). Preload(clause.Associations).
Preload("BankAccount"). Preload("Membership.SubscriptionModel").
Preload("Verification"). Preload("DriversLicence.LicenceCategories").
Preload("Membership", func(db *gorm.DB) *gorm.DB { Where(where).Find(&users)
return db.Preload("SubscriptionModel")
}).Where(where).Find(&users)
if result.Error != nil { if result.Error != nil {
if result.Error == gorm.ErrRecordNotFound { if result.Error == gorm.ErrRecordNotFound {
return nil, gorm.ErrRecordNotFound return nil, gorm.ErrRecordNotFound
@@ -86,12 +84,10 @@ func (ur *UserRepository) GetUsers(where map[string]interface{}) (*[]models.User
func (ur *UserRepository) GetUserByID(userID *uint) (*models.User, error) { func (ur *UserRepository) GetUserByID(userID *uint) (*models.User, error) {
var user models.User var user models.User
result := database.DB. result := database.DB.
Preload("Consents"). Preload(clause.Associations).
Preload("BankAccount"). Preload("Membership.SubscriptionModel").
Preload("Verification"). Preload("DriversLicence.LicenceCategories").
Preload("Membership", func(db *gorm.DB) *gorm.DB { First(&user, userID)
return db.Preload("SubscriptionModel")
}).First(&user, userID)
if result.Error != nil { if result.Error != nil {
if result.Error == gorm.ErrRecordNotFound { if result.Error == gorm.ErrRecordNotFound {
return nil, gorm.ErrRecordNotFound return nil, gorm.ErrRecordNotFound

View File

@@ -39,7 +39,8 @@ func Run() {
membershipService := &services.MembershipService{Repo: membershipRepo, SubscriptionRepo: subscriptionRepo} membershipService := &services.MembershipService{Repo: membershipRepo, SubscriptionRepo: subscriptionRepo}
var userRepo repositories.UserRepositoryInterface = &repositories.UserRepository{} var userRepo repositories.UserRepositoryInterface = &repositories.UserRepository{}
userService := &services.UserService{Repo: userRepo} var licenceRepo repositories.DriversLicenceInterface = &repositories.DriversLicenceRepository{}
userService := &services.UserService{Repo: userRepo, Licences: licenceRepo}
userController := &controllers.UserController{Service: userService, EmailService: emailService, ConsentService: consentService, BankAccountService: bankAccountService, MembershipService: membershipService} userController := &controllers.UserController{Service: userService, EmailService: emailService, ConsentService: consentService, BankAccountService: bankAccountService, MembershipService: membershipService}
membershipController := &controllers.MembershipController{Service: *membershipService} membershipController := &controllers.MembershipController{Service: *membershipService}

View File

@@ -1,49 +1,12 @@
package services package services
import ( import (
"GoMembership/internal/models"
"GoMembership/internal/repositories" "GoMembership/internal/repositories"
"crypto/rand"
"encoding/hex"
"fmt"
"strconv"
"time"
) )
type BankAccountServiceInterface interface { type BankAccountServiceInterface interface {
RegisterBankAccount(bankAccount *models.BankAccount) (int64, error)
} }
type BankAccountService struct { type BankAccountService struct {
Repo repositories.BankAccountRepositoryInterface Repo repositories.BankAccountRepositoryInterface
} }
func (service *BankAccountService) RegisterBankAccount(bankAccount *models.BankAccount) (int64, error) {
bankAccount.MandateDateSigned = time.Now()
ref, err := generateSEPAMandateReference(strconv.FormatInt(bankAccount.UserID, 10))
if err != nil {
return -1, err
}
bankAccount.MandateReference = ref
return service.Repo.CreateBankAccount(bankAccount)
}
func generateUniqueID(length int) (string, error) {
bytes := make([]byte, length)
_, err := rand.Read(bytes)
if err != nil {
return "", err
}
return hex.EncodeToString(bytes), nil
}
func generateSEPAMandateReference(userID string) (string, error) {
today := time.Now().Format("20060102") // Format YYYYMMDD
uniqueID, err := generateUniqueID(4) // 4 Bytes = 8 Hex-Zeichen
if err != nil {
return "", err
}
mandateReference := fmt.Sprintf("%s-%s-%s", userID, today, uniqueID)
return mandateReference, nil
}

View File

@@ -60,6 +60,9 @@ func (service *MembershipService) GetModelByName(modelname *string) (*models.Sub
} }
func (service *MembershipService) GetSubscriptions(where map[string]interface{}) (*[]models.SubscriptionModel, error) { func (service *MembershipService) GetSubscriptions(where map[string]interface{}) (*[]models.SubscriptionModel, error) {
if where == nil {
where = map[string]interface{}{}
}
return service.SubscriptionRepo.GetSubscriptions(where) return service.SubscriptionRepo.GetSubscriptions(where)
} }

View File

@@ -28,7 +28,8 @@ type UserServiceInterface interface {
} }
type UserService struct { type UserService struct {
Repo repositories.UserRepositoryInterface Repo repositories.UserRepositoryInterface
Licences repositories.DriversLicenceInterface
} }
func (service *UserService) UpdateUser(user *models.User, userRole int8) (*models.User, error) { func (service *UserService) UpdateUser(user *models.User, userRole int8) (*models.User, error) {
@@ -69,6 +70,8 @@ func (service *UserService) RegisterUser(user *models.User) (uint, string, error
user.Status = constants.UnverifiedStatus user.Status = constants.UnverifiedStatus
user.CreatedAt = time.Now() user.CreatedAt = time.Now()
user.UpdatedAt = time.Now() user.UpdatedAt = time.Now()
user.PaymentStatus = constants.AwaitingPaymentStatus
user.BankAccount.MandateDateSigned = time.Now()
id, err := service.Repo.CreateUser(user) id, err := service.Repo.CreateUser(user)
if err != nil && strings.Contains(err.Error(), "UNIQUE constraint failed") { if err != nil && strings.Contains(err.Error(), "UNIQUE constraint failed") {