This commit is contained in:
Alex
2025-04-10 15:40:22 +02:00
parent 87f08dd3be
commit 18f5dadb06
48 changed files with 1650 additions and 981 deletions

View File

@@ -62,6 +62,16 @@ type SecurityConfig struct {
Burst int `json:"Burst" default:"60" envconfig:"BURST_LIMIT"`
} `json:"RateLimits"`
}
type CompanyConfig struct {
Name string `json:"Name" envconfig:"COMPANY_NAME"`
Address string `json:"Address" envconfig:"COMPANY_ADDRESS"`
City string `json:"City" envconfig:"COMPANY_CITY"`
ZipCode string `json:"ZipCode" envconfig:"COMPANY_ZIPCODE"`
Country string `json:"Country" envconfig:"COMPANY_COUNTRY"`
SepaPrefix string `json:"SepaPrefix" envconfig:"COMPANY_SEPA_PREFIX"`
}
type Config struct {
Auth AuthenticationConfig `json:"auth"`
Site SiteConfig `json:"site"`
@@ -72,6 +82,7 @@ type Config struct {
DB DatabaseConfig `json:"db"`
SMTP SMTPConfig `json:"smtp"`
Security SecurityConfig `json:"security"`
Company CompanyConfig `json:"company"`
}
var (
@@ -85,7 +96,9 @@ var (
Recipients RecipientsConfig
Env string
Security SecurityConfig
Company CompanyConfig
)
var environmentOptions map[string]bool = map[string]bool{
"development": true,
"production": true,
@@ -124,6 +137,7 @@ func LoadConfig() {
Security = CFG.Security
Env = CFG.Env
Site = CFG.Site
Company = CFG.Company
logger.Info.Printf("Config loaded: %#v", CFG)
}

View File

@@ -9,13 +9,13 @@ const (
DelayedPaymentStatus
SettledPaymentStatus
AwaitingPaymentStatus
MailVerificationSubject = "Nur noch ein kleiner Schritt!"
MailChangePasswordSubject = "Passwort Änderung angefordert"
MailGrantBackendAccessSubject = "Dein Dörpsmobil Hasloh e.V. Zugang"
MailRegistrationSubject = "Neues Mitglied hat sich registriert"
MailWelcomeSubject = "Willkommen beim Dörpsmobil Hasloh e.V."
MailContactSubject = "Jemand hat das Kontaktformular gefunden"
SupporterSubscriptionModelName = "Keins"
MailVerificationSubject = "Nur noch ein kleiner Schritt!"
MailChangePasswordSubject = "Passwort Änderung angefordert"
MailGrantBackendAccessSubject = "Dein Dörpsmobil Hasloh e.V. Zugang"
MailRegistrationSubject = "Neues Mitglied hat sich registriert"
MailWelcomeSubject = "Willkommen beim Dörpsmobil Hasloh e.V."
MailContactSubject = "Jemand hat das Kontaktformular gefunden"
SupporterSubscriptionName = "Keins"
)
var Licences = struct {

View File

@@ -89,9 +89,7 @@ func (cr *CarController) GetAll(c *gin.Context) {
func (cr *CarController) Delete(c *gin.Context) {
type input struct {
Car struct {
ID uint `json:"id" binding:"required,numeric"`
} `json:"car"`
ID uint `json:"id" binding:"required,numeric"`
}
var deleteData input
requestUser, err := cr.UserService.FromContext(c)
@@ -109,7 +107,7 @@ func (cr *CarController) Delete(c *gin.Context) {
utils.HandleValidationError(c, err)
return
}
err = cr.S.Delete(&deleteData.Car.ID)
err = cr.S.Delete(&deleteData.ID)
if err != nil {
utils.RespondWithError(c, err, "Error deleting car", http.StatusInternalServerError, errors.Responses.Fields.Car, errors.Responses.Keys.InternalServerError)
return

View File

@@ -100,7 +100,7 @@ func TestMain(t *testing.T) {
bankAccountService := &services.BankAccountService{Repo: bankAccountRepo}
var membershipRepo repositories.MembershipRepositoryInterface = &repositories.MembershipRepository{}
var subscriptionRepo repositories.SubscriptionModelsRepositoryInterface = &repositories.SubscriptionModelsRepository{}
var subscriptionRepo repositories.SubscriptionsRepositoryInterface = &repositories.SubscriptionsRepository{}
membershipService := &services.MembershipService{Repo: membershipRepo, SubscriptionRepo: subscriptionRepo}
var licenceRepo repositories.LicenceInterface = &repositories.LicenceRepository{}
@@ -119,6 +119,7 @@ func TestMain(t *testing.T) {
if err := initLicenceCategories(); err != nil {
log.Fatalf("Failed to init Categories: %v", err)
}
password := "securepassword"
admin := models.User{
FirstName: "Ad",
LastName: "min",
@@ -130,7 +131,7 @@ func TestMain(t *testing.T) {
ZipCode: "12345",
City: "SampleCity",
Status: constants.ActiveStatus,
Password: "",
Password: password,
Notes: "",
RoleID: constants.Roles.Admin,
Consents: nil,
@@ -140,7 +141,6 @@ func TestMain(t *testing.T) {
Licence: &models.Licence{
Status: constants.UnverifiedStatus,
}}
admin.SetPassword("securepassword")
admin.Create(db)
validation.SetupValidators(db)
t.Run("userController", func(t *testing.T) {
@@ -203,7 +203,7 @@ func initLicenceCategories() error {
}
func initSubscriptionPlans() error {
subscriptions := []models.SubscriptionModel{
subscriptions := []models.Subscription{
{
Name: "Basic",
Details: "Test Plan",
@@ -284,7 +284,7 @@ func getBaseUser() models.User {
City: "Hasloh",
Phone: "01738484993",
BankAccount: &models.BankAccount{IBAN: "DE89370400440532013000"},
Membership: &models.Membership{SubscriptionModel: models.SubscriptionModel{Name: "Basic"}},
Membership: &models.Membership{Subscription: models.Subscription{Name: "Basic"}},
Licence: nil,
Password: "passw@#$#%$!-ord123",
Company: "",
@@ -303,7 +303,7 @@ func getBaseSupporter() models.User {
City: "Hasloh",
Phone: "01738484993",
BankAccount: &models.BankAccount{IBAN: "DE89370400440532013000"},
Membership: &models.Membership{SubscriptionModel: models.SubscriptionModel{Name: "Basic"}},
Membership: &models.Membership{Subscription: models.Subscription{Name: "Basic"}},
Licence: nil,
Password: "passw@#$#%$!-ord123",
Company: "",

View File

@@ -33,7 +33,7 @@ func (mc *MembershipController) RegisterSubscription(c *gin.Context) {
return
}
var subscription models.SubscriptionModel
var subscription models.Subscription
if err := c.ShouldBindJSON(&subscription); err != nil {
utils.HandleValidationError(c, err)
return
@@ -43,9 +43,9 @@ func (mc *MembershipController) RegisterSubscription(c *gin.Context) {
id, err := mc.Service.RegisterSubscription(&subscription)
if err != nil {
if strings.Contains(err.Error(), "UNIQUE constraint failed") {
utils.RespondWithError(c, err, "Subscription already exists", http.StatusConflict, errors.Responses.Fields.SubscriptionModel, errors.Responses.Keys.Duplicate)
utils.RespondWithError(c, err, "Subscription already exists", http.StatusConflict, errors.Responses.Fields.Subscription, errors.Responses.Keys.Duplicate)
} else {
utils.RespondWithError(c, err, "Couldn't register Membershipmodel", http.StatusInternalServerError, errors.Responses.Fields.SubscriptionModel, errors.Responses.Keys.InternalServerError)
utils.RespondWithError(c, err, "Couldn't register Membershipmodel", http.StatusInternalServerError, errors.Responses.Fields.Subscription, errors.Responses.Keys.InternalServerError)
}
return
}
@@ -69,7 +69,7 @@ func (mc *MembershipController) UpdateHandler(c *gin.Context) {
return
}
var subscription models.SubscriptionModel
var subscription models.Subscription
if err := c.ShouldBindJSON(&subscription); err != nil {
utils.HandleValidationError(c, err)
return
@@ -122,7 +122,7 @@ func (mc *MembershipController) DeleteSubscription(c *gin.Context) {
func (mc *MembershipController) GetSubscriptions(c *gin.Context) {
subscriptions, err := mc.Service.GetSubscriptions(nil)
if err != nil {
utils.RespondWithError(c, err, "Error retrieving subscriptions", http.StatusInternalServerError, errors.Responses.Fields.SubscriptionModel, errors.Responses.Keys.InternalServerError)
utils.RespondWithError(c, err, "Error retrieving subscriptions", http.StatusInternalServerError, errors.Responses.Fields.Subscription, errors.Responses.Keys.InternalServerError)
return
}

View File

@@ -148,8 +148,8 @@ func (dt *DeleteSubscriptionTest) ValidateResult() error {
return validateSubscription(dt.Assert, dt.WantDBData)
}
func getBaseSubscription() models.SubscriptionModel {
return models.SubscriptionModel{
func getBaseSubscription() models.Subscription {
return models.Subscription{
Name: "Premium",
Details: "A subscription detail",
MonthlyFee: 12.0,
@@ -157,7 +157,7 @@ func getBaseSubscription() models.SubscriptionModel {
}
}
func customizeSubscription(customize func(models.SubscriptionModel) models.SubscriptionModel) models.SubscriptionModel {
func customizeSubscription(customize func(models.Subscription) models.Subscription) models.Subscription {
subscription := getBaseSubscription()
return customize(subscription)
}
@@ -173,7 +173,7 @@ func getSubscriptionRegistrationData() []RegisterSubscriptionTest {
WantDBData: map[string]interface{}{"name": "Just a Subscription"},
Assert: false,
Input: GenerateInputJSON(
customizeSubscription(func(subscription models.SubscriptionModel) models.SubscriptionModel {
customizeSubscription(func(subscription models.Subscription) models.Subscription {
subscription.Details = ""
return subscription
})),
@@ -187,7 +187,7 @@ func getSubscriptionRegistrationData() []RegisterSubscriptionTest {
WantDBData: map[string]interface{}{"name": ""},
Assert: false,
Input: GenerateInputJSON(
customizeSubscription(func(subscription models.SubscriptionModel) models.SubscriptionModel {
customizeSubscription(func(subscription models.Subscription) models.Subscription {
subscription.Name = ""
return subscription
})),
@@ -200,7 +200,7 @@ func getSubscriptionRegistrationData() []RegisterSubscriptionTest {
WantResponse: http.StatusBadRequest,
WantDBData: map[string]interface{}{"name": "Premium"},
Assert: false,
Input: GenerateInputJSON(customizeSubscription(func(sub models.SubscriptionModel) models.SubscriptionModel {
Input: GenerateInputJSON(customizeSubscription(func(sub models.Subscription) models.Subscription {
sub.MonthlyFee = -10.0
return sub
})),
@@ -213,7 +213,7 @@ func getSubscriptionRegistrationData() []RegisterSubscriptionTest {
WantResponse: http.StatusBadRequest,
WantDBData: map[string]interface{}{"name": "Premium"},
Assert: false,
Input: GenerateInputJSON(customizeSubscription(func(sub models.SubscriptionModel) models.SubscriptionModel {
Input: GenerateInputJSON(customizeSubscription(func(sub models.Subscription) models.Subscription {
sub.HourlyRate = -1.0
return sub
})),
@@ -227,7 +227,7 @@ func getSubscriptionRegistrationData() []RegisterSubscriptionTest {
WantDBData: map[string]interface{}{"name": "Premium"},
Assert: false,
Input: GenerateInputJSON(
customizeSubscription(func(subscription models.SubscriptionModel) models.SubscriptionModel {
customizeSubscription(func(subscription models.Subscription) models.Subscription {
subscription.Conditions = "Some Condition"
subscription.IncludedPerYear = 0
subscription.IncludedPerMonth = 1
@@ -243,7 +243,7 @@ func getSubscriptionRegistrationData() []RegisterSubscriptionTest {
WantDBData: map[string]interface{}{"name": "Premium"},
Assert: true,
Input: GenerateInputJSON(
customizeSubscription(func(subscription models.SubscriptionModel) models.SubscriptionModel {
customizeSubscription(func(subscription models.Subscription) models.Subscription {
subscription.Conditions = "Some Condition"
subscription.IncludedPerYear = 0
subscription.IncludedPerMonth = 1
@@ -274,7 +274,7 @@ func getSubscriptionUpdateData() []UpdateSubscriptionTest {
WantDBData: map[string]interface{}{"name": "Premium", "monthly_fee": "12"},
Assert: true,
Input: GenerateInputJSON(
customizeSubscription(func(subscription models.SubscriptionModel) models.SubscriptionModel {
customizeSubscription(func(subscription models.Subscription) models.Subscription {
subscription.MonthlyFee = 123.0
return subscription
})),
@@ -288,7 +288,7 @@ func getSubscriptionUpdateData() []UpdateSubscriptionTest {
WantDBData: map[string]interface{}{"name": "Premium"},
Assert: true,
Input: GenerateInputJSON(
customizeSubscription(func(subscription models.SubscriptionModel) models.SubscriptionModel {
customizeSubscription(func(subscription models.Subscription) models.Subscription {
subscription.ID = 0
return subscription
})),
@@ -302,7 +302,7 @@ func getSubscriptionUpdateData() []UpdateSubscriptionTest {
WantDBData: map[string]interface{}{"name": "Premium", "hourly_rate": "14"},
Assert: true,
Input: GenerateInputJSON(
customizeSubscription(func(subscription models.SubscriptionModel) models.SubscriptionModel {
customizeSubscription(func(subscription models.Subscription) models.Subscription {
subscription.HourlyRate = 3254.0
return subscription
})),
@@ -316,7 +316,7 @@ func getSubscriptionUpdateData() []UpdateSubscriptionTest {
WantDBData: map[string]interface{}{"name": "Premium", "included_per_year": "0"},
Assert: true,
Input: GenerateInputJSON(
customizeSubscription(func(subscription models.SubscriptionModel) models.SubscriptionModel {
customizeSubscription(func(subscription models.Subscription) models.Subscription {
subscription.IncludedPerYear = 9873.0
return subscription
})),
@@ -330,7 +330,7 @@ func getSubscriptionUpdateData() []UpdateSubscriptionTest {
WantDBData: map[string]interface{}{"name": "Premium", "included_per_month": "1"},
Assert: true,
Input: GenerateInputJSON(
customizeSubscription(func(subscription models.SubscriptionModel) models.SubscriptionModel {
customizeSubscription(func(subscription models.Subscription) models.Subscription {
subscription.IncludedPerMonth = 23415.0
return subscription
})),
@@ -344,7 +344,7 @@ func getSubscriptionUpdateData() []UpdateSubscriptionTest {
WantDBData: map[string]interface{}{"name": "NonExistentSubscription"},
Assert: false,
Input: GenerateInputJSON(
customizeSubscription(func(subscription models.SubscriptionModel) models.SubscriptionModel {
customizeSubscription(func(subscription models.Subscription) models.Subscription {
subscription.Name = "NonExistentSubscription"
return subscription
})),
@@ -358,7 +358,7 @@ func getSubscriptionUpdateData() []UpdateSubscriptionTest {
WantDBData: map[string]interface{}{"name": "Premium", "details": "Altered Details"},
Assert: false,
Input: GenerateInputJSON(
customizeSubscription(func(subscription models.SubscriptionModel) models.SubscriptionModel {
customizeSubscription(func(subscription models.Subscription) models.Subscription {
subscription.Details = "Altered Details"
subscription.Conditions = "Some Condition"
subscription.IncludedPerYear = 0
@@ -375,7 +375,7 @@ func getSubscriptionUpdateData() []UpdateSubscriptionTest {
WantDBData: map[string]interface{}{"name": "Premium", "details": "Altered Details"},
Assert: true,
Input: GenerateInputJSON(
customizeSubscription(func(subscription models.SubscriptionModel) models.SubscriptionModel {
customizeSubscription(func(subscription models.Subscription) models.Subscription {
subscription.Details = "Altered Details"
subscription.Conditions = "Some Condition"
subscription.IncludedPerYear = 0
@@ -388,7 +388,7 @@ func getSubscriptionUpdateData() []UpdateSubscriptionTest {
func getSubscriptionDeleteData() []DeleteSubscriptionTest {
var premiumSub, basicSub models.SubscriptionModel
var premiumSub, basicSub models.Subscription
database.DB.Where("name = ?", "Premium").First(&premiumSub)
database.DB.Where("name = ?", "Basic").First(&basicSub)
@@ -402,7 +402,7 @@ func getSubscriptionDeleteData() []DeleteSubscriptionTest {
WantDBData: map[string]interface{}{"name": "NonExistentSubscription"},
Assert: false,
Input: GenerateInputJSON(
customizeSubscription(func(subscription models.SubscriptionModel) models.SubscriptionModel {
customizeSubscription(func(subscription models.Subscription) models.Subscription {
subscription.Name = "NonExistentSubscription"
subscription.ID = basicSub.ID
logger.Error.Printf("subscription to delete: %#v", subscription)
@@ -418,7 +418,7 @@ func getSubscriptionDeleteData() []DeleteSubscriptionTest {
WantDBData: map[string]interface{}{"name": ""},
Assert: false,
Input: GenerateInputJSON(
customizeSubscription(func(subscription models.SubscriptionModel) models.SubscriptionModel {
customizeSubscription(func(subscription models.Subscription) models.Subscription {
subscription.Name = ""
subscription.ID = basicSub.ID
return subscription
@@ -433,7 +433,7 @@ func getSubscriptionDeleteData() []DeleteSubscriptionTest {
WantDBData: map[string]interface{}{"name": "Basic"},
Assert: true,
Input: GenerateInputJSON(
customizeSubscription(func(subscription models.SubscriptionModel) models.SubscriptionModel {
customizeSubscription(func(subscription models.Subscription) models.Subscription {
subscription.Name = "Basic"
subscription.ID = basicSub.ID
return subscription
@@ -448,7 +448,7 @@ func getSubscriptionDeleteData() []DeleteSubscriptionTest {
WantDBData: map[string]interface{}{"name": "Premium"},
Assert: true,
Input: GenerateInputJSON(
customizeSubscription(func(subscription models.SubscriptionModel) models.SubscriptionModel {
customizeSubscription(func(subscription models.Subscription) models.Subscription {
subscription.Name = "Premium"
subscription.ID = premiumSub.ID
return subscription
@@ -463,7 +463,7 @@ func getSubscriptionDeleteData() []DeleteSubscriptionTest {
WantDBData: map[string]interface{}{"name": "Premium"},
Assert: false,
Input: GenerateInputJSON(
customizeSubscription(func(subscription models.SubscriptionModel) models.SubscriptionModel {
customizeSubscription(func(subscription models.Subscription) models.Subscription {
subscription.Name = "Premium"
subscription.ID = premiumSub.ID
return subscription

View File

@@ -132,9 +132,9 @@ func (uc *UserController) ChangePassword(c *gin.Context) {
utils.HandleValidationError(c, err)
return
}
if !user.Verify(input.Token, constants.VerificationTypes.Password) {
utils.RespondWithError(c, errors.ErrAlreadyVerified, "Couldn't verify user", http.StatusInternalServerError, errors.Responses.Fields.General, errors.Responses.Keys.InternalServerError)
err = user.Verify(input.Token, constants.VerificationTypes.Password)
if err != nil {
utils.RespondWithError(c, err, "Couldn't verify user", http.StatusInternalServerError, errors.Responses.Fields.General, errors.Responses.Keys.InternalServerError)
return
}

View File

@@ -34,7 +34,6 @@ func setupTestContext() (*TestContext, error) {
logger.Error.Printf("error fetching user: %#v", err)
return nil, err
}
logger.Error.Printf("found user: %#v", user)
return &TestContext{
router: gin.Default(),
response: httptest.NewRecorder(),
@@ -104,7 +103,6 @@ func testChangePassword(t *testing.T, tc *TestContext) {
var verification models.Verification
result := database.DB.Where("user_id = ? AND type = ?", tc.user.ID, constants.VerificationTypes.Password).First(&verification)
assert.NoError(t, result.Error)
logger.Error.Printf("token from db: %#v", verification.VerificationToken)
requestBody := map[string]interface{}{
"password": "new-pas9247A@!sword",
"token": verification.VerificationToken,

View File

@@ -89,6 +89,10 @@ func (uc *UserController) UpdateHandler(c *gin.Context) {
var updateData RegistrationData
if err := c.ShouldBindJSON(&updateData); err != nil {
if updateData.User.Password != "" {
logger.Error.Printf("u.password: %#v", updateData.User.Password)
}
utils.HandleValidationError(c, err)
return
}
@@ -240,12 +244,12 @@ func (uc *UserController) RegisterUser(c *gin.Context) {
}
logger.Info.Printf("Registering user %v", regData.User.Email)
selectedModel, err := uc.MembershipService.GetSubscriptionByName(&regData.User.Membership.SubscriptionModel.Name)
selectedModel, err := uc.MembershipService.GetSubscriptionByName(&regData.User.Membership.Subscription.Name)
if err != nil {
utils.RespondWithError(c, err, "Error in Registeruser, couldn't get selected model", http.StatusNotFound, errors.Responses.Fields.SubscriptionModel, errors.Responses.Keys.InvalidSubscriptionModel)
utils.RespondWithError(c, err, "Error in Registeruser, couldn't get selected model", http.StatusNotFound, errors.Responses.Fields.Subscription, errors.Responses.Keys.InvalidSubscription)
return
}
regData.User.Membership.SubscriptionModel = *selectedModel
regData.User.Membership.Subscription = *selectedModel
// Get Gin's binding validator engine with all registered validators
validate := binding.Validator.Engine().(*validator.Validate)
@@ -254,7 +258,7 @@ func (uc *UserController) RegisterUser(c *gin.Context) {
utils.HandleValidationError(c, err)
return
}
if regData.User.Membership.SubscriptionModel.Name == constants.SupporterSubscriptionModelName {
if regData.User.Membership.Subscription.Name == constants.SupporterSubscriptionName {
regData.User.RoleID = constants.Roles.Supporter
} else {
regData.User.RoleID = constants.Roles.Member
@@ -347,7 +351,8 @@ func (uc *UserController) VerifyMailHandler(c *gin.Context) {
c.HTML(http.StatusBadRequest, "verification_error.html", gin.H{"ErrorMessage": "Couldn't find user"})
return
}
if !user.Verify(token, constants.VerificationTypes.Email) {
err = user.Verify(token, constants.VerificationTypes.Email)
if err != nil {
logger.Error.Printf("Couldn't find user verification in verifyMailHandler: %v", err)
c.HTML(http.StatusBadRequest, "verification_error.html", gin.H{"ErrorMessage": "Couldn't find user verification request"})
return

View File

@@ -351,8 +351,8 @@ func testCurrentUserHandler(t *testing.T, loginEmail string) http.Cookie {
if tt.expectedStatus == http.StatusOK {
var response struct {
User models.User `json:"user"`
Subscriptions []models.SubscriptionModel `json:"subscriptions"`
User models.User `json:"user"`
Subscriptions []models.Subscription `json:"subscriptions"`
}
err := json.Unmarshal(w.Body.Bytes(), &response)
assert.NoError(t, err)
@@ -407,12 +407,15 @@ func validateUser(assert bool, wantDBData map[string]interface{}) error {
if assert {
user := (*users)[0]
// Check for mandate reference
if user.BankAccount.MandateReference == "" {
if user.BankAccount.IBAN != "" && user.BankAccount.MandateReference == "" {
return fmt.Errorf("Mandate reference not generated for user: %s", user.Email)
} else if user.BankAccount.IBAN == "" && user.BankAccount.MandateReference != "" {
return fmt.Errorf("Mandate reference generated without IBAN for user: %s", user.Email)
}
// Validate mandate reference format
expected := user.GenerateMandateReference()
expected := user.BankAccount.GenerateMandateReference(user.ID)
if !strings.HasPrefix(user.BankAccount.MandateReference, expected) {
return fmt.Errorf("Mandate reference is invalid. Expected: %s, Got: %s", expected, user.BankAccount.MandateReference)
}
@@ -686,6 +689,20 @@ func testUpdateUser(t *testing.T) {
},
expectedStatus: http.StatusAccepted,
},
{
name: "Admin Password Update low entropy should fail",
setupCookie: func(req *http.Request) {
req.AddCookie(AdminCookie)
},
updateFunc: func(u *models.User) {
u.Password = "newpassword"
},
expectedErrors: []map[string]string{
{"field": "server.validation.special server.validation.uppercase server.validation.numbers server.validation.longer", "key": "server.validation.insecure"},
},
expectedStatus: http.StatusBadRequest,
},
{
name: "Admin Password Update",
setupCookie: func(req *http.Request) {
@@ -792,7 +809,11 @@ func testUpdateUser(t *testing.T) {
if updatedUser.Password == "" {
assert.Equal(t, user.Password, (*updatedUserFromDB).Password)
} else {
assert.NotEqual(t, user.Password, (*updatedUserFromDB).Password)
matches, err := updatedUserFromDB.PasswordMatches(updatedUser.Password)
if err != nil {
t.Fatalf("Error matching password: %v", err)
}
assert.True(t, matches, "Password mismatch")
}
updatedUserFromDB.Password = ""
@@ -820,7 +841,7 @@ func testUpdateUser(t *testing.T) {
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.SubscriptionID, updatedUserFromDB.Membership.SubscriptionID, "Membership.SubscriptionID mismatch")
assert.Equal(t, updatedUser.Membership.ParentMembershipID, updatedUserFromDB.Membership.ParentMembershipID, "Membership.ParentMembershipID mismatch")
if updatedUser.Licence == nil {
@@ -871,11 +892,11 @@ func checkWelcomeMail(message *utils.Email, user *models.User) error {
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.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/Monat</strong>: %v", user.Membership.Subscription.MonthlyFee)) {
return fmt.Errorf("Users monthly subscription fee(%v) has not been rendered in registration mail.", user.Membership.Subscription.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 !strings.Contains(message.Body, fmt.Sprintf("Preis/h</strong>: %v", user.Membership.Subscription.HourlyRate)) {
return fmt.Errorf("Users hourly subscription fee(%v) has not been rendered in registration mail.", user.Membership.Subscription.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)
@@ -907,11 +928,11 @@ func checkRegistrationMail(message *utils.Email, user *models.User) error {
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.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/Monat</strong>: %v", user.Membership.Subscription.MonthlyFee)) {
return fmt.Errorf("Users monthly subscription fee(%v) has not been rendered in registration mail.", user.Membership.Subscription.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 !strings.Contains(message.Body, fmt.Sprintf("Preis/h</strong>: %v", user.Membership.Subscription.HourlyRate)) {
return fmt.Errorf("Users hourly subscription fee(%v) has not been rendered in registration mail.", user.Membership.Subscription.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)
@@ -951,7 +972,7 @@ func checkVerificationMail(message *utils.Email, user *models.User) error {
if err != nil {
return fmt.Errorf("Error parsing verification URL: %#v", err.Error())
}
v, err := user.GetVerification(constants.VerificationTypes.Email)
v, err := user.FindVerification(constants.VerificationTypes.Email)
if err != nil {
return fmt.Errorf("Error getting verification token: %v", err.Error())
}
@@ -1132,7 +1153,7 @@ func getTestUsers() []RegisterUserTest {
user.BankAccount.IBAN = "DE1234234123134"
user.RoleID = constants.Roles.Supporter
user.Email = "john.supporter@example.com"
user.Membership.SubscriptionModel.Name = constants.SupporterSubscriptionModelName
user.Membership.Subscription.Name = constants.SupporterSubscriptionName
return user
})),
},
@@ -1145,7 +1166,7 @@ func getTestUsers() []RegisterUserTest {
user.BankAccount.IBAN = ""
user.RoleID = constants.Roles.Supporter
user.Email = "john.supporter@example.com"
user.Membership.SubscriptionModel.Name = constants.SupporterSubscriptionModelName
user.Membership.Subscription.Name = constants.SupporterSubscriptionName
return user
})),
},
@@ -1155,7 +1176,7 @@ func getTestUsers() []RegisterUserTest {
WantDBData: map[string]interface{}{"email": "john.doe@example.com"},
Assert: false,
Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
user.Membership.SubscriptionModel.Name = ""
user.Membership.Subscription.Name = ""
return user
})),
},
@@ -1165,7 +1186,7 @@ func getTestUsers() []RegisterUserTest {
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"
user.Membership.Subscription.Name = "NOTEXISTENTPLAN"
return user
})),
},
@@ -1204,7 +1225,7 @@ func getTestUsers() []RegisterUserTest {
Assert: false,
Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
user.Email = "john.junior.doe@example.com"
user.Membership.SubscriptionModel.Name = "additional"
user.Membership.Subscription.Name = "additional"
return user
})),
},
@@ -1216,7 +1237,7 @@ func getTestUsers() []RegisterUserTest {
Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
user.Email = "john.junior.doe@example.com"
user.Membership.ParentMembershipID = 200
user.Membership.SubscriptionModel.Name = "additional"
user.Membership.Subscription.Name = "additional"
return user
})),
},
@@ -1228,7 +1249,7 @@ func getTestUsers() []RegisterUserTest {
Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
user.Email = "john.junior.doe@example.com"
user.Membership.ParentMembershipID = 1
user.Membership.SubscriptionModel.Name = "additional"
user.Membership.Subscription.Name = "additional"
return user
})),
},

View File

@@ -10,7 +10,6 @@ import (
"fmt"
"time"
"github.com/alexedwards/argon2id"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
@@ -53,7 +52,7 @@ func Open(dbPath string, adminMail string, debug bool) (*gorm.DB, error) {
if err := db.AutoMigrate(
&models.User{},
&models.SubscriptionModel{},
&models.Subscription{},
&models.Membership{},
&models.Consent{},
&models.Verification{},
@@ -83,12 +82,12 @@ func Open(dbPath string, adminMail string, debug bool) (*gorm.DB, error) {
}
var subscriptionsCount int64
db.Model(&models.SubscriptionModel{}).Count(&subscriptionsCount)
subscriptionModels := createSubscriptionModels()
for _, model := range subscriptionModels {
db.Model(&models.Subscription{}).Count(&subscriptionsCount)
subscriptions := createSubscriptions()
for _, model := range subscriptions {
var exists int64
db.
Model(&models.SubscriptionModel{}).
Model(&models.Subscription{}).
Where("name = ?", model.Name).
Count(&exists)
logger.Error.Printf("looked for model.name %v and found %v", model.Name, exists)
@@ -103,7 +102,7 @@ func Open(dbPath string, adminMail string, debug bool) (*gorm.DB, error) {
var userCount int64
db.Model(&models.User{}).Count(&userCount)
if userCount == 0 {
var createdModel models.SubscriptionModel
var createdModel models.Subscription
if err := db.First(&createdModel).Error; err != nil {
return nil, err
}
@@ -118,10 +117,10 @@ func Open(dbPath string, adminMail string, debug bool) (*gorm.DB, error) {
return db, nil
}
func createSubscriptionModels() []models.SubscriptionModel {
return []models.SubscriptionModel{
func createSubscriptions() []models.Subscription {
return []models.Subscription{
{
Name: constants.SupporterSubscriptionModelName,
Name: constants.SupporterSubscriptionName,
Details: "Dieses Modell ist für Sponsoren und Nichtmitglieder, die keinen Vereinsmitglied sind.",
HourlyRate: 999,
MonthlyFee: 0,
@@ -162,11 +161,6 @@ func createAdmin(userMail string) (*models.User, error) {
// Encode into a URL-safe base64 string
password := base64.URLEncoding.EncodeToString(passwordBytes)[:12]
hash, err := argon2id.CreateHash(password, argon2id.DefaultParams)
if err != nil {
return nil, err
}
logger.Error.Print("==============================================================")
logger.Error.Printf("Admin Email: %v", userMail)
logger.Error.Printf("Admin Password: %v", password)
@@ -176,7 +170,7 @@ func createAdmin(userMail string) (*models.User, error) {
FirstName: "Ad",
LastName: "Min",
DateOfBirth: time.Now().AddDate(-20, 0, 0),
Password: hash,
Password: password,
Company: "",
Address: "",
ZipCode: "",

View File

@@ -1,13 +1,54 @@
package models
import "time"
import (
"GoMembership/pkg/logger"
"time"
"gorm.io/gorm"
)
type Insurance struct {
ID uint `gorm:"primary_key" json:"id"`
OwnerID uint `gorm:"not null" json:"owner_id" binding:"numeric"`
Cars []Car `gorm:"many2many:car_insurances;" json:"-"`
Company string `json:"company" binding:"safe_content"`
Reference string `json:"reference" binding:"safe_content"`
Notes string `json:"notes" binding:"safe_content"`
StartDate time.Time `json:"start_date"`
EndDate time.Time `json:"end_date"`
}
func (i *Insurance) Create(db *gorm.DB) error {
return db.Transaction(func(tx *gorm.DB) error {
// Create the base User record (omit associations to handle them separately)
if err := tx.Create(i).Error; err != nil {
return err
}
logger.Info.Printf("Insurance created: %#v", i)
// Preload all associations to return the fully populated User
return tx.
First(i, i.ID).Error // Refresh the user object with all associations
})
}
func (i *Insurance) Update(db *gorm.DB) error {
return db.Transaction(func(tx *gorm.DB) error {
// Check if the user exists in the database
var existingInsurance Insurance
logger.Info.Printf("updating Insurance: %#v", i)
if err := tx.First(&existingInsurance, i.ID).Error; err != nil {
return err
}
if err := tx.Model(&existingInsurance).Updates(i).Error; err != nil {
return err
}
return tx.First(i, i.ID).Error
})
}
func (i *Insurance) Delete(db *gorm.DB) error {
return db.Delete(&i).Error
}

View File

@@ -1,7 +1,9 @@
package models
import (
"GoMembership/internal/config"
"GoMembership/pkg/logger"
"fmt"
"time"
"gorm.io/gorm"
@@ -48,3 +50,10 @@ func (b *BankAccount) Update(db *gorm.DB) error {
func (b *BankAccount) Delete(db *gorm.DB) error {
return db.Delete(&b).Error
}
func (b *BankAccount) GenerateMandateReference(id uint) string {
if b.IBAN == "" {
return ""
}
return fmt.Sprintf("%s-%s%d-%s", config.Company.SepaPrefix, time.Now().Format("20060102"), id, b.IBAN[len(b.IBAN)-4:])
}

View File

@@ -1,123 +1,151 @@
package models
import (
"GoMembership/pkg/errors"
"GoMembership/pkg/logger"
"time"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
type Car struct {
ID uint `gorm:"primarykey" json:"id"`
ID uint `gorm:"primaryKey" json:"id"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt *time.Time
Status uint `json:"status"`
Name string `json:"name"`
Brand string `gorm:"not null" json:"brand"`
Model string `gorm:"not null" json:"model"`
Color string `gorm:"not null" json:"color"`
LicencePlate string `gorm:"not null,unique" json:"licence_plate"`
Price float32 `json:"price"`
Rate float32 `json:"rate"`
StartDate time.Time `json:"start_date"`
EndDate time.Time `json:"end_date"`
Location Location `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"location"`
LocationID uint
Damages *[]Damage `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"damages"`
Insurances *[]Insurance `gorm:"foreignkey:OwnerID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"insurance"`
Notes string `json:"notes"`
}
type Location struct {
ID uint `gorm:"primarykey" json:"id"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt *time.Time
Latitude float32 `json:"latitude"`
Longitude float32 `json:"longitude"`
}
type Damage struct {
ID uint `gorm:"primarykey" json:"id"`
CarID uint `json:"car_id"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt *time.Time
Opponent *User `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"opponent"`
OpponentID uint
Insurance *Insurance `gorm:"foreignkey:OwnerID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"insurance"`
InsuranceID uint
Notes string `json:"notes"`
Status uint `json:"status"`
Name string `json:"name"`
Brand string `gorm:"not null" json:"brand"`
Model string `gorm:"not null" json:"model"`
Color string `gorm:"not null" json:"color"`
LicencePlate string `gorm:"not null,unique" json:"licence_plate"`
Price float32 `gorm:"type:decimal(10,2)" json:"price"`
Rate float32 `gorm:"type:decimal(10,2)" json:"rate"`
StartDate time.Time `json:"start_date"`
EndDate time.Time `json:"end_date"`
Location *Location `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"location"`
Damages []Damage `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"damages"`
Insurances []Insurance `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;many2many:car_insurances" json:"insurances"`
Notes string `json:"notes"`
}
func (c *Car) Create(db *gorm.DB) error {
return db.Transaction(func(tx *gorm.DB) error {
// Create the base User record (omit associations to handle them separately)
if err := tx.Create(c).Error; err != nil {
if err := tx.Preload(clause.Associations).Create(c).Error; err != nil {
return err
}
// Replace associated Categories (assumes Categories already exist)
if c.Insurances != nil {
if err := tx.Model(c).Association("Insurances").Replace(c.Insurances); err != nil {
return err
}
}
logger.Info.Printf("car created: %#v", c)
// Preload all associations to return the fully populated User
return tx.
Preload("Insurances").
First(c, c.ID).Error // Refresh the user object with all associations
Preload(clause.Associations).
First(c, c.ID).Error
})
}
func (c *Car) Update(db *gorm.DB) error {
err := db.Transaction(func(tx *gorm.DB) error {
return db.Transaction(func(tx *gorm.DB) error {
// Check if the user exists in the database
var existingCar Car
logger.Info.Printf("updating car: %#v", c)
if err := tx.
Preload("Insurances").
if err := tx.Preload("Damages.Insurance").
Preload("Damages.Opponent").
First(&existingCar, c.ID).Error; err != nil {
return err
}
result := tx.Session(&gorm.Session{FullSaveAssociations: true}).Updates(c)
if result.Error != nil {
logger.Error.Printf("car update error: %#v", result.Error)
return result.Error
}
if result.RowsAffected == 0 {
return errors.ErrNoRowsAffected
}
if c.Insurances != nil {
if err := tx.Save(*c.Insurances).Error; err != nil {
return err
}
if err := tx.Session(&gorm.Session{FullSaveAssociations: true}).Updates(c).Error; err != nil {
return err
}
return nil
// if err := tx.Model(c).Association("Damages").Replace(c.Damages); err != nil {
// return err
// }
// if err := tx.Model(c).Association("Insurances").Replace(c.Insurances); err != nil {
// return err
// }
// Calculate damage IDs to delete
// existingDamageIDs := make(map[uint]bool)
// for _, d := range existingCar.Damages {
// existingDamageIDs[d.ID] = true
// }
// newDamageIDs := make(map[uint]bool)
// for _, d := range c.Damages {
// if d.ID != 0 {
// newDamageIDs[d.ID] = true
// }
// }
// // Find IDs to delete
// var toDelete []uint
// for id := range existingDamageIDs {
// if !newDamageIDs[id] {
// toDelete = append(toDelete, id)
// }
// }
// // Batch delete orphaned damages
// if len(toDelete) > 0 {
// if err := tx.Where("id IN ?", toDelete).Delete(&Damage{}).Error; err != nil {
// return err
// }
// }
// if len(c.Insurances) > 0 {
// logger.Info.Printf("updating insurances: %#v", c.Insurances)
// if err := tx.Model(&existingCar).Association("Insurances").Replace(c.Insurances); err != nil {
// return err
// }
// }
// // Upsert new damages
// for _, damage := range c.Damages {
// // Process relationships
// if damage.Opponent != nil {
// if err := tx.Save(damage.Opponent).Error; err != nil {
// return err
// }
// damage.OpponentID = damage.Opponent.ID
// }
// if damage.Insurance != nil {
// if err := tx.Save(damage.Insurance).Error; err != nil {
// return err
// }
// damage.InsuranceID = damage.Insurance.ID
// }
// // Create or update damage
// if err := tx.Save(damage).Error; err != nil {
// return err
// }
// }
// // Update associations
// if err := tx.Model(&existingCar).Association("Damages").Replace(c.Damages); err != nil {
// return err
// }
return tx.
Preload(clause.Associations).
Preload("Damages").
Preload("Insurances").
First(c, c.ID).Error
})
if err != nil {
return err
}
return db.
Preload("Insurances").
First(&c, c.ID).Error
}
func (c *Car) Delete(db *gorm.DB) error {
return db.Delete(&c).Error
return db.Select(clause.Associations).Delete(&c).Error
}
func GetAllCars(db *gorm.DB) ([]Car, error) {
var cars []Car
if err := db.Find(&cars).Error; err != nil {
if err := db.
Preload(clause.Associations).
Preload("Damages").
Preload("Insurances").
Find(&cars).Error; err != nil {
return nil, err
}
return cars, nil
@@ -125,7 +153,11 @@ func GetAllCars(db *gorm.DB) ([]Car, error) {
func (c *Car) FromID(db *gorm.DB, id uint) error {
var car Car
if err := db.Preload("Insurances").First(&car, id).Error; err != nil {
if err := db.
Preload(clause.Associations).
Preload("Damages").
Preload("Insurances").
First(&car, id).Error; err != nil {
return err
}
*c = car

View File

@@ -0,0 +1,58 @@
package models
import (
"GoMembership/pkg/logger"
"time"
"gorm.io/gorm"
)
type Damage struct {
ID uint `gorm:"primaryKey" json:"id"`
CarID uint `json:"car_id"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt *time.Time
Name string `json:"name"`
Date time.Time `json:"date"`
Opponent *User `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"opponent"`
OpponentID uint `json:"opponent_id"`
Driver *User `json:"driver"`
DriverID uint `json:"driver_id"`
Insurance *Insurance `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"insurance"`
InsuranceID uint `json:"insurance_id"`
Notes string `json:"notes"`
}
func (d *Damage) Create(db *gorm.DB) error {
// Create the base User record (omit associations to handle them separately)
if err := db.Create(d).Error; err != nil {
return err
}
logger.Info.Printf("Damage created: %#v", d)
// Preload all associations to return the fully populated User
return db.First(d, d.ID).Error // Refresh the user object with all associations
}
func (d *Damage) Update(db *gorm.DB) error {
return db.Transaction(func(tx *gorm.DB) error {
// Check if the user exists in the database
var existingDamage Damage
logger.Info.Printf("updating Damage: %#v", d)
if err := tx.First(&existingDamage, d.ID).Error; err != nil {
return err
}
if err := tx.Model(&existingDamage).Updates(d).Error; err != nil {
return err
}
return tx.First(d, d.ID).Error
})
}
func (d *Damage) Delete(db *gorm.DB) error {
return db.Delete(&d).Error
}

View File

@@ -0,0 +1,54 @@
package models
import (
"GoMembership/pkg/logger"
"time"
"gorm.io/gorm"
)
type Location struct {
ID uint `gorm:"primaryKey" json:"id"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt *time.Time
CarID uint `gorm:"index" json:"car_id"`
Latitude float32 `json:"latitude"`
Longitude float32 `json:"longitude"`
}
func (l *Location) Create(db *gorm.DB) error {
return db.Transaction(func(tx *gorm.DB) error {
// Create the base User record (omit associations to handle them separately)
if err := tx.Create(l).Error; err != nil {
return err
}
logger.Info.Printf("Location created: %#v", l)
// Preload all associations to return the fully populated User
return tx.
First(l, l.ID).Error // Refresh the user object with all associations
})
}
func (l *Location) Update(db *gorm.DB) error {
return db.Transaction(func(tx *gorm.DB) error {
// Check if the user exists in the database
var existingLocation Location
logger.Info.Printf("updating Location: %#v", l)
if err := tx.First(&existingLocation, l.ID).Error; err != nil {
return err
}
if err := tx.Model(&existingLocation).Updates(l).Error; err != nil {
return err
}
return tx.First(l, l.ID).Error
})
}
func (l *Location) Delete(db *gorm.DB) error {
return db.Delete(&l).Error
}

View File

@@ -8,20 +8,20 @@ import (
)
type Membership struct {
ID uint `gorm:"primaryKey" json:"id"`
UserID uint `gorm:"index" json:"user_id"`
CreatedAt time.Time
UpdatedAt time.Time
StartDate time.Time `json:"start_date"`
EndDate time.Time `json:"end_date"`
Status int8 `json:"status" binding:"number,safe_content"`
SubscriptionModel SubscriptionModel `gorm:"foreignKey:SubscriptionModelID" json:"subscription"`
SubscriptionModelID uint `json:"subsription_model_id"`
ParentMembershipID uint `json:"parent_member_id" binding:"omitempty,omitnil,number"`
ID uint `gorm:"primaryKey" json:"id"`
UserID uint `gorm:"index" json:"user_id"`
CreatedAt time.Time
UpdatedAt time.Time
StartDate time.Time `json:"start_date"`
EndDate time.Time `json:"end_date"`
Status int8 `json:"status" binding:"number,safe_content"`
Subscription Subscription `gorm:"foreignKey:SubscriptionID" json:"subscription"`
SubscriptionID uint `json:"subscription_id"`
ParentMembershipID uint `json:"parent_member_id" binding:"omitempty,omitnil,number"`
}
func (m *Membership) BeforeSave(tx *gorm.DB) error {
m.SubscriptionModelID = m.SubscriptionModel.ID
m.SubscriptionID = m.Subscription.ID
return nil
}
@@ -31,7 +31,7 @@ func (m *Membership) Create(db *gorm.DB) error {
}
logger.Info.Printf("Membership created: %#v", m)
return db.Preload("SubscriptionModel").First(m, m.ID).Error // Refresh the user object with SubscriptionModel
return db.Preload("Subscription").First(m, m.ID).Error // Refresh the user object with Subscription
}
func (m *Membership) Update(db *gorm.DB) error {

View File

@@ -7,7 +7,7 @@ import (
"gorm.io/gorm"
)
type SubscriptionModel struct {
type Subscription struct {
ID uint `json:"id" gorm:"primaryKey"`
CreatedAt time.Time
UpdatedAt time.Time
@@ -21,31 +21,31 @@ type SubscriptionModel struct {
IncludedPerMonth int16 `json:"included_hours_per_month"`
}
func (s *SubscriptionModel) Create(db *gorm.DB) error {
func (s *Subscription) Create(db *gorm.DB) error {
return db.Transaction(func(tx *gorm.DB) error {
// Create the base User record (omit associations to handle them separately)
if err := tx.Create(s).Error; err != nil {
return err
}
logger.Info.Printf("SubscriptionModel created: %#v", s)
logger.Info.Printf("Subscription created: %#v", s)
// Preload all associations to retuvn the fully populated User
return tx.
First(s, s.ID).Error // Refresh the user object with all associations
})
}
func (s *SubscriptionModel) Update(db *gorm.DB) error {
func (s *Subscription) Update(db *gorm.DB) error {
return db.Transaction(func(tx *gorm.DB) error {
// Check if the user exists in the database
var existingSubscriptionModel SubscriptionModel
var existingSubscription Subscription
logger.Info.Printf("updating SubscriptionModel: %#v", s)
if err := tx.First(&existingSubscriptionModel, s.ID).Error; err != nil {
logger.Info.Printf("updating Subscription: %#v", s)
if err := tx.First(&existingSubscription, s.ID).Error; err != nil {
return err
}
if err := tx.Model(&existingSubscriptionModel).Updates(s).Error; err != nil {
if err := tx.Model(&existingSubscription).Updates(s).Error; err != nil {
return err
}
return tx.First(s, s.ID).Error
@@ -53,6 +53,6 @@ func (s *SubscriptionModel) Update(db *gorm.DB) error {
}
func (s *SubscriptionModel) Delete(db *gorm.DB) error {
func (s *Subscription) Delete(db *gorm.DB) error {
return db.Delete(&s).Error
}

View File

@@ -3,7 +3,6 @@ package models
import (
"GoMembership/internal/config"
"GoMembership/internal/constants"
"GoMembership/internal/utils"
"GoMembership/pkg/errors"
"GoMembership/pkg/logger"
"fmt"
@@ -45,7 +44,7 @@ type User struct {
func (u *User) AfterCreate(tx *gorm.DB) (err error) {
if u.BankAccount != nil && u.BankAccount.MandateReference == "" {
u.BankAccount.MandateReference = u.GenerateMandateReference()
u.BankAccount.MandateReference = u.BankAccount.GenerateMandateReference(u.ID)
u.BankAccount.Update(tx)
}
return nil
@@ -53,38 +52,16 @@ func (u *User) AfterCreate(tx *gorm.DB) (err error) {
func (u *User) BeforeSave(tx *gorm.DB) (err error) {
u.Email = strings.ToLower(u.Email)
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) SetPassword(plaintextPassword string) error {
if plaintextPassword == "" {
return nil
if u.Password != "" {
hash, err := argon2id.CreateHash(u.Password, argon2id.DefaultParams)
if err != nil {
return err
}
u.Password = hash
}
hash, err := argon2id.CreateHash(plaintextPassword, argon2id.DefaultParams)
if err != nil {
return err
}
u.Password = hash
return nil
}
func (u *User) PasswordMatches(plaintextPassword string) (bool, error) {
return argon2id.ComparePasswordAndHash(plaintextPassword, u.Password)
}
func (u *User) PasswordExists() bool {
return u.Password != ""
}
func (u *User) Delete(db *gorm.DB) error {
return db.Delete(&User{}, "id = ?", u.ID).Error
}
func (u *User) Create(db *gorm.DB) error {
return db.Transaction(func(tx *gorm.DB) error {
@@ -96,118 +73,6 @@ func (u *User) Create(db *gorm.DB) error {
})
}
// return db.Transaction(func(tx *gorm.DB) error {
// // Initialize slices/pointers if nil
// if u.Verifications == nil {
// u.Verifications = &[]Verification{}
// }
// // Create base user first
// if err := tx.Omit(clause.Associations).Create(u).Error; err != nil {
// return fmt.Errorf("failed to create user: %w", err)
// }
// // Handle BankAccount
// if u.BankAccount != (BankAccount{}) {
// u.BankAccount.MandateReference = u.GenerateMandateReference()
// if err := tx.Create(&u.BankAccount).Error; err != nil {
// return fmt.Errorf("failed to create bank account: %w", err)
// }
// if err := tx.Model(u).Update("bank_account_id", u.BankAccount.ID).Error; err != nil {
// return fmt.Errorf("failed to link bank account: %w", err)
// }
// }
// // Handle Membership and SubscriptionModel
// if u.Membership != (Membership{}) {
// if err := tx.Create(&u.Membership).Error; err != nil {
// return fmt.Errorf("failed to create membership: %w", err)
// }
// if err := tx.Model(u).Update("membership_id", u.Membership.ID).Error; err != nil {
// return fmt.Errorf("failed to link membership: %w", err)
// }
// }
// // Handle Licence and Categories
// if u.Licence != nil {
// u.Licence.UserID = u.ID
// if err := tx.Create(u.Licence).Error; err != nil {
// return fmt.Errorf("failed to create licence: %w", err)
// }
// if len(u.Licence.Categories) > 0 {
// if err := tx.Model(u.Licence).Association("Categories").Replace(u.Licence.Categories); err != nil {
// return fmt.Errorf("failed to link categories: %w", err)
// }
// }
// if err := tx.Model(u).Update("licence_id", u.Licence.ID).Error; err != nil {
// return fmt.Errorf("failed to link licence: %w", err)
// }
// }
// // Handle Consents
// for i := range u.Consents {
// u.Consents[i].UserID = u.ID
// }
// if len(u.Consents) > 0 {
// if err := tx.Create(&u.Consents).Error; err != nil {
// return fmt.Errorf("failed to create consents: %w", err)
// }
// }
// // Handle Verifications
// for i := range *u.Verifications {
// (*u.Verifications)[i].UserID = u.ID
// }
// if len(*u.Verifications) > 0 {
// if err := tx.Create(u.Verifications).Error; err != nil {
// return fmt.Errorf("failed to create verifications: %w", err)
// }
// }
// // Reload the complete user with all associations
// return tx.Preload(clause.Associations).
// Preload("Membership.SubscriptionModel").
// Preload("Licence.Categories").
// First(u, u.ID).Error
// })
// }
// func (u *User) Create(db *gorm.DB) error {
// return db.Transaction(func(tx *gorm.DB) error {
// // Create the base User record (omit associations to handle them separately)
// if err := tx.Create(u).Error; err != nil {
// return err
// }
// for i := range u.Consents {
// u.Consents[i].UserID = u.ID
// }
// for i := range *u.Verifications {
// (*u.Verifications)[i].UserID = u.ID
// }
// if err := tx.Session(&gorm.Session{FullSaveAssociations: true}).Updates(u).Error; err != nil {
// return err
// }
// // Replace associated Categories (assumes Categories already exist)
// if u.Licence != nil && len(u.Licence.Categories) > 0 {
// if err := tx.Model(u.Licence).Association("Categories").Replace(u.Licence.Categories); err != nil {
// return err
// }
// }
// logger.Info.Printf("user created: %#v", u.Safe())
// // Preload all associations to return the fully populated User
// return tx.
// Preload(clause.Associations).
// Preload("Membership.SubscriptionModel").
// Preload("Licence.Categories").
// First(u, u.ID).Error // Refresh the user object with all associations
// })
// }
func (u *User) Update(db *gorm.DB) error {
err := db.Transaction(func(tx *gorm.DB) error {
@@ -220,7 +85,7 @@ func (u *User) Update(db *gorm.DB) error {
return err
}
// Update the user's main fields
result := tx.Session(&gorm.Session{FullSaveAssociations: true}).Omit("Password", "Verifications", "Licence.Categories").Updates(u)
result := tx.Session(&gorm.Session{FullSaveAssociations: true}).Omit("Verifications", "Licence.Categories").Updates(u)
if result.Error != nil {
logger.Error.Printf("User update error in update user: %#v", result.Error)
return result.Error
@@ -229,57 +94,6 @@ func (u *User) Update(db *gorm.DB) error {
return errors.ErrNoRowsAffected
}
if u.Password != "" {
if err := tx.Model(&existingUser).
Update("Password", u.Password).Error; err != nil {
logger.Error.Printf("Password update error in update user: %#v", err)
return err
}
}
// // Update the Membership if provided
// if u.Membership.ID != 0 {
// if err := tx.Model(&existingUser.Membership).Updates(u.Membership).Where("id = ?", existingUser.Membership.ID).Error; err != nil {
// logger.Error.Printf("Membership update error in update user: %#v", err)
// return err
// }
// }
// if u.Licence != nil {
// u.Licence.UserID = existingUser.ID
// if err := tx.Save(u.Licence).Error; err != nil {
// return err
// }
// if err := tx.Model(&existingUser).Update("LicenceID", u.Licence.ID).Error; err != nil {
// return err
// }
// if err := tx.Model(u.Licence).Association("Categories").Replace(u.Licence.Categories); err != nil {
// return err
// }
// }
// if u.Licence != nil {
// if existingUser.Licence == nil || existingUser.LicenceID == 0 {
// u.Licence.UserID = existingUser.ID // Ensure Licence belongs to User
// if err := tx.Create(u.Licence).Error; err != nil {
// return err
// }
// existingUser.Licence = u.Licence
// existingUser.LicenceID = u.Licence.ID
// if err := tx.Model(&existingUser).Update("LicenceID", u.Licence.ID).Error; err != nil {
// return err
// }
// }
// if err := tx.Model(existingUser.Licence).Updates(u.Licence).Error; err != nil {
// return err
// }
// // Update Categories association
// if err := tx.Model(existingUser.Licence).Association("Categories").Replace(u.Licence.Categories); err != nil {
// return err
// }
// }
if u.Verifications != nil {
if err := tx.Save(u.Verifications).Error; err != nil {
return err
@@ -300,16 +114,20 @@ func (u *User) Update(db *gorm.DB) error {
return db.
Preload(clause.Associations).
Preload("Membership.SubscriptionModel").
Preload("Membership.Subscription").
Preload("Licence.Categories").
First(&u, u.ID).Error
}
func (u *User) Delete(db *gorm.DB) error {
return db.Delete(&User{}, "id = ?", u.ID).Error
}
func (u *User) FromID(db *gorm.DB, userID *uint) error {
var user User
result := db.
Preload(clause.Associations).
Preload("Membership.SubscriptionModel").
Preload("Membership.Subscription").
Preload("Licence.Categories").
First(&user, userID)
if result.Error != nil {
@@ -326,7 +144,7 @@ func (u *User) FromEmail(db *gorm.DB, email *string) error {
var user User
result := db.
Preload(clause.Associations).
Preload("Membership.SubscriptionModel").
Preload("Membership.Subscription").
Preload("Licence.Categories").
Where("email = ?", email).First(&user)
if result.Error != nil {
@@ -354,6 +172,14 @@ func (u *User) FromContext(db *gorm.DB, c *gin.Context) error {
return nil
}
func (u *User) PasswordMatches(plaintextPassword string) (bool, error) {
return argon2id.ComparePasswordAndHash(plaintextPassword, u.Password)
}
func (u *User) PasswordExists() bool {
return u.Password != ""
}
func (u *User) IsVerified() bool {
return u.Status > constants.DisabledStatus
}
@@ -378,24 +204,20 @@ func (u *User) SetVerification(verificationType string) (*Verification, error) {
if u.Verifications == nil {
u.Verifications = []Verification{}
}
token, err := utils.GenerateVerificationToken()
v, err := CreateVerification(verificationType)
if err != nil {
return nil, err
}
v := Verification{
UserID: u.ID,
VerificationToken: token,
Type: verificationType,
}
v.UserID = u.ID
if vi := slices.IndexFunc(u.Verifications, func(vsl Verification) bool { return vsl.Type == v.Type }); vi > -1 {
u.Verifications[vi] = v
u.Verifications[vi] = *v
} else {
u.Verifications = append(u.Verifications, v)
u.Verifications = append(u.Verifications, *v)
}
return &v, nil
return v, nil
}
func (u *User) GetVerification(verificationType string) (*Verification, error) {
func (u *User) FindVerification(verificationType string) (*Verification, error) {
if u.Verifications == nil {
return nil, errors.ErrNoData
}
@@ -406,10 +228,10 @@ func (u *User) GetVerification(verificationType string) (*Verification, error) {
return &u.Verifications[vi], nil
}
func (u *User) Verify(token string, verificationType string) bool {
func (u *User) Verify(token string, verificationType string) error {
if token == "" || verificationType == "" {
logger.Error.Printf("token or verification type are empty in user.Verify")
return false
return errors.ErrNoData
}
vi := slices.IndexFunc(u.Verifications, func(vsl Verification) bool {
@@ -418,17 +240,9 @@ func (u *User) Verify(token string, verificationType string) bool {
if vi == -1 {
logger.Error.Printf("Couldn't find verification in users verifications")
return false
return errors.ErrNotFound
}
if u.Verifications[vi].VerifiedAt != nil {
logger.Error.Printf("VerifiedAt is not nil, already verified?: %#v", u.Verifications[vi])
return false
}
t := time.Now()
u.Verifications[vi].VerifiedAt = &t
return true
return u.Verifications[vi].Validate()
}
func (u *User) Safe() map[string]interface{} {
@@ -442,14 +256,14 @@ func (u *User) Safe() map[string]interface{} {
"end_date": u.Membership.EndDate,
"status": u.Membership.Status,
"subscription": map[string]interface{}{
"id": u.Membership.SubscriptionModel.ID,
"name": u.Membership.SubscriptionModel.Name,
"details": u.Membership.SubscriptionModel.Details,
"conditions": u.Membership.SubscriptionModel.Conditions,
"monthly_fee": u.Membership.SubscriptionModel.MonthlyFee,
"hourly_rate": u.Membership.SubscriptionModel.HourlyRate,
"included_per_year": u.Membership.SubscriptionModel.IncludedPerYear,
"included_per_month": u.Membership.SubscriptionModel.IncludedPerMonth,
"id": u.Membership.Subscription.ID,
"name": u.Membership.Subscription.Name,
"details": u.Membership.Subscription.Details,
"conditions": u.Membership.Subscription.Conditions,
"monthly_fee": u.Membership.Subscription.MonthlyFee,
"hourly_rate": u.Membership.Subscription.HourlyRate,
"included_per_year": u.Membership.Subscription.IncludedPerYear,
"included_per_month": u.Membership.Subscription.IncludedPerMonth,
},
}
}
@@ -543,7 +357,7 @@ func GetUsersWhere(db *gorm.DB, where map[string]interface{}) (*[]User, error) {
var users []User
result := db.
Preload(clause.Associations).
Preload("Membership.SubscriptionModel").
Preload("Membership.Subscription").
Preload("Licence.Categories").
Where(where).Find(&users)
if result.Error != nil {

View File

@@ -1,6 +1,8 @@
package models
import (
"GoMembership/internal/utils"
"GoMembership/pkg/errors"
"GoMembership/pkg/logger"
"time"
@@ -46,3 +48,31 @@ func (v *Verification) Update(db *gorm.DB) error {
func (v *Verification) Delete(db *gorm.DB) error {
return db.Delete(&v).Error
}
func (v *Verification) Validate() error {
if v.VerifiedAt != nil {
return errors.ErrAlreadyVerified
}
t := time.Now()
v.VerifiedAt = &t
return nil
}
func CreateVerification(verificationType string) (*Verification, error) {
token, err := GenerateVerificationToken()
if err != nil {
return nil, err
}
v := Verification{
UserID: 0,
VerificationToken: token,
Type: verificationType,
}
return &v, nil
}
func GenerateVerificationToken() (string, error) {
return utils.GenerateRandomString(32)
}

View File

@@ -1,97 +0,0 @@
package repositories
import (
"GoMembership/internal/database"
"gorm.io/gorm"
"GoMembership/internal/models"
)
type SubscriptionModelsRepositoryInterface interface {
CreateSubscriptionModel(subscriptionModel *models.SubscriptionModel) (uint, error)
UpdateSubscription(subscription *models.SubscriptionModel) (*models.SubscriptionModel, error)
GetSubscriptionModelNames() ([]string, error)
GetSubscriptions(where map[string]interface{}) (*[]models.SubscriptionModel, error)
// GetUsersBySubscription(id uint) (*[]models.SubscriptionModel, error)
DeleteSubscription(id *uint) error
}
type SubscriptionModelsRepository struct{}
func (sr *SubscriptionModelsRepository) CreateSubscriptionModel(subscriptionModel *models.SubscriptionModel) (uint, error) {
result := database.DB.Create(subscriptionModel)
if result.Error != nil {
return 0, result.Error
}
return subscriptionModel.ID, nil
}
func (sr *SubscriptionModelsRepository) UpdateSubscription(subscription *models.SubscriptionModel) (*models.SubscriptionModel, error) {
result := database.DB.Model(&models.SubscriptionModel{ID: subscription.ID}).Updates(subscription)
if result.Error != nil {
return nil, result.Error
}
return subscription, nil
}
func (sr *SubscriptionModelsRepository) DeleteSubscription(id *uint) error {
result := database.DB.Delete(&models.SubscriptionModel{}, id)
if result.Error != nil {
return result.Error
}
return nil
}
func GetSubscriptionByName(modelname *string) (*models.SubscriptionModel, error) {
var model models.SubscriptionModel
result := database.DB.Where("name = ?", modelname).First(&model)
if result.Error != nil {
return nil, result.Error
}
return &model, nil
}
func (sr *SubscriptionModelsRepository) GetSubscriptionModelNames() ([]string, error) {
var names []string
if err := database.DB.Model(&models.SubscriptionModel{}).Pluck("name", &names).Error; err != nil {
return []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
}
func GetUsersBySubscription(subscriptionID uint) (*[]models.User, error) {
var users []models.User
err := database.DB.Preload("Membership").
Preload("Membership.SubscriptionModel").
Preload("BankAccount").
Preload("Licence").
Preload("Licence.Categories").
Joins("JOIN memberships ON users.id = memberships.user_id").
Joins("JOIN subscription_models ON memberships.subscription_model_id = subscription_models.id").
Where("subscription_models.id = ?", subscriptionID).
Find(&users).Error
if err != nil {
return nil, err
}
return &users, nil
}

View File

@@ -0,0 +1,97 @@
package repositories
import (
"GoMembership/internal/database"
"gorm.io/gorm"
"GoMembership/internal/models"
)
type SubscriptionsRepositoryInterface interface {
CreateSubscription(subscription *models.Subscription) (uint, error)
UpdateSubscription(subscription *models.Subscription) (*models.Subscription, error)
GetSubscriptionNames() ([]string, error)
GetSubscriptions(where map[string]interface{}) (*[]models.Subscription, error)
// GetUsersBySubscription(id uint) (*[]models.Subscription, error)
DeleteSubscription(id *uint) error
}
type SubscriptionsRepository struct{}
func (sr *SubscriptionsRepository) CreateSubscription(subscription *models.Subscription) (uint, error) {
result := database.DB.Create(subscription)
if result.Error != nil {
return 0, result.Error
}
return subscription.ID, nil
}
func (sr *SubscriptionsRepository) UpdateSubscription(subscription *models.Subscription) (*models.Subscription, error) {
result := database.DB.Model(&models.Subscription{ID: subscription.ID}).Updates(subscription)
if result.Error != nil {
return nil, result.Error
}
return subscription, nil
}
func (sr *SubscriptionsRepository) DeleteSubscription(id *uint) error {
result := database.DB.Delete(&models.Subscription{}, id)
if result.Error != nil {
return result.Error
}
return nil
}
func GetSubscriptionByName(modelname *string) (*models.Subscription, error) {
var model models.Subscription
result := database.DB.Where("name = ?", modelname).First(&model)
if result.Error != nil {
return nil, result.Error
}
return &model, nil
}
func (sr *SubscriptionsRepository) GetSubscriptionNames() ([]string, error) {
var names []string
if err := database.DB.Model(&models.Subscription{}).Pluck("name", &names).Error; err != nil {
return []string{}, err
}
return names, nil
}
func (sr *SubscriptionsRepository) GetSubscriptions(where map[string]interface{}) (*[]models.Subscription, error) {
var subscriptions []models.Subscription
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
}
func GetUsersBySubscription(subscriptionID uint) (*[]models.User, error) {
var users []models.User
err := database.DB.Preload("Membership").
Preload("Membership.Subscription").
Preload("BankAccount").
Preload("Licence").
Preload("Licence.Categories").
Joins("JOIN memberships ON users.id = memberships.user_id").
Joins("JOIN subscriptions ON memberships.subscription_id = subscriptions.id").
Where("subscriptions.id = ?", subscriptionID).
Find(&users).Error
if err != nil {
return nil, err
}
return &users, nil
}

View File

@@ -37,7 +37,7 @@ func Run(db *gorm.DB) {
bankAccountService := &services.BankAccountService{Repo: bankAccountRepo}
var membershipRepo repositories.MembershipRepositoryInterface = &repositories.MembershipRepository{}
var subscriptionRepo repositories.SubscriptionModelsRepositoryInterface = &repositories.SubscriptionModelsRepository{}
var subscriptionRepo repositories.SubscriptionsRepositoryInterface = &repositories.SubscriptionsRepository{}
membershipService := &services.MembershipService{Repo: membershipRepo, SubscriptionRepo: subscriptionRepo}
var licenceRepo repositories.LicenceInterface = &repositories.LicenceRepository{}

View File

@@ -59,8 +59,8 @@ func (s *CarService) FromID(id uint) (*models.Car, error) {
// GetAll retrieves all cars
func (s *CarService) GetAll() (*[]models.Car, error) {
var cars []models.Car
if err := s.DB.Find(&cars).Error; err != nil {
cars, err := models.GetAllCars(s.DB)
if err != nil {
return nil, err
}
return &cars, nil

View File

@@ -169,10 +169,10 @@ func (s *EmailService) SendWelcomeEmail(user *models.User) error {
}{
Company: user.Company,
FirstName: user.FirstName,
MembershipModel: user.Membership.SubscriptionModel.Name,
MembershipModel: user.Membership.Subscription.Name,
MembershipID: user.Membership.ID,
MembershipFee: float32(user.Membership.SubscriptionModel.MonthlyFee),
RentalFee: float32(user.Membership.SubscriptionModel.HourlyRate),
MembershipFee: float32(user.Membership.Subscription.MonthlyFee),
RentalFee: float32(user.Membership.Subscription.HourlyRate),
BASEURL: config.Site.BaseURL,
WebsiteTitle: config.Site.WebsiteTitle,
Logo: config.Templates.LogoURI,
@@ -216,10 +216,10 @@ func (s *EmailService) SendRegistrationNotification(user *models.User) error {
Company: user.Company,
FirstName: user.FirstName,
LastName: user.LastName,
MembershipModel: user.Membership.SubscriptionModel.Name,
MembershipModel: user.Membership.Subscription.Name,
MembershipID: user.Membership.ID,
MembershipFee: float32(user.Membership.SubscriptionModel.MonthlyFee),
RentalFee: float32(user.Membership.SubscriptionModel.HourlyRate),
MembershipFee: float32(user.Membership.Subscription.MonthlyFee),
RentalFee: float32(user.Membership.Subscription.HourlyRate),
Address: user.Address,
ZipCode: user.ZipCode,
City: user.City,

View File

@@ -11,17 +11,17 @@ import (
type MembershipServiceInterface interface {
RegisterMembership(membership *models.Membership) (uint, error)
FindMembershipByUserID(userID uint) (*models.Membership, error)
RegisterSubscription(subscription *models.SubscriptionModel) (uint, error)
UpdateSubscription(subscription *models.SubscriptionModel) (*models.SubscriptionModel, error)
RegisterSubscription(subscription *models.Subscription) (uint, error)
UpdateSubscription(subscription *models.Subscription) (*models.Subscription, error)
DeleteSubscription(id *uint, name *string) error
GetSubscriptionModelNames() ([]string, error)
GetSubscriptionByName(modelname *string) (*models.SubscriptionModel, error)
GetSubscriptions(where map[string]interface{}) (*[]models.SubscriptionModel, error)
GetSubscriptionNames() ([]string, error)
GetSubscriptionByName(modelname *string) (*models.Subscription, error)
GetSubscriptions(where map[string]interface{}) (*[]models.Subscription, error)
}
type MembershipService struct {
Repo repositories.MembershipRepositoryInterface
SubscriptionRepo repositories.SubscriptionModelsRepositoryInterface
SubscriptionRepo repositories.SubscriptionsRepositoryInterface
}
func (service *MembershipService) RegisterMembership(membership *models.Membership) (uint, error) {
@@ -29,7 +29,7 @@ func (service *MembershipService) RegisterMembership(membership *models.Membersh
return service.Repo.CreateMembership(membership)
}
func (service *MembershipService) UpdateSubscription(subscription *models.SubscriptionModel) (*models.SubscriptionModel, error) {
func (service *MembershipService) UpdateSubscription(subscription *models.Subscription) (*models.Subscription, error) {
existingSubscription, err := repositories.GetSubscriptionByName(&subscription.Name)
if err != nil {
@@ -82,19 +82,19 @@ func (service *MembershipService) FindMembershipByUserID(userID uint) (*models.M
}
// Membership_Subscriptions
func (service *MembershipService) RegisterSubscription(subscription *models.SubscriptionModel) (uint, error) {
return service.SubscriptionRepo.CreateSubscriptionModel(subscription)
func (service *MembershipService) RegisterSubscription(subscription *models.Subscription) (uint, error) {
return service.SubscriptionRepo.CreateSubscription(subscription)
}
func (service *MembershipService) GetSubscriptionModelNames() ([]string, error) {
return service.SubscriptionRepo.GetSubscriptionModelNames()
func (service *MembershipService) GetSubscriptionNames() ([]string, error) {
return service.SubscriptionRepo.GetSubscriptionNames()
}
func (service *MembershipService) GetSubscriptionByName(modelname *string) (*models.SubscriptionModel, error) {
func (service *MembershipService) GetSubscriptionByName(modelname *string) (*models.Subscription, error) {
return repositories.GetSubscriptionByName(modelname)
}
func (service *MembershipService) GetSubscriptions(where map[string]interface{}) (*[]models.SubscriptionModel, error) {
func (service *MembershipService) GetSubscriptions(where map[string]interface{}) (*[]models.Subscription, error) {
if where == nil {
where = map[string]interface{}{}
}

View File

@@ -74,15 +74,13 @@ func (s *UserService) Update(user *models.User) (*models.User, error) {
}
user.BankAccount.ID = existingUser.BankAccount.ID
user.SetPassword(user.Password)
// Validate subscription model
selectedModel, err := repositories.GetSubscriptionByName(&user.Membership.SubscriptionModel.Name)
selectedModel, err := repositories.GetSubscriptionByName(&user.Membership.Subscription.Name)
if err != nil {
return nil, errors.ErrSubscriptionNotFound
}
user.Membership.SubscriptionModel = *selectedModel
user.Membership.SubscriptionModelID = selectedModel.ID
user.Membership.Subscription = *selectedModel
user.Membership.SubscriptionID = selectedModel.ID
if err := user.Update(s.DB); err != nil {
if err == gorm.ErrRecordNotFound {
@@ -97,14 +95,13 @@ func (s *UserService) Update(user *models.User) (*models.User, error) {
}
func (s *UserService) Register(user *models.User) (id uint, token string, err error) {
user.SetPassword(user.Password)
selectedModel, err := repositories.GetSubscriptionByName(&user.Membership.SubscriptionModel.Name)
selectedModel, err := repositories.GetSubscriptionByName(&user.Membership.Subscription.Name)
if err != nil {
return 0, "", errors.ErrSubscriptionNotFound
}
user.Membership.SubscriptionModel = *selectedModel
user.Membership.SubscriptionModelID = selectedModel.ID
user.Membership.Subscription = *selectedModel
user.Membership.SubscriptionID = selectedModel.ID
user.Status = constants.UnverifiedStatus
user.BankAccount.MandateDateSigned = time.Now()
v, err := user.SetVerification(constants.VerificationTypes.Email)

View File

@@ -30,10 +30,6 @@ func GenerateRandomString(length int) (string, error) {
return base64.URLEncoding.EncodeToString(bytes), nil
}
func GenerateVerificationToken() (string, error) {
return GenerateRandomString(32)
}
func DecodeMail(message string) (*Email, error) {
msg, err := mail.ReadMessage(strings.NewReader(message))
if err != nil {

View File

@@ -44,7 +44,7 @@ func HandleUserUpdateError(c *gin.Context, err error) {
case errors.ErrDuplicateEntry:
RespondWithError(c, err, "User Unique constraint failed", http.StatusConflict, errors.Responses.Fields.User, errors.Responses.Keys.Duplicate)
case errors.ErrSubscriptionNotFound:
RespondWithError(c, err, "Couldn't find subscription", http.StatusNotFound, errors.Responses.Fields.SubscriptionModel, errors.Responses.Keys.NotFound)
RespondWithError(c, err, "Couldn't find subscription", http.StatusNotFound, errors.Responses.Fields.Subscription, errors.Responses.Keys.NotFound)
default:
RespondWithError(c, err, "Couldn't update user", http.StatusInternalServerError, errors.Responses.Fields.User, errors.Responses.Keys.InternalServerError)
}
@@ -53,30 +53,30 @@ func HandleUserUpdateError(c *gin.Context, err error) {
func HandleSubscriptionDeleteError(c *gin.Context, err error) {
switch err {
case errors.ErrNoData:
RespondWithError(c, err, "Missing subscription name during deletion", http.StatusExpectationFailed, errors.Responses.Fields.SubscriptionModel, errors.Responses.Keys.Invalid)
RespondWithError(c, err, "Missing subscription name during deletion", http.StatusExpectationFailed, errors.Responses.Fields.Subscription, errors.Responses.Keys.Invalid)
case errors.ErrSubscriptionNotFound:
RespondWithError(c, err, "Subscription not found", http.StatusNotFound, errors.Responses.Fields.SubscriptionModel, errors.Responses.Keys.NotFound)
RespondWithError(c, err, "Subscription not found", http.StatusNotFound, errors.Responses.Fields.Subscription, errors.Responses.Keys.NotFound)
case errors.ErrInvalidSubscriptionData:
RespondWithError(c, err, "Invalid subscription data", http.StatusBadRequest, errors.Responses.Fields.SubscriptionModel, errors.Responses.Keys.Invalid)
RespondWithError(c, err, "Invalid subscription data", http.StatusBadRequest, errors.Responses.Fields.Subscription, errors.Responses.Keys.Invalid)
case errors.ErrSubscriptionInUse:
RespondWithError(c, err, "Subscription is in use by at least one user", http.StatusExpectationFailed, errors.Responses.Fields.SubscriptionModel, errors.Responses.Keys.InUse)
RespondWithError(c, err, "Subscription is in use by at least one user", http.StatusExpectationFailed, errors.Responses.Fields.Subscription, errors.Responses.Keys.InUse)
default:
RespondWithError(c, err, "Error during subscription Deletion", http.StatusInternalServerError, errors.Responses.Fields.SubscriptionModel, errors.Responses.Keys.InternalServerError)
RespondWithError(c, err, "Error during subscription Deletion", http.StatusInternalServerError, errors.Responses.Fields.Subscription, errors.Responses.Keys.InternalServerError)
}
}
func HandleSubscriptionUpdateError(c *gin.Context, err error) {
if strings.Contains(err.Error(), "UNIQUE constraint failed") {
RespondWithError(c, err, "Subscription already exists", http.StatusConflict, errors.Responses.Fields.SubscriptionModel, errors.Responses.Keys.Duplicate)
RespondWithError(c, err, "Subscription already exists", http.StatusConflict, errors.Responses.Fields.Subscription, errors.Responses.Keys.Duplicate)
} else {
switch err {
case errors.ErrSubscriptionNotFound:
RespondWithError(c, err, "Subscription not found", http.StatusNotFound, errors.Responses.Fields.SubscriptionModel, errors.Responses.Keys.NotFound)
RespondWithError(c, err, "Subscription not found", http.StatusNotFound, errors.Responses.Fields.Subscription, errors.Responses.Keys.NotFound)
case errors.ErrInvalidSubscriptionData:
RespondWithError(c, err, "Invalid subscription data", http.StatusBadRequest, errors.Responses.Fields.SubscriptionModel, errors.Responses.Keys.Invalid)
RespondWithError(c, err, "Invalid subscription data", http.StatusBadRequest, errors.Responses.Fields.Subscription, errors.Responses.Keys.Invalid)
default:
RespondWithError(c, err, "Couldn't update subscription", http.StatusInternalServerError, errors.Responses.Fields.SubscriptionModel, errors.Responses.Keys.InternalServerError)
RespondWithError(c, err, "Couldn't update subscription", http.StatusInternalServerError, errors.Responses.Fields.Subscription, errors.Responses.Keys.InternalServerError)
}
}
}

View File

@@ -10,17 +10,17 @@ import (
)
func validateMembership(db *gorm.DB, user *models.User, sl validator.StructLevel) {
if user.Membership.SubscriptionModel.RequiredMembershipField != "" {
switch user.Membership.SubscriptionModel.RequiredMembershipField {
if user.Membership.Subscription.RequiredMembershipField != "" {
switch user.Membership.Subscription.RequiredMembershipField {
case "ParentMembershipID":
if err := CheckParentMembershipID(db, user); err != nil {
logger.Error.Printf("Error ParentMembershipValidation: %v", err.Error())
sl.ReportError(user.Membership.ParentMembershipID, user.Membership.SubscriptionModel.RequiredMembershipField,
sl.ReportError(user.Membership.ParentMembershipID, user.Membership.Subscription.RequiredMembershipField,
"RequiredMembershipField", "invalid", "")
}
default:
logger.Error.Printf("Error no matching RequiredMembershipField: %v", errors.ErrInvalidValue.Error())
sl.ReportError(user.Membership.ParentMembershipID, user.Membership.SubscriptionModel.RequiredMembershipField,
sl.ReportError(user.Membership.ParentMembershipID, user.Membership.Subscription.RequiredMembershipField,
"RequiredMembershipField", "not_implemented", "")
}
}

View File

@@ -16,6 +16,6 @@ func SetupValidators(db *gorm.DB) {
// Register struct-level validations
v.RegisterStructValidation(ValidateUserFactory(db), models.User{})
v.RegisterStructValidation(ValidateSubscription, models.SubscriptionModel{})
v.RegisterStructValidation(ValidateSubscription, models.Subscription{})
}
}

View File

@@ -3,19 +3,17 @@ package validation
import (
"GoMembership/internal/models"
"GoMembership/internal/repositories"
"GoMembership/pkg/logger"
"github.com/go-playground/validator/v10"
)
// ValidateNewSubscription validates a new subscription model being created
func ValidateSubscription(sl validator.StructLevel) {
subscription := sl.Current().Interface().(models.SubscriptionModel)
subscription := sl.Current().Interface().(models.Subscription)
if subscription.Name == "" {
sl.ReportError(subscription.Name, "Name", "name", "required", "")
}
logger.Error.Printf("parent.type.name: %#v", sl.Parent().Type().Name())
if sl.Parent().Type().Name() == "" {
// This is modifying a subscription directly
if subscription.Details == "" {

View File

@@ -30,16 +30,16 @@ func ValidateUserFactory(db *gorm.DB) validator.StructLevelFunc {
func validateUser(db *gorm.DB, sl validator.StructLevel) {
user := sl.Current().Interface().(models.User)
// validate subscriptionModel
if user.Membership.SubscriptionModel.Name == "" {
sl.ReportError(user.Membership.SubscriptionModel.Name, "subscription.name", "name", "required", "")
// validate subscription
if user.Membership.Subscription.Name == "" {
sl.ReportError(user.Membership.Subscription.Name, "subscription.name", "name", "required", "")
} else {
selectedModel, err := repositories.GetSubscriptionByName(&user.Membership.SubscriptionModel.Name)
selectedModel, err := repositories.GetSubscriptionByName(&user.Membership.Subscription.Name)
if err != nil {
logger.Error.Printf("Error finding subscription model for user %v: %v", user.Email, err)
sl.ReportError(user.Membership.SubscriptionModel.Name, "subscription.name", "name", "invalid", "")
sl.ReportError(user.Membership.Subscription.Name, "subscription.name", "name", "invalid", "")
} else {
user.Membership.SubscriptionModel = *selectedModel
user.Membership.Subscription = *selectedModel
}
}
if user.IsSupporter() {